1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtCore/QStringListModel>
31#include <QtCore/QSortFilterProxyModel>
32#include <QtGui/QStandardItemModel>
33#include <QtGui/QStyleHints>
34#include <QtQuick/qquickview.h>
35#include <QtQuickTest/QtQuickTest>
36#include <QStringListModel>
37#include <QQmlApplicationEngine>
38#include <QtQml/qqmlengine.h>
39#include <QtQml/qqmlcontext.h>
40#include <QtQml/qqmlexpression.h>
41#include <QtQml/qqmlincubator.h>
42#include <QtQuick/private/qquickitemview_p_p.h>
43#include <QtQuick/private/qquicklistview_p.h>
44#include <QtQuick/private/qquickmousearea_p.h>
45#include <QtQuick/private/qquicktext_p.h>
46#include <QtQuick/private/qquickrectangle_p.h>
47#include <QtQmlModels/private/qqmlobjectmodel_p.h>
48#include <QtQmlModels/private/qqmllistmodel_p.h>
49#include <QtQmlModels/private/qqmldelegatemodel_p.h>
50#include "../../shared/util.h"
51#include "../shared/viewtestutil.h"
52#include "../shared/visualtestutil.h"
53#include "incrementalmodel.h"
54#include "proxytestinnermodel.h"
55#include "randomsortmodel.h"
56#include "reusemodel.h"
57#include <math.h>
58
59Q_DECLARE_METATYPE(Qt::LayoutDirection)
60Q_DECLARE_METATYPE(QQuickItemView::VerticalLayoutDirection)
61Q_DECLARE_METATYPE(QQuickItemView::PositionMode)
62Q_DECLARE_METATYPE(QQuickListView::Orientation)
63Q_DECLARE_METATYPE(QQuickFlickable::FlickableDirection)
64Q_DECLARE_METATYPE(Qt::Key)
65
66using namespace QQuickViewTestUtil;
67using namespace QQuickVisualTestUtil;
68
69#define SHARE_VIEWS
70
71class tst_QQuickListView : public QQmlDataTest
72{
73 Q_OBJECT
74public:
75 tst_QQuickListView();
76
77private slots:
78 void init();
79 void cleanupTestCase();
80 // Test QAbstractItemModel model types
81 void qAbstractItemModel_package_items();
82 void qAbstractItemModel_items();
83
84 void qAbstractItemModel_package_changed();
85 void qAbstractItemModel_changed();
86
87 void qAbstractItemModel_package_inserted();
88 void qAbstractItemModel_inserted();
89 void qAbstractItemModel_inserted_more();
90 void qAbstractItemModel_inserted_more_data();
91 void qAbstractItemModel_inserted_more_bottomToTop();
92 void qAbstractItemModel_inserted_more_bottomToTop_data();
93
94 void qAbstractItemModel_package_removed();
95 void qAbstractItemModel_removed();
96 void qAbstractItemModel_removed_more();
97 void qAbstractItemModel_removed_more_data();
98 void qAbstractItemModel_removed_more_bottomToTop();
99 void qAbstractItemModel_removed_more_bottomToTop_data();
100
101 void qAbstractItemModel_package_moved();
102 void qAbstractItemModel_package_moved_data();
103 void qAbstractItemModel_moved();
104 void qAbstractItemModel_moved_data();
105 void qAbstractItemModel_moved_bottomToTop();
106 void qAbstractItemModel_moved_bottomToTop_data();
107
108 void multipleChanges_condensed() { multipleChanges(condensed: true); }
109 void multipleChanges_condensed_data() { multipleChanges_data(); }
110 void multipleChanges_uncondensed() { multipleChanges(condensed: false); }
111 void multipleChanges_uncondensed_data() { multipleChanges_data(); }
112
113 void qAbstractItemModel_package_clear();
114 void qAbstractItemModel_clear();
115 void qAbstractItemModel_clear_bottomToTop();
116
117 void insertBeforeVisible();
118 void insertBeforeVisible_data();
119 void swapWithFirstItem();
120 void itemList();
121 void itemListFlicker();
122 void currentIndex_delayedItemCreation();
123 void currentIndex_delayedItemCreation_data();
124 void currentIndex();
125 void noCurrentIndex();
126 void keyNavigation();
127 void keyNavigation_data();
128 void checkCountForMultiColumnModels();
129 void enforceRange();
130 void enforceRange_withoutHighlight();
131 void spacing();
132 void qAbstractItemModel_package_sections();
133 void qAbstractItemModel_sections();
134 void sectionsPositioning();
135 void sectionsDelegate_data();
136 void sectionsDelegate();
137 void sectionsDragOutsideBounds_data();
138 void sectionsDragOutsideBounds();
139 void sectionsDelegate_headerVisibility();
140 void sectionPropertyChange();
141 void sectionDelegateChange();
142 void sectionsItemInsertion();
143 void sectionsSnap_data();
144 void sectionsSnap();
145 void cacheBuffer();
146 void positionViewAtBeginningEnd();
147 void positionViewAtIndex();
148 void positionViewAtIndex_data();
149 void resetModel();
150 void propertyChanges();
151 void componentChanges();
152 void modelChanges();
153 void manualHighlight();
154 void initialZValues();
155 void initialZValues_data();
156 void header();
157 void header_data();
158 void header_delayItemCreation();
159 void headerChangesViewport();
160 void footer();
161 void footer_data();
162 void footer2();
163 void extents();
164 void extents_data();
165 void resetModel_headerFooter();
166 void resizeView();
167 void resizeViewAndRepaint();
168 void sizeLessThan1();
169 void QTBUG_14821();
170 void resizeDelegate();
171 void resizeFirstDelegate();
172 void repositionResizedDelegate();
173 void repositionResizedDelegate_data();
174 void QTBUG_16037();
175 void indexAt_itemAt_data();
176 void indexAt_itemAt();
177 void itemAtIndex();
178 void incrementalModel();
179 void onAdd();
180 void onAdd_data();
181 void onRemove();
182 void onRemove_data();
183 void attachedProperties_QTBUG_32836();
184 void rightToLeft();
185 void test_mirroring();
186 void margins();
187 void marginsResize();
188 void marginsResize_data();
189 void creationContext();
190 void snapToItem_data();
191 void snapToItem();
192 void headerSnapToItem_data();
193 void headerSnapToItem();
194 void snapToItemWithSpacing_QTBUG_59852();
195 void snapOneItemResize_QTBUG_43555();
196 void snapOneItem_data();
197 void snapOneItem();
198 void snapOneItemCurrentIndexRemoveAnimation();
199 void snapOneItemWrongDirection();
200
201 void QTBUG_9791();
202 void QTBUG_33568();
203 void QTBUG_11105();
204 void QTBUG_21742();
205
206 void asynchronous();
207 void unrequestedVisibility();
208
209 void populateTransitions();
210 void populateTransitions_data();
211 void sizeTransitions();
212 void sizeTransitions_data();
213
214 void addTransitions();
215 void addTransitions_data();
216 void moveTransitions();
217 void moveTransitions_data();
218 void removeTransitions();
219 void removeTransitions_data();
220 void displacedTransitions();
221 void displacedTransitions_data();
222 void multipleTransitions();
223 void multipleTransitions_data();
224 void multipleDisplaced();
225
226 void flickBeyondBounds();
227 void flickBothDirections();
228 void flickBothDirections_data();
229 void destroyItemOnCreation();
230
231 void parentBinding();
232 void defaultHighlightMoveDuration();
233 void accessEmptyCurrentItem_QTBUG_30227();
234 void delayedChanges_QTBUG_30555();
235 void outsideViewportChangeNotAffectingView();
236 void testProxyModelChangedAfterMove();
237
238 void typedModel();
239 void displayMargin();
240 void negativeDisplayMargin();
241
242 void highlightItemGeometryChanges();
243
244 void QTBUG_36481();
245 void QTBUG_35920();
246
247 void stickyPositioning();
248 void stickyPositioning_data();
249
250 void roundingErrors();
251 void roundingErrors_data();
252
253 void QTBUG_38209();
254 void programmaticFlickAtBounds();
255 void programmaticFlickAtBounds2();
256 void programmaticFlickAtBounds3();
257
258 void layoutChange();
259
260 void QTBUG_39492_data();
261 void QTBUG_39492();
262
263 void jsArrayChange();
264 void objectModel();
265
266 void contentHeightWithDelayRemove();
267 void contentHeightWithDelayRemove_data();
268
269 void QTBUG_48044_currentItemNotVisibleAfterTransition();
270 void QTBUG_48870_fastModelUpdates();
271
272 void QTBUG_50105();
273 void keyNavigationEnabled();
274 void QTBUG_61269_appendDuringScrollDown();
275 void QTBUG_61269_appendDuringScrollDown_data();
276 void QTBUG_50097_stickyHeader_positionViewAtIndex();
277 void QTBUG_63974_stickyHeader_positionViewAtIndex_Contain();
278 void QTBUG_66163_setModelViewPortSizeChange();
279 void itemFiltered();
280 void releaseItems();
281
282 void QTBUG_34576_velocityZero();
283 void QTBUG_61537_modelChangesAsync();
284
285 void useDelegateChooserWithoutDefault();
286
287 void addOnCompleted();
288 void setPositionOnLayout();
289 void touchCancel();
290 void resizeAfterComponentComplete();
291 void dragOverFloatingHeaderOrFooter();
292
293 void delegateWithRequiredProperties();
294
295 void reuse_reuseIsOffByDefault();
296 void reuse_checkThatItemsAreReused();
297 void moveObjectModelItemToAnotherObjectModel();
298 void changeModelAndDestroyTheOldOne();
299 void objectModelCulling();
300
301 void requiredObjectListModel();
302 void clickHeaderAndFooterWhenClip();
303 void animatedDelegate();
304 void dragDelegateWithMouseArea();
305 void dragDelegateWithMouseArea_data();
306
307
308 void singletonModelLifetime();
309 void QTBUG_92809();
310
311private:
312 template <class T> void items(const QUrl &source);
313 template <class T> void changed(const QUrl &source);
314 template <class T> void inserted(const QUrl &source);
315 template <class T> void inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
316 template <class T> void removed(const QUrl &source, bool animated);
317 template <class T> void removed_more(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
318 template <class T> void moved(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
319 template <class T> void clear(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
320 template <class T> void sections(const QUrl &source);
321
322 void multipleChanges(bool condensed);
323 void multipleChanges_data();
324
325 QList<int> toIntList(const QVariantList &list);
326 void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
327 void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
328 void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
329
330 void inserted_more_data();
331 void removed_more_data();
332 void moved_data();
333
334#ifdef SHARE_VIEWS
335 QQuickView *getView() {
336 if (m_view) {
337 if (QString(QTest::currentTestFunction()) != testForView) {
338 delete m_view;
339 m_view = nullptr;
340 } else {
341 m_view->setSource(QUrl());
342 return m_view;
343 }
344 }
345
346 testForView = QTest::currentTestFunction();
347 m_view = createView();
348 return m_view;
349 }
350 void releaseView(QQuickView *view) {
351 Q_ASSERT(view == m_view);
352 m_view->setSource(QUrl());
353 }
354#else
355 QQuickView *getView() {
356 return createView();
357 }
358 void releaseView(QQuickView *view) {
359 delete view;
360 }
361#endif
362
363 QQuickView *m_view;
364 QString testForView;
365 QTouchDevice *touchDevice = QTest::createTouchDevice();
366};
367
368class TestObject : public QObject
369{
370 Q_OBJECT
371
372 Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
373 Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
374 Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
375 Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
376
377public:
378 TestObject(QObject *parent = nullptr)
379 : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
380 , mCacheBuffer(0) {}
381
382 bool error() const { return mError; }
383 void setError(bool err) { mError = err; emit changedError(); }
384
385 bool animate() const { return mAnimate; }
386 void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
387
388 bool invalidHighlight() const { return mInvalidHighlight; }
389 void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
390
391 int cacheBuffer() const { return mCacheBuffer; }
392 void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
393
394signals:
395 void changedError();
396 void changedAnim();
397 void changedHl();
398 void changedCacheBuffer();
399
400public:
401 bool mError;
402 bool mAnimate;
403 bool mInvalidHighlight;
404 int mCacheBuffer;
405};
406
407tst_QQuickListView::tst_QQuickListView() : m_view(nullptr)
408{
409}
410
411void tst_QQuickListView::init()
412{
413#ifdef SHARE_VIEWS
414 if (m_view && QString(QTest::currentTestFunction()) != testForView) {
415 testForView = QString();
416 delete m_view;
417 m_view = nullptr;
418 }
419#endif
420 qmlRegisterAnonymousType<QAbstractItemModel>(uri: "Proxy", versionMajor: 1);
421 qmlRegisterType<ProxyTestInnerModel>(uri: "Proxy", versionMajor: 1, versionMinor: 0, qmlName: "ProxyTestInnerModel");
422 qmlRegisterType<QSortFilterProxyModel>(uri: "Proxy", versionMajor: 1, versionMinor: 0, qmlName: "QSortFilterProxyModel");
423}
424
425void tst_QQuickListView::cleanupTestCase()
426{
427#ifdef SHARE_VIEWS
428 testForView = QString();
429 delete m_view;
430 m_view = nullptr;
431#endif
432}
433
434template <class T>
435void tst_QQuickListView::items(const QUrl &source)
436{
437 QScopedPointer<QQuickView> window(createView());
438
439 T model;
440 model.addItem("Fred", "12345");
441 model.addItem("John", "2345");
442 model.addItem("Bob", "54321");
443
444 QQmlContext *ctxt = window->rootContext();
445 ctxt->setContextProperty("testModel", &model);
446
447 QScopedPointer<TestObject> testObject(new TestObject);
448 ctxt->setContextProperty("testObject", testObject.data());
449
450 window->setSource(source);
451 qApp->processEvents();
452
453 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
454 QTRY_VERIFY(listview != nullptr);
455 listview->forceLayout();
456
457 QQuickItem *contentItem = listview->contentItem();
458 QTRY_VERIFY(contentItem != nullptr);
459
460 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
461 QTRY_VERIFY(!testObject->error());
462
463 QTRY_VERIFY(listview->highlightItem() != nullptr);
464 QTRY_COMPARE(listview->count(), model.count());
465 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
466 listview->forceLayout();
467 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
468
469 // current item should be first item
470 QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
471
472 for (int i = 0; i < model.count(); ++i) {
473 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
474 QTRY_VERIFY(name != nullptr);
475 QTRY_COMPARE(name->text(), model.name(i));
476 QQuickText *number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: i);
477 QTRY_VERIFY(number != nullptr);
478 QTRY_COMPARE(number->text(), model.number(i));
479 }
480
481 // switch to other delegate
482 testObject->setAnimate(true);
483 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
484 QTRY_VERIFY(!testObject->error());
485 QTRY_VERIFY(listview->currentItem());
486
487 // set invalid highlight
488 testObject->setInvalidHighlight(true);
489 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
490 QTRY_VERIFY(!testObject->error());
491 QTRY_VERIFY(listview->currentItem());
492 QTRY_VERIFY(!listview->highlightItem());
493
494 // back to normal highlight
495 testObject->setInvalidHighlight(false);
496 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
497 QTRY_VERIFY(!testObject->error());
498 QTRY_VERIFY(listview->currentItem());
499 QTRY_VERIFY(listview->highlightItem() != nullptr);
500
501 // set an empty model and confirm that items are destroyed
502 T model2;
503 ctxt->setContextProperty("testModel", &model2);
504
505 // Force a layout, necessary if ListView is completed before DelegateModel.
506 listview->forceLayout();
507
508 QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").isEmpty());
509
510 QTRY_COMPARE(listview->highlightResizeVelocity(), 1000.0);
511 QTRY_COMPARE(listview->highlightMoveVelocity(), 100000.0);
512}
513
514
515template <class T>
516void tst_QQuickListView::changed(const QUrl &source)
517{
518 QScopedPointer<QQuickView> window(createView());
519
520 T model;
521 model.addItem("Fred", "12345");
522 model.addItem("John", "2345");
523 model.addItem("Bob", "54321");
524
525 QQmlContext *ctxt = window->rootContext();
526 ctxt->setContextProperty("testModel", &model);
527
528 QScopedPointer<TestObject> testObject(new TestObject);
529 ctxt->setContextProperty("testObject", testObject.data());
530
531 window->setSource(source);
532 qApp->processEvents();
533
534 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
535 QTRY_VERIFY(listview != nullptr);
536 listview->forceLayout();
537
538 QQuickItem *contentItem = listview->contentItem();
539 QTRY_VERIFY(contentItem != nullptr);
540
541 // Force a layout, necessary if ListView is completed before DelegateModel.
542 listview->forceLayout();
543
544 model.modifyItem(1, "Will", "9876");
545 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 1);
546 QTRY_VERIFY(name != nullptr);
547 QTRY_COMPARE(name->text(), model.name(1));
548 QQuickText *number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 1);
549 QTRY_VERIFY(number != nullptr);
550 QTRY_COMPARE(number->text(), model.number(1));
551}
552
553template <class T>
554void tst_QQuickListView::inserted(const QUrl &source)
555{
556 QScopedPointer<QQuickView> window(createView());
557 window->show();
558 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
559
560 T model;
561 model.addItem("Fred", "12345");
562 model.addItem("John", "2345");
563 model.addItem("Bob", "54321");
564
565 QQmlContext *ctxt = window->rootContext();
566 ctxt->setContextProperty("testModel", &model);
567
568 QScopedPointer<TestObject> testObject(new TestObject);
569 ctxt->setContextProperty("testObject", testObject.data());
570
571 window->setSource(source);
572 qApp->processEvents();
573
574 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
575 QTRY_VERIFY(listview != nullptr);
576
577 QQuickItem *contentItem = listview->contentItem();
578 QTRY_VERIFY(contentItem != nullptr);
579
580 model.insertItem(1, "Will", "9876");
581
582 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
583 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
584
585 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 1);
586 QTRY_VERIFY(name != nullptr);
587 QTRY_COMPARE(name->text(), model.name(1));
588 QQuickText *number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 1);
589 QTRY_VERIFY(number != nullptr);
590 QTRY_COMPARE(number->text(), model.number(1));
591
592 // Confirm items positioned correctly
593 for (int i = 0; i < model.count(); ++i) {
594 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
595 QTRY_COMPARE(item->y(), i*20.0);
596 }
597
598 model.insertItem(0, "Foo", "1111"); // zero index, and current item
599
600 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
601 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
602
603 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 0);
604 QTRY_VERIFY(name != nullptr);
605 QTRY_COMPARE(name->text(), model.name(0));
606 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 0);
607 QTRY_VERIFY(number != nullptr);
608 QTRY_COMPARE(number->text(), model.number(0));
609
610 QTRY_COMPARE(listview->currentIndex(), 1);
611
612 // Confirm items positioned correctly
613 for (int i = 0; i < model.count(); ++i) {
614 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
615 QTRY_COMPARE(item->y(), i*20.0);
616 }
617
618 for (int i = model.count(); i < 30; ++i)
619 model.insertItem(i, "Hello", QString::number(i));
620
621 listview->setContentY(80);
622
623 // Insert item outside visible area
624 model.insertItem(1, "Hello", "1324");
625
626 QTRY_COMPARE(listview->contentY(), qreal(80));
627
628 // Confirm items positioned correctly
629 for (int i = 5; i < 5+15; ++i) {
630 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
631 if (!item) qWarning() << "Item" << i << "not found";
632 QTRY_VERIFY(item);
633 QTRY_COMPARE(item->y(), i*20.0 - 20.0);
634 }
635
636// QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
637
638 // QTBUG-19675
639 model.clear();
640 model.insertItem(0, "Hello", "1234");
641 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
642
643 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
644 QVERIFY(item);
645 QTRY_COMPARE(item->y() - listview->contentY(), 0.);
646}
647
648template <class T>
649void tst_QQuickListView::inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
650{
651 QFETCH(qreal, contentY);
652 QFETCH(int, insertIndex);
653 QFETCH(int, insertCount);
654 QFETCH(qreal, itemsOffsetAfterMove);
655
656 T model;
657 for (int i = 0; i < 30; i++)
658 model.addItem("Item" + QString::number(i), "");
659
660 QQuickView *window = getView();
661 QQmlContext *ctxt = window->rootContext();
662 ctxt->setContextProperty("testModel", &model);
663
664 QScopedPointer<TestObject> testObject(new TestObject);
665 ctxt->setContextProperty("testObject", testObject.data());
666
667 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
668 window->show();
669 QVERIFY(QTest::qWaitForWindowExposed(window));
670
671 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
672 QTRY_VERIFY(listview != nullptr);
673 QQuickItem *contentItem = listview->contentItem();
674 QTRY_VERIFY(contentItem != nullptr);
675
676 if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
677 listview->setVerticalLayoutDirection(verticalLayoutDirection);
678 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
679 contentY = -listview->height() - contentY;
680 }
681 listview->setContentY(contentY);
682
683 QQuickItemViewPrivate::get(o: listview)->layout();
684
685 QList<QPair<QString, QString> > newData;
686 for (int i=0; i<insertCount; i++)
687 newData << qMakePair(x: QString("value %1").arg(a: i), y: QString::number(i));
688 model.insertItems(insertIndex, newData);
689
690 //Wait for polish (updates list to the model changes)
691 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
692
693 QTRY_COMPARE(listview->property("count").toInt(), model.count());
694
695 // FIXME This is NOT checking anything about visibleItems.first()
696#if 0
697 // check visibleItems.first() is in correct position
698 QQuickItem *item0 = findItem<QQuickItem>(contentItem, "wrapper", 0);
699 QVERIFY(item0);
700 if (verticalLayoutDirection == QQuickItemView::BottomToTop)
701 QCOMPARE(item0->y(), -item0->height() - itemsOffsetAfterMove);
702 else
703 QCOMPARE(item0->y(), itemsOffsetAfterMove);
704#endif
705
706 QList<FxViewItem *> visibleItems = QQuickItemViewPrivate::get(o: listview)->visibleItems;
707 for (QList<FxViewItem *>::const_iterator itemIt = visibleItems.begin(); itemIt != visibleItems.end(); ++itemIt) {
708 FxViewItem *item = *itemIt;
709 if (item->item->position().y() >= 0 && item->item->position().y() < listview->height()) {
710 QVERIFY(!QQuickItemPrivate::get(item->item)->culled);
711 }
712 }
713
714 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
715 int firstVisibleIndex = -1;
716 for (int i=0; i<items.count(); i++) {
717 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
718 if (item && !QQuickItemPrivate::get(item)->culled) {
719 firstVisibleIndex = i;
720 break;
721 }
722 }
723 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
724
725 // Confirm items positioned correctly and indexes correct
726 QQuickText *name;
727 QQuickText *number;
728 const qreal visibleFromPos = listview->contentY() - listview->displayMarginBeginning() - listview->cacheBuffer();
729 const qreal visibleToPos = listview->contentY() + listview->height() + listview->displayMarginEnd() + listview->cacheBuffer();
730 for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
731 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
732 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
733 qreal pos = i*20.0 + itemsOffsetAfterMove;
734 if (verticalLayoutDirection == QQuickItemView::BottomToTop)
735 pos = -item->height() - pos;
736 // Items outside the visible area (including cache buffer) should be skipped
737 if (pos > visibleToPos || pos < visibleFromPos) {
738 QTRY_VERIFY2(QQuickItemPrivate::get(item)->culled || item->y() < visibleFromPos || item->y() > visibleToPos,
739 qPrintable(QString("index %5, y %1, from %2, to %3, expected pos %4, culled %6").
740 arg(item->y()).arg(visibleFromPos).arg(visibleToPos).arg(pos).arg(i).arg(bool(QQuickItemPrivate::get(item)->culled))));
741 continue;
742 }
743 QTRY_COMPARE(item->y(), pos);
744 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
745 QVERIFY(name != nullptr);
746 QTRY_COMPARE(name->text(), model.name(i));
747 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: i);
748 QVERIFY(number != nullptr);
749 QTRY_COMPARE(number->text(), model.number(i));
750 }
751
752 releaseView(view: window);
753}
754
755void tst_QQuickListView::inserted_more_data()
756{
757 QTest::addColumn<qreal>(name: "contentY");
758 QTest::addColumn<int>(name: "insertIndex");
759 QTest::addColumn<int>(name: "insertCount");
760 QTest::addColumn<qreal>(name: "itemsOffsetAfterMove");
761
762 QTest::newRow(dataTag: "add 1, before visible items")
763 << 80.0 // show 4-19
764 << 3 << 1
765 << -20.0; // insert above first visible i.e. 0 is at -20, first visible should not move
766
767 QTest::newRow(dataTag: "add multiple, before visible")
768 << 80.0 // show 4-19
769 << 3 << 3
770 << -20.0 * 3; // again first visible should not move
771
772 QTest::newRow(dataTag: "add 1, at start of visible, content at start")
773 << 0.0
774 << 0 << 1
775 << 0.0;
776
777 QTest::newRow(dataTag: "add multiple, start of visible, content at start")
778 << 0.0
779 << 0 << 3
780 << 0.0;
781
782 QTest::newRow(dataTag: "add 1, at start of visible, content not at start")
783 << 80.0 // show 4-19
784 << 4 << 1
785 << 0.0;
786
787 QTest::newRow(dataTag: "add multiple, at start of visible, content not at start")
788 << 80.0 // show 4-19
789 << 4 << 3
790 << 0.0;
791
792
793 QTest::newRow(dataTag: "add 1, at end of visible, content at start")
794 << 0.0
795 << 15 << 1
796 << 0.0;
797
798 QTest::newRow(dataTag: "add multiple, at end of visible, content at start")
799 << 0.0
800 << 15 << 3
801 << 0.0;
802
803 QTest::newRow(dataTag: "add 1, at end of visible, content not at start")
804 << 80.0 // show 4-19
805 << 19 << 1
806 << 0.0;
807
808 QTest::newRow(dataTag: "add multiple, at end of visible, content not at start")
809 << 80.0 // show 4-19
810 << 19 << 3
811 << 0.0;
812
813
814 QTest::newRow(dataTag: "add 1, after visible, content at start")
815 << 0.0
816 << 16 << 1
817 << 0.0;
818
819 QTest::newRow(dataTag: "add multiple, after visible, content at start")
820 << 0.0
821 << 16 << 3
822 << 0.0;
823
824 QTest::newRow(dataTag: "add 1, after visible, content not at start")
825 << 80.0 // show 4-19
826 << 20 << 1
827 << 0.0;
828
829 QTest::newRow(dataTag: "add multiple, after visible, content not at start")
830 << 80.0 // show 4-19
831 << 20 << 3
832 << 0.0;
833
834 QTest::newRow(dataTag: "add multiple, within visible, content at start")
835 << 0.0
836 << 2 << 50
837 << 0.0;
838}
839
840void tst_QQuickListView::insertBeforeVisible()
841{
842 QFETCH(int, insertIndex);
843 QFETCH(int, insertCount);
844 QFETCH(int, removeIndex);
845 QFETCH(int, removeCount);
846 QFETCH(int, cacheBuffer);
847
848 QQuickText *name;
849 QQuickView *window = getView();
850
851 QaimModel model;
852 for (int i = 0; i < 30; i++)
853 model.addItem(name: "Item" + QString::number(i), number: "");
854
855 QQmlContext *ctxt = window->rootContext();
856 ctxt->setContextProperty("testModel", &model);
857
858 QScopedPointer<TestObject> testObject(new TestObject);
859 ctxt->setContextProperty("testObject", testObject.data());
860
861 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
862 window->show();
863 QVERIFY(QTest::qWaitForWindowExposed(window));
864
865 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
866 QTRY_VERIFY(listview != nullptr);
867 QQuickItem *contentItem = listview->contentItem();
868 QTRY_VERIFY(contentItem != nullptr);
869
870 listview->setCacheBuffer(cacheBuffer);
871 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
872
873 // trigger a refill (not just setting contentY) so that the visibleItems grid is updated
874 int firstVisibleIndex = 20; // move to an index where the top item is not visible
875 listview->setContentY(firstVisibleIndex * 20.0);
876 listview->setCurrentIndex(firstVisibleIndex);
877
878 qApp->processEvents();
879 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
880 QTRY_COMPARE(listview->currentIndex(), firstVisibleIndex);
881 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: firstVisibleIndex);
882 QVERIFY(item);
883 QCOMPARE(item->y(), listview->contentY());
884
885 if (removeCount > 0)
886 model.removeItems(index: removeIndex, count: removeCount);
887
888 if (insertCount > 0) {
889 QList<QPair<QString, QString> > newData;
890 for (int i=0; i<insertCount; i++)
891 newData << qMakePair(x: QString("value %1").arg(a: i), y: QString::number(i));
892 model.insertItems(index: insertIndex, items: newData);
893 QTRY_COMPARE(listview->property("count").toInt(), model.count());
894 }
895
896 // now, moving to the top of the view should position the inserted items correctly
897 int itemsOffsetAfterMove = (removeCount - insertCount) * 20;
898 listview->setCurrentIndex(0);
899 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
900 QTRY_COMPARE(listview->currentIndex(), 0);
901 QTRY_COMPARE(listview->contentY(), 0.0 + itemsOffsetAfterMove);
902
903 // Confirm items positioned correctly and indexes correct
904 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
905 for (int i = 0; i < model.count() && i < itemCount; ++i) {
906 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
907 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
908 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
909 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
910 QVERIFY(name != nullptr);
911 QTRY_COMPARE(name->text(), model.name(i));
912 }
913
914 releaseView(view: window);
915}
916
917void tst_QQuickListView::insertBeforeVisible_data()
918{
919 QTest::addColumn<int>(name: "insertIndex");
920 QTest::addColumn<int>(name: "insertCount");
921 QTest::addColumn<int>(name: "removeIndex");
922 QTest::addColumn<int>(name: "removeCount");
923 QTest::addColumn<int>(name: "cacheBuffer");
924
925 QTest::newRow(dataTag: "insert 1 at 0, 0 buffer") << 0 << 1 << 0 << 0 << 0;
926 QTest::newRow(dataTag: "insert 1 at 0, 100 buffer") << 0 << 1 << 0 << 0 << 100;
927 QTest::newRow(dataTag: "insert 1 at 0, 500 buffer") << 0 << 1 << 0 << 0 << 500;
928
929 QTest::newRow(dataTag: "insert 1 at 1, 0 buffer") << 1 << 1 << 0 << 0 << 0;
930 QTest::newRow(dataTag: "insert 1 at 1, 100 buffer") << 1 << 1 << 0 << 0 << 100;
931 QTest::newRow(dataTag: "insert 1 at 1, 500 buffer") << 1 << 1 << 0 << 0 << 500;
932
933 QTest::newRow(dataTag: "insert multiple at 0, 0 buffer") << 0 << 3 << 0 << 0 << 0;
934 QTest::newRow(dataTag: "insert multiple at 0, 100 buffer") << 0 << 3 << 0 << 0 << 100;
935 QTest::newRow(dataTag: "insert multiple at 0, 500 buffer") << 0 << 3 << 0 << 0 << 500;
936
937 QTest::newRow(dataTag: "insert multiple at 1, 0 buffer") << 1 << 3 << 0 << 0 << 0;
938 QTest::newRow(dataTag: "insert multiple at 1, 100 buffer") << 1 << 3 << 0 << 0 << 100;
939 QTest::newRow(dataTag: "insert multiple at 1, 500 buffer") << 1 << 3 << 0 << 0 << 500;
940
941 QTest::newRow(dataTag: "remove 1 at 0, 0 buffer") << 0 << 0 << 0 << 1 << 0;
942 QTest::newRow(dataTag: "remove 1 at 0, 100 buffer") << 0 << 0 << 0 << 1 << 100;
943 QTest::newRow(dataTag: "remove 1 at 0, 500 buffer") << 0 << 0 << 0 << 1 << 500;
944
945 QTest::newRow(dataTag: "remove 1 at 1, 0 buffer") << 0 << 0 << 1 << 1 << 0;
946 QTest::newRow(dataTag: "remove 1 at 1, 100 buffer") << 0 << 0 << 1 << 1 << 100;
947 QTest::newRow(dataTag: "remove 1 at 1, 500 buffer") << 0 << 0 << 1 << 1 << 500;
948
949 QTest::newRow(dataTag: "remove multiple at 0, 0 buffer") << 0 << 0 << 0 << 3 << 0;
950 QTest::newRow(dataTag: "remove multiple at 0, 100 buffer") << 0 << 0 << 0 << 3 << 100;
951 QTest::newRow(dataTag: "remove multiple at 0, 500 buffer") << 0 << 0 << 0 << 3 << 500;
952
953 QTest::newRow(dataTag: "remove multiple at 1, 0 buffer") << 0 << 0 << 1 << 3 << 0;
954 QTest::newRow(dataTag: "remove multiple at 1, 100 buffer") << 0 << 0 << 1 << 3 << 100;
955 QTest::newRow(dataTag: "remove multiple at 1, 500 buffer") << 0 << 0 << 1 << 3 << 500;
956}
957
958template <class T>
959void tst_QQuickListView::removed(const QUrl &source, bool /* animated */)
960{
961 QScopedPointer<QQuickView> window(createView());
962
963 T model;
964 for (int i = 0; i < 50; i++)
965 model.addItem("Item" + QString::number(i), "");
966
967 QQmlContext *ctxt = window->rootContext();
968 ctxt->setContextProperty("testModel", &model);
969
970 QScopedPointer<TestObject> testObject(new TestObject);
971 ctxt->setContextProperty("testObject", testObject.data());
972
973 window->setSource(source);
974 window->show();
975 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
976
977 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
978 QTRY_VERIFY(listview != nullptr);
979 QQuickItem *contentItem = listview->contentItem();
980 QTRY_VERIFY(contentItem != nullptr);
981 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
982
983 model.removeItem(1);
984 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
985
986 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 1);
987 QTRY_VERIFY(name != nullptr);
988 QTRY_COMPARE(name->text(), model.name(1));
989 QQuickText *number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 1);
990 QTRY_VERIFY(number != nullptr);
991 QTRY_COMPARE(number->text(), model.number(1));
992
993 // Confirm items positioned correctly
994 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
995 for (int i = 0; i < model.count() && i < itemCount; ++i) {
996 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
997 if (!item) qWarning() << "Item" << i << "not found";
998 QTRY_VERIFY(item);
999 QTRY_COMPARE(item->y(), qreal(i*20));
1000 }
1001
1002 // Remove first item (which is the current item);
1003 model.removeItem(0);
1004 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
1005
1006 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 0);
1007 QTRY_VERIFY(name != nullptr);
1008 QTRY_COMPARE(name->text(), model.name(0));
1009 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 0);
1010 QTRY_VERIFY(number != nullptr);
1011 QTRY_COMPARE(number->text(), model.number(0));
1012
1013 // Confirm items positioned correctly
1014 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
1015 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1016 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1017 if (!item) qWarning() << "Item" << i << "not found";
1018 QTRY_VERIFY(item);
1019 QTRY_COMPARE(item->y(),i*20.0);
1020 }
1021
1022 // Remove items not visible
1023 model.removeItem(18);
1024 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
1025
1026 // Confirm items positioned correctly
1027 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
1028 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1029 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1030 if (!item) qWarning() << "Item" << i << "not found";
1031 QTRY_VERIFY(item);
1032 QTRY_COMPARE(item->y(),i*20.0);
1033 }
1034
1035 // Remove items before visible
1036 listview->setContentY(80);
1037 listview->setCurrentIndex(10);
1038
1039 model.removeItem(1); // post: top item will be at 20
1040 QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
1041
1042 // Confirm items positioned correctly
1043 for (int i = 2; i < 18; ++i) {
1044 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1045 if (!item) qWarning() << "Item" << i << "not found";
1046 QTRY_VERIFY(item);
1047 QTRY_COMPARE(item->y(),20+i*20.0);
1048 }
1049
1050 // Remove current index
1051 QTRY_COMPARE(listview->currentIndex(), 9);
1052 QQuickItem *oldCurrent = listview->currentItem();
1053 model.removeItem(9);
1054
1055 QTRY_COMPARE(listview->currentIndex(), 9);
1056 QTRY_VERIFY(listview->currentItem() != oldCurrent);
1057
1058 listview->setContentY(20); // That's the top now
1059 // let transitions settle.
1060 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1061
1062 // Confirm items positioned correctly
1063 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
1064 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1065 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1066 if (!item) qWarning() << "Item" << i << "not found";
1067 QTRY_VERIFY(item);
1068 QTRY_COMPARE(item->y(),20+i*20.0);
1069 }
1070
1071 // remove current item beyond visible items.
1072 listview->setCurrentIndex(20);
1073 listview->setContentY(40);
1074 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1075
1076 model.removeItem(20);
1077 QTRY_COMPARE(listview->currentIndex(), 20);
1078 QTRY_VERIFY(listview->currentItem() != nullptr);
1079
1080 // remove item before current, but visible
1081 listview->setCurrentIndex(8);
1082 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1083 oldCurrent = listview->currentItem();
1084 model.removeItem(6);
1085
1086 QTRY_COMPARE(listview->currentIndex(), 7);
1087 QTRY_COMPARE(listview->currentItem(), oldCurrent);
1088
1089 listview->setContentY(80);
1090 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1091
1092 // remove all visible items
1093 model.removeItems(1, 18);
1094 QTRY_COMPARE(listview->count() , model.count());
1095
1096 // Confirm items positioned correctly
1097 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
1098 for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
1099 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i+1);
1100 if (!item) qWarning() << "Item" << i+1 << "not found";
1101 QTRY_VERIFY(item);
1102 QTRY_COMPARE(item->y(),80+i*20.0);
1103 }
1104
1105 model.removeItems(1, 17);
1106 QTRY_COMPARE(listview->count() , model.count());
1107
1108 model.removeItems(2, 1);
1109 QTRY_COMPARE(listview->count() , model.count());
1110
1111 model.addItem("New", "1");
1112 QTRY_COMPARE(listview->count() , model.count());
1113
1114 QTRY_VERIFY(name = findItem<QQuickText>(contentItem, "textName", model.count()-1));
1115 QCOMPARE(name->text(), QString("New"));
1116
1117 // Add some more items so that we don't run out
1118 model.clear();
1119 for (int i = 0; i < 50; i++)
1120 model.addItem("Item" + QString::number(i), "");
1121
1122 // QTBUG-QTBUG-20575
1123 listview->setCurrentIndex(0);
1124 listview->setContentY(30);
1125 model.removeItem(0);
1126 QTRY_VERIFY(name = findItem<QQuickText>(contentItem, "textName", 0));
1127
1128 // QTBUG-19198 move to end and remove all visible items one at a time.
1129 listview->positionViewAtEnd();
1130 for (int i = 0; i < 18; ++i)
1131 model.removeItems(model.count() - 1, 1);
1132 QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() > 16);
1133}
1134
1135template <class T>
1136void tst_QQuickListView::removed_more(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1137{
1138 QFETCH(qreal, contentY);
1139 QFETCH(int, removeIndex);
1140 QFETCH(int, removeCount);
1141 QFETCH(qreal, itemsOffsetAfterMove);
1142
1143 QQuickView *window = getView();
1144
1145 T model;
1146 for (int i = 0; i < 30; i++)
1147 model.addItem("Item" + QString::number(i), "");
1148
1149 QQmlContext *ctxt = window->rootContext();
1150 ctxt->setContextProperty("testModel", &model);
1151
1152 QScopedPointer<TestObject> testObject(new TestObject);
1153 ctxt->setContextProperty("testObject", testObject.data());
1154
1155 window->setSource(source);
1156 window->show();
1157 QVERIFY(QTest::qWaitForWindowExposed(window));
1158
1159 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1160 QTRY_VERIFY(listview != nullptr);
1161 QQuickItem *contentItem = listview->contentItem();
1162 QTRY_VERIFY(contentItem != nullptr);
1163
1164 if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
1165 listview->setVerticalLayoutDirection(verticalLayoutDirection);
1166 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1167 contentY = -listview->height() - contentY;
1168 }
1169 listview->setContentY(contentY);
1170
1171 model.removeItems(removeIndex, removeCount);
1172 //Wait for polish (updates list to the model changes)
1173 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1174
1175 QTRY_COMPARE(listview->property("count").toInt(), model.count());
1176
1177 // check visibleItems.first() is in correct position
1178 QQuickItem *item0 = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
1179 QVERIFY(item0);
1180 QVERIFY(item0);
1181 if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1182 QCOMPARE(item0->y(), -item0->height() - itemsOffsetAfterMove);
1183 else
1184 QCOMPARE(item0->y(), itemsOffsetAfterMove);
1185
1186 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
1187 int firstVisibleIndex = -1;
1188 for (int i=0; i<items.count(); i++) {
1189 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1190 if (item && delegateVisible(item)) {
1191 firstVisibleIndex = i;
1192 break;
1193 }
1194 }
1195 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
1196
1197 // Confirm items positioned correctly and indexes correct
1198 QQuickText *name;
1199 QQuickText *number;
1200 for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
1201 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1202 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
1203 qreal pos = i*20.0 + itemsOffsetAfterMove;
1204 if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1205 pos = -item0->height() - pos;
1206 QTRY_COMPARE(item->y(), pos);
1207 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
1208 QVERIFY(name != nullptr);
1209 QTRY_COMPARE(name->text(), model.name(i));
1210 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: i);
1211 QVERIFY(number != nullptr);
1212 QTRY_COMPARE(number->text(), model.number(i));
1213 }
1214
1215 releaseView(view: window);
1216}
1217
1218void tst_QQuickListView::removed_more_data()
1219{
1220 QTest::addColumn<qreal>(name: "contentY");
1221 QTest::addColumn<int>(name: "removeIndex");
1222 QTest::addColumn<int>(name: "removeCount");
1223 QTest::addColumn<qreal>(name: "itemsOffsetAfterMove");
1224
1225 QTest::newRow(dataTag: "remove 1, before visible items")
1226 << 80.0 // show 4-19
1227 << 3 << 1
1228 << 20.0; // visible items slide down by 1 item so that first visible does not move
1229
1230 QTest::newRow(dataTag: "remove multiple, all before visible items")
1231 << 80.0
1232 << 1 << 3
1233 << 20.0 * 3;
1234
1235 QTest::newRow(dataTag: "remove multiple, all before visible items, remove item 0")
1236 << 80.0
1237 << 0 << 4
1238 << 20.0 * 4;
1239
1240 // remove 1,2,3 before the visible pos, 0 moves down to just before the visible pos,
1241 // items 4,5 are removed from view, item 6 slides up to original pos of item 4 (80px)
1242 QTest::newRow(dataTag: "remove multiple, mix of items from before and within visible items")
1243 << 80.0
1244 << 1 << 5
1245 << 20.0 * 3; // adjust for the 3 items removed before the visible
1246
1247 QTest::newRow(dataTag: "remove multiple, mix of items from before and within visible items, remove item 0")
1248 << 80.0
1249 << 0 << 6
1250 << 20.0 * 4; // adjust for the 3 items removed before the visible
1251
1252
1253 QTest::newRow(dataTag: "remove 1, from start of visible, content at start")
1254 << 0.0
1255 << 0 << 1
1256 << 0.0;
1257
1258 QTest::newRow(dataTag: "remove multiple, from start of visible, content at start")
1259 << 0.0
1260 << 0 << 3
1261 << 0.0;
1262
1263 QTest::newRow(dataTag: "remove 1, from start of visible, content not at start")
1264 << 80.0 // show 4-19
1265 << 4 << 1
1266 << 0.0;
1267
1268 QTest::newRow(dataTag: "remove multiple, from start of visible, content not at start")
1269 << 80.0 // show 4-19
1270 << 4 << 3
1271 << 0.0;
1272
1273
1274 QTest::newRow(dataTag: "remove 1, from middle of visible, content at start")
1275 << 0.0
1276 << 10 << 1
1277 << 0.0;
1278
1279 QTest::newRow(dataTag: "remove multiple, from middle of visible, content at start")
1280 << 0.0
1281 << 10 << 5
1282 << 0.0;
1283
1284 QTest::newRow(dataTag: "remove 1, from middle of visible, content not at start")
1285 << 80.0 // show 4-19
1286 << 10 << 1
1287 << 0.0;
1288
1289 QTest::newRow(dataTag: "remove multiple, from middle of visible, content not at start")
1290 << 80.0 // show 4-19
1291 << 10 << 5
1292 << 0.0;
1293
1294
1295 QTest::newRow(dataTag: "remove 1, after visible, content at start")
1296 << 0.0
1297 << 16 << 1
1298 << 0.0;
1299
1300 QTest::newRow(dataTag: "remove multiple, after visible, content at start")
1301 << 0.0
1302 << 16 << 5
1303 << 0.0;
1304
1305 QTest::newRow(dataTag: "remove 1, after visible, content not at middle")
1306 << 80.0 // show 4-19
1307 << 16+4 << 1
1308 << 0.0;
1309
1310 QTest::newRow(dataTag: "remove multiple, after visible, content not at start")
1311 << 80.0 // show 4-19
1312 << 16+4 << 5
1313 << 0.0;
1314
1315 QTest::newRow(dataTag: "remove multiple, mix of items from within and after visible items")
1316 << 80.0
1317 << 18 << 5
1318 << 0.0;
1319}
1320
1321template <class T>
1322void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1323{
1324 QScopedPointer<QQuickView> window(createView());
1325
1326 T model;
1327 for (int i = 0; i < 30; i++)
1328 model.addItem("Item" + QString::number(i), "");
1329
1330 QQmlContext *ctxt = window->rootContext();
1331 ctxt->setContextProperty("testModel", &model);
1332
1333 QScopedPointer<TestObject> testObject(new TestObject);
1334 ctxt->setContextProperty("testObject", testObject.data());
1335
1336 window->setSource(source);
1337 window->show();
1338 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
1339
1340 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1341 QTRY_VERIFY(listview != nullptr);
1342 QQuickItem *contentItem = listview->contentItem();
1343 QTRY_VERIFY(contentItem != nullptr);
1344
1345 listview->setVerticalLayoutDirection(verticalLayoutDirection);
1346 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1347
1348 model.clear();
1349
1350 QTRY_COMPARE(findItems<QQuickListView>(contentItem, "wrapper").count(), 0);
1351 QTRY_COMPARE(listview->count(), 0);
1352 QTRY_VERIFY(!listview->currentItem());
1353 if (verticalLayoutDirection == QQuickItemView::TopToBottom)
1354 QTRY_COMPARE(listview->contentY(), 0.0);
1355 else
1356 QTRY_COMPARE(listview->contentY(), -listview->height());
1357 QCOMPARE(listview->currentIndex(), -1);
1358
1359 QCOMPARE(listview->contentHeight(), 0.0);
1360
1361 // confirm sanity when adding an item to cleared list
1362 model.addItem("New", "1");
1363 listview->forceLayout();
1364 QTRY_COMPARE(listview->count(), 1);
1365 QVERIFY(listview->currentItem() != nullptr);
1366 QCOMPARE(listview->currentIndex(), 0);
1367}
1368
1369template <class T>
1370void tst_QQuickListView::moved(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1371{
1372 QFETCH(qreal, contentY);
1373 QFETCH(int, from);
1374 QFETCH(int, to);
1375 QFETCH(int, count);
1376 QFETCH(qreal, itemsOffsetAfterMove);
1377
1378 QQuickText *name;
1379 QQuickText *number;
1380 QQuickView *window = getView();
1381
1382 T model;
1383 for (int i = 0; i < 30; i++)
1384 model.addItem("Item" + QString::number(i), "");
1385
1386 QQmlContext *ctxt = window->rootContext();
1387 ctxt->setContextProperty("testModel", &model);
1388
1389 QScopedPointer<TestObject> testObject(new TestObject);
1390 ctxt->setContextProperty("testObject", testObject.data());
1391
1392 window->setSource(source);
1393 window->show();
1394 QVERIFY(QTest::qWaitForWindowExposed(window));
1395
1396 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1397 QTRY_VERIFY(listview != nullptr);
1398 QQuickItem *contentItem = listview->contentItem();
1399 QTRY_VERIFY(contentItem != nullptr);
1400
1401 // always need to wait for view to be painted before the first move()
1402 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1403
1404 bool waitForPolish = (contentY != 0);
1405 if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
1406 listview->setVerticalLayoutDirection(verticalLayoutDirection);
1407 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1408 contentY = -listview->height() - contentY;
1409 }
1410 listview->setContentY(contentY);
1411 if (waitForPolish)
1412 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1413
1414 model.moveItems(from, to, count);
1415 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1416
1417 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
1418 int firstVisibleIndex = -1;
1419 for (int i=0; i<items.count(); i++) {
1420 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1421 if (item && delegateVisible(item)) {
1422 firstVisibleIndex = i;
1423 break;
1424 }
1425 }
1426 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
1427
1428 // Confirm items positioned correctly and indexes correct
1429 for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
1430 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1431 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
1432 qreal pos = i*20.0 + itemsOffsetAfterMove;
1433 if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1434 pos = -item->height() - pos;
1435 QTRY_COMPARE(item->y(), pos);
1436 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
1437 QVERIFY(name != nullptr);
1438 QTRY_COMPARE(name->text(), model.name(i));
1439 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: i);
1440 QVERIFY(number != nullptr);
1441 QTRY_COMPARE(number->text(), model.number(i));
1442
1443 // current index should have been updated
1444 if (item == listview->currentItem())
1445 QTRY_COMPARE(listview->currentIndex(), i);
1446 }
1447
1448 releaseView(view: window);
1449}
1450
1451void tst_QQuickListView::moved_data()
1452{
1453 QTest::addColumn<qreal>(name: "contentY");
1454 QTest::addColumn<int>(name: "from");
1455 QTest::addColumn<int>(name: "to");
1456 QTest::addColumn<int>(name: "count");
1457 QTest::addColumn<qreal>(name: "itemsOffsetAfterMove");
1458
1459 // model starts with 30 items, each 20px high, in area 320px high
1460 // 16 items should be visible at a time
1461 // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
1462
1463 QTest::newRow(dataTag: "move 1 forwards, within visible items")
1464 << 0.0
1465 << 1 << 4 << 1
1466 << 0.0;
1467
1468 QTest::newRow(dataTag: "move 1 forwards, from non-visible -> visible")
1469 << 80.0 // show 4-19
1470 << 1 << 18 << 1
1471 << 20.0; // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement
1472
1473 QTest::newRow(dataTag: "move 1 forwards, from non-visible -> visible (move first item)")
1474 << 80.0 // show 4-19
1475 << 0 << 4 << 1
1476 << 20.0; // first item has moved to below item4, everything drops down by size of 1 item
1477
1478 QTest::newRow(dataTag: "move 1 forwards, from visible -> non-visible")
1479 << 0.0
1480 << 1 << 16 << 1
1481 << 0.0;
1482
1483 QTest::newRow(dataTag: "move 1 forwards, from visible -> non-visible (move first item)")
1484 << 0.0
1485 << 0 << 16 << 1
1486 << 0.0;
1487
1488
1489 QTest::newRow(dataTag: "move 1 backwards, within visible items")
1490 << 0.0
1491 << 4 << 1 << 1
1492 << 0.0;
1493
1494 QTest::newRow(dataTag: "move 1 backwards, within visible items (to first index)")
1495 << 0.0
1496 << 4 << 0 << 1
1497 << 0.0;
1498
1499 QTest::newRow(dataTag: "move 1 backwards, from non-visible -> visible")
1500 << 0.0
1501 << 20 << 4 << 1
1502 << 0.0;
1503
1504 QTest::newRow(dataTag: "move 1 backwards, from non-visible -> visible (move last item)")
1505 << 0.0
1506 << 29 << 15 << 1
1507 << 0.0;
1508
1509 QTest::newRow(dataTag: "move 1 backwards, from visible -> non-visible")
1510 << 80.0 // show 4-19
1511 << 16 << 1 << 1
1512 << -20.0; // to minimize movement, item 0 moves to -20, and other items do not move
1513
1514 QTest::newRow(dataTag: "move 1 backwards, from visible -> non-visible (move first item)")
1515 << 80.0 // show 4-19
1516 << 16 << 0 << 1
1517 << -20.0; // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move
1518
1519
1520 QTest::newRow(dataTag: "move multiple forwards, within visible items")
1521 << 0.0
1522 << 0 << 5 << 3
1523 << 0.0;
1524
1525 QTest::newRow(dataTag: "move multiple forwards, before visible items")
1526 << 140.0 // show 7-22
1527 << 4 << 5 << 3 // 4,5,6 move to below 7
1528 << 20.0 * 3; // 4,5,6 moved down
1529
1530 QTest::newRow(dataTag: "move multiple forwards, from non-visible -> visible")
1531 << 80.0 // show 4-19
1532 << 1 << 5 << 3
1533 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1534
1535 QTest::newRow(dataTag: "move multiple forwards, from non-visible -> visible (move first item)")
1536 << 80.0 // show 4-19
1537 << 0 << 5 << 3
1538 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1539
1540 QTest::newRow(dataTag: "move multiple forwards, mix of non-visible/visible")
1541 << 40.0
1542 << 1 << 16 << 2
1543 << 20.0; // item 1,2 are removed, item 3 is now first visible
1544
1545 QTest::newRow(dataTag: "move multiple forwards, to bottom of view")
1546 << 0.0
1547 << 5 << 13 << 3
1548 << 0.0;
1549
1550 QTest::newRow(dataTag: "move multiple forwards, to bottom of view, first->last")
1551 << 0.0
1552 << 0 << 13 << 3
1553 << 0.0;
1554
1555 QTest::newRow(dataTag: "move multiple forwards, to bottom of view, content y not 0")
1556 << 80.0
1557 << 5+4 << 13+4 << 3
1558 << 0.0;
1559
1560 QTest::newRow(dataTag: "move multiple forwards, from visible -> non-visible")
1561 << 0.0
1562 << 1 << 16 << 3
1563 << 0.0;
1564
1565 QTest::newRow(dataTag: "move multiple forwards, from visible -> non-visible (move first item)")
1566 << 0.0
1567 << 0 << 16 << 3
1568 << 0.0;
1569
1570
1571 QTest::newRow(dataTag: "move multiple backwards, within visible items")
1572 << 0.0
1573 << 4 << 1 << 3
1574 << 0.0;
1575
1576 QTest::newRow(dataTag: "move multiple backwards, within visible items (move first item)")
1577 << 0.0
1578 << 10 << 0 << 3
1579 << 0.0;
1580
1581 QTest::newRow(dataTag: "move multiple backwards, from non-visible -> visible")
1582 << 0.0
1583 << 20 << 4 << 3
1584 << 0.0;
1585
1586 QTest::newRow(dataTag: "move multiple backwards, from non-visible -> visible (move last item)")
1587 << 0.0
1588 << 27 << 10 << 3
1589 << 0.0;
1590
1591 QTest::newRow(dataTag: "move multiple backwards, from visible -> non-visible")
1592 << 80.0 // show 4-19
1593 << 16 << 1 << 3
1594 << -20.0 * 3; // to minimize movement, 0 moves by -60, and other items do not move
1595
1596 QTest::newRow(dataTag: "move multiple backwards, from visible -> non-visible (move first item)")
1597 << 80.0 // show 4-19
1598 << 16 << 0 << 3
1599 << -20.0 * 3; // to minimize movement, 16,17,18 move to above item 0, and other items do not move
1600}
1601
1602void tst_QQuickListView::multipleChanges(bool condensed)
1603{
1604 QFETCH(int, startCount);
1605 QFETCH(QList<ListChange>, changes);
1606 QFETCH(int, newCount);
1607 QFETCH(int, newCurrentIndex);
1608
1609 QQuickView *window = getView();
1610
1611 QaimModel model;
1612 for (int i = 0; i < startCount; i++)
1613 model.addItem(name: "Item" + QString::number(i), number: "");
1614
1615 QQmlContext *ctxt = window->rootContext();
1616 ctxt->setContextProperty("testModel", &model);
1617
1618 QScopedPointer<TestObject> testObject(new TestObject);
1619 ctxt->setContextProperty("testObject", testObject.data());
1620
1621 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
1622 window->show();
1623 QVERIFY(QTest::qWaitForWindowExposed(window));
1624
1625 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1626 QTRY_VERIFY(listview != nullptr);
1627 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1628
1629 for (int i=0; i<changes.count(); i++) {
1630 switch (changes[i].type) {
1631 case ListChange::Inserted:
1632 {
1633 QList<QPair<QString, QString> > items;
1634 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
1635 items << qMakePair(x: QString("new item %1").arg(a: j), y: QString::number(j));
1636 model.insertItems(index: changes[i].index, items);
1637 break;
1638 }
1639 case ListChange::Removed:
1640 model.removeItems(index: changes[i].index, count: changes[i].count);
1641 break;
1642 case ListChange::Moved:
1643 model.moveItems(from: changes[i].index, to: changes[i].to, count: changes[i].count);
1644 break;
1645 case ListChange::SetCurrent:
1646 listview->setCurrentIndex(changes[i].index);
1647 break;
1648 case ListChange::SetContentY:
1649 listview->setContentY(changes[i].pos);
1650 break;
1651 default:
1652 continue;
1653 }
1654 if (!condensed) {
1655 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1656 }
1657 }
1658 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1659
1660 QCOMPARE(listview->count(), newCount);
1661 QCOMPARE(listview->count(), model.count());
1662 QCOMPARE(listview->currentIndex(), newCurrentIndex);
1663
1664 QQuickText *name;
1665 QQuickText *number;
1666 QQuickItem *contentItem = listview->contentItem();
1667 QTRY_VERIFY(contentItem != nullptr);
1668 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
1669 for (int i=0; i < model.count() && i < itemCount; ++i) {
1670 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
1671 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
1672 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
1673 QVERIFY(name != nullptr);
1674 QTRY_COMPARE(name->text(), model.name(i));
1675 number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: i);
1676 QVERIFY(number != nullptr);
1677 QTRY_COMPARE(number->text(), model.number(i));
1678 }
1679
1680 releaseView(view: window);
1681}
1682
1683void tst_QQuickListView::multipleChanges_data()
1684{
1685 QTest::addColumn<int>(name: "startCount");
1686 QTest::addColumn<QList<ListChange> >(name: "changes");
1687 QTest::addColumn<int>(name: "newCount");
1688 QTest::addColumn<int>(name: "newCurrentIndex");
1689
1690 QList<ListChange> changes;
1691
1692 for (int i=1; i<30; i++)
1693 changes << ListChange::remove(index: 0);
1694 QTest::newRow(dataTag: "remove all but 1, first->last") << 30 << changes << 1 << 0;
1695
1696 changes << ListChange::remove(index: 0);
1697 QTest::newRow(dataTag: "remove all") << 30 << changes << 0 << -1;
1698
1699 changes.clear();
1700 changes << ListChange::setCurrent(29);
1701 for (int i=29; i>0; i--)
1702 changes << ListChange::remove(index: i);
1703 QTest::newRow(dataTag: "remove last (current) -> first") << 30 << changes << 1 << 0;
1704
1705 QTest::newRow(dataTag: "remove then insert at 0") << 10 << (QList<ListChange>()
1706 << ListChange::remove(index: 0, count: 1)
1707 << ListChange::insert(index: 0, count: 1)
1708 ) << 10 << 1;
1709
1710 QTest::newRow(dataTag: "remove then insert at non-zero index") << 10 << (QList<ListChange>()
1711 << ListChange::setCurrent(2)
1712 << ListChange::remove(index: 2, count: 1)
1713 << ListChange::insert(index: 2, count: 1)
1714 ) << 10 << 3;
1715
1716 QTest::newRow(dataTag: "remove current then insert below it") << 10 << (QList<ListChange>()
1717 << ListChange::setCurrent(1)
1718 << ListChange::remove(index: 1, count: 3)
1719 << ListChange::insert(index: 2, count: 2)
1720 ) << 9 << 1;
1721
1722 QTest::newRow(dataTag: "remove current index then move it down") << 10 << (QList<ListChange>()
1723 << ListChange::setCurrent(2)
1724 << ListChange::remove(index: 1, count: 3)
1725 << ListChange::move(index: 1, to: 5, count: 1)
1726 ) << 7 << 5;
1727
1728 QTest::newRow(dataTag: "remove current index then move it up") << 10 << (QList<ListChange>()
1729 << ListChange::setCurrent(5)
1730 << ListChange::remove(index: 4, count: 3)
1731 << ListChange::move(index: 4, to: 1, count: 1)
1732 ) << 7 << 1;
1733
1734
1735 QTest::newRow(dataTag: "insert multiple times") << 0 << (QList<ListChange>()
1736 << ListChange::insert(index: 0, count: 2)
1737 << ListChange::insert(index: 0, count: 4)
1738 << ListChange::insert(index: 0, count: 6)
1739 ) << 12 << 10;
1740
1741 QTest::newRow(dataTag: "insert multiple times with current index changes") << 0 << (QList<ListChange>()
1742 << ListChange::insert(index: 0, count: 2)
1743 << ListChange::insert(index: 0, count: 4)
1744 << ListChange::insert(index: 0, count: 6)
1745 << ListChange::setCurrent(3)
1746 << ListChange::insert(index: 3, count: 2)
1747 ) << 14 << 5;
1748
1749 QTest::newRow(dataTag: "insert and remove all") << 0 << (QList<ListChange>()
1750 << ListChange::insert(index: 0, count: 30)
1751 << ListChange::remove(index: 0, count: 30)
1752 ) << 0 << -1;
1753
1754 QTest::newRow(dataTag: "insert and remove current") << 30 << (QList<ListChange>()
1755 << ListChange::insert(index: 1)
1756 << ListChange::setCurrent(1)
1757 << ListChange::remove(index: 1)
1758 ) << 30 << 1;
1759
1760 QTest::newRow(dataTag: "insert before 0, then remove cross section of new and old items") << 10 << (QList<ListChange>()
1761 << ListChange::insert(index: 0, count: 10)
1762 << ListChange::remove(index: 5, count: 10)
1763 ) << 10 << 5;
1764
1765 QTest::newRow(dataTag: "insert multiple, then move new items to end") << 10 << (QList<ListChange>()
1766 << ListChange::insert(index: 0, count: 3)
1767 << ListChange::move(index: 0, to: 10, count: 3)
1768 ) << 13 << 0;
1769
1770 QTest::newRow(dataTag: "insert multiple, then move new and some old items to end") << 10 << (QList<ListChange>()
1771 << ListChange::insert(index: 0, count: 3)
1772 << ListChange::move(index: 0, to: 8, count: 5)
1773 ) << 13 << 11;
1774
1775 QTest::newRow(dataTag: "insert multiple at end, then move new and some old items to start") << 10 << (QList<ListChange>()
1776 << ListChange::setCurrent(9)
1777 << ListChange::insert(index: 10, count: 3)
1778 << ListChange::move(index: 8, to: 0, count: 5)
1779 ) << 13 << 1;
1780
1781
1782 QTest::newRow(dataTag: "move back and forth to same index") << 10 << (QList<ListChange>()
1783 << ListChange::setCurrent(1)
1784 << ListChange::move(index: 1, to: 2, count: 2)
1785 << ListChange::move(index: 2, to: 1, count: 2)
1786 ) << 10 << 1;
1787
1788 QTest::newRow(dataTag: "move forwards then back") << 10 << (QList<ListChange>()
1789 << ListChange::setCurrent(2)
1790 << ListChange::move(index: 1, to: 2, count: 3)
1791 << ListChange::move(index: 3, to: 0, count: 5)
1792 ) << 10 << 0;
1793
1794 QTest::newRow(dataTag: "move current, then remove it") << 10 << (QList<ListChange>()
1795 << ListChange::setCurrent(5)
1796 << ListChange::move(index: 5, to: 0, count: 1)
1797 << ListChange::remove(index: 0)
1798 ) << 9 << 0;
1799
1800 QTest::newRow(dataTag: "move current, then insert before it") << 10 << (QList<ListChange>()
1801 << ListChange::setCurrent(5)
1802 << ListChange::move(index: 5, to: 0, count: 1)
1803 << ListChange::insert(index: 0)
1804 ) << 11 << 1;
1805
1806 QTest::newRow(dataTag: "move multiple, then remove them") << 10 << (QList<ListChange>()
1807 << ListChange::setCurrent(1)
1808 << ListChange::move(index: 5, to: 1, count: 3)
1809 << ListChange::remove(index: 1, count: 3)
1810 ) << 7 << 1;
1811
1812 QTest::newRow(dataTag: "move multiple, then insert before them") << 10 << (QList<ListChange>()
1813 << ListChange::setCurrent(5)
1814 << ListChange::move(index: 5, to: 1, count: 3)
1815 << ListChange::insert(index: 1, count: 5)
1816 ) << 15 << 6;
1817
1818 QTest::newRow(dataTag: "move multiple, then insert after them") << 10 << (QList<ListChange>()
1819 << ListChange::setCurrent(3)
1820 << ListChange::move(index: 0, to: 1, count: 2)
1821 << ListChange::insert(index: 3, count: 5)
1822 ) << 15 << 8;
1823
1824 QTest::newRow(dataTag: "clear current") << 0 << (QList<ListChange>()
1825 << ListChange::insert(index: 0, count: 5)
1826 << ListChange::setCurrent(-1)
1827 << ListChange::remove(index: 0, count: 5)
1828 << ListChange::insert(index: 0, count: 5)
1829 ) << 5 << -1;
1830
1831 QTest::newRow(dataTag: "remove, scroll") << 30 << (QList<ListChange>()
1832 << ListChange::remove(index: 20, count: 5)
1833 << ListChange::setContentY(20)
1834 ) << 25 << 0;
1835
1836 QTest::newRow(dataTag: "insert, scroll") << 10 << (QList<ListChange>()
1837 << ListChange::insert(index: 9, count: 5)
1838 << ListChange::setContentY(20)
1839 ) << 15 << 0;
1840
1841 QTest::newRow(dataTag: "move, scroll") << 20 << (QList<ListChange>()
1842 << ListChange::move(index: 15, to: 8, count: 3)
1843 << ListChange::setContentY(0)
1844 ) << 20 << 0;
1845
1846 QTest::newRow(dataTag: "clear, insert, scroll") << 30 << (QList<ListChange>()
1847 << ListChange::setContentY(20)
1848 << ListChange::remove(index: 0, count: 30)
1849 << ListChange::insert(index: 0, count: 2)
1850 << ListChange::setContentY(0)
1851 ) << 2 << 0;
1852}
1853
1854void tst_QQuickListView::swapWithFirstItem()
1855{
1856 QScopedPointer<QQuickView> window(createView());
1857
1858 QaimModel model;
1859 for (int i = 0; i < 30; i++)
1860 model.addItem(name: "Item" + QString::number(i), number: "");
1861
1862 QQmlContext *ctxt = window->rootContext();
1863 ctxt->setContextProperty("testModel", &model);
1864
1865 QScopedPointer<TestObject> testObject(new TestObject);
1866 ctxt->setContextProperty("testObject", testObject.data());
1867
1868 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
1869 window->show();
1870 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
1871
1872 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1873 QTRY_VERIFY(listview != nullptr);
1874 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1875
1876 // ensure content position is stable
1877 listview->setContentY(0);
1878 model.moveItem(from: 1, to: 0);
1879 QTRY_COMPARE(listview->contentY(), qreal(0));
1880}
1881
1882void tst_QQuickListView::checkCountForMultiColumnModels()
1883{
1884 // Check that a list view will only load items for the first
1885 // column, even if the model reports that it got several columns.
1886 // We test this since QQmlDelegateModel has been changed to
1887 // also understand multi-column models, but this should not affect ListView.
1888 QScopedPointer<QQuickView> window(createView());
1889
1890 const int rowCount = 10;
1891 const int columnCount = 10;
1892
1893 QaimModel model;
1894 model.columns = columnCount;
1895 for (int i = 0; i < rowCount; i++)
1896 model.addItem(name: "Item" + QString::number(i), number: "");
1897
1898 QQmlContext *ctxt = window->rootContext();
1899 ctxt->setContextProperty("testModel", &model);
1900
1901 QScopedPointer<TestObject> testObject(new TestObject);
1902 ctxt->setContextProperty("testObject", testObject.data());
1903
1904 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
1905 window->show();
1906 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
1907
1908 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1909 QTRY_VERIFY(listview != nullptr);
1910
1911 QCOMPARE(listview->count(), rowCount);
1912}
1913
1914void tst_QQuickListView::enforceRange()
1915{
1916 QScopedPointer<QQuickView> window(createView());
1917
1918 QaimModel model;
1919 for (int i = 0; i < 30; i++)
1920 model.addItem(name: "Item" + QString::number(i), number: "");
1921
1922 QQmlContext *ctxt = window->rootContext();
1923 ctxt->setContextProperty("testModel", &model);
1924
1925 window->setSource(testFileUrl(fileName: "listview-enforcerange.qml"));
1926 window->show();
1927 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
1928
1929 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
1930 QTRY_VERIFY(listview != nullptr);
1931
1932 QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1933 QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1934 QTRY_COMPARE(listview->highlightRangeMode(), QQuickListView::StrictlyEnforceRange);
1935 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
1936
1937 QQuickItem *contentItem = listview->contentItem();
1938 QTRY_VERIFY(contentItem != nullptr);
1939
1940 // view should be positioned at the top of the range.
1941 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
1942 QTRY_VERIFY(item);
1943 QTRY_COMPARE(listview->contentY(), -100.0);
1944
1945 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: 0);
1946 QTRY_VERIFY(name != nullptr);
1947 QTRY_COMPARE(name->text(), model.name(0));
1948 QQuickText *number = findItem<QQuickText>(parent: contentItem, objectName: "textNumber", index: 0);
1949 QTRY_VERIFY(number != nullptr);
1950 QTRY_COMPARE(number->text(), model.number(0));
1951
1952 // Check currentIndex is updated when contentItem moves
1953 listview->setContentY(20);
1954
1955 QTRY_COMPARE(listview->currentIndex(), 6);
1956
1957 // Test for [QTBUG-77418] {
1958 // explicit set current index
1959 listview->setCurrentIndex(5);
1960 QTRY_COMPARE(listview->contentY(), 0);
1961
1962 // then check if contentY changes if the highlight range is changed
1963 listview->setPreferredHighlightBegin(80);
1964 listview->setPreferredHighlightEnd(80);
1965 QTRY_COMPARE(listview->contentY(), 20);
1966
1967 // verify that current index does not change with no highlight
1968 listview->setHighlightRangeMode(QQuickListView::NoHighlightRange);
1969 listview->setContentY(100);
1970 QTRY_COMPARE(listview->currentIndex(), 5);
1971
1972 // explicit set current index, contentY should not change now
1973 listview->setCurrentIndex(6);
1974 QTRY_COMPARE(listview->contentY(), 100);
1975 QTest::qWait(ms: 50); // This was needed in order to reproduce a failure for the following test
1976
1977 // verify that contentY changes if we turn on highlight again
1978 listview->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
1979 QTRY_COMPARE(listview->contentY(), 40);
1980 // } Test for [QTBUG-77418]
1981
1982 // change model
1983 QaimModel model2;
1984 for (int i = 0; i < 5; i++)
1985 model2.addItem(name: "Item" + QString::number(i), number: "");
1986
1987 ctxt->setContextProperty("testModel", &model2);
1988 QCOMPARE(listview->count(), 5);
1989}
1990
1991void tst_QQuickListView::enforceRange_withoutHighlight()
1992{
1993 // QTBUG-20287
1994 // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1995 // to the correct position (i.e. to the next/previous item, not next/previous section)
1996 // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1997
1998 QScopedPointer<QQuickView> window(createView());
1999
2000 QaimModel model;
2001 model.addItem(name: "Item 0", number: "a");
2002 model.addItem(name: "Item 1", number: "b");
2003 model.addItem(name: "Item 2", number: "b");
2004 model.addItem(name: "Item 3", number: "c");
2005
2006 QQmlContext *ctxt = window->rootContext();
2007 ctxt->setContextProperty("testModel", &model);
2008
2009 window->setSource(testFileUrl(fileName: "listview-enforcerange-nohighlight.qml"));
2010 window->show();
2011 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2012
2013 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2014 QTRY_VERIFY(listview != nullptr);
2015 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2016
2017 qreal expectedPos = -100.0;
2018
2019 expectedPos += 10.0; // scroll past 1st section's delegate (10px height)
2020 QTRY_COMPARE(listview->contentY(), expectedPos);
2021
2022 expectedPos += 20 + 10; // scroll past 1st section and section delegate of 2nd section
2023 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
2024
2025 QTRY_COMPARE(listview->contentY(), expectedPos);
2026
2027 expectedPos += 20; // scroll past 1st item of 2nd section
2028 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
2029 QTRY_COMPARE(listview->contentY(), expectedPos);
2030
2031 expectedPos += 20 + 10; // scroll past 2nd item of 2nd section and section delegate of 3rd section
2032 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
2033 QTRY_COMPARE(listview->contentY(), expectedPos);
2034}
2035
2036void tst_QQuickListView::spacing()
2037{
2038 QScopedPointer<QQuickView> window(createView());
2039
2040 QaimModel model;
2041 for (int i = 0; i < 30; i++)
2042 model.addItem(name: "Item" + QString::number(i), number: "");
2043
2044 QQmlContext *ctxt = window->rootContext();
2045 ctxt->setContextProperty("testModel", &model);
2046
2047 QScopedPointer<TestObject> testObject(new TestObject);
2048 ctxt->setContextProperty("testObject", testObject.data());
2049
2050 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
2051 window->show();
2052 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2053
2054 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2055 QTRY_VERIFY(listview != nullptr);
2056
2057 QQuickItem *contentItem = listview->contentItem();
2058 QTRY_VERIFY(contentItem != nullptr);
2059 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2060
2061 // Confirm items positioned correctly
2062 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
2063 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2064 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2065 if (!item) qWarning() << "Item" << i << "not found";
2066 QTRY_VERIFY(item);
2067 QTRY_COMPARE(item->y(), qreal(i*20));
2068 }
2069
2070 listview->setSpacing(10);
2071 QTRY_COMPARE(listview->spacing(), qreal(10));
2072
2073 // Confirm items positioned correctly
2074 QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() == 11);
2075 for (int i = 0; i < 11; ++i) {
2076 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2077 if (!item) qWarning() << "Item" << i << "not found";
2078 QTRY_VERIFY(item);
2079 QTRY_COMPARE(item->y(), qreal(i*30));
2080 }
2081
2082 listview->setSpacing(0);
2083
2084 // Confirm items positioned correctly
2085 QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() >= 16);
2086 for (int i = 0; i < 16; ++i) {
2087 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2088 if (!item) qWarning() << "Item" << i << "not found";
2089 QTRY_VERIFY(item);
2090 QTRY_COMPARE(item->y(), i*20.0);
2091 }
2092}
2093
2094template <typename T>
2095void tst_QQuickListView::sections(const QUrl &source)
2096{
2097 QScopedPointer<QQuickView> window(createView());
2098
2099 T model;
2100 for (int i = 0; i < 30; i++)
2101 model.addItem("Item" + QString::number(i), QString::number(i/5));
2102
2103 QQmlContext *ctxt = window->rootContext();
2104 ctxt->setContextProperty("testModel", &model);
2105
2106 window->setSource(source);
2107 window->show();
2108 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2109
2110 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2111 QTRY_VERIFY(listview != nullptr);
2112
2113 QQuickItem *contentItem = listview->contentItem();
2114 QTRY_VERIFY(contentItem != nullptr);
2115
2116 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2117
2118 // Confirm items positioned correctly
2119 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
2120 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2121 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2122 QVERIFY(item);
2123 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
2124 QQuickText *next = findItem<QQuickText>(parent: item, objectName: "nextSection");
2125 QCOMPARE(next->text().toInt(), (i+1)/5);
2126 }
2127
2128 QVERIFY(!listview->property("sectionsInvalidOnCompletion").toBool());
2129
2130 QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
2131
2132 // Remove section boundary
2133 model.removeItem(5);
2134 listview->forceLayout();
2135 QTRY_COMPARE(listview->count(), model.count());
2136
2137 // New section header created
2138 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 5);
2139 QTRY_VERIFY(item);
2140 QTRY_COMPARE(item->height(), 40.0);
2141
2142 model.insertItem(3, "New Item", "0");
2143 listview->forceLayout();
2144 QTRY_COMPARE(listview->count(), model.count());
2145
2146 // Section header moved
2147 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 5);
2148 QTRY_VERIFY(item);
2149 QTRY_COMPARE(item->height(), 20.0);
2150
2151 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 6);
2152 QTRY_VERIFY(item);
2153 QTRY_COMPARE(item->height(), 40.0);
2154
2155 // insert item which will become a section header
2156 model.insertItem(6, "Replace header", "1");
2157 listview->forceLayout();
2158 QTRY_COMPARE(listview->count(), model.count());
2159
2160 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 6);
2161 QTRY_VERIFY(item);
2162 QTRY_COMPARE(item->height(), 40.0);
2163
2164 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 7);
2165 QTRY_VERIFY(item);
2166 QTRY_COMPARE(item->height(), 20.0);
2167
2168 QTRY_COMPARE(listview->currentSection(), QString("0"));
2169
2170 listview->setContentY(140);
2171 QTRY_COMPARE(listview->currentSection(), QString("1"));
2172
2173 QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
2174
2175 listview->setContentY(20);
2176 QTRY_COMPARE(listview->currentSection(), QString("0"));
2177
2178 QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
2179
2180 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 1);
2181 QTRY_VERIFY(item);
2182 QTRY_COMPARE(item->height(), 20.0);
2183
2184 // check that headers change when item changes
2185 listview->setContentY(0);
2186 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2187 model.modifyItem(0, "changed", "2");
2188 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2189
2190 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 1);
2191 QTRY_VERIFY(item);
2192 QTRY_COMPARE(item->height(), 40.0);
2193}
2194
2195void tst_QQuickListView::sectionsDelegate_data()
2196{
2197 QTest::addColumn<QUrl>(name: "path");
2198 QTest::addRow(format: "implicit") << testFileUrl(fileName: "listview-sections_delegate.qml");
2199 QTest::addRow(format: "required") << testFileUrl(fileName: "listview-sections_delegate_required.qml");
2200}
2201
2202void tst_QQuickListView::sectionsDelegate()
2203{
2204 QFETCH(QUrl, path);
2205
2206 QScopedPointer<QQuickView> window(createView());
2207
2208 QaimModel model;
2209 for (int i = 0; i < 30; i++)
2210 model.addItem(name: "Item" + QString::number(i), number: QString::number(i/5));
2211
2212 QQmlContext *ctxt = window->rootContext();
2213 ctxt->setContextProperty("testModel", &model);
2214
2215 window->setSource(path);
2216 window->show();
2217 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2218
2219 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2220 QTRY_VERIFY(listview != nullptr);
2221
2222 QQuickItem *contentItem = listview->contentItem();
2223 QTRY_VERIFY(contentItem != nullptr);
2224
2225 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2226
2227 // Confirm items positioned correctly
2228 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
2229 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2230 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2231 QTRY_VERIFY(item);
2232 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
2233 QQuickText *next = findItem<QQuickText>(parent: item, objectName: "nextSection");
2234 QCOMPARE(next->text().toInt(), (i+1)/5);
2235 }
2236
2237 for (int i = 0; i < 3; ++i) {
2238 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "sect_" + QString::number(i));
2239 QVERIFY(item);
2240 QTRY_COMPARE(item->y(), qreal(i*20*6));
2241 }
2242
2243 // change section
2244 model.modifyItem(idx: 0, name: "One", number: "aaa");
2245 model.modifyItem(idx: 1, name: "Two", number: "aaa");
2246 model.modifyItem(idx: 2, name: "Three", number: "aaa");
2247 model.modifyItem(idx: 3, name: "Four", number: "aaa");
2248 model.modifyItem(idx: 4, name: "Five", number: "aaa");
2249 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2250
2251 for (int i = 0; i < 3; ++i) {
2252 QQuickItem *item = findItem<QQuickItem>(parent: contentItem,
2253 objectName: "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2254 QVERIFY(item);
2255 QTRY_COMPARE(item->y(), qreal(i*20*6));
2256 }
2257
2258 // remove section boundary
2259 model.removeItem(index: 5);
2260 listview->forceLayout();
2261 QTRY_COMPARE(listview->count(), model.count());
2262 for (int i = 0; i < 3; ++i) {
2263 QQuickItem *item = findItem<QQuickItem>(parent: contentItem,
2264 objectName: "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2265 QVERIFY(item);
2266 }
2267
2268 // QTBUG-17606
2269 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "sect_1");
2270 QCOMPARE(items.count(), 1);
2271
2272 // QTBUG-17759
2273 model.modifyItem(idx: 0, name: "One", number: "aaa");
2274 model.modifyItem(idx: 1, name: "One", number: "aaa");
2275 model.modifyItem(idx: 2, name: "One", number: "aaa");
2276 model.modifyItem(idx: 3, name: "Four", number: "aaa");
2277 model.modifyItem(idx: 4, name: "Four", number: "aaa");
2278 model.modifyItem(idx: 5, name: "Four", number: "aaa");
2279 model.modifyItem(idx: 6, name: "Five", number: "aaa");
2280 model.modifyItem(idx: 7, name: "Five", number: "aaa");
2281 model.modifyItem(idx: 8, name: "Five", number: "aaa");
2282 model.modifyItem(idx: 9, name: "Two", number: "aaa");
2283 model.modifyItem(idx: 10, name: "Two", number: "aaa");
2284 model.modifyItem(idx: 11, name: "Two", number: "aaa");
2285 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2286 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "sect_aaa").count(), 1);
2287 window->rootObject()->setProperty(name: "sectionProperty", value: "name");
2288 // ensure view has settled.
2289 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "sect_Four").count(), 1);
2290 for (int i = 0; i < 4; ++i) {
2291 QQuickItem *item = findItem<QQuickItem>(parent: contentItem,
2292 objectName: "sect_" + model.name(index: i*3));
2293 QVERIFY(item);
2294 QTRY_COMPARE(item->y(), qreal(i*20*4));
2295 }
2296}
2297
2298void tst_QQuickListView::sectionsDragOutsideBounds_data()
2299{
2300 QTest::addColumn<int>(name: "distance");
2301 QTest::addColumn<int>(name: "cacheBuffer");
2302
2303 QTest::newRow(dataTag: "500, no cache buffer") << 500 << 0;
2304 QTest::newRow(dataTag: "1000, no cache buffer") << 1000 << 0;
2305 QTest::newRow(dataTag: "500, cache buffer") << 500 << 320;
2306 QTest::newRow(dataTag: "1000, cache buffer") << 1000 << 320;
2307}
2308
2309void tst_QQuickListView::sectionsDragOutsideBounds()
2310{
2311 QFETCH(int, distance);
2312 QFETCH(int, cacheBuffer);
2313
2314 QQuickView *window = getView();
2315 QQuickViewTestUtil::moveMouseAway(window);
2316
2317 QaimModel model;
2318 for (int i = 0; i < 10; i++)
2319 model.addItem(name: "Item" + QString::number(i), number: QString::number(i/5));
2320
2321 QQmlContext *ctxt = window->rootContext();
2322 ctxt->setContextProperty("testModel", &model);
2323
2324 window->setSource(testFileUrl(fileName: "listview-sections_delegate.qml"));
2325 window->show();
2326 QVERIFY(QTest::qWaitForWindowExposed(window));
2327
2328 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2329 QTRY_VERIFY(listview != nullptr);
2330 listview->setCacheBuffer(cacheBuffer);
2331
2332 QQuickItem *contentItem = listview->contentItem();
2333 QTRY_VERIFY(contentItem != nullptr);
2334
2335 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2336
2337 // QTBUG-17769
2338 // Drag view up beyond bounds
2339 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(20,20));
2340 QTest::mouseMove(window, pos: QPoint(20,0));
2341 QTest::mouseMove(window, pos: QPoint(20,-50));
2342 QTest::mouseMove(window, pos: QPoint(20,-distance));
2343 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(20,-distance));
2344 // view should settle back at 0
2345 QTRY_COMPARE(listview->contentY(), 0.0);
2346
2347 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(20,0));
2348 QTest::mouseMove(window, pos: QPoint(20,20));
2349 QTest::mouseMove(window, pos: QPoint(20,70));
2350 QTest::mouseMove(window, pos: QPoint(20,distance));
2351
2352 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(20,distance));
2353 // view should settle back at 0
2354 QTRY_COMPARE(listview->contentY(), 0.0);
2355
2356 releaseView(view: window);
2357}
2358
2359void tst_QQuickListView::sectionsDelegate_headerVisibility()
2360{
2361 QSKIP("QTBUG-24395");
2362
2363 QScopedPointer<QQuickView> window(createView());
2364
2365 QaimModel model;
2366 for (int i = 0; i < 30; i++)
2367 model.addItem(name: "Item" + QString::number(i), number: QString::number(i/5));
2368
2369 window->rootContext()->setContextProperty("testModel", &model);
2370 window->setSource(testFileUrl(fileName: "listview-sections_delegate.qml"));
2371 window->show();
2372 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2373 window->requestActivate();
2374 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2375
2376 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2377 QTRY_VERIFY(listview != nullptr);
2378
2379 QQuickItem *contentItem = listview->contentItem();
2380 QTRY_VERIFY(contentItem != nullptr);
2381 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2382
2383 // ensure section header is maintained in view
2384 listview->setCurrentIndex(20);
2385 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2386 QTRY_VERIFY(qFuzzyCompare(listview->contentY(), 200.0));
2387 QTRY_VERIFY(!listview->isMoving());
2388 listview->setCurrentIndex(0);
2389 QTRY_VERIFY(qFuzzyIsNull(listview->contentY()));
2390}
2391
2392void tst_QQuickListView::sectionsPositioning()
2393{
2394 QScopedPointer<QQuickView> window(createView());
2395
2396 QaimModel model;
2397 for (int i = 0; i < 30; i++)
2398 model.addItem(name: "Item" + QString::number(i), number: QString::number(i/5));
2399
2400 QQmlContext *ctxt = window->rootContext();
2401 ctxt->setContextProperty("testModel", &model);
2402
2403 window->setSource(testFileUrl(fileName: "listview-sections_delegate.qml"));
2404 window->show();
2405 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2406 window->rootObject()->setProperty(name: "sectionPositioning", value: QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2407
2408 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2409 QTRY_VERIFY(listview != nullptr);
2410 QQuickItem *contentItem = listview->contentItem();
2411 QTRY_VERIFY(contentItem != nullptr);
2412 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2413
2414 for (int i = 0; i < 3; ++i) {
2415 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "sect_" + QString::number(i));
2416 QVERIFY(item);
2417 QTRY_COMPARE(item->y(), qreal(i*20*6));
2418 }
2419
2420 QQuickItem *topItem = findVisibleChild(parent: contentItem, objectName: "sect_0"); // section header
2421 QVERIFY(topItem);
2422 QCOMPARE(topItem->y(), 0.);
2423
2424 QQuickItem *bottomItem = findVisibleChild(parent: contentItem, objectName: "sect_3"); // section footer
2425 QVERIFY(bottomItem);
2426 QCOMPARE(bottomItem->y(), 300.);
2427
2428 // move down a little and check that section header is at top
2429 listview->setContentY(10);
2430 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2431 QCOMPARE(topItem->y(), 0.);
2432
2433 // push the top header up
2434 listview->setContentY(110);
2435 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2436 topItem = findVisibleChild(parent: contentItem, objectName: "sect_0"); // section header
2437 QVERIFY(topItem);
2438 QCOMPARE(topItem->y(), 100.);
2439
2440 QQuickItem *item = findVisibleChild(parent: contentItem, objectName: "sect_1");
2441 QVERIFY(item);
2442 QCOMPARE(item->y(), 120.);
2443
2444 bottomItem = findVisibleChild(parent: contentItem, objectName: "sect_4"); // section footer
2445 QVERIFY(bottomItem);
2446 QCOMPARE(bottomItem->y(), 410.);
2447
2448 // Move past section 0
2449 listview->setContentY(120);
2450 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2451 topItem = findVisibleChild(parent: contentItem, objectName: "sect_0"); // section header
2452 QVERIFY(!topItem);
2453
2454 // Push section footer down
2455 listview->setContentY(70);
2456 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2457 bottomItem = findVisibleChild(parent: contentItem, objectName: "sect_4"); // section footer
2458 QVERIFY(bottomItem);
2459 QCOMPARE(bottomItem->y(), 380.);
2460
2461 // Change current section, and verify case insensitive comparison
2462 listview->setContentY(10);
2463 model.modifyItem(idx: 0, name: "One", number: "aaa");
2464 model.modifyItem(idx: 1, name: "Two", number: "AAA");
2465 model.modifyItem(idx: 2, name: "Three", number: "aAa");
2466 model.modifyItem(idx: 3, name: "Four", number: "aaA");
2467 model.modifyItem(idx: 4, name: "Five", number: "Aaa");
2468 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2469
2470 QTRY_COMPARE(listview->currentSection(), QString("aaa"));
2471
2472 for (int i = 0; i < 3; ++i) {
2473 QQuickItem *item = findItem<QQuickItem>(parent: contentItem,
2474 objectName: "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2475 QVERIFY(item);
2476 QTRY_COMPARE(item->y(), qreal(i*20*6));
2477 }
2478
2479 QTRY_VERIFY((topItem = findVisibleChild(contentItem, "sect_aaa"))); // section header
2480 QCOMPARE(topItem->y(), 10.);
2481
2482 // remove section boundary
2483 listview->setContentY(120);
2484 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2485 model.removeItem(index: 5);
2486 listview->forceLayout();
2487 QTRY_COMPARE(listview->count(), model.count());
2488 for (int i = 1; i < 3; ++i) {
2489 QQuickItem *item = findVisibleChild(parent: contentItem,
2490 objectName: "sect_" + QString::number(i));
2491 QVERIFY(item);
2492 QTRY_COMPARE(item->y(), qreal(i*20*6));
2493 }
2494
2495 topItem = findVisibleChild(parent: contentItem, objectName: "sect_1");
2496 QVERIFY(topItem);
2497 QTRY_COMPARE(topItem->y(), 120.);
2498
2499 // Change the next section
2500 listview->setContentY(0);
2501 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2502 bottomItem = findVisibleChild(parent: contentItem, objectName: "sect_3"); // section footer
2503 QVERIFY(bottomItem);
2504 QTRY_COMPARE(bottomItem->y(), 300.);
2505
2506 model.modifyItem(idx: 14, name: "New", number: "new");
2507 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2508
2509 QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
2510 QTRY_COMPARE(bottomItem->y(), 300.);
2511
2512 // delegate size increase should push section footer down
2513 listview->setContentY(70);
2514 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2515 QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_3")); // section footer
2516 QTRY_COMPARE(bottomItem->y(), 370.);
2517 QQuickItem *inlineSection = findVisibleChild(parent: contentItem, objectName: "sect_new");
2518 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 13);
2519 QVERIFY(item);
2520 item->setHeight(40.);
2521 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2522 QTRY_COMPARE(bottomItem->y(), 380.);
2523 QCOMPARE(inlineSection->y(), 360.);
2524 item->setHeight(20.);
2525
2526 // Turn sticky footer off
2527 listview->setContentY(20);
2528 window->rootObject()->setProperty(name: "sectionPositioning", value: QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart)));
2529 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2530 QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_new")); // inline label restored
2531 QCOMPARE(item->y(), 340.);
2532
2533 // Turn sticky header off
2534 listview->setContentY(30);
2535 window->rootObject()->setProperty(name: "sectionPositioning", value: QVariant(int(QQuickViewSection::InlineLabels)));
2536 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2537 QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_aaa")); // inline label restored
2538 QCOMPARE(item->y(), 0.);
2539
2540 // if an empty model is set the header/footer should be cleaned up
2541 window->rootObject()->setProperty(name: "sectionPositioning", value: QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2542 QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2543 QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2544 QaimModel model1;
2545 ctxt->setContextProperty("testModel", &model1);
2546 QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2547 QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2548
2549 // clear model - header/footer should be cleaned up
2550 ctxt->setContextProperty("testModel", &model);
2551 QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2552 QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2553 model.clear();
2554 QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2555 QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2556}
2557
2558void tst_QQuickListView::sectionPropertyChange()
2559{
2560 QScopedPointer<QQuickView> window(createView());
2561
2562 window->setSource(testFileUrl(fileName: "sectionpropertychange.qml"));
2563 window->show();
2564 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2565
2566 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2567 QTRY_VERIFY(listview != nullptr);
2568
2569 QQuickItem *contentItem = listview->contentItem();
2570 QTRY_VERIFY(contentItem != nullptr);
2571
2572 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2573
2574 // Confirm items positioned correctly
2575 for (int i = 0; i < 2; ++i) {
2576 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2577 QTRY_VERIFY(item);
2578 QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2579 }
2580
2581 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchGroups");
2582 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2583
2584 // Confirm items positioned correctly
2585 for (int i = 0; i < 2; ++i) {
2586 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2587 QTRY_VERIFY(item);
2588 QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2589 }
2590
2591 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchGroups");
2592 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2593
2594 // Confirm items positioned correctly
2595 for (int i = 0; i < 2; ++i) {
2596 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2597 QTRY_VERIFY(item);
2598 QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2599 }
2600
2601 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchGrouped");
2602 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2603
2604 // Confirm items positioned correctly
2605 for (int i = 0; i < 2; ++i) {
2606 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2607 QTRY_VERIFY(item);
2608 QTRY_COMPARE(item->y(), qreal(25. + i*50.));
2609 }
2610
2611 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchGrouped");
2612 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2613
2614 // Confirm items positioned correctly
2615 for (int i = 0; i < 2; ++i) {
2616 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2617 QTRY_VERIFY(item);
2618 QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2619 }
2620}
2621
2622void tst_QQuickListView::sectionDelegateChange()
2623{
2624 QScopedPointer<QQuickView> window(createView());
2625
2626 window->setSource(testFileUrl(fileName: "sectiondelegatechange.qml"));
2627 window->show();
2628 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2629
2630 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
2631 QVERIFY(listview != nullptr);
2632
2633 QQuickItem *contentItem = listview->contentItem();
2634 QVERIFY(contentItem != nullptr);
2635
2636 QQuickTest::qWaitForItemPolished(item: listview);
2637
2638 QVERIFY(findItems<QQuickItem>(contentItem, "section1").count() > 0);
2639 QCOMPARE(findItems<QQuickItem>(contentItem, "section2").count(), 0);
2640
2641 for (int i = 0; i < 3; ++i) {
2642 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "item", index: i);
2643 QTRY_VERIFY(item);
2644 QTRY_COMPARE(item->y(), qreal(25. + i*50.));
2645 }
2646
2647 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchDelegates");
2648 QQuickTest::qWaitForItemPolished(item: listview);
2649
2650 QCOMPARE(findItems<QQuickItem>(contentItem, "section1").count(), 0);
2651 QVERIFY(findItems<QQuickItem>(contentItem, "section2").count() > 0);
2652
2653 for (int i = 0; i < 3; ++i) {
2654 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "item", index: i);
2655 QVERIFY(item);
2656 QTRY_COMPARE(item->y(), qreal(50. + i*75.));
2657 }
2658}
2659
2660// QTBUG-43873
2661void tst_QQuickListView::sectionsItemInsertion()
2662{
2663 QScopedPointer<QQuickView> window(createView());
2664
2665 QaimModel model;
2666 for (int i = 0; i < 30; i++)
2667 model.addItem(name: "Item" + QString::number(i), number: QString::number(i/5));
2668
2669 QQmlContext *ctxt = window->rootContext();
2670 ctxt->setContextProperty("testModel", &model);
2671
2672 window->setSource(testFileUrl(fileName: "listview-sections_delegate.qml"));
2673 window->show();
2674 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2675
2676 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2677 QTRY_VERIFY(listview != nullptr);
2678 QQuickItem *contentItem = listview->contentItem();
2679 QTRY_VERIFY(contentItem != nullptr);
2680 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2681
2682 for (int i = 0; i < 3; ++i) {
2683 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "sect_" + QString::number(i));
2684 QVERIFY(item);
2685 QTRY_COMPARE(item->y(), qreal(i*20*6));
2686 }
2687
2688 QQuickItem *topItem = findVisibleChild(parent: contentItem, objectName: "sect_0"); // section header
2689 QVERIFY(topItem);
2690 QCOMPARE(topItem->y(), 0.);
2691
2692 // Insert a full screen of items at the beginning.
2693 for (int i = 0; i < 10; i++)
2694 model.insertItem(index: i, name: "Item" + QString::number(i), number: QLatin1String("A"));
2695
2696 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2697
2698 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
2699 QVERIFY(itemCount > 10);
2700
2701 // Verify that the new items are postioned correctly, and have the correct attached section properties
2702 for (int i = 0; i < 10 && i < itemCount; ++i) {
2703 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2704 QVERIFY(item);
2705 QTRY_COMPARE(item->y(), 20+i*20.0);
2706 QCOMPARE(item->property("section").toString(), QLatin1String("A"));
2707 QCOMPARE(item->property("nextSection").toString(), i < 9 ? QLatin1String("A") : QLatin1String("0"));
2708 QCOMPARE(item->property("prevSection").toString(), i > 0 ? QLatin1String("A") : QLatin1String(""));
2709 }
2710 // Verify that the exiting items are postioned correctly, and have the correct attached section properties
2711 for (int i = 10; i < 15 && i < itemCount; ++i) {
2712 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
2713 QVERIFY(item);
2714 QTRY_COMPARE(item->y(), 40+i*20.0);
2715 QCOMPARE(item->property("section").toString(), QLatin1String("0"));
2716 QCOMPARE(item->property("nextSection").toString(), i < 14 ? QLatin1String("0") : QLatin1String("1"));
2717 QCOMPARE(item->property("prevSection").toString(), i > 10 ? QLatin1String("0") : QLatin1String("A"));
2718 }
2719}
2720
2721void tst_QQuickListView::sectionsSnap_data()
2722{
2723 QTest::addColumn<QQuickListView::SnapMode>(name: "snapMode");
2724 QTest::addColumn<QPoint>(name: "point");
2725 QTest::addColumn<int>(name: "duration");
2726
2727 QTest::newRow(dataTag: "drag") << QQuickListView::NoSnap << QPoint(100, 45) << 500;
2728 QTest::newRow(dataTag: "flick") << QQuickListView::SnapOneItem << QPoint(100, 60) << 100;
2729}
2730
2731void tst_QQuickListView::sectionsSnap()
2732{
2733 QFETCH(QQuickListView::SnapMode, snapMode);
2734 QFETCH(QPoint, point);
2735 QFETCH(int, duration);
2736
2737 QScopedPointer<QQuickView> window(createView());
2738 window->setSource(testFileUrl(fileName: "sectionSnapping.qml"));
2739 window->show();
2740 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2741
2742 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
2743 QTRY_VERIFY(listview != nullptr);
2744 listview->setSnapMode(snapMode);
2745
2746 QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2747 QTRY_COMPARE(listview->currentIndex(), 0);
2748 QCOMPARE(listview->contentY(), qreal(-50));
2749
2750 // move down
2751 flick(window: window.data(), from: QPoint(100, 100), to: point, duration);
2752 QTRY_VERIFY(!listview->isMovingVertically());
2753 QCOMPARE(listview->contentY(), qreal(0));
2754
2755 flick(window: window.data(), from: QPoint(100, 100), to: point, duration);
2756 QTRY_VERIFY(!listview->isMovingVertically());
2757 QCOMPARE(listview->contentY(), qreal(50));
2758
2759 flick(window: window.data(), from: QPoint(100, 100), to: point, duration);
2760 QTRY_VERIFY(!listview->isMovingVertically());
2761 QCOMPARE(listview->contentY(), qreal(150));
2762
2763 // move back up
2764 flick(window: window.data(), from: point, to: QPoint(100, 100), duration);
2765 QTRY_VERIFY(!listview->isMovingVertically());
2766 QCOMPARE(listview->contentY(), qreal(50));
2767
2768 flick(window: window.data(), from: point, to: QPoint(100, 100), duration);
2769 QTRY_VERIFY(!listview->isMovingVertically());
2770 QCOMPARE(listview->contentY(), qreal(0));
2771
2772 flick(window: window.data(), from: point, to: QPoint(100, 100), duration);
2773 QTRY_VERIFY(!listview->isMovingVertically());
2774 QCOMPARE(listview->contentY(), qreal(-50));
2775}
2776
2777void tst_QQuickListView::currentIndex_delayedItemCreation()
2778{
2779 QFETCH(bool, setCurrentToZero);
2780
2781 QQuickView *window = getView();
2782
2783 // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2784 // (since the currentItem will have changed and that shares the same index)
2785 window->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2786
2787 window->setSource(testFileUrl(fileName: "fillModelOnComponentCompleted.qml"));
2788 qApp->processEvents();
2789
2790 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2791 QTRY_VERIFY(listview != nullptr);
2792 QQuickItem *contentItem = listview->contentItem();
2793 QTRY_VERIFY(contentItem != nullptr);
2794
2795 QSignalSpy spy(listview, SIGNAL(currentItemChanged()));
2796 //QCOMPARE(listview->currentIndex(), 0);
2797 listview->forceLayout();
2798 QTRY_COMPARE(spy.count(), 1);
2799
2800 releaseView(view: window);
2801}
2802
2803void tst_QQuickListView::currentIndex_delayedItemCreation_data()
2804{
2805 QTest::addColumn<bool>(name: "setCurrentToZero");
2806
2807 QTest::newRow(dataTag: "set to 0") << true;
2808 QTest::newRow(dataTag: "don't set to 0") << false;
2809}
2810
2811void tst_QQuickListView::currentIndex()
2812{
2813 QaimModel initModel;
2814
2815 for (int i = 0; i < 30; i++)
2816 initModel.addItem(name: "Item" + QString::number(i), number: QString::number(i));
2817
2818 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
2819 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
2820
2821 QQmlContext *ctxt = window->rootContext();
2822 ctxt->setContextProperty("testModel", &initModel);
2823 ctxt->setContextProperty("testWrap", QVariant(false));
2824
2825 QString filename(testFile(fileName: "listview-initCurrent.qml"));
2826 window->setSource(QUrl::fromLocalFile(localfile: filename));
2827 window->show();
2828 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2829
2830 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2831 QTRY_VERIFY(listview != nullptr);
2832 QQuickItem *contentItem = listview->contentItem();
2833 QTRY_VERIFY(contentItem != nullptr);
2834 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2835
2836 // currentIndex is initialized to 20
2837 // currentItem should be in view
2838 QCOMPARE(listview->currentIndex(), 20);
2839 QCOMPARE(listview->contentY(), 100.0);
2840 QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 20));
2841 QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2842
2843 // changing model should reset currentIndex to 0
2844 QaimModel model;
2845 for (int i = 0; i < 30; i++)
2846 model.addItem(name: "Item" + QString::number(i), number: QString::number(i));
2847 ctxt->setContextProperty("testModel", &model);
2848 listview->forceLayout();
2849
2850 QCOMPARE(listview->currentIndex(), 0);
2851 QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
2852
2853 // confirm that the velocity is updated
2854 listview->setCurrentIndex(20);
2855 QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2856 listview->setCurrentIndex(0);
2857 QTRY_COMPARE(listview->verticalVelocity(), 0.0);
2858
2859 // footer should become visible if it is out of view, and then current index is set to count-1
2860 window->rootObject()->setProperty(name: "showFooter", value: true);
2861 QTRY_VERIFY(listview->footerItem());
2862 listview->setCurrentIndex(model.count()-2);
2863 QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2864 listview->setCurrentIndex(model.count()-1);
2865 QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2866 window->rootObject()->setProperty(name: "showFooter", value: false);
2867
2868 // header should become visible if it is out of view, and then current index is set to 0
2869 window->rootObject()->setProperty(name: "showHeader", value: true);
2870 QTRY_VERIFY(listview->headerItem());
2871 listview->setCurrentIndex(1);
2872 QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2873 listview->setCurrentIndex(0);
2874 QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2875 window->rootObject()->setProperty(name: "showHeader", value: false);
2876
2877 // turn off auto highlight
2878 listview->setHighlightFollowsCurrentItem(false);
2879 QVERIFY(!listview->highlightFollowsCurrentItem());
2880
2881 QVERIFY(listview->highlightItem());
2882 qreal hlPos = listview->highlightItem()->y();
2883
2884 listview->setCurrentIndex(4);
2885 QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2886
2887 // insert item before currentIndex
2888 window->rootObject()->setProperty(name: "currentItemChangedCount", value: QVariant(0));
2889 listview->setCurrentIndex(28);
2890 QTRY_COMPARE(window->rootObject()->property("currentItemChangedCount").toInt(), 1);
2891 model.insertItem(index: 0, name: "Foo", number: "1111");
2892 QTRY_COMPARE(window->rootObject()->property("current").toInt(), 29);
2893 QCOMPARE(window->rootObject()->property("currentItemChangedCount").toInt(), 1);
2894
2895 // check removing highlight by setting currentIndex to -1;
2896 listview->setCurrentIndex(-1);
2897
2898 QCOMPARE(listview->currentIndex(), -1);
2899 QVERIFY(!listview->highlightItem());
2900 QVERIFY(!listview->currentItem());
2901
2902 // moving currentItem out of view should make it invisible
2903 listview->setCurrentIndex(0);
2904 QTRY_VERIFY(delegateVisible(listview->currentItem()));
2905 listview->setContentY(200);
2906 QTRY_VERIFY(!delegateVisible(listview->currentItem()));
2907
2908 // empty model should reset currentIndex to -1
2909 QaimModel emptyModel;
2910 window->rootObject()->setProperty(name: "currentItemChangedCount", value: QVariant(0));
2911 QVERIFY(QQmlProperty(window->rootObject(), "s").read().toString() != QLatin1String("-1"));
2912 ctxt->setContextProperty("testModel", &emptyModel);
2913 QCOMPARE(QQmlProperty(window->rootObject(), "s").read().toString(), "-1");
2914 QCOMPARE(window->rootObject()->property("currentItemChangedCount").toInt(), 1);
2915 QCOMPARE(listview->currentIndex(), -1);
2916}
2917
2918void tst_QQuickListView::noCurrentIndex()
2919{
2920 QaimModel model;
2921 for (int i = 0; i < 30; i++)
2922 model.addItem(name: "Item" + QString::number(i), number: QString::number(i));
2923
2924 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
2925 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
2926
2927 QQmlContext *ctxt = window->rootContext();
2928 ctxt->setContextProperty("testModel", &model);
2929
2930 QString filename(testFile(fileName: "listview-noCurrent.qml"));
2931 window->setSource(QUrl::fromLocalFile(localfile: filename));
2932 window->show();
2933 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
2934
2935 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2936 QTRY_VERIFY(listview != nullptr);
2937 QQuickItem *contentItem = listview->contentItem();
2938 QTRY_VERIFY(contentItem != nullptr);
2939 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2940
2941 // current index should be -1 at startup
2942 // and we should not have a currentItem or highlightItem
2943 QCOMPARE(listview->currentIndex(), -1);
2944 QCOMPARE(listview->contentY(), 0.0);
2945 QVERIFY(!listview->highlightItem());
2946 QVERIFY(!listview->currentItem());
2947
2948 listview->setCurrentIndex(2);
2949 QCOMPARE(listview->currentIndex(), 2);
2950 QVERIFY(listview->highlightItem());
2951 QVERIFY(listview->currentItem());
2952}
2953
2954void tst_QQuickListView::keyNavigation()
2955{
2956 QFETCH(QQuickListView::Orientation, orientation);
2957 QFETCH(Qt::LayoutDirection, layoutDirection);
2958 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
2959 QFETCH(Qt::Key, forwardsKey);
2960 QFETCH(Qt::Key, backwardsKey);
2961 QFETCH(QPointF, contentPosAtFirstItem);
2962 QFETCH(QPointF, contentPosAtLastItem);
2963
2964 QaimModel model;
2965 for (int i = 0; i < 30; i++)
2966 model.addItem(name: "Item" + QString::number(i), number: "");
2967
2968 QQuickView *window = getView();
2969 QScopedPointer<TestObject> testObject(new TestObject);
2970 window->rootContext()->setContextProperty("testModel", &model);
2971 window->rootContext()->setContextProperty("testObject", testObject.data());
2972 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
2973 window->show();
2974 QVERIFY(QTest::qWaitForWindowActive(window));
2975
2976 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
2977 QTRY_VERIFY(listview != nullptr);
2978
2979 listview->setOrientation(orientation);
2980 listview->setLayoutDirection(layoutDirection);
2981 listview->setVerticalLayoutDirection(verticalLayoutDirection);
2982 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
2983
2984 window->requestActivate();
2985 QVERIFY(QTest::qWaitForWindowActive(window));
2986 QTRY_COMPARE(qGuiApp->focusWindow(), window);
2987
2988 QTest::keyClick(window, key: forwardsKey);
2989 QCOMPARE(listview->currentIndex(), 1);
2990
2991 QTest::keyClick(window, key: backwardsKey);
2992 QCOMPARE(listview->currentIndex(), 0);
2993
2994 // hold down a key to go forwards
2995 for (int i=0; i<model.count()-1; i++) {
2996 QTest::simulateEvent(window, press: true, code: forwardsKey, modifier: Qt::NoModifier, text: "", repeat: true);
2997 QCOMPARE(listview->currentIndex(), i+1);
2998 }
2999 QTest::keyRelease(window, key: forwardsKey);
3000 listview->forceLayout();
3001 QTRY_COMPARE(listview->currentIndex(), model.count()-1);
3002 QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
3003 QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
3004
3005 // hold down a key to go backwards
3006 for (int i=model.count()-1; i > 0; i--) {
3007 QTest::simulateEvent(window, press: true, code: backwardsKey, modifier: Qt::NoModifier, text: "", repeat: true);
3008 QTRY_COMPARE(listview->currentIndex(), i-1);
3009 }
3010 QTest::keyRelease(window, key: backwardsKey);
3011 listview->forceLayout();
3012 QTRY_COMPARE(listview->currentIndex(), 0);
3013 QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
3014 QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
3015
3016 // no wrap
3017 QVERIFY(!listview->isWrapEnabled());
3018 listview->incrementCurrentIndex();
3019 QCOMPARE(listview->currentIndex(), 1);
3020 listview->decrementCurrentIndex();
3021 QCOMPARE(listview->currentIndex(), 0);
3022
3023 listview->decrementCurrentIndex();
3024 QCOMPARE(listview->currentIndex(), 0);
3025
3026 // with wrap
3027 listview->setWrapEnabled(true);
3028 QVERIFY(listview->isWrapEnabled());
3029
3030 listview->decrementCurrentIndex();
3031 QCOMPARE(listview->currentIndex(), model.count()-1);
3032 QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
3033 QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
3034
3035 listview->incrementCurrentIndex();
3036 QCOMPARE(listview->currentIndex(), 0);
3037 QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
3038 QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
3039
3040 releaseView(view: window);
3041}
3042
3043void tst_QQuickListView::keyNavigation_data()
3044{
3045 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
3046 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
3047 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
3048 QTest::addColumn<Qt::Key>(name: "forwardsKey");
3049 QTest::addColumn<Qt::Key>(name: "backwardsKey");
3050 QTest::addColumn<QPointF>(name: "contentPosAtFirstItem");
3051 QTest::addColumn<QPointF>(name: "contentPosAtLastItem");
3052
3053 QTest::newRow(dataTag: "Vertical, TopToBottom")
3054 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3055 << Qt::Key_Down << Qt::Key_Up
3056 << QPointF(0, 0)
3057 << QPointF(0, 30*20 - 320);
3058
3059 QTest::newRow(dataTag: "Vertical, BottomToTop")
3060 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3061 << Qt::Key_Up << Qt::Key_Down
3062 << QPointF(0, -320)
3063 << QPointF(0, -(30 * 20));
3064
3065 QTest::newRow(dataTag: "Horizontal, LeftToRight")
3066 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3067 << Qt::Key_Right << Qt::Key_Left
3068 << QPointF(0, 0)
3069 << QPointF(30*240 - 240, 0);
3070
3071 QTest::newRow(dataTag: "Horizontal, RightToLeft")
3072 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3073 << Qt::Key_Left << Qt::Key_Right
3074 << QPointF(-240, 0)
3075 << QPointF(-(30 * 240), 0);
3076}
3077
3078void tst_QQuickListView::itemList()
3079{
3080 QScopedPointer<QQuickView> window(createView());
3081 window->setSource(testFileUrl(fileName: "itemlist.qml"));
3082 window->show();
3083 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3084
3085 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "view");
3086 QTRY_VERIFY(listview != nullptr);
3087
3088 QQuickItem *contentItem = listview->contentItem();
3089 QTRY_VERIFY(contentItem != nullptr);
3090
3091 QQmlObjectModel *model = window->rootObject()->findChild<QQmlObjectModel*>(aName: "itemModel");
3092 QTRY_VERIFY(model != nullptr);
3093
3094 QTRY_COMPARE(model->count(), 3);
3095 QTRY_COMPARE(listview->currentIndex(), 0);
3096
3097 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "item1");
3098 QTRY_VERIFY(item);
3099 QTRY_COMPARE(item->x(), 0.0);
3100 QCOMPARE(item->height(), listview->height());
3101
3102 QQuickText *text = findItem<QQuickText>(parent: contentItem, objectName: "text1");
3103 QTRY_VERIFY(text);
3104 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
3105
3106 listview->setCurrentIndex(2);
3107
3108 item = findItem<QQuickItem>(parent: contentItem, objectName: "item3");
3109 QTRY_VERIFY(item);
3110 QTRY_COMPARE(item->x(), 480.0);
3111
3112 text = findItem<QQuickText>(parent: contentItem, objectName: "text3");
3113 QTRY_VERIFY(text);
3114 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3115}
3116
3117void tst_QQuickListView::itemListFlicker()
3118{
3119 QScopedPointer<QQuickView> window(createView());
3120 window->setSource(testFileUrl(fileName: "itemlist-flicker.qml"));
3121 window->show();
3122 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3123
3124 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "view");
3125 QTRY_VERIFY(listview != nullptr);
3126
3127 QQuickItem *contentItem = listview->contentItem();
3128 QTRY_VERIFY(contentItem != nullptr);
3129
3130 QQmlObjectModel *model = window->rootObject()->findChild<QQmlObjectModel*>(aName: "itemModel");
3131 QTRY_VERIFY(model != nullptr);
3132
3133 QTRY_COMPARE(model->count(), 3);
3134 QTRY_COMPARE(listview->currentIndex(), 0);
3135
3136 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "item1");
3137 QVERIFY(item);
3138 QVERIFY(delegateVisible(item));
3139 item = findItem<QQuickItem>(parent: contentItem, objectName: "item2");
3140 QVERIFY(item);
3141 QVERIFY(delegateVisible(item));
3142 item = findItem<QQuickItem>(parent: contentItem, objectName: "item3");
3143 QVERIFY(item);
3144 QVERIFY(delegateVisible(item));
3145
3146 listview->setCurrentIndex(1);
3147
3148 item = findItem<QQuickItem>(parent: contentItem, objectName: "item1");
3149 QVERIFY(item);
3150 QVERIFY(delegateVisible(item));
3151 item = findItem<QQuickItem>(parent: contentItem, objectName: "item2");
3152 QVERIFY(item);
3153 QVERIFY(delegateVisible(item));
3154 item = findItem<QQuickItem>(parent: contentItem, objectName: "item3");
3155 QVERIFY(item);
3156 QVERIFY(delegateVisible(item));
3157
3158 listview->setCurrentIndex(2);
3159
3160 item = findItem<QQuickItem>(parent: contentItem, objectName: "item1");
3161 QVERIFY(item);
3162 QVERIFY(delegateVisible(item));
3163 item = findItem<QQuickItem>(parent: contentItem, objectName: "item2");
3164 QVERIFY(item);
3165 QVERIFY(delegateVisible(item));
3166 item = findItem<QQuickItem>(parent: contentItem, objectName: "item3");
3167 QVERIFY(item);
3168 QVERIFY(delegateVisible(item));
3169}
3170
3171void tst_QQuickListView::cacheBuffer()
3172{
3173 QScopedPointer<QQuickView> window(createView());
3174
3175 QaimModel model;
3176 for (int i = 0; i < 90; i++)
3177 model.addItem(name: "Item" + QString::number(i), number: "");
3178
3179 QQmlContext *ctxt = window->rootContext();
3180 ctxt->setContextProperty("testModel", &model);
3181
3182 QScopedPointer<TestObject> testObject(new TestObject);
3183 ctxt->setContextProperty("testObject", testObject.data());
3184
3185 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
3186 window->show();
3187 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3188
3189 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3190 QTRY_VERIFY(listview != nullptr);
3191
3192 QQuickItem *contentItem = listview->contentItem();
3193 QTRY_VERIFY(contentItem != nullptr);
3194 QTRY_VERIFY(listview->delegate() != nullptr);
3195 QTRY_VERIFY(listview->model() != 0);
3196 QTRY_VERIFY(listview->highlight() != nullptr);
3197
3198 // Confirm items positioned correctly
3199 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
3200 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3201 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3202 if (!item) qWarning() << "Item" << i << "not found";
3203 QTRY_VERIFY(item);
3204 QTRY_COMPARE(item->y(), qreal(i*20));
3205 }
3206
3207 QQmlIncubationController controller;
3208 window->engine()->setIncubationController(&controller);
3209
3210 testObject->setCacheBuffer(200);
3211 QTRY_COMPARE(listview->cacheBuffer(), 200);
3212
3213 // items will be created one at a time
3214 for (int i = itemCount; i < qMin(a: itemCount+10,b: model.count()); ++i) {
3215 QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == nullptr);
3216 QQuickItem *item = nullptr;
3217 while (!item) {
3218 bool b = false;
3219 controller.incubateWhile(flag: &b);
3220 item = findItem<QQuickItem>(parent: listview, objectName: "wrapper", index: i);
3221 }
3222 }
3223
3224 {
3225 bool b = true;
3226 controller.incubateWhile(flag: &b);
3227 }
3228
3229 int newItemCount = 0;
3230 newItemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
3231
3232 // Confirm items positioned correctly
3233 for (int i = 0; i < model.count() && i < newItemCount; ++i) {
3234 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3235 if (!item) qWarning() << "Item" << i << "not found";
3236 QTRY_VERIFY(item);
3237 QTRY_COMPARE(item->y(), qreal(i*20));
3238 }
3239
3240 // move view and confirm items in view are visible immediately and outside are created async
3241 listview->setContentY(300);
3242
3243 for (int i = 15; i < 32; ++i) {
3244 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3245 if (!item) qWarning() << "Item" << i << "not found";
3246 QVERIFY(item);
3247 QCOMPARE(item->y(), qreal(i*20));
3248 }
3249
3250 QVERIFY(findItem<QQuickItem>(listview, "wrapper", 32) == nullptr);
3251
3252 // ensure buffered items are created
3253 for (int i = 32; i < qMin(a: 41,b: model.count()); ++i) {
3254 QQuickItem *item = nullptr;
3255 while (!item) {
3256 qGuiApp->processEvents(); // allow refill to happen
3257 bool b = false;
3258 controller.incubateWhile(flag: &b);
3259 item = findItem<QQuickItem>(parent: listview, objectName: "wrapper", index: i);
3260 }
3261 }
3262
3263 {
3264 bool b = true;
3265 controller.incubateWhile(flag: &b);
3266 }
3267
3268 // negative cache buffer is ignored
3269 listview->setCacheBuffer(-1);
3270 QCOMPARE(listview->cacheBuffer(), 200);
3271}
3272
3273void tst_QQuickListView::positionViewAtBeginningEnd()
3274{
3275 QScopedPointer<QQuickView> window(createView());
3276
3277 QaimModel model;
3278 for (int i = 0; i < 40; i++)
3279 model.addItem(name: "Item" + QString::number(i), number: "");
3280
3281 QQmlContext *ctxt = window->rootContext();
3282 ctxt->setContextProperty("testModel", &model);
3283
3284 QScopedPointer<TestObject> testObject(new TestObject);
3285 ctxt->setContextProperty("testObject", testObject.data());
3286 window->show();
3287 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
3288 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3289
3290 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3291 QTRY_VERIFY(listview != nullptr);
3292 QQuickItem *contentItem = listview->contentItem();
3293 QTRY_VERIFY(contentItem != nullptr);
3294 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3295
3296 listview->setContentY(100);
3297
3298 // positionAtBeginnging
3299 listview->positionViewAtBeginning();
3300 QTRY_COMPARE(listview->contentY(), 0.);
3301
3302 listview->setContentY(80);
3303 window->rootObject()->setProperty(name: "showHeader", value: true);
3304 listview->positionViewAtBeginning();
3305 QTRY_COMPARE(listview->contentY(), -30.);
3306
3307 // positionAtEnd
3308 listview->positionViewAtEnd();
3309 QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
3310
3311 listview->setContentY(80);
3312 window->rootObject()->setProperty(name: "showFooter", value: true);
3313 listview->positionViewAtEnd();
3314 QTRY_COMPARE(listview->contentY(), 510.);
3315
3316 // set current item to outside visible view, position at beginning
3317 // and ensure highlight moves to current item
3318 listview->setCurrentIndex(1);
3319 listview->positionViewAtBeginning();
3320 QTRY_COMPARE(listview->contentY(), -30.);
3321 QVERIFY(listview->highlightItem());
3322 QCOMPARE(listview->highlightItem()->y(), 20.);
3323}
3324
3325void tst_QQuickListView::positionViewAtIndex()
3326{
3327 QFETCH(bool, enforceRange);
3328 QFETCH(qreal, initContentY);
3329 QFETCH(int, index);
3330 QFETCH(QQuickListView::PositionMode, mode);
3331 QFETCH(qreal, contentY);
3332
3333 QQuickView *window = getView();
3334
3335 QaimModel model;
3336 for (int i = 0; i < 40; i++)
3337 model.addItem(name: "Item" + QString::number(i), number: "");
3338
3339 QQmlContext *ctxt = window->rootContext();
3340 ctxt->setContextProperty("testModel", &model);
3341
3342 QScopedPointer<TestObject> testObject(new TestObject);
3343 ctxt->setContextProperty("testObject", testObject.data());
3344 window->show();
3345 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
3346 QVERIFY(QTest::qWaitForWindowExposed(window));
3347
3348 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3349 QTRY_VERIFY(listview != nullptr);
3350 QQuickItem *contentItem = listview->contentItem();
3351 QTRY_VERIFY(contentItem != nullptr);
3352 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3353
3354 window->rootObject()->setProperty(name: "enforceRange", value: enforceRange);
3355 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3356
3357 listview->setContentY(initContentY);
3358
3359 listview->positionViewAtIndex(index, mode);
3360 QTRY_COMPARE(listview->contentY(), contentY);
3361
3362 // Confirm items positioned correctly
3363 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
3364 for (int i = index; i < model.count() && i < itemCount-index-1; ++i) {
3365 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3366 if (!item) qWarning() << "Item" << i << "not found";
3367 QTRY_VERIFY(item);
3368 QTRY_COMPARE(item->y(), i*20.);
3369 }
3370
3371 releaseView(view: window);
3372}
3373
3374void tst_QQuickListView::positionViewAtIndex_data()
3375{
3376 QTest::addColumn<bool>(name: "enforceRange");
3377 QTest::addColumn<qreal>(name: "initContentY");
3378 QTest::addColumn<int>(name: "index");
3379 QTest::addColumn<QQuickListView::PositionMode>(name: "mode");
3380 QTest::addColumn<qreal>(name: "contentY");
3381
3382 QTest::newRow(dataTag: "no range, 3 at Beginning") << false << 0. << 3 << QQuickListView::Beginning << 60.;
3383 QTest::newRow(dataTag: "no range, 3 at End") << false << 0. << 3 << QQuickListView::End << 0.;
3384 QTest::newRow(dataTag: "no range, 22 at Beginning") << false << 0. << 22 << QQuickListView::Beginning << 440.;
3385 // Position on an item that would leave empty space if positioned at the top
3386 QTest::newRow(dataTag: "no range, 28 at Beginning") << false << 0. << 28 << QQuickListView::Beginning << 480.;
3387 // Position at End using last index
3388 QTest::newRow(dataTag: "no range, last at End") << false << 0. << 39 << QQuickListView::End << 480.;
3389 // Position at End
3390 QTest::newRow(dataTag: "no range, 20 at End") << false << 0. << 20 << QQuickListView::End << 100.;
3391 // Position in Center
3392 QTest::newRow(dataTag: "no range, 15 at Center") << false << 0. << 15 << QQuickListView::Center << 150.;
3393 // Ensure at least partially visible
3394 QTest::newRow(dataTag: "no range, 15 visible => Visible") << false << 150. << 15 << QQuickListView::Visible << 150.;
3395 QTest::newRow(dataTag: "no range, 15 partially visible => Visible") << false << 302. << 15 << QQuickListView::Visible << 302.;
3396 QTest::newRow(dataTag: "no range, 15 before visible => Visible") << false << 320. << 15 << QQuickListView::Visible << 300.;
3397 QTest::newRow(dataTag: "no range, 20 visible => Visible") << false << 85. << 20 << QQuickListView::Visible << 85.;
3398 QTest::newRow(dataTag: "no range, 20 before visible => Visible") << false << 75. << 20 << QQuickListView::Visible << 100.;
3399 QTest::newRow(dataTag: "no range, 20 after visible => Visible") << false << 480. << 20 << QQuickListView::Visible << 400.;
3400 // Ensure completely visible
3401 QTest::newRow(dataTag: "no range, 20 visible => Contain") << false << 120. << 20 << QQuickListView::Contain << 120.;
3402 QTest::newRow(dataTag: "no range, 15 partially visible => Contain") << false << 302. << 15 << QQuickListView::Contain << 300.;
3403 QTest::newRow(dataTag: "no range, 20 partially visible => Contain") << false << 85. << 20 << QQuickListView::Contain << 100.;
3404
3405 QTest::newRow(dataTag: "strict range, 3 at End") << true << 0. << 3 << QQuickListView::End << -120.;
3406 QTest::newRow(dataTag: "strict range, 38 at Beginning") << true << 0. << 38 << QQuickListView::Beginning << 660.;
3407 QTest::newRow(dataTag: "strict range, 15 at Center") << true << 0. << 15 << QQuickListView::Center << 140.;
3408 QTest::newRow(dataTag: "strict range, 3 at SnapPosition") << true << 0. << 3 << QQuickListView::SnapPosition << -60.;
3409 QTest::newRow(dataTag: "strict range, 10 at SnapPosition") << true << 0. << 10 << QQuickListView::SnapPosition << 80.;
3410 QTest::newRow(dataTag: "strict range, 38 at SnapPosition") << true << 0. << 38 << QQuickListView::SnapPosition << 640.;
3411}
3412
3413void tst_QQuickListView::resetModel()
3414{
3415 QScopedPointer<QQuickView> window(createView());
3416
3417 QStringList strings;
3418 strings << "one" << "two" << "three";
3419 QStringListModel model(strings);
3420
3421 QQmlContext *ctxt = window->rootContext();
3422 ctxt->setContextProperty("testModel", &model);
3423
3424 window->setSource(testFileUrl(fileName: "displaylist.qml"));
3425 window->show();
3426 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3427
3428 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3429 QTRY_VERIFY(listview != nullptr);
3430 QQuickItem *contentItem = listview->contentItem();
3431 QTRY_VERIFY(contentItem != nullptr);
3432 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3433
3434 QTRY_COMPARE(listview->count(), model.rowCount());
3435
3436 for (int i = 0; i < model.rowCount(); ++i) {
3437 QQuickText *display = findItem<QQuickText>(parent: contentItem, objectName: "displayText", index: i);
3438 QTRY_VERIFY(display != nullptr);
3439 QTRY_COMPARE(display->text(), strings.at(i));
3440 }
3441
3442 strings.clear();
3443 strings << "four" << "five" << "six" << "seven";
3444 model.setStringList(strings);
3445
3446 listview->forceLayout();
3447 QTRY_COMPARE(listview->count(), model.rowCount());
3448
3449 for (int i = 0; i < model.rowCount(); ++i) {
3450 QQuickText *display = findItem<QQuickText>(parent: contentItem, objectName: "displayText", index: i);
3451 QTRY_VERIFY(display != nullptr);
3452 QTRY_COMPARE(display->text(), strings.at(i));
3453 }
3454}
3455
3456void tst_QQuickListView::propertyChanges()
3457{
3458 QScopedPointer<QQuickView> window(createView());
3459 window->setSource(testFileUrl(fileName: "propertychangestest.qml"));
3460
3461 QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>(aName: "listView");
3462 QTRY_VERIFY(listView);
3463
3464 QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
3465 QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
3466 QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
3467 QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
3468 QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
3469 QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
3470 QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
3471
3472 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
3473 QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
3474 QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
3475 QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::ApplyRange);
3476 QTRY_COMPARE(listView->isWrapEnabled(), true);
3477 QTRY_COMPARE(listView->cacheBuffer(), 10);
3478 QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapToItem);
3479
3480 listView->setHighlightFollowsCurrentItem(false);
3481 listView->setPreferredHighlightBegin(1.0);
3482 listView->setPreferredHighlightEnd(1.0);
3483 listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3484 listView->setWrapEnabled(false);
3485 listView->setCacheBuffer(3);
3486 listView->setSnapMode(QQuickListView::SnapOneItem);
3487
3488 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
3489 QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
3490 QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
3491 QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::StrictlyEnforceRange);
3492 QTRY_COMPARE(listView->isWrapEnabled(), false);
3493 QTRY_COMPARE(listView->cacheBuffer(), 3);
3494 QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapOneItem);
3495
3496 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3497 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3498 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3499 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3500 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3501 QTRY_COMPARE(cacheBufferSpy.count(),1);
3502 QTRY_COMPARE(snapModeSpy.count(),1);
3503
3504 listView->setHighlightFollowsCurrentItem(false);
3505 listView->setPreferredHighlightBegin(1.0);
3506 listView->setPreferredHighlightEnd(1.0);
3507 listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3508 listView->setWrapEnabled(false);
3509 listView->setCacheBuffer(3);
3510 listView->setSnapMode(QQuickListView::SnapOneItem);
3511
3512 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3513 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3514 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3515 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3516 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3517 QTRY_COMPARE(cacheBufferSpy.count(),1);
3518 QTRY_COMPARE(snapModeSpy.count(),1);
3519}
3520
3521void tst_QQuickListView::componentChanges()
3522{
3523 QScopedPointer<QQuickView> window(createView());
3524 window->setSource(testFileUrl(fileName: "propertychangestest.qml"));
3525
3526 QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>(aName: "listView");
3527 QTRY_VERIFY(listView);
3528
3529 QQmlComponent component(window->engine());
3530 component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", baseUrl: QUrl::fromLocalFile(localfile: ""));
3531
3532 QQmlComponent delegateComponent(window->engine());
3533 delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", baseUrl: QUrl::fromLocalFile(localfile: ""));
3534
3535 QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
3536 QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
3537 QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
3538 QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
3539
3540 listView->setHighlight(&component);
3541 listView->setHeader(&component);
3542 listView->setFooter(&component);
3543 listView->setDelegate(&delegateComponent);
3544
3545 QTRY_COMPARE(listView->highlight(), &component);
3546 QTRY_COMPARE(listView->header(), &component);
3547 QTRY_COMPARE(listView->footer(), &component);
3548 QTRY_COMPARE(listView->delegate(), &delegateComponent);
3549
3550 QTRY_COMPARE(highlightSpy.count(),1);
3551 QTRY_COMPARE(delegateSpy.count(),1);
3552 QTRY_COMPARE(headerSpy.count(),1);
3553 QTRY_COMPARE(footerSpy.count(),1);
3554
3555 listView->setHighlight(&component);
3556 listView->setHeader(&component);
3557 listView->setFooter(&component);
3558 listView->setDelegate(&delegateComponent);
3559
3560 QTRY_COMPARE(highlightSpy.count(),1);
3561 QTRY_COMPARE(delegateSpy.count(),1);
3562 QTRY_COMPARE(headerSpy.count(),1);
3563 QTRY_COMPARE(footerSpy.count(),1);
3564}
3565
3566void tst_QQuickListView::modelChanges()
3567{
3568 QScopedPointer<QQuickView> window(createView());
3569 window->setSource(testFileUrl(fileName: "propertychangestest.qml"));
3570
3571 QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>(aName: "listView");
3572 QTRY_VERIFY(listView);
3573
3574 QQmlListModel *alternateModel = window->rootObject()->findChild<QQmlListModel*>(aName: "alternateModel");
3575 QTRY_VERIFY(alternateModel);
3576 QVariant modelVariant = QVariant::fromValue<QObject *>(value: alternateModel);
3577 QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
3578
3579 listView->setModel(modelVariant);
3580 QTRY_COMPARE(listView->model(), modelVariant);
3581 QTRY_COMPARE(modelSpy.count(),1);
3582
3583 listView->setModel(modelVariant);
3584 QTRY_COMPARE(modelSpy.count(),1);
3585
3586 listView->setModel(QVariant());
3587 QTRY_COMPARE(modelSpy.count(),2);
3588}
3589
3590void tst_QQuickListView::QTBUG_9791()
3591{
3592 QScopedPointer<QQuickView> window(createView());
3593 window->setSource(testFileUrl(fileName: "strictlyenforcerange.qml"));
3594 window->show();
3595 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3596
3597 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
3598 QTRY_VERIFY(listview != nullptr);
3599
3600 QQuickItem *contentItem = listview->contentItem();
3601 QTRY_VERIFY(contentItem != nullptr);
3602 QTRY_VERIFY(listview->delegate() != nullptr);
3603 QTRY_VERIFY(listview->model() != 0);
3604
3605 QMetaObject::invokeMethod(obj: listview, member: "fillModel");
3606 qApp->processEvents();
3607
3608 // Confirm items positioned correctly
3609 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", visibleOnly: false).count();
3610 QCOMPARE(itemCount, 3);
3611
3612 for (int i = 0; i < itemCount; ++i) {
3613 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3614 if (!item) qWarning() << "Item" << i << "not found";
3615 QTRY_VERIFY(item);
3616 QTRY_COMPARE(item->x(), i*300.0);
3617 }
3618
3619 // check that view is positioned correctly
3620 QTRY_COMPARE(listview->contentX(), 590.0);
3621}
3622
3623void tst_QQuickListView::QTBUG_33568()
3624{
3625 QScopedPointer<QQuickView> window(createView());
3626 window->setSource(testFileUrl(fileName: "strictlyenforcerange-resize.qml"));
3627 window->show();
3628 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3629
3630 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
3631 QVERIFY(listview != nullptr);
3632
3633 // we want to verify that the change animates smoothly, rather than jumping into place
3634 QSignalSpy spy(listview, SIGNAL(contentYChanged()));
3635
3636 listview->incrementCurrentIndex();
3637 QTRY_COMPARE(listview->contentY(), -100.0);
3638 QVERIFY(spy.count() > 1);
3639
3640 spy.clear();
3641 listview->incrementCurrentIndex();
3642 QTRY_COMPARE(listview->contentY(), -50.0);
3643 QVERIFY(spy.count() > 1);
3644}
3645
3646void tst_QQuickListView::manualHighlight()
3647{
3648 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
3649 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
3650
3651 QString filename(testFile(fileName: "manual-highlight.qml"));
3652 window->setSource(QUrl::fromLocalFile(localfile: filename));
3653
3654 qApp->processEvents();
3655
3656 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3657 QTRY_VERIFY(listview != nullptr);
3658
3659 QQuickItem *contentItem = listview->contentItem();
3660 QTRY_VERIFY(contentItem != nullptr);
3661
3662 QTRY_COMPARE(listview->currentIndex(), 0);
3663 QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
3664 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3665
3666 listview->setCurrentIndex(2);
3667
3668 QTRY_COMPARE(listview->currentIndex(), 2);
3669 QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3670 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3671
3672 // QTBUG-15972
3673 listview->positionViewAtIndex(index: 3, mode: QQuickListView::Contain);
3674
3675 QTRY_COMPARE(listview->currentIndex(), 2);
3676 QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3677 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3678}
3679
3680void tst_QQuickListView::QTBUG_11105()
3681{
3682 QScopedPointer<QQuickView> window(createView());
3683 QaimModel model;
3684 for (int i = 0; i < 30; i++)
3685 model.addItem(name: "Item" + QString::number(i), number: "");
3686
3687 QQmlContext *ctxt = window->rootContext();
3688 ctxt->setContextProperty("testModel", &model);
3689
3690 QScopedPointer<TestObject> testObject(new TestObject);
3691 ctxt->setContextProperty("testObject", testObject.data());
3692
3693 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
3694 window->show();
3695 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
3696
3697 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3698 QTRY_VERIFY(listview != nullptr);
3699 QQuickItem *contentItem = listview->contentItem();
3700 QTRY_VERIFY(contentItem != nullptr);
3701 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3702
3703 // Confirm items positioned correctly
3704 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
3705 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3706 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
3707 if (!item) qWarning() << "Item" << i << "not found";
3708 QTRY_VERIFY(item);
3709 QTRY_COMPARE(item->y(), qreal(i*20));
3710 }
3711
3712 listview->positionViewAtIndex(index: 20, mode: QQuickListView::Beginning);
3713 QCOMPARE(listview->contentY(), 280.);
3714
3715 QaimModel model2;
3716 for (int i = 0; i < 5; i++)
3717 model2.addItem(name: "Item" + QString::number(i), number: "");
3718
3719 ctxt->setContextProperty("testModel", &model2);
3720
3721 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
3722 QCOMPARE(itemCount, 5);
3723}
3724
3725void tst_QQuickListView::initialZValues()
3726{
3727 QFETCH(QString, fileName);
3728 QScopedPointer<QQuickView> window(createView());
3729 window->setSource(testFileUrl(fileName));
3730 qApp->processEvents();
3731
3732 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3733 QTRY_VERIFY(listview != nullptr);
3734 QQuickItem *contentItem = listview->contentItem();
3735 QTRY_VERIFY(contentItem != nullptr);
3736
3737 QVERIFY(listview->currentItem());
3738 QTRY_COMPARE(listview->currentItem()->z(), listview->property("itemZ").toReal());
3739
3740 QVERIFY(listview->headerItem());
3741 QTRY_COMPARE(listview->headerItem()->z(), listview->property("headerZ").toReal());
3742
3743 QVERIFY(listview->footerItem());
3744 QTRY_COMPARE(listview->footerItem()->z(), listview->property("footerZ").toReal());
3745
3746 QVERIFY(listview->highlightItem());
3747 QTRY_COMPARE(listview->highlightItem()->z(), listview->property("highlightZ").toReal());
3748
3749 QQuickText *sectionItem = nullptr;
3750 QTRY_VERIFY(sectionItem = findItem<QQuickText>(contentItem, "section"));
3751 QTRY_COMPARE(sectionItem->z(), listview->property("sectionZ").toReal());
3752}
3753
3754void tst_QQuickListView::initialZValues_data()
3755{
3756 QTest::addColumn<QString>(name: "fileName");
3757 QTest::newRow(dataTag: "defaults") << "defaultZValues.qml";
3758 QTest::newRow(dataTag: "constants") << "constantZValues.qml";
3759 QTest::newRow(dataTag: "bindings") << "boundZValues.qml";
3760}
3761
3762void tst_QQuickListView::header()
3763{
3764 QFETCH(QQuickListView::Orientation, orientation);
3765 QFETCH(Qt::LayoutDirection, layoutDirection);
3766 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3767 QFETCH(QPointF, initialHeaderPos);
3768 QFETCH(QPointF, changedHeaderPos);
3769 QFETCH(QPointF, initialContentPos);
3770 QFETCH(QPointF, changedContentPos);
3771 QFETCH(QPointF, firstDelegatePos);
3772 QFETCH(QPointF, resizeContentPos);
3773
3774 QaimModel model;
3775 for (int i = 0; i < 30; i++)
3776 model.addItem(name: "Item" + QString::number(i), number: "");
3777
3778 QQuickView *window = getView();
3779 window->rootContext()->setContextProperty("testModel", &model);
3780 window->rootContext()->setContextProperty("initialViewWidth", 240);
3781 window->rootContext()->setContextProperty("initialViewHeight", 320);
3782 window->setSource(testFileUrl(fileName: "header.qml"));
3783 window->show();
3784 QVERIFY(QTest::qWaitForWindowExposed(window));
3785
3786 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3787 QTRY_VERIFY(listview != nullptr);
3788 listview->setOrientation(orientation);
3789 listview->setLayoutDirection(layoutDirection);
3790 listview->setVerticalLayoutDirection(verticalLayoutDirection);
3791 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3792
3793 QQuickItem *contentItem = listview->contentItem();
3794 QTRY_VERIFY(contentItem != nullptr);
3795
3796 QQuickText *header = nullptr;
3797 QTRY_VERIFY(header = findItem<QQuickText>(contentItem, "header"));
3798 QCOMPARE(header, listview->headerItem());
3799
3800 QCOMPARE(header->width(), 100.);
3801 QCOMPARE(header->height(), 30.);
3802 QCOMPARE(header->position(), initialHeaderPos);
3803 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3804
3805 if (orientation == QQuickListView::Vertical)
3806 QCOMPARE(listview->contentHeight(), model.count() * 30. + header->height());
3807 else
3808 QCOMPARE(listview->contentWidth(), model.count() * 240. + header->width());
3809
3810 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
3811 QVERIFY(item);
3812 QCOMPARE(item->position(), firstDelegatePos);
3813
3814 model.clear();
3815 listview->forceLayout();
3816 QTRY_COMPARE(listview->count(), model.count());
3817 QCOMPARE(header->position(), initialHeaderPos); // header should stay where it is
3818 if (orientation == QQuickListView::Vertical)
3819 QCOMPARE(listview->contentHeight(), header->height());
3820 else
3821 QCOMPARE(listview->contentWidth(), header->width());
3822
3823 for (int i = 0; i < 30; i++)
3824 model.addItem(name: "Item" + QString::number(i), number: "");
3825
3826 QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
3827 QMetaObject::invokeMethod(obj: window->rootObject(), member: "changeHeader");
3828
3829 QCOMPARE(headerItemSpy.count(), 1);
3830
3831 header = findItem<QQuickText>(parent: contentItem, objectName: "header");
3832 QVERIFY(!header);
3833 header = findItem<QQuickText>(parent: contentItem, objectName: "header2");
3834 QVERIFY(header);
3835
3836 QCOMPARE(header, listview->headerItem());
3837
3838 QCOMPARE(header->position(), changedHeaderPos);
3839 QCOMPARE(header->width(), 50.);
3840 QCOMPARE(header->height(), 20.);
3841 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3842
3843 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
3844 QVERIFY(item);
3845 QCOMPARE(item->position(), firstDelegatePos);
3846
3847 listview->positionViewAtBeginning();
3848 header->setHeight(10);
3849 header->setWidth(40);
3850 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3851
3852 releaseView(view: window);
3853
3854
3855 // QTBUG-21207 header should become visible if view resizes from initial empty size
3856
3857 window = getView();
3858 window->rootContext()->setContextProperty("testModel", &model);
3859 window->rootContext()->setContextProperty("initialViewWidth", 0.0);
3860 window->rootContext()->setContextProperty("initialViewHeight", 0.0);
3861 window->setSource(testFileUrl(fileName: "header.qml"));
3862 window->show();
3863 QVERIFY(QTest::qWaitForWindowExposed(window));
3864
3865 listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3866 QTRY_VERIFY(listview != nullptr);
3867 listview->setOrientation(orientation);
3868 listview->setLayoutDirection(layoutDirection);
3869 listview->setVerticalLayoutDirection(verticalLayoutDirection);
3870 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3871
3872 listview->setWidth(240);
3873 listview->setHeight(320);
3874 QTRY_COMPARE(listview->headerItem()->position(), initialHeaderPos);
3875 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3876
3877 releaseView(view: window);
3878}
3879
3880void tst_QQuickListView::header_data()
3881{
3882 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
3883 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
3884 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
3885 QTest::addColumn<QPointF>(name: "initialHeaderPos");
3886 QTest::addColumn<QPointF>(name: "changedHeaderPos");
3887 QTest::addColumn<QPointF>(name: "initialContentPos");
3888 QTest::addColumn<QPointF>(name: "changedContentPos");
3889 QTest::addColumn<QPointF>(name: "firstDelegatePos");
3890 QTest::addColumn<QPointF>(name: "resizeContentPos");
3891
3892 // header1 = 100 x 30
3893 // header2 = 50 x 20
3894 // delegates = 240 x 30
3895 // view width = 240
3896
3897 // header above items, top left
3898 QTest::newRow(dataTag: "vertical, left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3899 << QPointF(0, -30)
3900 << QPointF(0, -20)
3901 << QPointF(0, -30)
3902 << QPointF(0, -20)
3903 << QPointF(0, 0)
3904 << QPointF(0, -10);
3905
3906 // header above items, top right
3907 QTest::newRow(dataTag: "vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
3908 << QPointF(0, -30)
3909 << QPointF(0, -20)
3910 << QPointF(0, -30)
3911 << QPointF(0, -20)
3912 << QPointF(0, 0)
3913 << QPointF(0, -10);
3914
3915 // header to left of items
3916 QTest::newRow(dataTag: "horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3917 << QPointF(-100, 0)
3918 << QPointF(-50, 0)
3919 << QPointF(-100, 0)
3920 << QPointF(-50, 0)
3921 << QPointF(0, 0)
3922 << QPointF(-40, 0);
3923
3924 // header to right of items
3925 QTest::newRow(dataTag: "horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3926 << QPointF(0, 0)
3927 << QPointF(0, 0)
3928 << QPointF(-240 + 100, 0)
3929 << QPointF(-240 + 50, 0)
3930 << QPointF(-240, 0)
3931 << QPointF(-240 + 40, 0);
3932
3933 // header below items
3934 QTest::newRow(dataTag: "vertical, bottom to top") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3935 << QPointF(0, 0)
3936 << QPointF(0, 0)
3937 << QPointF(0, -320 + 30)
3938 << QPointF(0, -320 + 20)
3939 << QPointF(0, -30)
3940 << QPointF(0, -320 + 10);
3941}
3942
3943void tst_QQuickListView::header_delayItemCreation()
3944{
3945 QScopedPointer<QQuickView> window(createView());
3946 QaimModel model;
3947
3948 window->rootContext()->setContextProperty("setCurrentToZero", QVariant(false));
3949 window->setSource(testFileUrl(fileName: "fillModelOnComponentCompleted.qml"));
3950 qApp->processEvents();
3951
3952 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3953 QTRY_VERIFY(listview != nullptr);
3954
3955 QQuickItem *contentItem = listview->contentItem();
3956 QTRY_VERIFY(contentItem != nullptr);
3957
3958 QQuickText *header = findItem<QQuickText>(parent: contentItem, objectName: "header");
3959 QVERIFY(header);
3960 QCOMPARE(header->y(), -header->height());
3961
3962 QCOMPARE(listview->contentY(), -header->height());
3963
3964 model.clear();
3965 QTRY_COMPARE(header->y(), -header->height());
3966}
3967
3968void tst_QQuickListView::headerChangesViewport()
3969{
3970 QQuickView *window = getView();
3971 window->rootContext()->setContextProperty("headerHeight", 20);
3972 window->rootContext()->setContextProperty("headerWidth", 240);
3973 window->setSource(testFileUrl(fileName: "headerchangesviewport.qml"));
3974
3975 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
3976 QTRY_VERIFY(listview != nullptr);
3977 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
3978
3979 QQuickItem *contentItem = listview->contentItem();
3980 QTRY_VERIFY(contentItem != nullptr);
3981
3982 QQuickText *header = nullptr;
3983 QTRY_VERIFY(header = findItem<QQuickText>(contentItem, "header"));
3984 QCOMPARE(header, listview->headerItem());
3985
3986 QCOMPARE(header->height(), 20.);
3987 QCOMPARE(listview->contentHeight(), 20.);
3988
3989 // change height
3990 window->rootContext()->setContextProperty("headerHeight", 50);
3991
3992 // verify that list content height updates also
3993 QCOMPARE(header->height(), 50.);
3994 QCOMPARE(listview->contentHeight(), 50.);
3995}
3996
3997void tst_QQuickListView::footer()
3998{
3999 QFETCH(QQuickListView::Orientation, orientation);
4000 QFETCH(Qt::LayoutDirection, layoutDirection);
4001 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4002 QFETCH(QPointF, initialFooterPos);
4003 QFETCH(QPointF, firstDelegatePos);
4004 QFETCH(QPointF, initialContentPos);
4005 QFETCH(QPointF, changedFooterPos);
4006 QFETCH(QPointF, changedContentPos);
4007 QFETCH(QPointF, resizeContentPos);
4008
4009 QQuickView *window = getView();
4010
4011 QaimModel model;
4012 for (int i = 0; i < 3; i++)
4013 model.addItem(name: "Item" + QString::number(i), number: "");
4014
4015 QQmlContext *ctxt = window->rootContext();
4016 ctxt->setContextProperty("testModel", &model);
4017
4018 window->setSource(testFileUrl(fileName: "footer.qml"));
4019 window->show();
4020 QVERIFY(QTest::qWaitForWindowExposed(window));
4021
4022 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4023 QTRY_VERIFY(listview != nullptr);
4024 listview->setOrientation(orientation);
4025 listview->setLayoutDirection(layoutDirection);
4026 listview->setVerticalLayoutDirection(verticalLayoutDirection);
4027 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4028
4029 QQuickItem *contentItem = listview->contentItem();
4030 QTRY_VERIFY(contentItem != nullptr);
4031
4032 QQuickText *footer = findItem<QQuickText>(parent: contentItem, objectName: "footer");
4033 QVERIFY(footer);
4034
4035 QCOMPARE(footer, listview->footerItem());
4036
4037 QCOMPARE(footer->position(), initialFooterPos);
4038 QCOMPARE(footer->width(), 100.);
4039 QCOMPARE(footer->height(), 30.);
4040 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
4041
4042 if (orientation == QQuickListView::Vertical)
4043 QCOMPARE(listview->contentHeight(), model.count() * 20. + footer->height());
4044 else
4045 QCOMPARE(listview->contentWidth(), model.count() * 40. + footer->width());
4046
4047 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
4048 QVERIFY(item);
4049 QCOMPARE(item->position(), firstDelegatePos);
4050
4051 // remove one item
4052 model.removeItem(index: 1);
4053
4054 if (orientation == QQuickListView::Vertical) {
4055 QTRY_COMPARE(footer->y(), verticalLayoutDirection == QQuickItemView::TopToBottom ?
4056 initialFooterPos.y() - 20 : initialFooterPos.y() + 20); // delegate width = 40
4057 } else {
4058 QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
4059 initialFooterPos.x() - 40 : initialFooterPos.x() + 40); // delegate width = 40
4060 }
4061
4062 // remove all items
4063 model.clear();
4064 if (orientation == QQuickListView::Vertical)
4065 QTRY_COMPARE(listview->contentHeight(), footer->height());
4066 else
4067 QTRY_COMPARE(listview->contentWidth(), footer->width());
4068
4069 QPointF posWhenNoItems(0, 0);
4070 if (orientation == QQuickListView::Horizontal && layoutDirection == Qt::RightToLeft)
4071 posWhenNoItems.setX(-100);
4072 else if (orientation == QQuickListView::Vertical && verticalLayoutDirection == QQuickItemView::BottomToTop)
4073 posWhenNoItems.setY(-30);
4074 QTRY_COMPARE(footer->position(), posWhenNoItems);
4075
4076 // if header is present, it's at a negative pos, so the footer should not move
4077 window->rootObject()->setProperty(name: "showHeader", value: true);
4078 QTRY_COMPARE(footer->position(), posWhenNoItems);
4079 window->rootObject()->setProperty(name: "showHeader", value: false);
4080
4081 // add 30 items
4082 for (int i = 0; i < 30; i++)
4083 model.addItem(name: "Item" + QString::number(i), number: "");
4084
4085 QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
4086 QMetaObject::invokeMethod(obj: window->rootObject(), member: "changeFooter");
4087
4088 QCOMPARE(footerItemSpy.count(), 1);
4089
4090 footer = findItem<QQuickText>(parent: contentItem, objectName: "footer");
4091 QVERIFY(!footer);
4092 footer = findItem<QQuickText>(parent: contentItem, objectName: "footer2");
4093 QVERIFY(footer);
4094
4095 QCOMPARE(footer, listview->footerItem());
4096
4097 QCOMPARE(footer->position(), changedFooterPos);
4098 QCOMPARE(footer->width(), 50.);
4099 QCOMPARE(footer->height(), 20.);
4100 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
4101
4102 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
4103 QVERIFY(item);
4104 QCOMPARE(item->position(), firstDelegatePos);
4105
4106 listview->positionViewAtEnd();
4107 footer->setHeight(10);
4108 footer->setWidth(40);
4109 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
4110
4111 releaseView(view: window);
4112}
4113
4114void tst_QQuickListView::footer_data()
4115{
4116 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
4117 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
4118 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
4119 QTest::addColumn<QPointF>(name: "initialFooterPos");
4120 QTest::addColumn<QPointF>(name: "changedFooterPos");
4121 QTest::addColumn<QPointF>(name: "initialContentPos");
4122 QTest::addColumn<QPointF>(name: "changedContentPos");
4123 QTest::addColumn<QPointF>(name: "firstDelegatePos");
4124 QTest::addColumn<QPointF>(name: "resizeContentPos");
4125
4126 // footer1 = 100 x 30
4127 // footer2 = 50 x 20
4128 // delegates = 40 x 20
4129 // view width = 240
4130 // view height = 320
4131
4132 // footer below items, bottom left
4133 QTest::newRow(dataTag: "vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4134 << QPointF(0, 3 * 20)
4135 << QPointF(0, 30 * 20) // added 30 items
4136 << QPointF(0, 0)
4137 << QPointF(0, 0)
4138 << QPointF(0, 0)
4139 << QPointF(0, 30 * 20 - 320 + 10);
4140
4141 // footer below items, bottom right
4142 QTest::newRow(dataTag: "vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
4143 << QPointF(0, 3 * 20)
4144 << QPointF(0, 30 * 20)
4145 << QPointF(0, 0)
4146 << QPointF(0, 0)
4147 << QPointF(0, 0)
4148 << QPointF(0, 30 * 20 - 320 + 10);
4149
4150 // footer to right of items
4151 QTest::newRow(dataTag: "horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
4152 << QPointF(40 * 3, 0)
4153 << QPointF(40 * 30, 0)
4154 << QPointF(0, 0)
4155 << QPointF(0, 0)
4156 << QPointF(0, 0)
4157 << QPointF(40 * 30 - 240 + 40, 0);
4158
4159 // footer to left of items
4160 QTest::newRow(dataTag: "horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4161 << QPointF(-(40 * 3) - 100, 0)
4162 << QPointF(-(40 * 30) - 50, 0) // 50 = new footer width
4163 << QPointF(-240, 0)
4164 << QPointF(-240, 0)
4165 << QPointF(-40, 0)
4166 << QPointF(-(40 * 30) - 40, 0);
4167
4168 // footer above items
4169 QTest::newRow(dataTag: "vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4170 << QPointF(0, -(3 * 20) - 30)
4171 << QPointF(0, -(30 * 20) - 20)
4172 << QPointF(0, -320)
4173 << QPointF(0, -320)
4174 << QPointF(0, -20)
4175 << QPointF(0, -(30 * 20) - 10);
4176}
4177
4178void tst_QQuickListView::footer2() // QTBUG-31677
4179{
4180 QQuickView *window = getView();
4181 window->setSource(testFileUrl(fileName: "footer2.qml"));
4182 window->show();
4183 QVERIFY(QTest::qWaitForWindowExposed(window));
4184
4185 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4186 QTRY_VERIFY(listview != nullptr);
4187
4188 QQuickItem *footer = listview->footerItem();
4189 QVERIFY(footer != nullptr);
4190 QTRY_COMPARE(footer->y(), 0.0);
4191}
4192
4193class LVAccessor : public QQuickListView
4194{
4195public:
4196 qreal minY() const { return minYExtent(); }
4197 qreal maxY() const { return maxYExtent(); }
4198 qreal minX() const { return minXExtent(); }
4199 qreal maxX() const { return maxXExtent(); }
4200};
4201
4202
4203void tst_QQuickListView::extents()
4204{
4205 QFETCH(QQuickListView::Orientation, orientation);
4206 QFETCH(Qt::LayoutDirection, layoutDirection);
4207 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4208 QFETCH(QPointF, headerPos);
4209 QFETCH(QPointF, footerPos);
4210 QFETCH(QPointF, minPos);
4211 QFETCH(QPointF, maxPos);
4212 QFETCH(QPointF, origin_empty);
4213 QFETCH(QPointF, origin_short);
4214 QFETCH(QPointF, origin_long);
4215
4216 QQuickView *window = getView();
4217
4218 QaimModel model;
4219 QQmlContext *ctxt = window->rootContext();
4220
4221 ctxt->setContextProperty("testModel", &model);
4222 window->setSource(testFileUrl(fileName: "headerfooter.qml"));
4223 window->show();
4224 QVERIFY(QTest::qWaitForWindowExposed(window));
4225
4226 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4227 QTRY_VERIFY(listview != nullptr);
4228 listview->setOrientation(orientation);
4229 listview->setLayoutDirection(layoutDirection);
4230 listview->setVerticalLayoutDirection(verticalLayoutDirection);
4231 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4232
4233 QQuickItem *contentItem = listview->contentItem();
4234 QTRY_VERIFY(contentItem != nullptr);
4235
4236 QQuickItem *header = findItem<QQuickItem>(parent: contentItem, objectName: "header");
4237 QVERIFY(header);
4238 QCOMPARE(header->position(), headerPos);
4239
4240 QQuickItem *footer = findItem<QQuickItem>(parent: contentItem, objectName: "footer");
4241 QVERIFY(footer);
4242 QCOMPARE(footer->position(), footerPos);
4243
4244 QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), minPos.x());
4245 QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), minPos.y());
4246 QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), maxPos.x());
4247 QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), maxPos.y());
4248
4249 QCOMPARE(listview->originX(), origin_empty.x());
4250 QCOMPARE(listview->originY(), origin_empty.y());
4251
4252 for (int i=0; i<3; i++)
4253 model.addItem(name: "Item" + QString::number(i), number: "");
4254 listview->forceLayout();
4255 QTRY_COMPARE(listview->count(), model.count());
4256 QCOMPARE(listview->originX(), origin_short.x());
4257 QCOMPARE(listview->originY(), origin_short.y());
4258
4259 for (int i=3; i<30; i++)
4260 model.addItem(name: "Item" + QString::number(i), number: "");
4261 listview->forceLayout();
4262 QTRY_COMPARE(listview->count(), model.count());
4263 QCOMPARE(listview->originX(), origin_long.x());
4264 QCOMPARE(listview->originY(), origin_long.y());
4265
4266 releaseView(view: window);
4267}
4268
4269void tst_QQuickListView::extents_data()
4270{
4271 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
4272 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
4273 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
4274 QTest::addColumn<QPointF>(name: "headerPos");
4275 QTest::addColumn<QPointF>(name: "footerPos");
4276 QTest::addColumn<QPointF>(name: "minPos");
4277 QTest::addColumn<QPointF>(name: "maxPos");
4278 QTest::addColumn<QPointF>(name: "origin_empty");
4279 QTest::addColumn<QPointF>(name: "origin_short");
4280 QTest::addColumn<QPointF>(name: "origin_long");
4281
4282 // header is 240x20 (or 20x320 in Horizontal orientation)
4283 // footer is 240x30 (or 30x320 in Horizontal orientation)
4284
4285 QTest::newRow(dataTag: "Vertical, TopToBottom")
4286 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4287 << QPointF(0, -20) << QPointF(0, 0)
4288 << QPointF(0, 20) << QPointF(240, 20)
4289 << QPointF(0, -20) << QPointF(0, -20) << QPointF(0, -20);
4290
4291 QTest::newRow(dataTag: "Vertical, BottomToTop")
4292 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4293 << QPointF(0, 0) << QPointF(0, -30)
4294 << QPointF(0, 320 - 20) << QPointF(240, 320 - 20) // content flow is reversed
4295 << QPointF(0, -30) << QPointF(0, (-30.0 * 3) - 30) << QPointF(0, (-30.0 * 30) - 30);
4296
4297 QTest::newRow(dataTag: "Horizontal, LeftToRight")
4298 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
4299 << QPointF(-20, 0) << QPointF(0, 0)
4300 << QPointF(20, 0) << QPointF(20, 320)
4301 << QPointF(-20, 0) << QPointF(-20, 0) << QPointF(-20, 0);
4302
4303 QTest::newRow(dataTag: "Horizontal, RightToLeft")
4304 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4305 << QPointF(0, 0) << QPointF(-30, 0)
4306 << QPointF(240 - 20, 0) << QPointF(240 - 20, 320) // content flow is reversed
4307 << QPointF(-30, 0) << QPointF((-240.0 * 3) - 30, 0) << QPointF((-240.0 * 30) - 30, 0);
4308}
4309
4310void tst_QQuickListView::resetModel_headerFooter()
4311{
4312 // Resetting a model shouldn't crash in views with header/footer
4313 QScopedPointer<QQuickView> window(createView());
4314
4315 QaimModel model;
4316 for (int i = 0; i < 4; i++)
4317 model.addItem(name: "Item" + QString::number(i), number: "");
4318 QQmlContext *ctxt = window->rootContext();
4319 ctxt->setContextProperty("testModel", &model);
4320
4321 window->setSource(testFileUrl(fileName: "headerfooter.qml"));
4322 qApp->processEvents();
4323
4324 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4325 QTRY_VERIFY(listview != nullptr);
4326
4327 QQuickItem *contentItem = listview->contentItem();
4328 QTRY_VERIFY(contentItem != nullptr);
4329
4330 QQuickItem *header = findItem<QQuickItem>(parent: contentItem, objectName: "header");
4331 QVERIFY(header);
4332 QCOMPARE(header->y(), -header->height());
4333
4334 QQuickItem *footer = findItem<QQuickItem>(parent: contentItem, objectName: "footer");
4335 QVERIFY(footer);
4336 QCOMPARE(footer->y(), 30.*4);
4337
4338 model.reset();
4339
4340 // A reset should not force a new header or footer to be created.
4341 QQuickItem *newHeader = findItem<QQuickItem>(parent: contentItem, objectName: "header");
4342 QCOMPARE(newHeader, header);
4343 QCOMPARE(header->y(), -header->height());
4344
4345 QQuickItem *newFooter = findItem<QQuickItem>(parent: contentItem, objectName: "footer");
4346 QCOMPARE(newFooter, footer);
4347 QCOMPARE(footer->y(), 30.*4);
4348}
4349
4350void tst_QQuickListView::resizeView()
4351{
4352 QScopedPointer<QQuickView> window(createView());
4353 QaimModel model;
4354 for (int i = 0; i < 40; i++)
4355 model.addItem(name: "Item" + QString::number(i), number: "");
4356
4357 QQmlContext *ctxt = window->rootContext();
4358 ctxt->setContextProperty("testModel", &model);
4359
4360 QScopedPointer<TestObject> testObject(new TestObject);
4361 ctxt->setContextProperty("testObject", testObject.data());
4362
4363 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
4364 window->show();
4365 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4366
4367 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4368 QTRY_VERIFY(listview != nullptr);
4369 QQuickItem *contentItem = listview->contentItem();
4370 QTRY_VERIFY(contentItem != nullptr);
4371 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4372
4373 // Confirm items positioned correctly
4374 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
4375 for (int i = 0; i < model.count() && i < itemCount; ++i) {
4376 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4377 if (!item) qWarning() << "Item" << i << "not found";
4378 QTRY_VERIFY(item);
4379 QTRY_COMPARE(item->y(), i*20.);
4380 }
4381
4382 QVariant heightRatio;
4383 QMetaObject::invokeMethod(obj: window->rootObject(), member: "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
4384 QCOMPARE(heightRatio.toReal(), 0.4);
4385
4386 listview->setHeight(200);
4387 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4388
4389 QMetaObject::invokeMethod(obj: window->rootObject(), member: "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
4390 QCOMPARE(heightRatio.toReal(), 0.25);
4391
4392 // Ensure we handle -ve sizes
4393 listview->setHeight(-100);
4394 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 1);
4395
4396 listview->setCacheBuffer(200);
4397 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 11);
4398
4399 // ensure items in cache become visible
4400 listview->setHeight(200);
4401 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 21);
4402
4403 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", visibleOnly: false).count();
4404 for (int i = 0; i < model.count() && i < itemCount; ++i) {
4405 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4406 if (!item) qWarning() << "Item" << i << "not found";
4407 QTRY_VERIFY(item);
4408 QTRY_COMPARE(item->y(), i*20.);
4409 QCOMPARE(delegateVisible(item), i < 11); // inside view visible, outside not visible
4410 }
4411
4412 // ensure items outside view become invisible
4413 listview->setHeight(100);
4414 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 16);
4415
4416 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", visibleOnly: false).count();
4417 for (int i = 0; i < model.count() && i < itemCount; ++i) {
4418 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4419 if (!item) qWarning() << "Item" << i << "not found";
4420 QTRY_VERIFY(item);
4421 QTRY_COMPARE(item->y(), i*20.);
4422 QCOMPARE(delegateVisible(item), i < 6); // inside view visible, outside not visible
4423 }
4424}
4425
4426void tst_QQuickListView::resizeViewAndRepaint()
4427{
4428 QScopedPointer<QQuickView> window(createView());
4429
4430 QaimModel model;
4431 for (int i = 0; i < 40; i++)
4432 model.addItem(name: "Item" + QString::number(i), number: "");
4433
4434 QQmlContext *ctxt = window->rootContext();
4435 ctxt->setContextProperty("testModel", &model);
4436 ctxt->setContextProperty("initialHeight", 100);
4437
4438 window->setSource(testFileUrl(fileName: "resizeview.qml"));
4439 window->show();
4440 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4441
4442 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4443 QTRY_VERIFY(listview != nullptr);
4444 QQuickItem *contentItem = listview->contentItem();
4445 QTRY_VERIFY(contentItem != nullptr);
4446 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4447
4448 // item at index 10 should not be currently visible
4449 QVERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
4450
4451 listview->setHeight(320);
4452
4453 QTRY_VERIFY(findItem<QQuickItem>(contentItem, "wrapper", 10));
4454
4455 listview->setHeight(100);
4456 QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
4457}
4458
4459void tst_QQuickListView::sizeLessThan1()
4460{
4461 QScopedPointer<QQuickView> window(createView());
4462
4463 QaimModel model;
4464 for (int i = 0; i < 30; i++)
4465 model.addItem(name: "Item" + QString::number(i), number: "");
4466
4467 QQmlContext *ctxt = window->rootContext();
4468 ctxt->setContextProperty("testModel", &model);
4469
4470 QScopedPointer<TestObject> testObject(new TestObject);
4471 ctxt->setContextProperty("testObject", testObject.data());
4472
4473 window->setSource(testFileUrl(fileName: "sizelessthan1.qml"));
4474 window->show();
4475 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4476
4477 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4478 QTRY_VERIFY(listview != nullptr);
4479 QQuickItem *contentItem = listview->contentItem();
4480 QTRY_VERIFY(contentItem != nullptr);
4481 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4482
4483 // Confirm items positioned correctly
4484 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
4485 for (int i = 0; i < model.count() && i < itemCount; ++i) {
4486 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4487 if (!item) qWarning() << "Item" << i << "not found";
4488 QTRY_VERIFY(item);
4489 QTRY_COMPARE(item->y(), i*0.5);
4490 }
4491}
4492
4493void tst_QQuickListView::QTBUG_14821()
4494{
4495 QScopedPointer<QQuickView> window(createView());
4496 window->setSource(testFileUrl(fileName: "qtbug14821.qml"));
4497 qApp->processEvents();
4498
4499 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4500 QVERIFY(listview != nullptr);
4501
4502 QQuickItem *contentItem = listview->contentItem();
4503 QVERIFY(contentItem != nullptr);
4504
4505 listview->decrementCurrentIndex();
4506 QCOMPARE(listview->currentIndex(), 99);
4507
4508 listview->incrementCurrentIndex();
4509 QCOMPARE(listview->currentIndex(), 0);
4510}
4511
4512void tst_QQuickListView::resizeDelegate()
4513{
4514 QScopedPointer<QQuickView> window(createView());
4515 QStringList strings;
4516 for (int i = 0; i < 30; ++i)
4517 strings << QString::number(i);
4518 QStringListModel model(strings);
4519
4520 QQmlContext *ctxt = window->rootContext();
4521 ctxt->setContextProperty("testModel", &model);
4522
4523 window->setSource(testFileUrl(fileName: "displaylist.qml"));
4524 window->show();
4525 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4526
4527 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4528 QVERIFY(listview != nullptr);
4529 QQuickItem *contentItem = listview->contentItem();
4530 QVERIFY(contentItem != nullptr);
4531 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4532
4533 QCOMPARE(listview->count(), model.rowCount());
4534
4535 listview->setCurrentIndex(25);
4536 listview->setContentY(0);
4537 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4538
4539 for (int i = 0; i < 16; ++i) {
4540 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4541 QVERIFY(item != nullptr);
4542 QCOMPARE(item->y(), i*20.0);
4543 }
4544
4545 QCOMPARE(listview->currentItem()->y(), 500.0);
4546 QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
4547
4548 window->rootObject()->setProperty(name: "delegateHeight", value: 30);
4549 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4550
4551 for (int i = 0; i < 11; ++i) {
4552 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4553 QVERIFY(item != nullptr);
4554 QTRY_COMPARE(item->y(), i*30.0);
4555 }
4556
4557 QTRY_COMPARE(listview->currentItem()->y(), 750.0);
4558 QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
4559
4560 listview->setCurrentIndex(1);
4561 listview->positionViewAtIndex(index: 25, mode: QQuickListView::Beginning);
4562 listview->positionViewAtIndex(index: 5, mode: QQuickListView::Beginning);
4563 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4564
4565 for (int i = 5; i < 16; ++i) {
4566 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4567 QVERIFY(item != nullptr);
4568 QCOMPARE(item->y(), i*30.0);
4569 }
4570
4571 QTRY_COMPARE(listview->currentItem()->y(), 30.0);
4572 QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
4573
4574 window->rootObject()->setProperty(name: "delegateHeight", value: 20);
4575 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4576
4577 for (int i = 5; i < 11; ++i) {
4578 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4579 QVERIFY(item != nullptr);
4580 QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
4581 }
4582
4583 QTRY_COMPARE(listview->currentItem()->y(), 70.0);
4584 QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
4585}
4586
4587void tst_QQuickListView::resizeFirstDelegate()
4588{
4589 // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
4590 // and other delegates have height > 0
4591 QScopedPointer<QQuickView> window(createView());
4592
4593 // bug only occurs when all items in the model are visible
4594 QaimModel model;
4595 for (int i = 0; i < 10; i++)
4596 model.addItem(name: "Item" + QString::number(i), number: "");
4597
4598 QQmlContext *ctxt = window->rootContext();
4599 ctxt->setContextProperty("testModel", &model);
4600
4601 QScopedPointer<TestObject> testObject(new TestObject);
4602 ctxt->setContextProperty("testObject", testObject.data());
4603
4604 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
4605 window->show();
4606 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4607
4608 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4609 QVERIFY(listview != nullptr);
4610 QQuickItem *contentItem = listview->contentItem();
4611 QVERIFY(contentItem != nullptr);
4612 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4613
4614 QQuickItem *item = nullptr;
4615 for (int i = 0; i < model.count(); ++i) {
4616 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4617 QVERIFY(item != nullptr);
4618 QCOMPARE(item->y(), i*20.0);
4619 }
4620
4621 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
4622 item->setHeight(0);
4623
4624 // check the content y has not jumped up and down
4625 QCOMPARE(listview->contentY(), 0.0);
4626 QSignalSpy spy(listview, SIGNAL(contentYChanged()));
4627 QTest::qWait(ms: 100);
4628 QCOMPARE(spy.count(), 0);
4629
4630 for (int i = 1; i < model.count(); ++i) {
4631 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
4632 QVERIFY(item != nullptr);
4633 QTRY_COMPARE(item->y(), (i-1)*20.0);
4634 }
4635
4636
4637 // QTBUG-22014: refill doesn't clear items scrolling off the top of the
4638 // list if they follow a zero-sized delegate
4639
4640 for (int i = 0; i < 10; i++)
4641 model.addItem(name: "Item" + QString::number(i), number: "");
4642 listview->forceLayout();
4643 QTRY_COMPARE(listview->count(), model.count());
4644
4645 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 1);
4646 QVERIFY(item);
4647 item->setHeight(0);
4648
4649 listview->setCurrentIndex(19);
4650 qApp->processEvents();
4651 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4652
4653 // items 0-2 should have been deleted
4654 for (int i=0; i<3; i++) {
4655 QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", i));
4656 }
4657}
4658
4659void tst_QQuickListView::repositionResizedDelegate()
4660{
4661 QFETCH(QQuickListView::Orientation, orientation);
4662 QFETCH(Qt::LayoutDirection, layoutDirection);
4663 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4664 QFETCH(QPointF, contentPos_itemFirstHalfVisible);
4665 QFETCH(QPointF, contentPos_itemSecondHalfVisible);
4666 QFETCH(QRectF, origPositionerRect);
4667 QFETCH(QRectF, resizedPositionerRect);
4668
4669 QQuickView *window = getView();
4670 QQmlContext *ctxt = window->rootContext();
4671 ctxt->setContextProperty("testHorizontal", orientation == QQuickListView::Horizontal);
4672 ctxt->setContextProperty("testRightToLeft", layoutDirection == Qt::RightToLeft);
4673 ctxt->setContextProperty("testBottomToTop", verticalLayoutDirection == QQuickListView::BottomToTop);
4674 window->setSource(testFileUrl(fileName: "repositionResizedDelegate.qml"));
4675 window->show();
4676 QVERIFY(QTest::qWaitForWindowExposed(window));
4677
4678 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4679 QTRY_VERIFY(listview != nullptr);
4680 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4681
4682 QQuickItem *positioner = findItem<QQuickItem>(parent: window->rootObject(), objectName: "positioner");
4683 QVERIFY(positioner);
4684 QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4685 QTRY_COMPARE(positioner->position(), origPositionerRect.topLeft());
4686 QSignalSpy spy(listview, orientation == QQuickListView::Vertical ? SIGNAL(contentYChanged()) : SIGNAL(contentXChanged()));
4687 int prevSpyCount = 0;
4688
4689 // When an item is resized while it is partially visible, it should resize in the
4690 // direction of the content flow. If a RightToLeft or BottomToTop layout is used,
4691 // the item should also be re-positioned so its end position stays the same.
4692
4693 listview->setContentX(contentPos_itemFirstHalfVisible.x());
4694 listview->setContentY(contentPos_itemFirstHalfVisible.y());
4695 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4696 prevSpyCount = spy.count();
4697 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "incrementRepeater"));
4698 QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4699 QTRY_COMPARE(positioner->position(), resizedPositionerRect.topLeft());
4700 QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4701 QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4702 QCOMPARE(spy.count(), prevSpyCount);
4703
4704 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "decrementRepeater"));
4705 QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4706 QTRY_COMPARE(positioner->position(), origPositionerRect.topLeft());
4707 QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4708 QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4709
4710 listview->setContentX(contentPos_itemSecondHalfVisible.x());
4711 listview->setContentY(contentPos_itemSecondHalfVisible.y());
4712 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4713 prevSpyCount = spy.count();
4714
4715 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "incrementRepeater"));
4716 positioner = findItem<QQuickItem>(parent: window->rootObject(), objectName: "positioner");
4717 QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4718 QTRY_COMPARE(positioner->position(), resizedPositionerRect.topLeft());
4719 QCOMPARE(listview->contentX(), contentPos_itemSecondHalfVisible.x());
4720 QCOMPARE(listview->contentY(), contentPos_itemSecondHalfVisible.y());
4721 qApp->processEvents();
4722 QCOMPARE(spy.count(), prevSpyCount);
4723
4724 releaseView(view: window);
4725}
4726
4727void tst_QQuickListView::repositionResizedDelegate_data()
4728{
4729 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
4730 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
4731 QTest::addColumn<QQuickListView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
4732 QTest::addColumn<QPointF>(name: "contentPos_itemFirstHalfVisible");
4733 QTest::addColumn<QPointF>(name: "contentPos_itemSecondHalfVisible");
4734 QTest::addColumn<QRectF>(name: "origPositionerRect");
4735 QTest::addColumn<QRectF>(name: "resizedPositionerRect");
4736
4737 QTest::newRow(dataTag: "vertical")
4738 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4739 << QPointF(0, 60) << QPointF(0, 200 + 60)
4740 << QRectF(0, 200, 120, 120)
4741 << QRectF(0, 200, 120, 120 * 2);
4742
4743 QTest::newRow(dataTag: "vertical, BottomToTop")
4744 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4745 << QPointF(0, -200 - 60) << QPointF(0, -200 - 260)
4746 << QRectF(0, -200 - 120, 120, 120)
4747 << QRectF(0, -200 - 120*2, 120, 120 * 2);
4748
4749 QTest::newRow(dataTag: "horizontal")
4750 << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
4751 << QPointF(60, 0) << QPointF(260, 0)
4752 << QRectF(200, 0, 120, 120)
4753 << QRectF(200, 0, 120 * 2, 120);
4754
4755 QTest::newRow(dataTag: "horizontal, rtl")
4756 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4757 << QPointF(-200 - 60, 0) << QPointF(-200 - 260, 0)
4758 << QRectF(-200 - 120, 0, 120, 120)
4759 << QRectF(-200 - 120 * 2, 0, 120 * 2, 120);
4760}
4761
4762void tst_QQuickListView::QTBUG_16037()
4763{
4764 QScopedPointer<QQuickView> window(createView());
4765 window->show();
4766
4767 window->setSource(testFileUrl(fileName: "qtbug16037.qml"));
4768 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4769
4770 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "listview");
4771 QTRY_VERIFY(listview != nullptr);
4772
4773 QVERIFY(listview->contentHeight() <= 0.0);
4774
4775 QMetaObject::invokeMethod(obj: window->rootObject(), member: "setModel");
4776
4777 QTRY_COMPARE(listview->contentHeight(), 80.0);
4778}
4779
4780void tst_QQuickListView::indexAt_itemAt_data()
4781{
4782 QTest::addColumn<qreal>(name: "x");
4783 QTest::addColumn<qreal>(name: "y");
4784 QTest::addColumn<int>(name: "index");
4785
4786 QTest::newRow(dataTag: "Item 0 - 0, 0") << 0. << 0. << 0;
4787 QTest::newRow(dataTag: "Item 0 - 0, 19") << 0. << 19. << 0;
4788 QTest::newRow(dataTag: "Item 0 - 239, 19") << 239. << 19. << 0;
4789 QTest::newRow(dataTag: "Item 1 - 0, 20") << 0. << 20. << 1;
4790 QTest::newRow(dataTag: "No Item - 240, 20") << 240. << 20. << -1;
4791}
4792
4793void tst_QQuickListView::indexAt_itemAt()
4794{
4795 QFETCH(qreal, x);
4796 QFETCH(qreal, y);
4797 QFETCH(int, index);
4798
4799 QQuickView *window = getView();
4800
4801 QaimModel model;
4802 for (int i = 0; i < 30; i++)
4803 model.addItem(name: "Item" + QString::number(i), number: "");
4804
4805 QQmlContext *ctxt = window->rootContext();
4806 ctxt->setContextProperty("testModel", &model);
4807
4808 QScopedPointer<TestObject> testObject(new TestObject);
4809 ctxt->setContextProperty("testObject", testObject.data());
4810
4811 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
4812 window->show();
4813 QVERIFY(QTest::qWaitForWindowExposed(window));
4814
4815 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4816 QTRY_VERIFY(listview != nullptr);
4817
4818 QQuickItem *contentItem = listview->contentItem();
4819 QTRY_VERIFY(contentItem != nullptr);
4820 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
4821
4822 QQuickItem *item = nullptr;
4823 if (index >= 0) {
4824 item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index);
4825 QVERIFY(item);
4826 }
4827 QCOMPARE(listview->indexAt(x,y), index);
4828 QCOMPARE(listview->itemAt(x,y), item);
4829
4830 releaseView(view: window);
4831}
4832
4833void tst_QQuickListView::itemAtIndex()
4834{
4835 QScopedPointer<QQuickView> window(createView());
4836 window->setSource(testFileUrl(fileName: "listview-itematindex.qml"));
4837 window->show();
4838 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4839
4840 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4841 QVERIFY(listview != nullptr);
4842
4843 QCOMPARE(listview->itemAtIndex(-1), nullptr);
4844 QCOMPARE(listview->itemAtIndex(3), nullptr);
4845 QQuickItem *item = listview->itemAtIndex(index: 0);
4846 QVERIFY(item);
4847 QCOMPARE(item->property("idx"), 0);
4848 item = listview->itemAtIndex(index: 1);
4849 QVERIFY(item);
4850 QCOMPARE(item->property("idx"), 1);
4851 item = listview->itemAtIndex(index: 2);
4852 QVERIFY(item);
4853 QCOMPARE(item->property("idx"), 2);
4854}
4855
4856void tst_QQuickListView::incrementalModel()
4857{
4858 QScopedPointer<QQuickView> window(createView());
4859
4860 IncrementalModel model;
4861 QQmlContext *ctxt = window->rootContext();
4862 ctxt->setContextProperty("testModel", &model);
4863
4864 window->setSource(testFileUrl(fileName: "displaylist.qml"));
4865 window->show();
4866 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4867
4868 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
4869 QTRY_VERIFY(listview != nullptr);
4870
4871 QQuickItem *contentItem = listview->contentItem();
4872 QTRY_VERIFY(contentItem != nullptr);
4873 QTRY_COMPARE(listview->count(), 35);
4874
4875 listview->positionViewAtIndex(index: 10, mode: QQuickListView::Beginning);
4876 QTRY_COMPARE(listview->count(), 45);
4877}
4878
4879void tst_QQuickListView::onAdd()
4880{
4881 QFETCH(int, initialItemCount);
4882 QFETCH(int, itemsToAdd);
4883
4884 const int delegateHeight = 10;
4885 QaimModel model;
4886
4887 // these initial items should not trigger ListView.onAdd
4888 for (int i=0; i<initialItemCount; i++)
4889 model.addItem(name: "dummy value", number: "dummy value");
4890
4891 QScopedPointer<QQuickView> window(createView());
4892 window->setGeometry(posx: 0,posy: 0,w: 200, h: delegateHeight * (initialItemCount + itemsToAdd));
4893 QQmlContext *ctxt = window->rootContext();
4894 ctxt->setContextProperty("testModel", &model);
4895 ctxt->setContextProperty("delegateHeight", delegateHeight);
4896 window->setSource(testFileUrl(fileName: "attachedSignals.qml"));
4897
4898 QQuickListView* listview = qobject_cast<QQuickListView*>(object: window->rootObject());
4899 listview->setProperty(name: "width", value: window->width());
4900 listview->setProperty(name: "height", value: window->height());
4901 qApp->processEvents();
4902
4903 QList<QPair<QString, QString> > items;
4904 for (int i=0; i<itemsToAdd; i++)
4905 items << qMakePair(x: QString("value %1").arg(a: i), y: QString::number(i));
4906 model.addItems(items);
4907 listview->forceLayout();
4908 QTRY_COMPARE(listview->property("count").toInt(), model.count());
4909
4910 QVariantList result = listview->property(name: "addedDelegates").toList();
4911 QCOMPARE(result.count(), items.count());
4912 for (int i=0; i<items.count(); i++)
4913 QCOMPARE(result[i].toString(), items[i].first);
4914}
4915
4916void tst_QQuickListView::onAdd_data()
4917{
4918 QTest::addColumn<int>(name: "initialItemCount");
4919 QTest::addColumn<int>(name: "itemsToAdd");
4920
4921 QTest::newRow(dataTag: "0, add 1") << 0 << 1;
4922 QTest::newRow(dataTag: "0, add 2") << 0 << 2;
4923 QTest::newRow(dataTag: "0, add 10") << 0 << 10;
4924
4925 QTest::newRow(dataTag: "1, add 1") << 1 << 1;
4926 QTest::newRow(dataTag: "1, add 2") << 1 << 2;
4927 QTest::newRow(dataTag: "1, add 10") << 1 << 10;
4928
4929 QTest::newRow(dataTag: "5, add 1") << 5 << 1;
4930 QTest::newRow(dataTag: "5, add 2") << 5 << 2;
4931 QTest::newRow(dataTag: "5, add 10") << 5 << 10;
4932}
4933
4934void tst_QQuickListView::onRemove()
4935{
4936 QFETCH(int, initialItemCount);
4937 QFETCH(int, indexToRemove);
4938 QFETCH(int, removeCount);
4939
4940 const int delegateHeight = 10;
4941 QaimModel model;
4942 for (int i=0; i<initialItemCount; i++)
4943 model.addItem(name: QString("value %1").arg(a: i), number: "dummy value");
4944
4945 QQuickView *window = getView();
4946 QQmlContext *ctxt = window->rootContext();
4947 ctxt->setContextProperty("testModel", &model);
4948 ctxt->setContextProperty("delegateHeight", delegateHeight);
4949 window->setSource(testFileUrl(fileName: "attachedSignals.qml"));
4950
4951 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
4952
4953 model.removeItems(index: indexToRemove, count: removeCount);
4954 listview->forceLayout();
4955 QTRY_COMPARE(listview->property("count").toInt(), model.count());
4956
4957 QCOMPARE(listview->property("removedDelegateCount"), QVariant(removeCount));
4958
4959 releaseView(view: window);
4960}
4961
4962void tst_QQuickListView::onRemove_data()
4963{
4964 QTest::addColumn<int>(name: "initialItemCount");
4965 QTest::addColumn<int>(name: "indexToRemove");
4966 QTest::addColumn<int>(name: "removeCount");
4967
4968 QTest::newRow(dataTag: "remove first") << 1 << 0 << 1;
4969 QTest::newRow(dataTag: "two items, remove first") << 2 << 0 << 1;
4970 QTest::newRow(dataTag: "two items, remove last") << 2 << 1 << 1;
4971 QTest::newRow(dataTag: "two items, remove all") << 2 << 0 << 2;
4972
4973 QTest::newRow(dataTag: "four items, remove first") << 4 << 0 << 1;
4974 QTest::newRow(dataTag: "four items, remove 0-2") << 4 << 0 << 2;
4975 QTest::newRow(dataTag: "four items, remove 1-3") << 4 << 1 << 2;
4976 QTest::newRow(dataTag: "four items, remove 2-4") << 4 << 2 << 2;
4977 QTest::newRow(dataTag: "four items, remove last") << 4 << 3 << 1;
4978 QTest::newRow(dataTag: "four items, remove all") << 4 << 0 << 4;
4979
4980 QTest::newRow(dataTag: "ten items, remove 1-8") << 10 << 0 << 8;
4981 QTest::newRow(dataTag: "ten items, remove 2-7") << 10 << 2 << 5;
4982 QTest::newRow(dataTag: "ten items, remove 4-10") << 10 << 4 << 6;
4983}
4984
4985void tst_QQuickListView::rightToLeft()
4986{
4987 QScopedPointer<QQuickView> window(createView());
4988 window->setGeometry(posx: 0,posy: 0,w: 640,h: 320);
4989 window->setSource(testFileUrl(fileName: "rightToLeft.qml"));
4990 window->show();
4991 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4992
4993 QVERIFY(window->rootObject() != nullptr);
4994 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "view");
4995 QTRY_VERIFY(listview != nullptr);
4996
4997 QQuickItem *contentItem = listview->contentItem();
4998 QTRY_VERIFY(contentItem != nullptr);
4999
5000 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5001
5002 QQmlObjectModel *model = window->rootObject()->findChild<QQmlObjectModel*>(aName: "itemModel");
5003 QTRY_VERIFY(model != nullptr);
5004
5005 QTRY_COMPARE(model->count(), 3);
5006 QTRY_COMPARE(listview->currentIndex(), 0);
5007
5008 // initial position at first item, right edge aligned
5009 QCOMPARE(listview->contentX(), -640.);
5010
5011 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "item1");
5012 QTRY_VERIFY(item);
5013 QTRY_COMPARE(item->x(), -100.0);
5014 QCOMPARE(item->height(), listview->height());
5015
5016 QQuickText *text = findItem<QQuickText>(parent: contentItem, objectName: "text1");
5017 QTRY_VERIFY(text);
5018 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
5019
5020 listview->setCurrentIndex(2);
5021
5022 item = findItem<QQuickItem>(parent: contentItem, objectName: "item3");
5023 QTRY_VERIFY(item);
5024 QTRY_COMPARE(item->x(), -540.0);
5025
5026 text = findItem<QQuickText>(parent: contentItem, objectName: "text3");
5027 QTRY_VERIFY(text);
5028 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
5029
5030 QCOMPARE(listview->contentX(), -640.);
5031
5032 // Ensure resizing maintains position relative to right edge
5033 qobject_cast<QQuickItem*>(object: window->rootObject())->setWidth(600);
5034 QTRY_COMPARE(listview->contentX(), -600.);
5035}
5036
5037void tst_QQuickListView::test_mirroring()
5038{
5039 QScopedPointer<QQuickView> windowA(createView());
5040 windowA->setSource(testFileUrl(fileName: "rightToLeft.qml"));
5041 QQuickListView *listviewA = findItem<QQuickListView>(parent: windowA->rootObject(), objectName: "view");
5042 QTRY_VERIFY(listviewA != nullptr);
5043
5044 QScopedPointer<QQuickView> windowB(createView());
5045 windowB->setSource(testFileUrl(fileName: "rightToLeft.qml"));
5046 QQuickListView *listviewB = findItem<QQuickListView>(parent: windowB->rootObject(), objectName: "view");
5047 QTRY_VERIFY(listviewA != nullptr);
5048 qApp->processEvents();
5049
5050 QList<QString> objectNames;
5051 objectNames << "item1" << "item2"; // << "item3"
5052
5053 listviewA->setProperty(name: "layoutDirection", value: Qt::LeftToRight);
5054 listviewB->setProperty(name: "layoutDirection", value: Qt::RightToLeft);
5055 QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
5056
5057 // LTR != RTL
5058 foreach (const QString objectName, objectNames)
5059 QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
5060
5061 listviewA->setProperty(name: "layoutDirection", value: Qt::LeftToRight);
5062 listviewB->setProperty(name: "layoutDirection", value: Qt::LeftToRight);
5063
5064 // LTR == LTR
5065 foreach (const QString objectName, objectNames)
5066 QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
5067
5068 QCOMPARE(listviewB->layoutDirection(), listviewB->effectiveLayoutDirection());
5069 QQuickItemPrivate::get(item: listviewB)->setLayoutMirror(true);
5070 QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
5071
5072 // LTR != LTR+mirror
5073 foreach (const QString objectName, objectNames)
5074 QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
5075
5076 listviewA->setProperty(name: "layoutDirection", value: Qt::RightToLeft);
5077
5078 // RTL == LTR+mirror
5079 foreach (const QString objectName, objectNames)
5080 QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
5081
5082 listviewB->setProperty(name: "layoutDirection", value: Qt::RightToLeft);
5083
5084 // RTL != RTL+mirror
5085 foreach (const QString objectName, objectNames)
5086 QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
5087
5088 listviewA->setProperty(name: "layoutDirection", value: Qt::LeftToRight);
5089
5090 // LTR == RTL+mirror
5091 foreach (const QString objectName, objectNames)
5092 QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
5093}
5094
5095void tst_QQuickListView::margins()
5096{
5097 QScopedPointer<QQuickView> window(createView());
5098 QaimModel model;
5099 for (int i = 0; i < 50; i++)
5100 model.addItem(name: "Item" + QString::number(i), number: "");
5101
5102 QQmlContext *ctxt = window->rootContext();
5103 ctxt->setContextProperty("testModel", &model);
5104
5105 window->setSource(testFileUrl(fileName: "margins.qml"));
5106 window->show();
5107 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
5108
5109 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
5110 QTRY_VERIFY(listview != nullptr);
5111 QQuickItem *contentItem = listview->contentItem();
5112 QTRY_VERIFY(contentItem != nullptr);
5113 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5114
5115 QCOMPARE(listview->contentY(), -30.);
5116 QCOMPARE(listview->originY(), 0.);
5117
5118 // check end bound
5119 listview->positionViewAtEnd();
5120 qreal pos = listview->contentY();
5121 listview->setContentY(pos + 80);
5122 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5123 listview->returnToBounds();
5124 QTRY_COMPARE(listview->contentY(), pos + 50);
5125
5126 // remove item before visible and check that top margin is maintained
5127 // and originY is updated
5128 listview->setContentY(100);
5129 model.removeItem(index: 1);
5130 listview->forceLayout();
5131 QTRY_COMPARE(listview->count(), model.count());
5132 listview->setContentY(-50);
5133 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5134 listview->returnToBounds();
5135 QCOMPARE(listview->originY(), 20.);
5136 QTRY_COMPARE(listview->contentY(), -10.);
5137
5138 // reduce top margin
5139 listview->setTopMargin(20);
5140 QCOMPARE(listview->originY(), 20.);
5141 QTRY_COMPARE(listview->contentY(), 0.);
5142
5143 // check end bound
5144 listview->positionViewAtEnd();
5145 pos = listview->contentY();
5146 listview->setContentY(pos + 80);
5147 listview->returnToBounds();
5148 QTRY_COMPARE(listview->contentY(), pos + 50);
5149
5150 // reduce bottom margin
5151 pos = listview->contentY();
5152 listview->setBottomMargin(40);
5153 QCOMPARE(listview->originY(), 20.);
5154 QTRY_COMPARE(listview->contentY(), pos-10);
5155}
5156
5157// QTBUG-24028
5158void tst_QQuickListView::marginsResize()
5159{
5160 QFETCH(QQuickListView::Orientation, orientation);
5161 QFETCH(Qt::LayoutDirection, layoutDirection);
5162 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
5163 QFETCH(qreal, start);
5164 QFETCH(qreal, end);
5165
5166 QPoint flickStart(20, 20);
5167 QPoint flickEnd(20, 20);
5168 if (orientation == QQuickListView::Vertical)
5169 flickStart.ry() += (verticalLayoutDirection == QQuickItemView::TopToBottom) ? 180 : -180;
5170 else
5171 flickStart.rx() += (layoutDirection == Qt::LeftToRight) ? 180 : -180;
5172
5173 QQuickView *window = getView();
5174
5175 window->setSource(testFileUrl(fileName: "margins2.qml"));
5176 QQuickViewTestUtil::moveMouseAway(window);
5177 window->show();
5178 QVERIFY(QTest::qWaitForWindowExposed(window));
5179
5180 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "listview");
5181 QTRY_VERIFY(listview != nullptr);
5182
5183 listview->setOrientation(orientation);
5184 listview->setLayoutDirection(layoutDirection);
5185 listview->setVerticalLayoutDirection(verticalLayoutDirection);
5186 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5187
5188 // view is resized after componentCompleted - top margin should still be visible
5189 if (orientation == QQuickListView::Vertical)
5190 QCOMPARE(listview->contentY(), start);
5191 else
5192 QCOMPARE(listview->contentX(), start);
5193
5194 // move to last index and ensure bottom margin is visible.
5195 listview->setCurrentIndex(19);
5196 if (orientation == QQuickListView::Vertical)
5197 QTRY_COMPARE(listview->contentY(), end);
5198 else
5199 QTRY_COMPARE(listview->contentX(), end);
5200
5201 // flick past the end and check content pos still settles on correct extents
5202 flick(window, from: flickStart, to: flickEnd, duration: 180);
5203 QTRY_VERIFY(!listview->isMoving());
5204 if (orientation == QQuickListView::Vertical)
5205 QTRY_COMPARE(listview->contentY(), end);
5206 else
5207 QTRY_COMPARE(listview->contentX(), end);
5208
5209 // back to top - top margin should be visible.
5210 listview->setCurrentIndex(0);
5211 if (orientation == QQuickListView::Vertical)
5212 QTRY_COMPARE(listview->contentY(), start);
5213 else
5214 QTRY_COMPARE(listview->contentX(), start);
5215
5216 // flick past the beginning and check content pos still settles on correct extents
5217 flick(window, from: flickEnd, to: flickStart, duration: 180);
5218 QTRY_VERIFY(!listview->isMoving());
5219 if (orientation == QQuickListView::Vertical)
5220 QTRY_COMPARE(listview->contentY(), start);
5221 else
5222 QTRY_COMPARE(listview->contentX(), start);
5223
5224 releaseView(view: window);
5225}
5226
5227void tst_QQuickListView::marginsResize_data()
5228{
5229 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
5230 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
5231 QTest::addColumn<QQuickListView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
5232 QTest::addColumn<qreal>(name: "start");
5233 QTest::addColumn<qreal>(name: "end");
5234
5235 // in Right to Left mode, leftMargin still means leftMargin - it doesn't reverse to mean rightMargin
5236
5237 QTest::newRow(dataTag: "vertical")
5238 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
5239 << -40.0 << 1020.0;
5240
5241 QTest::newRow(dataTag: "vertical, BottomToTop")
5242 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
5243 << -180.0 << -1240.0;
5244
5245 QTest::newRow(dataTag: "horizontal")
5246 << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
5247 << -40.0 << 1020.0;
5248
5249 QTest::newRow(dataTag: "horizontal, rtl")
5250 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
5251 << -180.0 << -1240.0;
5252}
5253
5254void tst_QQuickListView::snapToItem_data()
5255{
5256 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
5257 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
5258 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
5259 QTest::addColumn<int>(name: "highlightRangeMode");
5260 QTest::addColumn<QPoint>(name: "flickStart");
5261 QTest::addColumn<QPoint>(name: "flickEnd");
5262 QTest::addColumn<qreal>(name: "snapAlignment");
5263 QTest::addColumn<qreal>(name: "endExtent");
5264 QTest::addColumn<qreal>(name: "startExtent");
5265
5266 QTest::newRow(dataTag: "vertical, top to bottom")
5267 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5268 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
5269
5270 QTest::newRow(dataTag: "vertical, bottom to top")
5271 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
5272 << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 << -240.0;
5273
5274 QTest::newRow(dataTag: "horizontal, left to right")
5275 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5276 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
5277
5278 QTest::newRow(dataTag: "horizontal, right to left")
5279 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5280 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 << -240.0;
5281
5282 QTest::newRow(dataTag: "vertical, top to bottom, enforce range")
5283 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5284 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
5285
5286 QTest::newRow(dataTag: "vertical, bottom to top, enforce range")
5287 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
5288 << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
5289
5290 QTest::newRow(dataTag: "horizontal, left to right, enforce range")
5291 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5292 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
5293
5294 QTest::newRow(dataTag: "horizontal, right to left, enforce range")
5295 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5296 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
5297}
5298
5299void tst_QQuickListView::snapToItem()
5300{
5301 QFETCH(QQuickListView::Orientation, orientation);
5302 QFETCH(Qt::LayoutDirection, layoutDirection);
5303 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
5304 QFETCH(int, highlightRangeMode);
5305 QFETCH(QPoint, flickStart);
5306 QFETCH(QPoint, flickEnd);
5307 QFETCH(qreal, snapAlignment);
5308 QFETCH(qreal, endExtent);
5309 QFETCH(qreal, startExtent);
5310
5311 QQuickView *window = getView();
5312 QQuickViewTestUtil::moveMouseAway(window);
5313
5314 window->setSource(testFileUrl(fileName: "snapToItem.qml"));
5315 window->show();
5316 QVERIFY(QTest::qWaitForWindowExposed(window));
5317
5318
5319 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
5320 QTRY_VERIFY(listview != nullptr);
5321
5322 listview->setOrientation(orientation);
5323 listview->setLayoutDirection(layoutDirection);
5324 listview->setVerticalLayoutDirection(verticalLayoutDirection);
5325 listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
5326 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5327
5328 QQuickItem *contentItem = listview->contentItem();
5329 QTRY_VERIFY(contentItem != nullptr);
5330
5331 // confirm that a flick hits an item boundary
5332 flick(window, from: flickStart, to: flickEnd, duration: 180);
5333 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5334 if (orientation == QQuickListView::Vertical)
5335 QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
5336 else
5337 QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
5338
5339 // flick to end
5340 do {
5341 flick(window, from: flickStart, to: flickEnd, duration: 180);
5342 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5343 } while (orientation == QQuickListView::Vertical
5344 ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
5345 : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
5346
5347 if (orientation == QQuickListView::Vertical)
5348 QCOMPARE(listview->contentY(), endExtent);
5349 else
5350 QCOMPARE(listview->contentX(), endExtent);
5351
5352 // flick to start
5353 do {
5354 flick(window, from: flickEnd, to: flickStart, duration: 180);
5355 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5356 } while (orientation == QQuickListView::Vertical
5357 ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
5358 : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
5359
5360 if (orientation == QQuickListView::Vertical)
5361 QCOMPARE(listview->contentY(), startExtent);
5362 else
5363 QCOMPARE(listview->contentX(), startExtent);
5364
5365 releaseView(view: window);
5366}
5367void tst_QQuickListView::snapToItemWithSpacing_QTBUG_59852()
5368{
5369 QQuickView *window = getView();
5370
5371 window->setSource(testFileUrl(fileName: "snapToItemWithSpacing.qml"));
5372 window->show();
5373 QVERIFY(QTest::qWaitForWindowExposed(window));
5374
5375 auto *listView = qobject_cast<QQuickListView*>(object: window->rootObject());
5376 QVERIFY(listView);
5377
5378 QVERIFY(QQuickTest::qWaitForItemPolished(listView));
5379
5380 // each item in the list is 100 pixels tall, and the spacing is 100
5381
5382 listView->setContentY(110); // this is right below the first item
5383 listView->returnToBounds();
5384 QCOMPARE(listView->contentY(), 200); // the position of the second item
5385
5386 listView->setContentY(60); // this is right below the middle of the first item
5387 listView->returnToBounds();
5388 QCOMPARE(listView->contentY(), 0); // it's farther to go to the next item, so snaps to the first
5389
5390 releaseView(view: window);
5391}
5392
5393static void drag_helper(QWindow *window, QPoint *startPos, const QPoint &delta)
5394{
5395 QPoint pos = *startPos;
5396 int dragDistance = delta.manhattanLength();
5397 Q_ASSERT(qAbs(delta.x()) >= 1 || qAbs(delta.y()) >= 1);
5398
5399 const int stepSize = 8;
5400 QPoint unitVector(0, 0);
5401 if (delta.x())
5402 unitVector.setX(qBound(min: -1, val: delta.x(), max: 1));
5403 if (delta.y())
5404 unitVector.setY(qBound(min: -1, val: delta.y(), max: 1));
5405 QPoint dragStepSize = unitVector * stepSize;
5406 int nDragSteps = qAbs(t: dragDistance/stepSize);
5407
5408 for (int i = 0 ; i < nDragSteps; ++i) {
5409 QTest::mouseMove(window, pos);
5410 pos += dragStepSize;
5411 }
5412 // Move to the final position
5413 pos = *startPos + delta;
5414 QTest::mouseMove(window, pos);
5415 *startPos = pos;
5416}
5417
5418static void dragtwice(QWindow *window, QPoint *startPos, const QPoint &delta1, const QPoint &delta2)
5419{
5420 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
5421 QPoint &pos = *startPos;
5422 QPoint unitVector(0, 0);
5423 if (delta1.x())
5424 unitVector.setX(qBound(min: -1, val: delta1.x(), max: 1));
5425 if (delta1.y())
5426 unitVector.setY(qBound(min: -1, val: delta1.y(), max: 1));
5427
5428 // go just beyond the drag theshold
5429 drag_helper(window, startPos: &pos, delta: unitVector * (dragThreshold + 1));
5430 drag_helper(window, startPos: &pos, delta: unitVector);
5431
5432 // next drag will actually scroll the listview
5433 if (delta1.manhattanLength() >= 1)
5434 drag_helper(window, startPos: &pos, delta: delta1);
5435 if (delta2.manhattanLength() >= 1)
5436 drag_helper(window, startPos: &pos, delta: delta2);
5437}
5438
5439struct MyListView : public QQuickListView{
5440 qreal contentPosition() const
5441 {
5442 return (orientation() == QQuickListView::Horizontal ? contentX(): contentY());
5443 }
5444
5445 qreal headerPosition() const
5446 {
5447 return (orientation() == QQuickListView::Horizontal ? headerItem()->x() : headerItem()->y());
5448 }
5449};
5450
5451void tst_QQuickListView::headerSnapToItem()
5452{
5453 QFETCH(QQuickItemView::LayoutDirection, layoutDirection);
5454 QFETCH(QQuickListView::HeaderPositioning, headerPositioning);
5455 QFETCH(int, firstDragDistance);
5456 QFETCH(int, secondDragDistance);
5457 QFETCH(int, expectedContentPosition);
5458 QFETCH(int, expectedHeaderPosition);
5459
5460 QQuickView *window = getView();
5461 window->setSource(testFileUrl(fileName: "headerSnapToItem.qml"));
5462 window->show();
5463 QVERIFY(QTest::qWaitForWindowExposed(window));
5464
5465 MyListView *listview = static_cast<MyListView *>(findItem<QQuickListView>(parent: window->rootObject(), objectName: "list"));
5466 QVERIFY(listview != nullptr);
5467
5468 const bool horizontal = layoutDirection < QQuickItemView::VerticalTopToBottom;
5469 listview->setOrientation(horizontal ? QQuickListView::Horizontal : QQuickListView::Vertical);
5470
5471 if (horizontal)
5472 listview->setLayoutDirection(static_cast<Qt::LayoutDirection>(layoutDirection));
5473 else
5474 listview->setVerticalLayoutDirection(static_cast<QQuickItemView::VerticalLayoutDirection>(layoutDirection));
5475
5476 listview->setHeaderPositioning(headerPositioning);
5477 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
5478
5479 QQuickItem *contentItem = listview->contentItem();
5480 QVERIFY(contentItem != nullptr);
5481 QQuickItem *header = findItem<QQuickItem>(parent: contentItem, objectName: "header");
5482 QVERIFY(header != nullptr);
5483 QCOMPARE(header, listview->headerItem());
5484
5485 QPoint startPos = (QPointF(listview->width(), listview->height())/2).toPoint();
5486 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: startPos, delay: 200);
5487
5488 QPoint firstDragDelta(0, firstDragDistance);
5489 QPoint secondDragDelta = QPoint(0, secondDragDistance);
5490 if (horizontal) {
5491 firstDragDelta = firstDragDelta.transposed();
5492 secondDragDelta = secondDragDelta.transposed();
5493 }
5494
5495 dragtwice(window, startPos: &startPos, delta1: firstDragDelta, delta2: secondDragDelta);
5496
5497 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: startPos, delay: 200); // Wait 200 ms before we release to avoid trigger a flick
5498
5499 // wait for the "fixup" animation to finish
5500 QTest::qWaitFor(predicate: [&]()
5501 { return !listview->isMoving();}
5502 );
5503
5504 QCOMPARE(listview->contentPosition(), expectedContentPosition);
5505 QCOMPARE(listview->headerPosition(), expectedHeaderPosition);
5506}
5507
5508void tst_QQuickListView::headerSnapToItem_data()
5509{
5510 QTest::addColumn<QQuickItemView::LayoutDirection>(name: "layoutDirection");
5511 QTest::addColumn<QQuickListView::HeaderPositioning>(name: "headerPositioning");
5512 QTest::addColumn<int>(name: "firstDragDistance");
5513 QTest::addColumn<int>(name: "secondDragDistance");
5514 QTest::addColumn<int>(name: "expectedContentPosition");
5515 QTest::addColumn<int>(name: "expectedHeaderPosition");
5516
5517 // --------------------
5518 // InlineHeader TopToBottom
5519 QTest::newRow(dataTag: "InlineHeader TopToBottom -10") << QQuickItemView::VerticalTopToBottom
5520 << QQuickListView::InlineHeader
5521 << -10 << 0
5522 << -30 << -30;
5523
5524 QTest::newRow(dataTag: "InlineHeader TopToBottom -14") << QQuickItemView::VerticalTopToBottom
5525 << QQuickListView::InlineHeader
5526 << -14 << 0
5527 << -30 << -30;
5528
5529 QTest::newRow(dataTag: "InlineHeader TopToBottom -16") << QQuickItemView::VerticalTopToBottom
5530 << QQuickListView::InlineHeader
5531 << -16 << 0
5532 << 0 << -30;
5533
5534 QTest::newRow(dataTag: "InlineHeader TopToBottom -30") << QQuickItemView::VerticalTopToBottom
5535 << QQuickListView::InlineHeader
5536 << -30 << 0
5537 << 0 << -30;
5538
5539 QTest::newRow(dataTag: "InlineHeader TopToBottom -39") << QQuickItemView::VerticalTopToBottom
5540 << QQuickListView::InlineHeader
5541 << -39 << 0
5542 << 0 << -30;
5543
5544 QTest::newRow(dataTag: "InlineHeader TopToBottom -41") << QQuickItemView::VerticalTopToBottom
5545 << QQuickListView::InlineHeader
5546 << -41 << 0
5547 << 20 << -30;
5548
5549 QTest::newRow(dataTag: "InlineHeader TopToBottom -65+10") << QQuickItemView::VerticalTopToBottom
5550 << QQuickListView::InlineHeader
5551 << -65 << 10
5552 << 20 << -30;
5553
5554 // --------------------
5555 // InlineHeader BottomToTop
5556 QTest::newRow(dataTag: "InlineHeader BottomToTop +10") << QQuickItemView::VerticalBottomToTop
5557 << QQuickListView::InlineHeader
5558 << 10 << 0
5559 << -170 << 0;
5560
5561 QTest::newRow(dataTag: "InlineHeader BottomToTop +14") << QQuickItemView::VerticalBottomToTop
5562 << QQuickListView::InlineHeader
5563 << 14 << 0
5564 << -170 << 0;
5565
5566 QTest::newRow(dataTag: "InlineHeader BottomToTop +16") << QQuickItemView::VerticalBottomToTop
5567 << QQuickListView::InlineHeader
5568 << 16 << 0
5569 << -200 << 0;
5570
5571 QTest::newRow(dataTag: "InlineHeader BottomToTop +30") << QQuickItemView::VerticalBottomToTop
5572 << QQuickListView::InlineHeader
5573 << 30 << 0
5574 << -200 << 0;
5575
5576 QTest::newRow(dataTag: "InlineHeader BottomToTop +39") << QQuickItemView::VerticalBottomToTop
5577 << QQuickListView::InlineHeader
5578 << 39 << 0
5579 << -200 << 0;
5580
5581 QTest::newRow(dataTag: "InlineHeader BottomToTop +41") << QQuickItemView::VerticalBottomToTop
5582 << QQuickListView::InlineHeader
5583 << 41 << 0
5584 << -220 << 0;
5585
5586 QTest::newRow(dataTag: "InlineHeader BottomToTop +65-10") << QQuickItemView::VerticalBottomToTop
5587 << QQuickListView::InlineHeader
5588 << 65 << -10
5589 << -220 << 0;
5590
5591 // --------------------
5592 // InlineHeader LeftToRight
5593 QTest::newRow(dataTag: "InlineHeader LeftToRight -10") << QQuickItemView::LeftToRight
5594 << QQuickListView::InlineHeader
5595 << -10 << 0
5596 << -30 << -30;
5597
5598 QTest::newRow(dataTag: "InlineHeader LeftToRight -14") << QQuickItemView::LeftToRight
5599 << QQuickListView::InlineHeader
5600 << -14 << 0
5601 << -30 << -30;
5602
5603 QTest::newRow(dataTag: "InlineHeader LeftToRight -16") << QQuickItemView::LeftToRight
5604 << QQuickListView::InlineHeader
5605 << -16 << 0
5606 << 0 << -30;
5607
5608 QTest::newRow(dataTag: "InlineHeader LeftToRight -30") << QQuickItemView::LeftToRight
5609 << QQuickListView::InlineHeader
5610 << -30 << 0
5611 << 0 << -30;
5612
5613 QTest::newRow(dataTag: "InlineHeader LeftToRight -39") << QQuickItemView::LeftToRight
5614 << QQuickListView::InlineHeader
5615 << -39 << 0
5616 << 0 << -30;
5617
5618 QTest::newRow(dataTag: "InlineHeader LeftToRight -41") << QQuickItemView::LeftToRight
5619 << QQuickListView::InlineHeader
5620 << -41 << 0
5621 << 20 << -30;
5622
5623 QTest::newRow(dataTag: "InlineHeader LeftToRight -65+10") << QQuickItemView::LeftToRight
5624 << QQuickListView::InlineHeader
5625 << -65 << 10
5626 << 20 << -30;
5627
5628 // --------------------
5629 // InlineHeader RightToLeft
5630 QTest::newRow(dataTag: "InlineHeader RightToLeft +10") << QQuickItemView::RightToLeft
5631 << QQuickListView::InlineHeader
5632 << 10 << 0
5633 << -210 << 0;
5634
5635 QTest::newRow(dataTag: "InlineHeader RightToLeft +14") << QQuickItemView::RightToLeft
5636 << QQuickListView::InlineHeader
5637 << 14 << 0
5638 << -210 << 0;
5639
5640 QTest::newRow(dataTag: "InlineHeader RightToLeft +16") << QQuickItemView::RightToLeft
5641 << QQuickListView::InlineHeader
5642 << 16 << 0
5643 << -240 << 0;
5644
5645 QTest::newRow(dataTag: "InlineHeader RightToLeft +30") << QQuickItemView::RightToLeft
5646 << QQuickListView::InlineHeader
5647 << 30 << 0
5648 << -240 << 0;
5649
5650 QTest::newRow(dataTag: "InlineHeader RightToLeft +39") << QQuickItemView::RightToLeft
5651 << QQuickListView::InlineHeader
5652 << 39 << 0
5653 << -240 << 0;
5654
5655 QTest::newRow(dataTag: "InlineHeader RightToLeft +41") << QQuickItemView::RightToLeft
5656 << QQuickListView::InlineHeader
5657 << 41 << 0
5658 << -260 << 0;
5659
5660 QTest::newRow(dataTag: "InlineHeader RightToLeft +65-10") << QQuickItemView::RightToLeft
5661 << QQuickListView::InlineHeader
5662 << 65 << -10
5663 << -260 << 0;
5664
5665 // --------------------
5666 // OverlayHeader TopToBottom
5667 QTest::newRow(dataTag: "OverlayHeader TopToBottom +9") << QQuickItemView::VerticalTopToBottom
5668 << QQuickListView::OverlayHeader
5669 << 9 << 0
5670 << -30 << -30;
5671
5672 QTest::newRow(dataTag: "OverlayHeader TopToBottom -9") << QQuickItemView::VerticalTopToBottom
5673 << QQuickListView::OverlayHeader
5674 << -9 << 0
5675 << -30 << -30;
5676
5677 QTest::newRow(dataTag: "OverlayHeader TopToBottom -11") << QQuickItemView::VerticalTopToBottom
5678 << QQuickListView::OverlayHeader
5679 << -11 << 0
5680 << -10 << -10;
5681
5682 QTest::newRow(dataTag: "OverlayHeader TopToBottom -29") << QQuickItemView::VerticalTopToBottom
5683 << QQuickListView::OverlayHeader
5684 << -29 << 0
5685 << -10 << -10;
5686
5687 QTest::newRow(dataTag: "OverlayHeader TopToBottom -31") << QQuickItemView::VerticalTopToBottom
5688 << QQuickListView::OverlayHeader
5689 << -31 << 0
5690 << 10 << 10;
5691
5692 // --------------------
5693 // OverlayHeader BottomToTop
5694 QTest::newRow(dataTag: "OverlayHeader BottomToTop -9") << QQuickItemView::VerticalBottomToTop
5695 << QQuickListView::OverlayHeader
5696 << -9 << 0
5697 << -170 << 0;
5698
5699 QTest::newRow(dataTag: "OverlayHeader BottomToTop +9") << QQuickItemView::VerticalBottomToTop
5700 << QQuickListView::OverlayHeader
5701 << 9 << 0
5702 << -170 << 0;
5703
5704 QTest::newRow(dataTag: "OverlayHeader BottomToTop +11") << QQuickItemView::VerticalBottomToTop
5705 << QQuickListView::OverlayHeader
5706 << 11 << 0
5707 << -190 << -20;
5708
5709 QTest::newRow(dataTag: "OverlayHeader BottomToTop +29") << QQuickItemView::VerticalBottomToTop
5710 << QQuickListView::OverlayHeader
5711 << 29 << 0
5712 << -190 << -20;
5713
5714 QTest::newRow(dataTag: "OverlayHeader BottomToTop +31") << QQuickItemView::VerticalBottomToTop
5715 << QQuickListView::OverlayHeader
5716 << 31 << 0
5717 << -210 << -40;
5718
5719 // --------------------
5720 // OverlayHeader LeftToRight
5721 QTest::newRow(dataTag: "OverlayHeader LeftToRight +9") << QQuickItemView::LeftToRight
5722 << QQuickListView::OverlayHeader
5723 << 9 << 0
5724 << -30 << -30;
5725
5726 QTest::newRow(dataTag: "OverlayHeader LeftToRight -9") << QQuickItemView::LeftToRight
5727 << QQuickListView::OverlayHeader
5728 << -9 << 0
5729 << -30 << -30;
5730
5731 QTest::newRow(dataTag: "OverlayHeader LeftToRight -11") << QQuickItemView::LeftToRight
5732 << QQuickListView::OverlayHeader
5733 << -11 << 0
5734 << -10 << -10;
5735
5736 QTest::newRow(dataTag: "OverlayHeader LeftToRight -29") << QQuickItemView::LeftToRight
5737 << QQuickListView::OverlayHeader
5738 << -29 << 0
5739 << -10 << -10;
5740
5741 QTest::newRow(dataTag: "OverlayHeader LeftToRight -31") << QQuickItemView::LeftToRight
5742 << QQuickListView::OverlayHeader
5743 << -31 << 0
5744 << 10 << 10;
5745
5746 // --------------------
5747 // OverlayHeader RightToLeft
5748 QTest::newRow(dataTag: "OverlayHeader RightToLeft -9") << QQuickItemView::RightToLeft
5749 << QQuickListView::OverlayHeader
5750 << -9 << 0
5751 << -210 << 0;
5752
5753 QTest::newRow(dataTag: "OverlayHeader RightToLeft +9") << QQuickItemView::RightToLeft
5754 << QQuickListView::OverlayHeader
5755 << 9 << 0
5756 << -210 << 0;
5757
5758 QTest::newRow(dataTag: "OverlayHeader RightToLeft +11") << QQuickItemView::RightToLeft
5759 << QQuickListView::OverlayHeader
5760 << 11 << 0
5761 << -230 << -20;
5762
5763 QTest::newRow(dataTag: "OverlayHeader RightToLeft +29") << QQuickItemView::RightToLeft
5764 << QQuickListView::OverlayHeader
5765 << 29 << 0
5766 << -230 << -20;
5767
5768 QTest::newRow(dataTag: "OverlayHeader RightToLeft +31") << QQuickItemView::RightToLeft
5769 << QQuickListView::OverlayHeader
5770 << 31 << 0
5771 << -250 << -40;
5772
5773 // --------------------
5774 // PullbackHeader TopToBottom
5775 QTest::newRow(dataTag: "PullbackHeader TopToBottom -2") << QQuickItemView::VerticalTopToBottom
5776 << QQuickListView::PullBackHeader
5777 << -2 << 0
5778 << -30 << -30;
5779
5780 QTest::newRow(dataTag: "PullbackHeader TopToBottom -10") << QQuickItemView::VerticalTopToBottom
5781 << QQuickListView::PullBackHeader
5782 << -10 << 0
5783 << -30 << -30;
5784
5785 QTest::newRow(dataTag: "PullbackHeader TopToBottom -11") << QQuickItemView::VerticalTopToBottom
5786 << QQuickListView::PullBackHeader
5787 << -11 << 0
5788 << -10 << -10;
5789
5790 QTest::newRow(dataTag: "PullbackHeader TopToBottom -14") << QQuickItemView::VerticalTopToBottom
5791 << QQuickListView::PullBackHeader
5792 << -14 << 0
5793 << -10 << -10;
5794
5795 QTest::newRow(dataTag: "PullbackHeader TopToBottom -16") << QQuickItemView::VerticalTopToBottom
5796 << QQuickListView::PullBackHeader
5797 << -16 << 0
5798 << 0 << -30;
5799
5800 QTest::newRow(dataTag: "PullbackHeader TopToBottom -20") << QQuickItemView::VerticalTopToBottom
5801 << QQuickListView::PullBackHeader
5802 << -20 << 0
5803 << 0 << -30;
5804
5805 QTest::newRow(dataTag: "PullbackHeader TopToBottom -65+10") << QQuickItemView::VerticalTopToBottom
5806 << QQuickListView::PullBackHeader
5807 << -65 << 10
5808 << 20 << -10;
5809
5810 QTest::newRow(dataTag: "PullbackHeader TopToBottom -65+20") << QQuickItemView::VerticalTopToBottom
5811 << QQuickListView::PullBackHeader
5812 << -65 << 20
5813 << 10 << 10;
5814
5815 // Should move header even if contentY doesn't move (its aligned with top)
5816 QTest::newRow(dataTag: "PullbackHeader TopToBottom -55+5") << QQuickItemView::VerticalTopToBottom
5817 << QQuickListView::PullBackHeader
5818 << -55 << 5
5819 << 20 << -10;
5820
5821 // Should move header even if contentY doesn't move (it's aligned with header)
5822 QTest::newRow(dataTag: "PullbackHeader TopToBottom -76+16") << QQuickItemView::VerticalTopToBottom
5823 << QQuickListView::PullBackHeader
5824 << -76 << 16
5825 << 30 << 30;
5826
5827 // --------------------
5828 // PullbackHeader BottomToTop
5829 QTest::newRow(dataTag: "PullbackHeader BottomToTop +2") << QQuickItemView::VerticalBottomToTop
5830 << QQuickListView::PullBackHeader
5831 << +2 << 0
5832 << -170 << 0;
5833
5834 QTest::newRow(dataTag: "PullbackHeader BottomToTop +9") << QQuickItemView::VerticalBottomToTop
5835 << QQuickListView::PullBackHeader
5836 << +9 << 0
5837 << -170 << 0;
5838
5839 QTest::newRow(dataTag: "PullbackHeader BottomToTop +11") << QQuickItemView::VerticalBottomToTop
5840 << QQuickListView::PullBackHeader
5841 << +11 << 0
5842 << -190 << -20;
5843
5844 QTest::newRow(dataTag: "PullbackHeader BottomToTop +14") << QQuickItemView::VerticalBottomToTop
5845 << QQuickListView::PullBackHeader
5846 << +14 << 0
5847 << -190 << -20;
5848
5849 QTest::newRow(dataTag: "PullbackHeader BottomToTop +16") << QQuickItemView::VerticalBottomToTop
5850 << QQuickListView::PullBackHeader
5851 << +16 << 0
5852 << -200 << 0;
5853
5854 QTest::newRow(dataTag: "PullbackHeader BottomToTop +20") << QQuickItemView::VerticalBottomToTop
5855 << QQuickListView::PullBackHeader
5856 << +20 << 0
5857 << -200 << 0;
5858
5859 QTest::newRow(dataTag: "PullbackHeader BottomToTop +65-10") << QQuickItemView::VerticalBottomToTop
5860 << QQuickListView::PullBackHeader
5861 << +65 << -10
5862 << -220 << -20;
5863
5864 QTest::newRow(dataTag: "PullbackHeader BottomToTop +65-20") << QQuickItemView::VerticalBottomToTop
5865 << QQuickListView::PullBackHeader
5866 << +65 << -20
5867 << -210 << -40;
5868
5869 // Should move header even if contentY doesn't move (it's aligned with top)
5870 QTest::newRow(dataTag: "PullbackHeader BottomToTop +55-5") << QQuickItemView::VerticalBottomToTop
5871 << QQuickListView::PullBackHeader
5872 << 55 << -5
5873 << -220 << -20;
5874
5875 // Should move header even if contentY doesn't move (it's aligned with header)
5876 QTest::newRow(dataTag: "PullbackHeader BottomToTop +76-16") << QQuickItemView::VerticalBottomToTop
5877 << QQuickListView::PullBackHeader
5878 << 76 << -16
5879 << -230 << -60;
5880
5881 // --------------------
5882 // PullbackHeader LeftToRight
5883 QTest::newRow(dataTag: "PullbackHeader LeftToRight -2") << QQuickItemView::LeftToRight
5884 << QQuickListView::PullBackHeader
5885 << -2 << 0
5886 << -30 << -30;
5887
5888 QTest::newRow(dataTag: "PullbackHeader LeftToRight -10") << QQuickItemView::LeftToRight
5889 << QQuickListView::PullBackHeader
5890 << -10 << 0
5891 << -30 << -30;
5892
5893 QTest::newRow(dataTag: "PullbackHeader LeftToRight -11") << QQuickItemView::LeftToRight
5894 << QQuickListView::PullBackHeader
5895 << -11 << 0
5896 << -10 << -10;
5897
5898 QTest::newRow(dataTag: "PullbackHeader LeftToRight -14") << QQuickItemView::LeftToRight
5899 << QQuickListView::PullBackHeader
5900 << -14 << 0
5901 << -10 << -10;
5902
5903 QTest::newRow(dataTag: "PullbackHeader LeftToRight -16") << QQuickItemView::LeftToRight
5904 << QQuickListView::PullBackHeader
5905 << -16 << 0
5906 << 0 << -30;
5907
5908 QTest::newRow(dataTag: "PullbackHeader LeftToRight -20") << QQuickItemView::LeftToRight
5909 << QQuickListView::PullBackHeader
5910 << -20 << 0
5911 << 0 << -30;
5912
5913 QTest::newRow(dataTag: "PullbackHeader LeftToRight -65+10") << QQuickItemView::LeftToRight
5914 << QQuickListView::PullBackHeader
5915 << -65 << 10
5916 << 20 << -10;
5917
5918 QTest::newRow(dataTag: "PullbackHeader LeftToRight -65+20") << QQuickItemView::LeftToRight
5919 << QQuickListView::PullBackHeader
5920 << -65 << 20
5921 << 10 << 10;
5922
5923 // Should move header even if contentX doesn't move (its aligned with top)
5924 QTest::newRow(dataTag: "PullbackHeader LeftToRight -55+5") << QQuickItemView::LeftToRight
5925 << QQuickListView::PullBackHeader
5926 << -55 << 5
5927 << 20 << -10;
5928
5929 // Should move header even if contentX doesn't move (it's aligned with header)
5930 QTest::newRow(dataTag: "PullbackHeader LeftToRight -76+16") << QQuickItemView::LeftToRight
5931 << QQuickListView::PullBackHeader
5932 << -76 << 16
5933 << 30 << 30;
5934
5935 // --------------------
5936 // PullbackHeader RightToLeft
5937 QTest::newRow(dataTag: "PullbackHeader RightToLeft +2") << QQuickItemView::RightToLeft
5938 << QQuickListView::PullBackHeader
5939 << +2 << 0
5940 << -210 << 0;
5941
5942 QTest::newRow(dataTag: "PullbackHeader RightToLeft +9") << QQuickItemView::RightToLeft
5943 << QQuickListView::PullBackHeader
5944 << +9 << 0
5945 << -210 << 0;
5946
5947 QTest::newRow(dataTag: "PullbackHeader RightToLeft +11") << QQuickItemView::RightToLeft
5948 << QQuickListView::PullBackHeader
5949 << +11 << 0
5950 << -230 << -20;
5951
5952 QTest::newRow(dataTag: "PullbackHeader RightToLeft +14") << QQuickItemView::RightToLeft
5953 << QQuickListView::PullBackHeader
5954 << +14 << 0
5955 << -230 << -20;
5956
5957 QTest::newRow(dataTag: "PullbackHeader RightToLeft +16") << QQuickItemView::RightToLeft
5958 << QQuickListView::PullBackHeader
5959 << +16 << 0
5960 << -240 << 0;
5961
5962 QTest::newRow(dataTag: "PullbackHeader RightToLeft +20") << QQuickItemView::RightToLeft
5963 << QQuickListView::PullBackHeader
5964 << +20 << 0
5965 << -240 << 0;
5966
5967 QTest::newRow(dataTag: "PullbackHeader RightToLeft +65-10") << QQuickItemView::RightToLeft
5968 << QQuickListView::PullBackHeader
5969 << +65 << -10
5970 << -260 << -20;
5971
5972 QTest::newRow(dataTag: "PullbackHeader RightToLeft +65-20") << QQuickItemView::RightToLeft
5973 << QQuickListView::PullBackHeader
5974 << +65 << -20
5975 << -250 << -40;
5976
5977 // Should move header even if contentX doesn't move (it's aligned with top)
5978 QTest::newRow(dataTag: "PullbackHeader RightToLeft +55-5") << QQuickItemView::RightToLeft
5979 << QQuickListView::PullBackHeader
5980 << 55 << -5
5981 << -260 << -20;
5982
5983 // Should move header even if contentX doesn't move (it's aligned with header)
5984 QTest::newRow(dataTag: "PullbackHeader RightToLeft +76-16") << QQuickItemView::RightToLeft
5985 << QQuickListView::PullBackHeader
5986 << 76 << -16
5987 << -270 << -60;
5988
5989}
5990
5991void tst_QQuickListView::snapOneItemResize_QTBUG_43555()
5992{
5993 QScopedPointer<QQuickView> window(createView());
5994 window->resize(newSize: QSize(100, 320));
5995 window->setResizeMode(QQuickView::SizeRootObjectToView);
5996 QQuickViewTestUtil::moveMouseAway(window: window.data());
5997
5998 window->setSource(testFileUrl(fileName: "snapOneItemResize.qml"));
5999 window->show();
6000 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
6001
6002 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
6003 QTRY_VERIFY(listview != nullptr);
6004
6005 QSignalSpy currentIndexSpy(listview, SIGNAL(currentIndexChanged()));
6006
6007 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6008 QTRY_COMPARE(listview->currentIndex(), 5);
6009 currentIndexSpy.clear();
6010
6011 window->resize(newSize: QSize(400, 320));
6012
6013 QTRY_COMPARE(int(listview->width()), 400);
6014 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6015
6016 QTRY_COMPARE(listview->currentIndex(), 5);
6017 QCOMPARE(currentIndexSpy.count(), 0);
6018}
6019
6020void tst_QQuickListView::qAbstractItemModel_package_items()
6021{
6022 items<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"));
6023}
6024
6025void tst_QQuickListView::qAbstractItemModel_items()
6026{
6027 items<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6028}
6029
6030void tst_QQuickListView::qAbstractItemModel_package_changed()
6031{
6032 changed<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"));
6033}
6034
6035void tst_QQuickListView::qAbstractItemModel_changed()
6036{
6037 changed<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6038}
6039
6040void tst_QQuickListView::qAbstractItemModel_package_inserted()
6041{
6042 inserted<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"));
6043}
6044
6045void tst_QQuickListView::qAbstractItemModel_inserted()
6046{
6047 inserted<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6048}
6049
6050void tst_QQuickListView::qAbstractItemModel_inserted_more()
6051{
6052 inserted_more<QaimModel>();
6053}
6054
6055void tst_QQuickListView::qAbstractItemModel_inserted_more_data()
6056{
6057 inserted_more_data();
6058}
6059
6060void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop()
6061{
6062 inserted_more<QaimModel>(verticalLayoutDirection: QQuickItemView::BottomToTop);
6063}
6064
6065void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop_data()
6066{
6067 inserted_more_data();
6068}
6069
6070void tst_QQuickListView::qAbstractItemModel_package_removed()
6071{
6072 removed<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"), false);
6073 removed<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"), true);
6074}
6075
6076void tst_QQuickListView::qAbstractItemModel_removed()
6077{
6078 removed<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"), false);
6079 removed<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"), true);
6080}
6081
6082void tst_QQuickListView::qAbstractItemModel_removed_more()
6083{
6084 removed_more<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6085}
6086
6087void tst_QQuickListView::qAbstractItemModel_removed_more_data()
6088{
6089 removed_more_data();
6090}
6091
6092void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop()
6093{
6094 removed_more<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"), verticalLayoutDirection: QQuickItemView::BottomToTop);
6095}
6096
6097void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop_data()
6098{
6099 removed_more_data();
6100}
6101
6102void tst_QQuickListView::qAbstractItemModel_package_moved()
6103{
6104 moved<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"));
6105}
6106
6107void tst_QQuickListView::qAbstractItemModel_package_moved_data()
6108{
6109 moved_data();
6110}
6111
6112void tst_QQuickListView::qAbstractItemModel_moved()
6113{
6114 moved<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6115}
6116
6117void tst_QQuickListView::qAbstractItemModel_moved_data()
6118{
6119 moved_data();
6120}
6121
6122void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop()
6123{
6124 moved<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"), verticalLayoutDirection: QQuickItemView::BottomToTop);
6125}
6126
6127void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop_data()
6128{
6129 moved_data();
6130}
6131
6132void tst_QQuickListView::qAbstractItemModel_package_clear()
6133{
6134 clear<QaimModel>(source: testFileUrl(fileName: "listviewtest-package.qml"));
6135}
6136
6137void tst_QQuickListView::qAbstractItemModel_clear()
6138{
6139 clear<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"));
6140}
6141
6142void tst_QQuickListView::qAbstractItemModel_clear_bottomToTop()
6143{
6144 clear<QaimModel>(source: testFileUrl(fileName: "listviewtest.qml"), verticalLayoutDirection: QQuickItemView::BottomToTop);
6145}
6146
6147void tst_QQuickListView::qAbstractItemModel_package_sections()
6148{
6149 sections<QaimModel>(source: testFileUrl(fileName: "listview-sections-package.qml"));
6150}
6151
6152void tst_QQuickListView::qAbstractItemModel_sections()
6153{
6154 sections<QaimModel>(source: testFileUrl(fileName: "listview-sections.qml"));
6155}
6156
6157void tst_QQuickListView::creationContext()
6158{
6159 QQuickView window;
6160 window.setGeometry(posx: 0,posy: 0,w: 240,h: 320);
6161 window.setSource(testFileUrl(fileName: "creationContext.qml"));
6162 qApp->processEvents();
6163
6164 QQuickItem *rootItem = qobject_cast<QQuickItem *>(object: window.rootObject());
6165 QVERIFY(rootItem);
6166 QVERIFY(rootItem->property("count").toInt() > 0);
6167
6168 QQuickItem *item = findItem<QQuickItem>(parent: rootItem, objectName: "listItem");
6169 QVERIFY(item);
6170 QCOMPARE(item->property("text").toString(), QString("Hello!"));
6171 item = rootItem->findChild<QQuickItem *>(aName: "header");
6172 QVERIFY(item);
6173 QCOMPARE(item->property("text").toString(), QString("Hello!"));
6174 item = rootItem->findChild<QQuickItem *>(aName: "footer");
6175 QVERIFY(item);
6176 QCOMPARE(item->property("text").toString(), QString("Hello!"));
6177 item = rootItem->findChild<QQuickItem *>(aName: "section");
6178 QVERIFY(item);
6179 QCOMPARE(item->property("text").toString(), QString("Hello!"));
6180}
6181
6182void tst_QQuickListView::QTBUG_21742()
6183{
6184 QQuickView window;
6185 window.setGeometry(posx: 0,posy: 0,w: 200,h: 200);
6186 window.setSource(testFileUrl(fileName: "qtbug-21742.qml"));
6187 qApp->processEvents();
6188
6189 QQuickItem *rootItem = qobject_cast<QQuickItem *>(object: window.rootObject());
6190 QVERIFY(rootItem);
6191 QCOMPARE(rootItem->property("count").toInt(), 1);
6192}
6193
6194void tst_QQuickListView::asynchronous()
6195{
6196 QScopedPointer<QQuickView> window(createView());
6197 window->show();
6198 QQmlIncubationController controller;
6199 window->engine()->setIncubationController(&controller);
6200
6201 window->setSource(testFileUrl(fileName: "asyncloader.qml"));
6202
6203 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: window->rootObject());
6204 QVERIFY(rootObject);
6205
6206 QQuickListView *listview = nullptr;
6207 while (!listview) {
6208 bool b = false;
6209 controller.incubateWhile(flag: &b);
6210 listview = rootObject->findChild<QQuickListView*>(aName: "view");
6211 }
6212
6213 // items will be created one at a time
6214 for (int i = 0; i < 8; ++i) {
6215 QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == nullptr);
6216 QQuickItem *item = nullptr;
6217 while (!item) {
6218 bool b = false;
6219 controller.incubateWhile(flag: &b);
6220 item = findItem<QQuickItem>(parent: listview, objectName: "wrapper", index: i);
6221 }
6222 }
6223
6224 {
6225 bool b = true;
6226 controller.incubateWhile(flag: &b);
6227 }
6228
6229 // verify positioning
6230 QQuickItem *contentItem = listview->contentItem();
6231 for (int i = 0; i < 8; ++i) {
6232 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
6233 QTRY_COMPARE(item->y(), i*50.0);
6234 }
6235}
6236
6237void tst_QQuickListView::snapOneItem_data()
6238{
6239 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
6240 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
6241 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
6242 QTest::addColumn<int>(name: "highlightRangeMode");
6243 QTest::addColumn<QPoint>(name: "flickStart");
6244 QTest::addColumn<QPoint>(name: "flickEnd");
6245 QTest::addColumn<qreal>(name: "snapAlignment");
6246 QTest::addColumn<qreal>(name: "endExtent");
6247 QTest::addColumn<qreal>(name: "startExtent");
6248 QTest::addColumn<qreal>(name: "flickSlowdown");
6249
6250 QTest::newRow(dataTag: "vertical, top to bottom")
6251 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
6252 << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 560.0 << 0.0 << 1.0;
6253
6254 QTest::newRow(dataTag: "vertical, bottom to top")
6255 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
6256 << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -560.0 - 240.0 << -240.0 << 1.0;
6257
6258 QTest::newRow(dataTag: "horizontal, left to right")
6259 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
6260 << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 560.0 << 0.0 << 1.0;
6261
6262 QTest::newRow(dataTag: "horizontal, right to left")
6263 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
6264 << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -560.0 - 240.0 << -240.0 << 1.0;
6265
6266 QTest::newRow(dataTag: "vertical, top to bottom, enforce range")
6267 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
6268 << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 580.0 << -20.0 << 1.0;
6269
6270 QTest::newRow(dataTag: "vertical, bottom to top, enforce range")
6271 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
6272 << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -580.0 - 240.0 << -220.0 << 1.0;
6273
6274 QTest::newRow(dataTag: "horizontal, left to right, enforce range")
6275 << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
6276 << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 580.0 << -20.0 << 1.0;
6277
6278 QTest::newRow(dataTag: "horizontal, right to left, enforce range")
6279 << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
6280 << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -580.0 - 240.0 << -220.0 << 1.0;
6281
6282 // Using e.g. 120 rather than 95 always went to the next item.
6283 // Ensure this further movement has the same behavior
6284 QTest::newRow(dataTag: "vertical, top to bottom, no more blindspot")
6285 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
6286 << QPoint(20, 200) << QPoint(20, 95) << 180.0 << 560.0 << 0.0 << 6.0;
6287
6288 // StrictlyEnforceRange should not override valid SnapOneItem decisions
6289 QTest::newRow(dataTag: "vertical, top to bottom, no more blindspot, enforce range")
6290 << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
6291 << QPoint(20, 200) << QPoint(20, 95) << 180.0 << 580.0 << -20.0 << 6.0;
6292}
6293
6294void tst_QQuickListView::snapOneItem()
6295{
6296 QFETCH(QQuickListView::Orientation, orientation);
6297 QFETCH(Qt::LayoutDirection, layoutDirection);
6298 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
6299 QFETCH(int, highlightRangeMode);
6300 QFETCH(QPoint, flickStart);
6301 QFETCH(QPoint, flickEnd);
6302 QFETCH(qreal, snapAlignment);
6303 QFETCH(qreal, endExtent);
6304 QFETCH(qreal, startExtent);
6305 QFETCH(qreal, flickSlowdown);
6306
6307 qreal flickDuration = 180 * flickSlowdown;
6308
6309 QQuickView *window = getView();
6310 QQuickViewTestUtil::moveMouseAway(window);
6311
6312 window->setSource(testFileUrl(fileName: "snapOneItem.qml"));
6313 window->show();
6314 QVERIFY(QTest::qWaitForWindowExposed(window));
6315
6316
6317 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
6318 QTRY_VERIFY(listview != nullptr);
6319
6320 listview->setOrientation(orientation);
6321 listview->setLayoutDirection(layoutDirection);
6322 listview->setVerticalLayoutDirection(verticalLayoutDirection);
6323 listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
6324 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6325
6326 QQuickItem *contentItem = listview->contentItem();
6327 QTRY_VERIFY(contentItem != nullptr);
6328
6329 QSignalSpy currentIndexSpy(listview, SIGNAL(currentIndexChanged()));
6330
6331 // confirm that a flick hits the next item boundary
6332 flick(window, from: flickStart, to: flickEnd, duration: flickDuration);
6333 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
6334 if (orientation == QQuickListView::Vertical)
6335 QCOMPARE(listview->contentY(), snapAlignment);
6336 else
6337 QCOMPARE(listview->contentX(), snapAlignment);
6338
6339 if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
6340 QCOMPARE(listview->currentIndex(), 1);
6341 QCOMPARE(currentIndexSpy.count(), 1);
6342 }
6343
6344 // flick to end
6345 do {
6346 flick(window, from: flickStart, to: flickEnd, duration: flickDuration);
6347 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
6348 } while (orientation == QQuickListView::Vertical
6349 ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
6350 : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
6351
6352 if (orientation == QQuickListView::Vertical)
6353 QCOMPARE(listview->contentY(), endExtent);
6354 else
6355 QCOMPARE(listview->contentX(), endExtent);
6356
6357 if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
6358 QCOMPARE(listview->currentIndex(), 3);
6359 QCOMPARE(currentIndexSpy.count(), 3);
6360 }
6361
6362 // flick to start
6363 do {
6364 flick(window, from: flickEnd, to: flickStart, duration: flickDuration);
6365 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
6366 } while (orientation == QQuickListView::Vertical
6367 ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
6368 : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
6369
6370 if (orientation == QQuickListView::Vertical)
6371 QCOMPARE(listview->contentY(), startExtent);
6372 else
6373 QCOMPARE(listview->contentX(), startExtent);
6374
6375 if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
6376 QCOMPARE(listview->currentIndex(), 0);
6377 QCOMPARE(currentIndexSpy.count(), 6);
6378 }
6379
6380 releaseView(view: window);
6381}
6382
6383void tst_QQuickListView::snapOneItemCurrentIndexRemoveAnimation()
6384{
6385 QScopedPointer<QQuickView> window(createView());
6386 window->setSource(testFileUrl(fileName: "snapOneItemCurrentIndexRemoveAnimation.qml"));
6387 window->show();
6388 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
6389
6390 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
6391 QTRY_VERIFY(listview != nullptr);
6392
6393 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6394 QTRY_COMPARE(listview->currentIndex(), 0);
6395 QSignalSpy currentIndexSpy(listview, SIGNAL(currentIndexChanged()));
6396
6397 QMetaObject::invokeMethod(obj: window->rootObject(), member: "removeItemZero");
6398 QTRY_COMPARE(listview->property("transitionsRun").toInt(), 1);
6399
6400 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6401
6402 QCOMPARE(listview->currentIndex(), 0);
6403 QCOMPARE(currentIndexSpy.count(), 0);
6404}
6405
6406void tst_QQuickListView::snapOneItemWrongDirection()
6407{
6408 QScopedPointer<QQuickView> window(createView());
6409 window->setSource(testFileUrl(fileName: "snapOneItemWrongDirection.qml"));
6410 window->show();
6411 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
6412
6413 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
6414 QTRY_VERIFY(listview != nullptr);
6415
6416 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6417 QTRY_COMPARE(listview->currentIndex(), 0);
6418
6419 listview->flick(xVelocity: 0,yVelocity: 500);
6420 QTRY_VERIFY(!listview->isMovingHorizontally());
6421 QCOMPARE(listview->contentX(), qreal(0));
6422}
6423
6424void tst_QQuickListView::attachedProperties_QTBUG_32836()
6425{
6426 QScopedPointer<QQuickView> window(createView());
6427 window->setSource(testFileUrl(fileName: "attachedProperties.qml"));
6428 window->show();
6429 qApp->processEvents();
6430
6431 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
6432 QVERIFY(listview != nullptr);
6433
6434 QQuickItem *header = listview->headerItem();
6435 QVERIFY(header);
6436 QCOMPARE(header->width(), listview->width());
6437
6438 QQuickItem *footer = listview->footerItem();
6439 QVERIFY(footer);
6440 QCOMPARE(footer->width(), listview->width());
6441
6442 QQuickItem *highlight = listview->highlightItem();
6443 QVERIFY(highlight);
6444 QCOMPARE(highlight->width(), listview->width());
6445
6446 QQuickItem *currentItem = listview->currentItem();
6447 QVERIFY(currentItem);
6448 QCOMPARE(currentItem->width(), listview->width());
6449
6450 QQuickItem *sectionItem = findItem<QQuickItem>(parent: window->rootObject(), objectName: "sectionItem");
6451 QVERIFY(sectionItem);
6452 QCOMPARE(sectionItem->width(), listview->width());
6453}
6454
6455void tst_QQuickListView::unrequestedVisibility()
6456{
6457 QaimModel model;
6458 for (int i = 0; i < 30; i++)
6459 model.addItem(name: "Item" + QString::number(i), number: QString::number(i));
6460
6461 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
6462 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
6463
6464 QQmlContext *ctxt = window->rootContext();
6465 ctxt->setContextProperty("testModel", &model);
6466 ctxt->setContextProperty("testWrap", QVariant(false));
6467
6468 window->setSource(testFileUrl(fileName: "unrequestedItems.qml"));
6469 window->show();
6470 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
6471
6472
6473 QQuickListView *leftview;
6474 QTRY_VERIFY((leftview = findItem<QQuickListView>(window->rootObject(), "leftList")));
6475
6476 QQuickListView *rightview;
6477 QTRY_VERIFY((rightview = findItem<QQuickListView>(window->rootObject(), "rightList")));
6478
6479 QQuickItem *leftContent = leftview->contentItem();
6480 QTRY_VERIFY((leftContent = leftview->contentItem()));
6481
6482 QQuickItem *rightContent;
6483 QTRY_VERIFY((rightContent = rightview->contentItem()));
6484
6485 rightview->setCurrentIndex(20);
6486
6487 QTRY_COMPARE(leftview->contentY(), 0.0);
6488 QTRY_COMPARE(rightview->contentY(), 100.0);
6489
6490 const QString wrapperObjectName = QStringLiteral("wrapper");
6491 QQuickItem *item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 1);
6492 QVERIFY(item);
6493 QCOMPARE(delegateVisible(item), true);
6494 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 1);
6495 QVERIFY(item);
6496 QCOMPARE(delegateVisible(item), false);
6497
6498 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 19);
6499 QVERIFY(item);
6500 QCOMPARE(delegateVisible(item), false);
6501 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 19);
6502 QVERIFY(item);
6503 QCOMPARE(delegateVisible(item), true);
6504
6505 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 16);
6506 QVERIFY(item);
6507 QCOMPARE(delegateVisible(item), true);
6508 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 17);
6509 QVERIFY(item);
6510 QCOMPARE(delegateVisible(item), false);
6511 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 3);
6512 QVERIFY(item);
6513 QCOMPARE(delegateVisible(item), false);
6514 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 4);
6515 QVERIFY(item);
6516 QCOMPARE(delegateVisible(item), true);
6517
6518 rightview->setCurrentIndex(0);
6519
6520 QTRY_COMPARE(leftview->contentY(), 0.0);
6521 QTRY_COMPARE(rightview->contentY(), 0.0);
6522
6523 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 1);
6524 QVERIFY(item);
6525 QCOMPARE(delegateVisible(item), true);
6526 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 1);
6527 QVERIFY(item);
6528 QTRY_COMPARE(delegateVisible(item), true);
6529
6530 QVERIFY(!findItem<QQuickItem>(leftContent, wrapperObjectName, 19));
6531 QVERIFY(!findItem<QQuickItem>(rightContent, wrapperObjectName, 19));
6532
6533 leftview->setCurrentIndex(20);
6534
6535 QTRY_COMPARE(leftview->contentY(), 100.0);
6536 QTRY_COMPARE(rightview->contentY(), 0.0);
6537
6538 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 1);
6539 QVERIFY(item);
6540 QTRY_COMPARE(delegateVisible(item), false);
6541 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 1);
6542 QVERIFY(item);
6543 QCOMPARE(delegateVisible(item), true);
6544
6545 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 19);
6546 QVERIFY(item);
6547 QCOMPARE(delegateVisible(item), true);
6548 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 19);
6549 QVERIFY(item);
6550 QCOMPARE(delegateVisible(item), false);
6551
6552 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 3);
6553 QVERIFY(item);
6554 QCOMPARE(delegateVisible(item), false);
6555 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6556 QVERIFY(item);
6557 QCOMPARE(delegateVisible(item), true);
6558 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6559 QVERIFY(item);
6560 QCOMPARE(delegateVisible(item), true);
6561 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6562 QVERIFY(item);
6563 QCOMPARE(delegateVisible(item), false);
6564
6565 model.moveItems(from: 19, to: 1, count: 1);
6566 QVERIFY(QQuickTest::qWaitForItemPolished(leftview));
6567
6568 QTRY_VERIFY((item = findItem<QQuickItem>(leftContent, wrapperObjectName, 1)));
6569 QCOMPARE(delegateVisible(item), false);
6570 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 1);
6571 QVERIFY(item);
6572 QCOMPARE(delegateVisible(item), true);
6573
6574 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 19);
6575 QVERIFY(item);
6576 QCOMPARE(delegateVisible(item), true);
6577 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 19);
6578 QVERIFY(item);
6579 QCOMPARE(delegateVisible(item), false);
6580
6581 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6582 QVERIFY(item);
6583 QCOMPARE(delegateVisible(item), false);
6584 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 5);
6585 QVERIFY(item);
6586 QCOMPARE(delegateVisible(item), true);
6587 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6588 QVERIFY(item);
6589 QCOMPARE(delegateVisible(item), true);
6590 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6591 QVERIFY(item);
6592 QCOMPARE(delegateVisible(item), false);
6593
6594 model.moveItems(from: 3, to: 4, count: 1);
6595 QVERIFY(QQuickTest::qWaitForItemPolished(leftview));
6596
6597 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6598 QVERIFY(item);
6599 QCOMPARE(delegateVisible(item), false);
6600 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 5);
6601 QVERIFY(item);
6602 QCOMPARE(delegateVisible(item), true);
6603 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6604 QVERIFY(item);
6605 QCOMPARE(delegateVisible(item), true);
6606 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6607 QVERIFY(item);
6608 QCOMPARE(delegateVisible(item), false);
6609
6610 model.moveItems(from: 4, to: 3, count: 1);
6611 QVERIFY(QQuickTest::qWaitForItemPolished(leftview));
6612
6613 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6614 QVERIFY(item);
6615 QCOMPARE(delegateVisible(item), false);
6616 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 5);
6617 QVERIFY(item);
6618 QCOMPARE(delegateVisible(item), true);
6619 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6620 QVERIFY(item);
6621 QCOMPARE(delegateVisible(item), true);
6622 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6623 QVERIFY(item);
6624 QCOMPARE(delegateVisible(item), false);
6625
6626 model.moveItems(from: 16, to: 17, count: 1);
6627 QVERIFY(QQuickTest::qWaitForItemPolished(leftview));
6628
6629 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6630 QVERIFY(item);
6631 QCOMPARE(delegateVisible(item), false);
6632 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 5);
6633 QVERIFY(item);
6634 QCOMPARE(delegateVisible(item), true);
6635 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6636 QVERIFY(item);
6637 QCOMPARE(delegateVisible(item), true);
6638 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6639 QVERIFY(item);
6640 QCOMPARE(delegateVisible(item), false);
6641
6642 model.moveItems(from: 17, to: 16, count: 1);
6643 QVERIFY(QQuickTest::qWaitForItemPolished(leftview));
6644
6645 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 4);
6646 QVERIFY(item);
6647 QCOMPARE(delegateVisible(item), false);
6648 item = findItem<QQuickItem>(parent: leftContent, objectName: wrapperObjectName, index: 5);
6649 QVERIFY(item);
6650 QCOMPARE(delegateVisible(item), true);
6651 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 16);
6652 QVERIFY(item);
6653 QCOMPARE(delegateVisible(item), true);
6654 item = findItem<QQuickItem>(parent: rightContent, objectName: wrapperObjectName, index: 17);
6655 QVERIFY(item);
6656 QCOMPARE(delegateVisible(item), false);
6657}
6658
6659void tst_QQuickListView::populateTransitions()
6660{
6661 QFETCH(bool, staticallyPopulate);
6662 QFETCH(bool, dynamicallyPopulate);
6663 QFETCH(bool, usePopulateTransition);
6664
6665 QPointF transitionFrom(-50, -50);
6666 QPointF transitionVia(100, 100);
6667 QaimModel model_transitionFrom;
6668 QaimModel model_transitionVia;
6669
6670 QaimModel model;
6671 if (staticallyPopulate) {
6672 for (int i = 0; i < 30; i++)
6673 model.addItem(name: "item" + QString::number(i), number: "");
6674 }
6675
6676 QQuickView *window = getView();
6677 QScopedPointer<TestObject> testObject(new TestObject(window->rootContext()));
6678 window->rootContext()->setContextProperty("testModel", &model);
6679 window->rootContext()->setContextProperty("testObject", testObject.data());
6680 window->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
6681 window->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
6682 window->rootContext()->setContextProperty("transitionFrom", transitionFrom);
6683 window->rootContext()->setContextProperty("transitionVia", transitionVia);
6684 window->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
6685 window->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
6686 window->setSource(testFileUrl(fileName: "populateTransitions.qml"));
6687 window->show();
6688 QVERIFY(QTest::qWaitForWindowExposed(window));
6689
6690 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
6691 QVERIFY(listview);
6692 QQuickItem *contentItem = listview->contentItem();
6693 QVERIFY(contentItem);
6694
6695 if (staticallyPopulate && usePopulateTransition) {
6696 QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 16);
6697 QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
6698 } else if (dynamicallyPopulate) {
6699 QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
6700 QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 16);
6701 } else {
6702 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6703 QCOMPARE(listview->property("countPopulateTransitions").toInt(), 0);
6704 QCOMPARE(listview->property("countAddTransitions").toInt(), 0);
6705 }
6706
6707 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
6708 for (int i=0; i < model.count() && i < itemCount; ++i) {
6709 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
6710 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
6711 QTRY_COMPARE(item->x(), 0.0);
6712 QTRY_COMPARE(item->y(), i*20.0);
6713 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
6714 QVERIFY(name != nullptr);
6715 QTRY_COMPARE(name->text(), model.name(i));
6716 }
6717
6718 listview->setProperty(name: "countPopulateTransitions", value: 0);
6719 listview->setProperty(name: "countAddTransitions", value: 0);
6720
6721 // add an item and check this is done with add transition, not populate
6722 model.insertItem(index: 0, name: "another item", number: "");
6723 QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 1);
6724 QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
6725
6726 // clear the model
6727 window->rootContext()->setContextProperty("testModel", QVariant());
6728 listview->forceLayout();
6729 QTRY_COMPARE(listview->count(), 0);
6730 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
6731 listview->setProperty(name: "countPopulateTransitions", value: 0);
6732 listview->setProperty(name: "countAddTransitions", value: 0);
6733
6734 // set to a valid model and check populate transition is run a second time
6735 model.clear();
6736 for (int i = 0; i < 30; i++)
6737 model.addItem(name: "item" + QString::number(i), number: "");
6738 window->rootContext()->setContextProperty("testModel", &model);
6739 QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
6740 QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
6741
6742 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
6743 for (int i=0; i < model.count() && i < itemCount; ++i) {
6744 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
6745 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
6746 QTRY_COMPARE(item->x(), 0.0);
6747 QTRY_COMPARE(item->y(), i*20.0);
6748 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
6749 QVERIFY(name != nullptr);
6750 QTRY_COMPARE(name->text(), model.name(i));
6751 }
6752
6753 // reset model and check populate transition is run again
6754 listview->setProperty(name: "countPopulateTransitions", value: 0);
6755 listview->setProperty(name: "countAddTransitions", value: 0);
6756 model.reset();
6757 QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
6758 QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
6759
6760 itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
6761 for (int i=0; i < model.count() && i < itemCount; ++i) {
6762 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
6763 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
6764 QTRY_COMPARE(item->x(), 0.0);
6765 QTRY_COMPARE(item->y(), i*20.0);
6766 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
6767 QVERIFY(name != nullptr);
6768 QTRY_COMPARE(name->text(), model.name(i));
6769 }
6770
6771 releaseView(view: window);
6772}
6773
6774void tst_QQuickListView::populateTransitions_data()
6775{
6776 QTest::addColumn<bool>(name: "staticallyPopulate");
6777 QTest::addColumn<bool>(name: "dynamicallyPopulate");
6778 QTest::addColumn<bool>(name: "usePopulateTransition");
6779
6780 QTest::newRow(dataTag: "static") << true << false << true;
6781 QTest::newRow(dataTag: "static, no populate") << true << false << false;
6782
6783 QTest::newRow(dataTag: "dynamic") << false << true << true;
6784 QTest::newRow(dataTag: "dynamic, no populate") << false << true << false;
6785
6786 QTest::newRow(dataTag: "empty to start with") << false << false << true;
6787 QTest::newRow(dataTag: "empty to start with, no populate") << false << false << false;
6788}
6789
6790
6791/*
6792 * Tests if the first visible item is not repositioned if the same item
6793 * resized + changes position during a transition. The test does not test the
6794 * actual position while it is transitioning (since its timing sensitive), but
6795 * rather tests if the transition has reached its target state properly.
6796 **/
6797void tst_QQuickListView::sizeTransitions()
6798{
6799 QFETCH(bool, topToBottom);
6800 QQuickView *window = getView();
6801 QQmlContext *ctxt = window->rootContext();
6802 QaimModel model;
6803 ctxt->setContextProperty("testModel", &model);
6804 ctxt->setContextProperty("topToBottom", topToBottom);
6805 QScopedPointer<TestObject> testObject(new TestObject);
6806 ctxt->setContextProperty("testObject", &model);
6807 window->setSource(testFileUrl(fileName: "sizeTransitions.qml"));
6808 window->show();
6809 QVERIFY(QTest::qWaitForWindowExposed(window));
6810
6811 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
6812 QTRY_VERIFY(listview != nullptr);
6813 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6814
6815 // the following will start the transition
6816 model.addItem(name: QLatin1String("Test"), number: "");
6817
6818 // This ensures early failure in case of failure (in which case
6819 // transitionFinished == true and scriptActionExecuted == false)
6820 QTRY_COMPARE(listview->property("scriptActionExecuted").toBool() ||
6821 listview->property("transitionFinished").toBool(), true);
6822 QCOMPARE(listview->property("scriptActionExecuted").toBool(), true);
6823 QCOMPARE(listview->property("transitionFinished").toBool(), true);
6824
6825 releaseView(view: window);
6826}
6827
6828void tst_QQuickListView::sizeTransitions_data()
6829{
6830 QTest::addColumn<bool>(name: "topToBottom");
6831
6832 QTest::newRow(dataTag: "TopToBottom")
6833 << true;
6834
6835 QTest::newRow(dataTag: "LeftToRight")
6836 << false;
6837}
6838
6839void tst_QQuickListView::addTransitions()
6840{
6841 QFETCH(int, initialItemCount);
6842 QFETCH(bool, shouldAnimateTargets);
6843 QFETCH(qreal, contentY);
6844 QFETCH(int, insertionIndex);
6845 QFETCH(int, insertionCount);
6846 QFETCH(ListRange, expectedDisplacedIndexes);
6847
6848 // added items should start here
6849 QPointF targetItems_transitionFrom(-50, -50);
6850
6851 // displaced items should pass through this point
6852 QPointF displacedItems_transitionVia(100, 100);
6853
6854 QaimModel model;
6855 for (int i = 0; i < initialItemCount; i++)
6856 model.addItem(name: "Original item" + QString::number(i), number: "");
6857 QaimModel model_targetItems_transitionFrom;
6858 QaimModel model_displacedItems_transitionVia;
6859
6860 QQuickView *window = getView();
6861 QQmlContext *ctxt = window->rootContext();
6862 QScopedPointer<TestObject> testObject(new TestObject);
6863 ctxt->setContextProperty("testModel", &model);
6864 ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
6865 ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
6866 ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
6867 ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
6868 ctxt->setContextProperty("testObject", testObject.data());
6869 window->setSource(testFileUrl(fileName: "addTransitions.qml"));
6870 window->show();
6871 QVERIFY(QTest::qWaitForWindowExposed(window));
6872
6873 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
6874 QTRY_VERIFY(listview != nullptr);
6875 QQuickItem *contentItem = listview->contentItem();
6876 QVERIFY(contentItem != nullptr);
6877 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6878
6879 if (contentY != 0) {
6880 listview->setContentY(contentY);
6881 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
6882 }
6883
6884 QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
6885
6886 // only target items that will become visible should be animated
6887 QList<QPair<QString, QString> > newData;
6888 QList<QPair<QString, QString> > expectedTargetData;
6889 QList<int> targetIndexes;
6890 if (shouldAnimateTargets) {
6891 for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
6892 newData << qMakePair(x: QString("New item %1").arg(a: i), y: QString(""));
6893
6894 if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) { // only grab visible items
6895 expectedTargetData << newData.last();
6896 targetIndexes << i;
6897 }
6898 }
6899 QVERIFY(expectedTargetData.count() > 0);
6900 }
6901
6902 // start animation
6903 if (!newData.isEmpty()) {
6904 model.insertItems(index: insertionIndex, items: newData);
6905 QTRY_COMPARE(model.count(), listview->count());
6906 listview->forceLayout();
6907 }
6908
6909 QList<QQuickItem *> targetItems = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", indexes: targetIndexes);
6910
6911 if (shouldAnimateTargets) {
6912 QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
6913 QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
6914 expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
6915
6916 // check the target and displaced items were animated
6917 model_targetItems_transitionFrom.matchAgainst(other: expectedTargetData, error1: "wasn't animated from target 'from' pos", error2: "shouldn't have been animated from target 'from' pos");
6918 model_displacedItems_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with displaced anim", error2: "shouldn't have been animated with displaced anim");
6919
6920 // check attached properties
6921 matchItemsAndIndexes(items: listview->property(name: "targetTrans_items").toMap(), model, expectedIndexes: targetIndexes);
6922 matchIndexLists(indexLists: listview->property(name: "targetTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
6923 matchItemLists(itemLists: listview->property(name: "targetTrans_targetItems").toList(), expectedItems: targetItems);
6924 if (expectedDisplacedIndexes.isValid()) {
6925 // adjust expectedDisplacedIndexes to their final values after the move
6926 QList<int> displacedIndexes = adjustIndexesForAddDisplaced(indexes: expectedDisplacedIndexes.indexes, index: insertionIndex, count: insertionCount);
6927 matchItemsAndIndexes(items: listview->property(name: "displacedTrans_items").toMap(), model, expectedIndexes: displacedIndexes);
6928 matchIndexLists(indexLists: listview->property(name: "displacedTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
6929 matchItemLists(itemLists: listview->property(name: "displacedTrans_targetItems").toList(), expectedItems: targetItems);
6930 }
6931
6932 } else {
6933 QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
6934 QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
6935 }
6936
6937 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
6938 int firstVisibleIndex = -1;
6939 int itemCount = items.count();
6940 for (int i=0; i<items.count(); i++) {
6941 if (items[i]->y() >= contentY) {
6942 QQmlExpression e(qmlContext(items[i]), items[i], "index");
6943 firstVisibleIndex = e.evaluate().toInt();
6944 break;
6945 }
6946 }
6947 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
6948
6949 // verify all items moved to the correct final positions
6950 for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
6951 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
6952 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
6953 QTRY_COMPARE(item->y(), i*20.0);
6954 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
6955 QVERIFY(name != nullptr);
6956 QTRY_COMPARE(name->text(), model.name(i));
6957 }
6958
6959 releaseView(view: window);
6960}
6961
6962void tst_QQuickListView::addTransitions_data()
6963{
6964 QTest::addColumn<int>(name: "initialItemCount");
6965 QTest::addColumn<qreal>(name: "contentY");
6966 QTest::addColumn<bool>(name: "shouldAnimateTargets");
6967 QTest::addColumn<int>(name: "insertionIndex");
6968 QTest::addColumn<int>(name: "insertionCount");
6969 QTest::addColumn<ListRange>(name: "expectedDisplacedIndexes");
6970
6971 // if inserting before visible index, items should not appear or animate in, even if there are > 1 new items
6972 QTest::newRow(dataTag: "insert 1, just before start")
6973 << 30 << 20.0 << false
6974 << 0 << 1 << ListRange();
6975 QTest::newRow(dataTag: "insert 1, way before start")
6976 << 30 << 20.0 << false
6977 << 0 << 1 << ListRange();
6978 QTest::newRow(dataTag: "insert multiple, just before start")
6979 << 30 << 100.0 << false
6980 << 0 << 3 << ListRange();
6981 QTest::newRow(dataTag: "insert multiple, way before start")
6982 << 30 << 100.0 << false
6983 << 0 << 3 << ListRange();
6984
6985 QTest::newRow(dataTag: "insert 1 at start")
6986 << 30 << 0.0 << true
6987 << 0 << 1 << ListRange(0, 15);
6988 QTest::newRow(dataTag: "insert multiple at start")
6989 << 30 << 0.0 << true
6990 << 0 << 3 << ListRange(0, 15);
6991 QTest::newRow(dataTag: "insert 1 at start, content y not 0")
6992 << 30 << 40.0 << true // first visible is index 2, so translate the displaced indexes by 2
6993 << 2 << 1 << ListRange(0 + 2, 15 + 2);
6994 QTest::newRow(dataTag: "insert multiple at start, content y not 0")
6995 << 30 << 40.0 << true // first visible is index 2
6996 << 2 << 3 << ListRange(0 + 2, 15 + 2);
6997
6998 QTest::newRow(dataTag: "insert 1 at start, to empty list")
6999 << 0 << 0.0 << true
7000 << 0 << 1 << ListRange();
7001 QTest::newRow(dataTag: "insert multiple at start, to empty list")
7002 << 0 << 0.0 << true
7003 << 0 << 3 << ListRange();
7004
7005 QTest::newRow(dataTag: "insert 1 at middle")
7006 << 30 << 0.0 << true
7007 << 5 << 1 << ListRange(5, 15);
7008 QTest::newRow(dataTag: "insert multiple at middle")
7009 << 30 << 0.0 << true
7010 << 5 << 3 << ListRange(5, 15);
7011
7012 QTest::newRow(dataTag: "insert 1 at bottom")
7013 << 30 << 0.0 << true
7014 << 15 << 1 << ListRange(15, 15);
7015 QTest::newRow(dataTag: "insert multiple at bottom")
7016 << 30 << 0.0 << true
7017 << 15 << 3 << ListRange(15, 15);
7018 QTest::newRow(dataTag: "insert 1 at bottom, content y not 0")
7019 << 30 << 20.0 * 3 << true
7020 << 15 + 3 << 1 << ListRange(15 + 3, 15 + 3);
7021 QTest::newRow(dataTag: "insert multiple at bottom, content y not 0")
7022 << 30 << 20.0 * 3 << true
7023 << 15 + 3 << 3 << ListRange(15 + 3, 15 + 3);
7024
7025 // items added after the last visible will not be animated in, since they
7026 // do not appear in the final view
7027 QTest::newRow(dataTag: "insert 1 after end")
7028 << 30 << 0.0 << false
7029 << 17 << 1 << ListRange();
7030 QTest::newRow(dataTag: "insert multiple after end")
7031 << 30 << 0.0 << false
7032 << 17 << 3 << ListRange();
7033}
7034
7035void tst_QQuickListView::moveTransitions()
7036{
7037 QFETCH(int, initialItemCount);
7038 QFETCH(qreal, contentY);
7039 QFETCH(qreal, itemsOffsetAfterMove);
7040 QFETCH(int, moveFrom);
7041 QFETCH(int, moveTo);
7042 QFETCH(int, moveCount);
7043 QFETCH(ListRange, expectedDisplacedIndexes);
7044
7045 // target and displaced items should pass through these points
7046 QPointF targetItems_transitionVia(-50, 50);
7047 QPointF displacedItems_transitionVia(100, 100);
7048
7049 QaimModel model;
7050 for (int i = 0; i < initialItemCount; i++)
7051 model.addItem(name: "Original item" + QString::number(i), number: "");
7052 QaimModel model_targetItems_transitionVia;
7053 QaimModel model_displacedItems_transitionVia;
7054
7055 QQuickView *window = getView();
7056 QQmlContext *ctxt = window->rootContext();
7057 QScopedPointer<TestObject> testObject(new TestObject);
7058 ctxt->setContextProperty("testModel", &model);
7059 ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
7060 ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
7061 ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
7062 ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
7063 ctxt->setContextProperty("testObject", testObject.data());
7064 window->setSource(testFileUrl(fileName: "moveTransitions.qml"));
7065 window->show();
7066 QVERIFY(QTest::qWaitForWindowExposed(window));
7067
7068 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7069 QTRY_VERIFY(listview != nullptr);
7070 QQuickItem *contentItem = listview->contentItem();
7071 QVERIFY(contentItem != nullptr);
7072 QQuickText *name;
7073
7074 if (contentY != 0) {
7075 listview->setContentY(contentY);
7076 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7077 }
7078
7079 QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
7080
7081 // Items moving to *or* from visible positions should be animated.
7082 // Otherwise, they should not be animated.
7083 QList<QPair<QString, QString> > expectedTargetData;
7084 QList<int> targetIndexes;
7085 for (int i=moveFrom; i<moveFrom+moveCount; i++) {
7086 int toIndex = moveTo + (i - moveFrom);
7087 if (i <= (contentY + listview->height()) / 20
7088 || toIndex < (contentY + listview->height()) / 20) {
7089 expectedTargetData << qMakePair(x: model.name(index: i), y: model.number(index: i));
7090 targetIndexes << i;
7091 }
7092 }
7093 // ViewTransition.index provides the indices that items are moving to, not from
7094 targetIndexes = adjustIndexesForMove(indexes: targetIndexes, from: moveFrom, to: moveTo, count: moveCount);
7095
7096 // start animation
7097 model.moveItems(from: moveFrom, to: moveTo, count: moveCount);
7098
7099 QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
7100 QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
7101 expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
7102
7103 QList<QQuickItem *> targetItems = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", indexes: targetIndexes);
7104
7105 // check the target and displaced items were animated
7106 model_targetItems_transitionVia.matchAgainst(other: expectedTargetData, error1: "wasn't animated from target 'from' pos", error2: "shouldn't have been animated from target 'from' pos");
7107 model_displacedItems_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with displaced anim", error2: "shouldn't have been animated with displaced anim");
7108
7109 // check attached properties
7110 matchItemsAndIndexes(items: listview->property(name: "targetTrans_items").toMap(), model, expectedIndexes: targetIndexes);
7111 matchIndexLists(indexLists: listview->property(name: "targetTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
7112 matchItemLists(itemLists: listview->property(name: "targetTrans_targetItems").toList(), expectedItems: targetItems);
7113 if (expectedDisplacedIndexes.isValid()) {
7114 // adjust expectedDisplacedIndexes to their final values after the move
7115 QList<int> displacedIndexes = adjustIndexesForMove(indexes: expectedDisplacedIndexes.indexes, from: moveFrom, to: moveTo, count: moveCount);
7116 matchItemsAndIndexes(items: listview->property(name: "displacedTrans_items").toMap(), model, expectedIndexes: displacedIndexes);
7117 matchIndexLists(indexLists: listview->property(name: "displacedTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
7118 matchItemLists(itemLists: listview->property(name: "displacedTrans_targetItems").toList(), expectedItems: targetItems);
7119 }
7120
7121 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
7122 int firstVisibleIndex = -1;
7123 for (int i=0; i<items.count(); i++) {
7124 if (items[i]->y() >= contentY) {
7125 QQmlExpression e(qmlContext(items[i]), items[i], "index");
7126 firstVisibleIndex = e.evaluate().toInt();
7127 break;
7128 }
7129 }
7130 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
7131
7132 // verify all items moved to the correct final positions
7133 int itemCount = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper").count();
7134 for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
7135 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7136 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
7137 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
7138 name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
7139 QVERIFY(name != nullptr);
7140 QTRY_COMPARE(name->text(), model.name(i));
7141 }
7142
7143 releaseView(view: window);
7144}
7145
7146void tst_QQuickListView::moveTransitions_data()
7147{
7148 QTest::addColumn<int>(name: "initialItemCount");
7149 QTest::addColumn<qreal>(name: "contentY");
7150 QTest::addColumn<qreal>(name: "itemsOffsetAfterMove");
7151 QTest::addColumn<int>(name: "moveFrom");
7152 QTest::addColumn<int>(name: "moveTo");
7153 QTest::addColumn<int>(name: "moveCount");
7154 QTest::addColumn<ListRange>(name: "expectedDisplacedIndexes");
7155
7156 // when removing from above the visible, all items shift down depending on how many
7157 // items have been removed from above the visible
7158 QTest::newRow(dataTag: "move from above view, outside visible items, move 1") << 30 << 4*20.0 << 20.0
7159 << 1 << 10 << 1 << ListRange(11, 15+4);
7160 QTest::newRow(dataTag: "move from above view, outside visible items, move 1 (first item)") << 30 << 4*20.0 << 20.0
7161 << 0 << 10 << 1 << ListRange(11, 15+4);
7162 QTest::newRow(dataTag: "move from above view, outside visible items, move multiple") << 30 << 4*20.0 << 2*20.0
7163 << 1 << 10 << 2 << ListRange(12, 15+4);
7164 QTest::newRow(dataTag: "move from above view, outside visible items, move multiple (first item)") << 30 << 4*20.0 << 3*20.0
7165 << 0 << 10 << 3 << ListRange(13, 15+4);
7166 QTest::newRow(dataTag: "move from above view, mix of visible/non-visible") << 30 << 4*20.0 << 3*20.0
7167 << 1 << 10 << 5 << ListRange(6, 14) + ListRange(15, 15+4);
7168 QTest::newRow(dataTag: "move from above view, mix of visible/non-visible (move first)") << 30 << 4*20.0 << 4*20.0
7169 << 0 << 10 << 5 << ListRange(5, 14) + ListRange(15, 15+4);
7170
7171 QTest::newRow(dataTag: "move within view, move 1 down") << 30 << 0.0 << 0.0
7172 << 1 << 10 << 1 << ListRange(2, 10);
7173 QTest::newRow(dataTag: "move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
7174 << 0 << 10 << 1 << ListRange(1, 10);
7175 QTest::newRow(dataTag: "move within view, move 1 down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
7176 << 0+4 << 10+4 << 1 << ListRange(1+4, 10+4);
7177 QTest::newRow(dataTag: "move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
7178 << 10 << 15 << 1 << ListRange(11, 15);
7179 QTest::newRow(dataTag: "move within view, move first->last") << 30 << 0.0 << 0.0
7180 << 0 << 15 << 1 << ListRange(1, 15);
7181
7182 QTest::newRow(dataTag: "move within view, move multiple down") << 30 << 0.0 << 0.0
7183 << 1 << 10 << 3 << ListRange(4, 12);
7184 QTest::newRow(dataTag: "move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
7185 << 0 << 10 << 3 << ListRange(3, 12);
7186 QTest::newRow(dataTag: "move within view, move multiple down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
7187 << 0+4 << 10+4 << 3 << ListRange(3+4, 12+4);
7188 QTest::newRow(dataTag: "move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
7189 << 5 << 13 << 3 << ListRange(8, 15);
7190 QTest::newRow(dataTag: "move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
7191 << 0 << 13 << 3 << ListRange(3, 15);
7192
7193 QTest::newRow(dataTag: "move within view, move 1 up") << 30 << 0.0 << 0.0
7194 << 10 << 1 << 1 << ListRange(1, 9);
7195 QTest::newRow(dataTag: "move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
7196 << 10 << 0 << 1 << ListRange(0, 9);
7197 QTest::newRow(dataTag: "move within view, move 1 up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
7198 << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
7199 QTest::newRow(dataTag: "move within view, move 1 up, move to first index, contentY not on item border") << 30 << 4*20.0 - 10 << 0.0
7200 << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
7201 QTest::newRow(dataTag: "move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
7202 << 15 << 10 << 1 << ListRange(10, 14);
7203 QTest::newRow(dataTag: "move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
7204 << 15 << 0 << 1 << ListRange(0, 14);
7205
7206 QTest::newRow(dataTag: "move within view, move multiple up") << 30 << 0.0 << 0.0
7207 << 10 << 1 << 3 << ListRange(1, 9);
7208 QTest::newRow(dataTag: "move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
7209 << 10 << 0 << 3 << ListRange(0, 9);
7210 QTest::newRow(dataTag: "move within view, move multiple up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
7211 << 10+4 << 0+4 << 3 << ListRange(0+4, 9+4);
7212 QTest::newRow(dataTag: "move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
7213 << 13 << 5 << 3 << ListRange(5, 12);
7214 QTest::newRow(dataTag: "move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
7215 << 13 << 0 << 3 << ListRange(0, 12);
7216
7217 QTest::newRow(dataTag: "move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
7218 << 20 << 0 << 1 << ListRange(0, 15);
7219 QTest::newRow(dataTag: "move from below view, move 1 up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
7220 << 25 << 4 << 1 << ListRange(0+4, 15+4);
7221 QTest::newRow(dataTag: "move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
7222 << 20 << 0 << 3 << ListRange(0, 15);
7223 QTest::newRow(dataTag: "move from below view, move multiple up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
7224 << 25 << 4 << 3 << ListRange(0+4, 15+4);
7225
7226 QTest::newRow(dataTag: "move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
7227 << 20 << 15 << 1 << ListRange(15, 15);
7228 QTest::newRow(dataTag: "move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
7229 << 25 << 15+4 << 1 << ListRange(15+4, 15+4);
7230 QTest::newRow(dataTag: "move from below view, move multiple up, move to bottom") << 30 << 0.0 << 0.0
7231 << 20 << 15 << 3 << ListRange(15, 15);
7232 QTest::newRow(dataTag: "move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
7233 << 25 << 15+4 << 3 << ListRange(15+4, 15+4);
7234}
7235
7236void tst_QQuickListView::removeTransitions()
7237{
7238 QFETCH(int, initialItemCount);
7239 QFETCH(bool, shouldAnimateTargets);
7240 QFETCH(qreal, contentY);
7241 QFETCH(int, removalIndex);
7242 QFETCH(int, removalCount);
7243 QFETCH(ListRange, expectedDisplacedIndexes);
7244
7245 // added items should end here
7246 QPointF targetItems_transitionTo(-50, -50);
7247
7248 // displaced items should pass through this points
7249 QPointF displacedItems_transitionVia(100, 100);
7250
7251 QaimModel model;
7252 for (int i = 0; i < initialItemCount; i++)
7253 model.addItem(name: "Original item" + QString::number(i), number: "");
7254 QaimModel model_targetItems_transitionTo;
7255 QaimModel model_displacedItems_transitionVia;
7256
7257 QQuickView *window = getView();
7258 QQmlContext *ctxt = window->rootContext();
7259 QScopedPointer<TestObject> testObject(new TestObject);
7260 ctxt->setContextProperty("testModel", &model);
7261 ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
7262 ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
7263 ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
7264 ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
7265 ctxt->setContextProperty("testObject", testObject.data());
7266 window->setSource(testFileUrl(fileName: "removeTransitions.qml"));
7267 window->show();
7268 QVERIFY(QTest::qWaitForWindowExposed(window));
7269
7270 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7271 QTRY_VERIFY(listview != nullptr);
7272 QQuickItem *contentItem = listview->contentItem();
7273 QVERIFY(contentItem != nullptr);
7274 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7275
7276 if (contentY != 0) {
7277 listview->setContentY(contentY);
7278 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7279 }
7280
7281 QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
7282
7283 // only target items that are visible should be animated
7284 QList<QPair<QString, QString> > expectedTargetData;
7285 QList<int> targetIndexes;
7286 if (shouldAnimateTargets) {
7287 for (int i=removalIndex; i<removalIndex+removalCount; i++) {
7288 if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {
7289 expectedTargetData << qMakePair(x: model.name(index: i), y: model.number(index: i));
7290 targetIndexes << i;
7291 }
7292 }
7293 QVERIFY(expectedTargetData.count() > 0);
7294 }
7295
7296 // calculate targetItems and expectedTargets before model changes
7297 QList<QQuickItem *> targetItems = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper", indexes: targetIndexes);
7298 QVariantMap expectedTargets;
7299 for (int i=0; i<targetIndexes.count(); i++)
7300 expectedTargets[model.name(index: targetIndexes[i])] = targetIndexes[i];
7301
7302 // start animation
7303 model.removeItems(index: removalIndex, count: removalCount);
7304 QTRY_COMPARE(model.count(), listview->count());
7305
7306 if (shouldAnimateTargets) {
7307 QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
7308 QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
7309 expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
7310
7311 // check the target and displaced items were animated
7312 model_targetItems_transitionTo.matchAgainst(other: expectedTargetData, error1: "wasn't animated to target 'to' pos", error2: "shouldn't have been animated to target 'to' pos");
7313 model_displacedItems_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with displaced anim", error2: "shouldn't have been animated with displaced anim");
7314
7315 // check attached properties
7316 QCOMPARE(listview->property("targetTrans_items").toMap(), expectedTargets);
7317 matchIndexLists(indexLists: listview->property(name: "targetTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
7318 matchItemLists(itemLists: listview->property(name: "targetTrans_targetItems").toList(), expectedItems: targetItems);
7319 if (expectedDisplacedIndexes.isValid()) {
7320 // adjust expectedDisplacedIndexes to their final values after the move
7321 QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(indexes: expectedDisplacedIndexes.indexes, index: removalIndex, count: removalCount);
7322 matchItemsAndIndexes(items: listview->property(name: "displacedTrans_items").toMap(), model, expectedIndexes: displacedIndexes);
7323 matchIndexLists(indexLists: listview->property(name: "displacedTrans_targetIndexes").toList(), expectedIndexes: targetIndexes);
7324 matchItemLists(itemLists: listview->property(name: "displacedTrans_targetItems").toList(), expectedItems: targetItems);
7325 }
7326 } else {
7327 QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
7328 QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
7329 }
7330
7331 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
7332 int firstVisibleIndex = -1;
7333 int itemCount = items.count();
7334
7335 for (int i=0; i<items.count(); i++) {
7336 QQmlExpression e(qmlContext(items[i]), items[i], "index");
7337 int index = e.evaluate().toInt();
7338 if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
7339 firstVisibleIndex = index;
7340 if (index < 0)
7341 itemCount--; // exclude deleted items
7342 }
7343 QVERIFY2(firstVisibleIndex >= 0, QByteArray::number(firstVisibleIndex));
7344
7345 // verify all items moved to the correct final positions
7346 for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
7347 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7348 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
7349 QCOMPARE(item->x(), 0.0);
7350 QCOMPARE(item->y(), contentY + (i-firstVisibleIndex) * 20.0);
7351 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
7352 QVERIFY(name != nullptr);
7353 QTRY_COMPARE(name->text(), model.name(i));
7354 }
7355
7356 releaseView(view: window);
7357}
7358
7359void tst_QQuickListView::removeTransitions_data()
7360{
7361 QTest::addColumn<int>(name: "initialItemCount");
7362 QTest::addColumn<qreal>(name: "contentY");
7363 QTest::addColumn<bool>(name: "shouldAnimateTargets");
7364 QTest::addColumn<int>(name: "removalIndex");
7365 QTest::addColumn<int>(name: "removalCount");
7366 QTest::addColumn<ListRange>(name: "expectedDisplacedIndexes");
7367
7368 // All items that are visible following the remove operation should be animated.
7369 // Remove targets that are outside of the view should not be animated.
7370
7371 QTest::newRow(dataTag: "remove 1 before start")
7372 << 30 << 20.0 * 3 << false
7373 << 2 << 1 << ListRange();
7374 QTest::newRow(dataTag: "remove multiple, all before start")
7375 << 30 << 20.0 * 3 << false
7376 << 0 << 3 << ListRange();
7377 QTest::newRow(dataTag: "remove mix of before and after start")
7378 << 30 << 20.0 * 3 << true
7379 << 2 << 3 << ListRange(5, 20); // 5-20 are visible after the remove
7380
7381 QTest::newRow(dataTag: "remove 1 from start")
7382 << 30 << 0.0 << true
7383 << 0 << 1 << ListRange(1, 16); // 1-16 are visible after the remove
7384 QTest::newRow(dataTag: "remove multiple from start")
7385 << 30 << 0.0 << true
7386 << 0 << 3 << ListRange(3, 18); // 3-18 are visible after the remove
7387 QTest::newRow(dataTag: "remove 1 from start, content y not 0")
7388 << 30 << 20.0 * 2 << true // first visible is index 2, so translate the displaced indexes by 2
7389 << 2 << 1 << ListRange(1 + 2, 16 + 2);
7390 QTest::newRow(dataTag: "remove multiple from start, content y not 0")
7391 << 30 << 20.0 * 2 << true // first visible is index 2
7392 << 2 << 3 << ListRange(3 + 2, 18 + 2);
7393
7394 QTest::newRow(dataTag: "remove 1 from middle")
7395 << 30 << 0.0 << true
7396 << 5 << 1 << ListRange(6, 16);
7397 QTest::newRow(dataTag: "remove multiple from middle")
7398 << 30 << 0.0 << true
7399 << 5 << 3 << ListRange(8, 18);
7400
7401
7402 QTest::newRow(dataTag: "remove 1 from bottom")
7403 << 30 << 0.0 << true
7404 << 15 << 1 << ListRange(16, 16);
7405
7406 // remove 15, 16, 17
7407 // 15 will animate as the target item, 16 & 17 won't be animated since they are outside
7408 // the view, and 18 will be animated as the displaced item to replace the last item
7409 QTest::newRow(dataTag: "remove multiple from bottom")
7410 << 30 << 0.0 << true
7411 << 15 << 3 << ListRange(18, 18);
7412
7413 QTest::newRow(dataTag: "remove 1 from bottom, content y not 0")
7414 << 30 << 20.0 * 2 << true
7415 << 15 + 2 << 1 << ListRange(16 + 2, 16 + 2);
7416 QTest::newRow(dataTag: "remove multiple from bottom, content y not 0")
7417 << 30 << 20.0 * 2 << true
7418 << 15 + 2 << 3 << ListRange(18 + 2, 18 + 2);
7419
7420
7421 QTest::newRow(dataTag: "remove 1 after end")
7422 << 30 << 0.0 << false
7423 << 17 << 1 << ListRange();
7424 QTest::newRow(dataTag: "remove multiple after end")
7425 << 30 << 0.0 << false
7426 << 17 << 3 << ListRange();
7427}
7428
7429void tst_QQuickListView::displacedTransitions()
7430{
7431 QFETCH(bool, useDisplaced);
7432 QFETCH(bool, displacedEnabled);
7433 QFETCH(bool, useAddDisplaced);
7434 QFETCH(bool, addDisplacedEnabled);
7435 QFETCH(bool, useMoveDisplaced);
7436 QFETCH(bool, moveDisplacedEnabled);
7437 QFETCH(bool, useRemoveDisplaced);
7438 QFETCH(bool, removeDisplacedEnabled);
7439 QFETCH(ListChange, change);
7440 QFETCH(ListRange, expectedDisplacedIndexes);
7441
7442 QaimModel model;
7443 for (int i = 0; i < 30; i++)
7444 model.addItem(name: "Original item" + QString::number(i), number: "");
7445 QaimModel model_displaced_transitionVia;
7446 QaimModel model_addDisplaced_transitionVia;
7447 QaimModel model_moveDisplaced_transitionVia;
7448 QaimModel model_removeDisplaced_transitionVia;
7449
7450 QPointF displaced_transitionVia(-50, -100);
7451 QPointF addDisplaced_transitionVia(-150, 100);
7452 QPointF moveDisplaced_transitionVia(50, -100);
7453 QPointF removeDisplaced_transitionVia(150, 100);
7454
7455 QQuickView *window = getView();
7456 QQmlContext *ctxt = window->rootContext();
7457 QScopedPointer<TestObject> testObject(new TestObject(window));
7458 ctxt->setContextProperty("testModel", &model);
7459 ctxt->setContextProperty("testObject", testObject.data());
7460 ctxt->setContextProperty("model_displaced_transitionVia", &model_displaced_transitionVia);
7461 ctxt->setContextProperty("model_addDisplaced_transitionVia", &model_addDisplaced_transitionVia);
7462 ctxt->setContextProperty("model_moveDisplaced_transitionVia", &model_moveDisplaced_transitionVia);
7463 ctxt->setContextProperty("model_removeDisplaced_transitionVia", &model_removeDisplaced_transitionVia);
7464 ctxt->setContextProperty("displaced_transitionVia", displaced_transitionVia);
7465 ctxt->setContextProperty("addDisplaced_transitionVia", addDisplaced_transitionVia);
7466 ctxt->setContextProperty("moveDisplaced_transitionVia", moveDisplaced_transitionVia);
7467 ctxt->setContextProperty("removeDisplaced_transitionVia", removeDisplaced_transitionVia);
7468 ctxt->setContextProperty("useDisplaced", useDisplaced);
7469 ctxt->setContextProperty("displacedEnabled", displacedEnabled);
7470 ctxt->setContextProperty("useAddDisplaced", useAddDisplaced);
7471 ctxt->setContextProperty("addDisplacedEnabled", addDisplacedEnabled);
7472 ctxt->setContextProperty("useMoveDisplaced", useMoveDisplaced);
7473 ctxt->setContextProperty("moveDisplacedEnabled", moveDisplacedEnabled);
7474 ctxt->setContextProperty("useRemoveDisplaced", useRemoveDisplaced);
7475 ctxt->setContextProperty("removeDisplacedEnabled", removeDisplacedEnabled);
7476 window->setSource(testFileUrl(fileName: "displacedTransitions.qml"));
7477 window->show();
7478 QVERIFY(QTest::qWaitForWindowExposed(window));
7479
7480 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7481 QTRY_VERIFY(listview != nullptr);
7482 QQuickItem *contentItem = listview->contentItem();
7483 QVERIFY(contentItem != nullptr);
7484 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7485
7486 QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
7487 listview->setProperty(name: "displaceTransitionsDone", value: false);
7488
7489 switch (change.type) {
7490 case ListChange::Inserted:
7491 {
7492 QList<QPair<QString, QString> > targetItemData;
7493 for (int i=change.index; i<change.index + change.count; ++i)
7494 targetItemData << qMakePair(x: QString("new item %1").arg(a: i), y: QString::number(i));
7495 model.insertItems(index: change.index, items: targetItemData);
7496 QTRY_COMPARE(model.count(), listview->count());
7497 break;
7498 }
7499 case ListChange::Removed:
7500 model.removeItems(index: change.index, count: change.count);
7501 QTRY_COMPARE(model.count(), listview->count());
7502 break;
7503 case ListChange::Moved:
7504 model.moveItems(from: change.index, to: change.to, count: change.count);
7505 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7506 break;
7507 case ListChange::SetCurrent:
7508 case ListChange::SetContentY:
7509 case ListChange::Polish:
7510 break;
7511 }
7512 listview->forceLayout();
7513
7514 QVariantList resultTargetIndexes = listview->property(name: "displacedTargetIndexes").toList();
7515 QVariantList resultTargetItems = listview->property(name: "displacedTargetItems").toList();
7516
7517 if ((useDisplaced && displacedEnabled)
7518 || (useAddDisplaced && addDisplacedEnabled)
7519 || (useMoveDisplaced && moveDisplacedEnabled)
7520 || (useRemoveDisplaced && removeDisplacedEnabled)) {
7521 QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
7522
7523 // check the correct number of target items and indexes were received
7524 QCOMPARE(resultTargetIndexes.count(), expectedDisplacedIndexes.count());
7525 for (int i=0; i<resultTargetIndexes.count(); i++)
7526 QCOMPARE(resultTargetIndexes[i].value<QList<int> >().count(), change.count);
7527 QCOMPARE(resultTargetItems.count(), expectedDisplacedIndexes.count());
7528 for (int i=0; i<resultTargetItems.count(); i++)
7529 QCOMPARE(resultTargetItems[i].toList().count(), change.count);
7530 } else {
7531 QCOMPARE(resultTargetIndexes.count(), 0);
7532 QCOMPARE(resultTargetItems.count(), 0);
7533 }
7534
7535 if (change.type == ListChange::Inserted && useAddDisplaced && addDisplacedEnabled)
7536 model_addDisplaced_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with add displaced", error2: "shouldn't have been animated with add displaced");
7537 else
7538 QCOMPARE(model_addDisplaced_transitionVia.count(), 0);
7539 if (change.type == ListChange::Moved && useMoveDisplaced && moveDisplacedEnabled)
7540 model_moveDisplaced_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with move displaced", error2: "shouldn't have been animated with move displaced");
7541 else
7542 QCOMPARE(model_moveDisplaced_transitionVia.count(), 0);
7543 if (change.type == ListChange::Removed && useRemoveDisplaced && removeDisplacedEnabled)
7544 model_removeDisplaced_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with remove displaced", error2: "shouldn't have been animated with remove displaced");
7545 else
7546 QCOMPARE(model_removeDisplaced_transitionVia.count(), 0);
7547
7548 if (useDisplaced && displacedEnabled
7549 && ( (change.type == ListChange::Inserted && (!useAddDisplaced || !addDisplacedEnabled))
7550 || (change.type == ListChange::Moved && (!useMoveDisplaced || !moveDisplacedEnabled))
7551 || (change.type == ListChange::Removed && (!useRemoveDisplaced || !removeDisplacedEnabled))) ) {
7552 model_displaced_transitionVia.matchAgainst(other: expectedDisplacedValues, error1: "wasn't animated with generic displaced", error2: "shouldn't have been animated with generic displaced");
7553 } else {
7554 QCOMPARE(model_displaced_transitionVia.count(), 0);
7555 }
7556
7557 // verify all items moved to the correct final positions
7558 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
7559 for (int i=0; i < model.count() && i < items.count(); ++i) {
7560 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7561 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
7562 QCOMPARE(item->x(), 0.0);
7563 QCOMPARE(item->y(), i * 20.0);
7564 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
7565 QVERIFY(name != nullptr);
7566 QTRY_COMPARE(name->text(), model.name(i));
7567 }
7568
7569 releaseView(view: window);
7570}
7571
7572void tst_QQuickListView::displacedTransitions_data()
7573{
7574 QTest::addColumn<bool>(name: "useDisplaced");
7575 QTest::addColumn<bool>(name: "displacedEnabled");
7576 QTest::addColumn<bool>(name: "useAddDisplaced");
7577 QTest::addColumn<bool>(name: "addDisplacedEnabled");
7578 QTest::addColumn<bool>(name: "useMoveDisplaced");
7579 QTest::addColumn<bool>(name: "moveDisplacedEnabled");
7580 QTest::addColumn<bool>(name: "useRemoveDisplaced");
7581 QTest::addColumn<bool>(name: "removeDisplacedEnabled");
7582 QTest::addColumn<ListChange>(name: "change");
7583 QTest::addColumn<ListRange>(name: "expectedDisplacedIndexes");
7584
7585 QTest::newRow(dataTag: "no displaced transitions at all")
7586 << false << false
7587 << false << false
7588 << false << false
7589 << false << false
7590 << ListChange::insert(index: 0, count: 1) << ListRange(0, 15);
7591
7592 QTest::newRow(dataTag: "just displaced")
7593 << true << true
7594 << false << false
7595 << false << false
7596 << false << false
7597 << ListChange::insert(index: 0, count: 1) << ListRange(0, 15);
7598
7599 QTest::newRow(dataTag: "just displaced (not enabled)")
7600 << true << false
7601 << false << false
7602 << false << false
7603 << false << false
7604 << ListChange::insert(index: 0, count: 1) << ListRange(0, 15);
7605
7606 QTest::newRow(dataTag: "displaced + addDisplaced")
7607 << true << true
7608 << true << true
7609 << false << false
7610 << false << false
7611 << ListChange::insert(index: 0, count: 1) << ListRange(0, 15);
7612
7613 QTest::newRow(dataTag: "displaced + addDisplaced (not enabled)")
7614 << true << true
7615 << true << false
7616 << false << false
7617 << false << false
7618 << ListChange::insert(index: 0, count: 1) << ListRange(0, 15);
7619
7620 QTest::newRow(dataTag: "displaced + moveDisplaced")
7621 << true << true
7622 << false << false
7623 << true << true
7624 << false << false
7625 << ListChange::move(index: 0, to: 10, count: 1) << ListRange(1, 10);
7626
7627 QTest::newRow(dataTag: "displaced + moveDisplaced (not enabled)")
7628 << true << true
7629 << false << false
7630 << true << false
7631 << false << false
7632 << ListChange::move(index: 0, to: 10, count: 1) << ListRange(1, 10);
7633
7634 QTest::newRow(dataTag: "displaced + removeDisplaced")
7635 << true << true
7636 << false << false
7637 << false << false
7638 << true << true
7639 << ListChange::remove(index: 0, count: 1) << ListRange(1, 16);
7640
7641 QTest::newRow(dataTag: "displaced + removeDisplaced (not enabled)")
7642 << true << true
7643 << false << false
7644 << false << false
7645 << true << false
7646 << ListChange::remove(index: 0, count: 1) << ListRange(1, 16);
7647
7648
7649 QTest::newRow(dataTag: "displaced + add, should use generic displaced for a remove")
7650 << true << true
7651 << true << true
7652 << false << false
7653 << true << false
7654 << ListChange::remove(index: 0, count: 1) << ListRange(1, 16);
7655}
7656
7657void tst_QQuickListView::multipleTransitions()
7658{
7659 // Tests that if you interrupt a transition in progress with another action that
7660 // cancels the previous transition, the resulting items are still placed correctly.
7661
7662 QFETCH(int, initialCount);
7663 QFETCH(qreal, contentY);
7664 QFETCH(QList<ListChange>, changes);
7665 QFETCH(bool, enableAddTransitions);
7666 QFETCH(bool, enableMoveTransitions);
7667 QFETCH(bool, enableRemoveTransitions);
7668 QFETCH(bool, rippleAddDisplaced);
7669
7670 QPointF addTargets_transitionFrom(-50, -50);
7671 QPointF addDisplaced_transitionFrom(-50, 50);
7672 QPointF moveTargets_transitionFrom(50, -50);
7673 QPointF moveDisplaced_transitionFrom(50, 50);
7674 QPointF removeTargets_transitionTo(-100, 300);
7675 QPointF removeDisplaced_transitionFrom(100, 300);
7676
7677 QaimModel model;
7678 for (int i = 0; i < initialCount; i++)
7679 model.addItem(name: "Original item" + QString::number(i), number: "");
7680
7681 QQuickView *window = getView();
7682 QQmlContext *ctxt = window->rootContext();
7683 QScopedPointer<TestObject> testObject(new TestObject);
7684 ctxt->setContextProperty("testModel", &model);
7685 ctxt->setContextProperty("testObject", testObject.data());
7686 ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
7687 ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
7688 ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
7689 ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
7690 ctxt->setContextProperty("removeTargets_transitionTo", removeTargets_transitionTo);
7691 ctxt->setContextProperty("removeDisplaced_transitionFrom", removeDisplaced_transitionFrom);
7692 ctxt->setContextProperty("enableAddTransitions", enableAddTransitions);
7693 ctxt->setContextProperty("enableMoveTransitions", enableMoveTransitions);
7694 ctxt->setContextProperty("enableRemoveTransitions", enableRemoveTransitions);
7695 ctxt->setContextProperty("rippleAddDisplaced", rippleAddDisplaced);
7696 window->setSource(testFileUrl(fileName: "multipleTransitions.qml"));
7697 window->show();
7698 QVERIFY(QTest::qWaitForWindowExposed(window));
7699
7700 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7701 QTRY_VERIFY(listview != nullptr);
7702 QQuickItem *contentItem = listview->contentItem();
7703 QVERIFY(contentItem != nullptr);
7704 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7705
7706 if (contentY != 0) {
7707 listview->setContentY(contentY);
7708 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7709 }
7710
7711 int timeBetweenActions = window->rootObject()->property(name: "timeBetweenActions").toInt();
7712
7713 for (int i=0; i<changes.count(); i++) {
7714 switch (changes[i].type) {
7715 case ListChange::Inserted:
7716 {
7717 QList<QPair<QString, QString> > targetItems;
7718 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
7719 targetItems << qMakePair(x: QString("new item %1").arg(a: j), y: QString::number(j));
7720 model.insertItems(index: changes[i].index, items: targetItems);
7721 QTRY_COMPARE(model.count(), listview->count());
7722 if (i == changes.count() - 1) {
7723 QTRY_VERIFY(!listview->property("runningAddTargets").toBool());
7724 QTRY_VERIFY(!listview->property("runningAddDisplaced").toBool());
7725 } else {
7726 QTest::qWait(ms: timeBetweenActions);
7727 }
7728 break;
7729 }
7730 case ListChange::Removed:
7731 model.removeItems(index: changes[i].index, count: changes[i].count);
7732 QTRY_COMPARE(model.count(), listview->count());
7733 if (i == changes.count() - 1) {
7734 QTRY_VERIFY(!listview->property("runningRemoveTargets").toBool());
7735 QTRY_VERIFY(!listview->property("runningRemoveDisplaced").toBool());
7736 } else {
7737 QTest::qWait(ms: timeBetweenActions);
7738 }
7739 break;
7740 case ListChange::Moved:
7741 model.moveItems(from: changes[i].index, to: changes[i].to, count: changes[i].count);
7742 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7743 if (i == changes.count() - 1) {
7744 QTRY_VERIFY(!listview->property("runningMoveTargets").toBool());
7745 QTRY_VERIFY(!listview->property("runningMoveDisplaced").toBool());
7746 } else {
7747 QTest::qWait(ms: timeBetweenActions);
7748 }
7749 break;
7750 case ListChange::SetCurrent:
7751 listview->setCurrentIndex(changes[i].index);
7752 break;
7753 case ListChange::SetContentY:
7754 listview->setContentY(changes[i].pos);
7755 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7756 break;
7757 case ListChange::Polish:
7758 break;
7759 }
7760 }
7761 listview->forceLayout();
7762 QTest::qWait(ms: 200);
7763 QCOMPARE(listview->count(), model.count());
7764
7765 // verify all items moved to the correct final positions
7766 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
7767 for (int i=0; i < model.count() && i < items.count(); ++i) {
7768 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7769 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
7770 QTRY_COMPARE(item->x(), 0.0);
7771 QTRY_COMPARE(item->y(), i*20.0);
7772 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
7773 QVERIFY(name != nullptr);
7774 QTRY_COMPARE(name->text(), model.name(i));
7775 }
7776
7777 releaseView(view: window);
7778}
7779
7780void tst_QQuickListView::multipleTransitions_data()
7781{
7782 QTest::addColumn<int>(name: "initialCount");
7783 QTest::addColumn<qreal>(name: "contentY");
7784 QTest::addColumn<QList<ListChange> >(name: "changes");
7785 QTest::addColumn<bool>(name: "enableAddTransitions");
7786 QTest::addColumn<bool>(name: "enableMoveTransitions");
7787 QTest::addColumn<bool>(name: "enableRemoveTransitions");
7788 QTest::addColumn<bool>(name: "rippleAddDisplaced");
7789
7790 // the added item and displaced items should move to final dest correctly
7791 QTest::newRow(dataTag: "add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
7792 << ListChange::insert(index: 0, count: 1)
7793 << ListChange::move(index: 0, to: 3, count: 1)
7794 )
7795 << true << true << true << false;
7796
7797 // items affected by the add should change from move to add transition
7798 QTest::newRow(dataTag: "move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
7799 << ListChange::move(index: 1, to: 10, count: 3)
7800 << ListChange::insert(index: 0, count: 1)
7801 )
7802 << true << true << true << false;
7803
7804 // items should be placed correctly if you trigger a transition then refill for that index
7805 QTest::newRow(dataTag: "add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
7806 << ListChange::insert(index: 0, count: 1)
7807 << ListChange::setContentY(80.0)
7808 << ListChange::setContentY(0.0)
7809 << ListChange::insert(index: 0, count: 1)
7810 )
7811 << true << true << true << false;
7812
7813 QTest::newRow(dataTag: "insert then remove same index, with ripple effect on add displaced") << 20 << 0.0 << (QList<ListChange>()
7814 << ListChange::insert(index: 1, count: 1)
7815 << ListChange::remove(index: 1, count: 1)
7816 )
7817 << true << true << true << true;
7818
7819 // if item is removed while undergoing a displaced transition, all other items should end up at their correct positions,
7820 // even if a remove-displace transition is not present to re-animate them
7821 QTest::newRow(dataTag: "insert then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
7822 << ListChange::insert(index: 0, count: 1)
7823 << ListChange::remove(index: 2, count: 1)
7824 )
7825 << true << true << false << false;
7826
7827 // if last item is not flush with the edge of the view, it should still be refilled in correctly after a
7828 // remove has changed the position of where it will move to
7829 QTest::newRow(dataTag: "insert twice then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
7830 << ListChange::setContentY(-10.0)
7831 << ListChange::insert(index: 0, count: 1)
7832 << ListChange::insert(index: 0, count: 1)
7833 << ListChange::remove(index: 2, count: 1)
7834 )
7835 << true << true << false << false;
7836}
7837
7838void tst_QQuickListView::multipleDisplaced()
7839{
7840 // multiple move() operations should only restart displace transitions for items that
7841 // moved from previously set positions, and not those that have moved from their current
7842 // item positions (which may e.g. still be changing from easing bounces in the last transition)
7843
7844 QaimModel model;
7845 for (int i = 0; i < 30; i++)
7846 model.addItem(name: "Original item" + QString::number(i), number: "");
7847
7848 QQuickView *window = getView();
7849 QQmlContext *ctxt = window->rootContext();
7850 QScopedPointer<TestObject> testObject(new TestObject(window));
7851 ctxt->setContextProperty("testModel", &model);
7852 ctxt->setContextProperty("testObject", testObject.data());
7853 window->setSource(testFileUrl(fileName: "multipleDisplaced.qml"));
7854 window->show();
7855 QVERIFY(QTest::qWaitForWindowExposed(window));
7856
7857 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7858 QTRY_VERIFY(listview != nullptr);
7859 QQuickItem *contentItem = listview->contentItem();
7860 QVERIFY(contentItem != nullptr);
7861 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7862
7863 model.moveItems(from: 12, to: 8, count: 1);
7864 QTest::qWait(ms: window->rootObject()->property(name: "duration").toInt() / 2);
7865 model.moveItems(from: 8, to: 3, count: 1);
7866 QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
7867
7868 QVariantMap transitionsStarted = listview->property(name: "displaceTransitionsStarted").toMap();
7869 foreach (const QString &name, transitionsStarted.keys()) {
7870 QVERIFY2(transitionsStarted[name] == 1,
7871 qPrintable(QString("%1 was displaced %2 times").arg(name).arg(transitionsStarted[name].toInt())));
7872 }
7873
7874 // verify all items moved to the correct final positions
7875 QList<QQuickItem*> items = findItems<QQuickItem>(parent: contentItem, objectName: "wrapper");
7876 for (int i=0; i < model.count() && i < items.count(); ++i) {
7877 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7878 QVERIFY2(item, qPrintable(QString("Item %1 not found").arg(i)));
7879 QTRY_COMPARE(item->x(), 0.0);
7880 QTRY_COMPARE(item->y(), i*20.0);
7881 QQuickText *name = findItem<QQuickText>(parent: contentItem, objectName: "textName", index: i);
7882 QVERIFY(name != nullptr);
7883 QTRY_COMPARE(name->text(), model.name(i));
7884 }
7885
7886 releaseView(view: window);
7887}
7888
7889QList<int> tst_QQuickListView::toIntList(const QVariantList &list)
7890{
7891 QList<int> ret;
7892 bool ok = true;
7893 for (int i=0; i<list.count(); i++) {
7894 ret << list[i].toInt(ok: &ok);
7895 if (!ok)
7896 qWarning() << "tst_QQuickListView::toIntList(): not a number:" << list[i];
7897 }
7898
7899 return ret;
7900}
7901
7902void tst_QQuickListView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
7903{
7904 const QSet<int> expectedIndexSet(expectedIndexes.cbegin(), expectedIndexes.cend());
7905 for (int i=0; i<indexLists.count(); i++) {
7906 const auto &currentList = indexLists[i].value<QList<int> >();
7907 const QSet<int> current(currentList.cbegin(), currentList.cend());
7908 if (current != expectedIndexSet)
7909 qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
7910 QCOMPARE(current, expectedIndexSet);
7911 }
7912}
7913
7914void tst_QQuickListView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
7915{
7916 for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
7917 QCOMPARE(it.value().type(), QVariant::Int);
7918 QString name = it.key();
7919 int itemIndex = it.value().toInt();
7920 QVERIFY2(expectedIndexes.contains(itemIndex), qPrintable(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
7921 if (model.name(index: itemIndex) != name)
7922 qDebug() << itemIndex;
7923 QCOMPARE(model.name(itemIndex), name);
7924 }
7925 QCOMPARE(items.count(), expectedIndexes.count());
7926}
7927
7928void tst_QQuickListView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
7929{
7930 for (int i=0; i<itemLists.count(); i++) {
7931 QCOMPARE(itemLists[i].type(), QVariant::List);
7932 QVariantList current = itemLists[i].toList();
7933 for (int j=0; j<current.count(); j++) {
7934 QQuickItem *o = qobject_cast<QQuickItem*>(object: current[j].value<QObject*>());
7935 QVERIFY2(o, qPrintable(QString("Invalid actual item at %1").arg(j)));
7936 QVERIFY2(expectedItems.contains(o), qPrintable(QString("Cannot match item %1").arg(j)));
7937 }
7938 QCOMPARE(current.count(), expectedItems.count());
7939 }
7940}
7941
7942void tst_QQuickListView::flickBeyondBounds()
7943{
7944 QScopedPointer<QQuickView> window(createView());
7945 QQuickViewTestUtil::moveMouseAway(window: window.data());
7946
7947 window->setSource(testFileUrl(fileName: "flickBeyondBoundsBug.qml"));
7948 window->show();
7949 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
7950
7951
7952 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
7953 QTRY_VERIFY(listview != nullptr);
7954
7955 QQuickItem *contentItem = listview->contentItem();
7956 QTRY_VERIFY(contentItem != nullptr);
7957 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
7958
7959 // Flick view up beyond bounds
7960 flick(window: window.data(), from: QPoint(10, 10), to: QPoint(10, -2000), duration: 180);
7961#ifdef Q_OS_MAC
7962 QSKIP("Disabled due to flaky behavior on CI system (QTBUG-44493)");
7963 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
7964#endif
7965
7966 // We're really testing that we don't get stuck in a loop,
7967 // but also confirm items positioned correctly.
7968 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 2);
7969 for (int i = 0; i < 2; ++i) {
7970 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: i);
7971 if (!item) qWarning() << "Item" << i << "not found";
7972 QTRY_VERIFY(item);
7973 QTRY_COMPARE(item->y(), qreal(i*45));
7974 }
7975}
7976
7977void tst_QQuickListView::flickBothDirections()
7978{
7979 QFETCH(bool, initValues);
7980 QFETCH(QQuickListView::Orientation, orientation);
7981 QFETCH(QQuickFlickable::FlickableDirection, flickableDirection);
7982 QFETCH(qreal, contentWidth);
7983 QFETCH(qreal, contentHeight);
7984 QFETCH(QPointF, targetPos);
7985
7986 QQuickView *window = getView();
7987 QQuickViewTestUtil::moveMouseAway(window);
7988
7989 QQmlContext *ctxt = window->rootContext();
7990 ctxt->setContextProperty("initialOrientation", initValues ? orientation : QQuickListView::Vertical);
7991 ctxt->setContextProperty("initialFlickableDirection", initValues ? flickableDirection : QQuickFlickable::VerticalFlick);
7992 ctxt->setContextProperty("initialContentWidth", initValues ? contentWidth : -1);
7993 ctxt->setContextProperty("initialContentHeight", initValues ? contentHeight : -1);
7994
7995 window->setSource(testFileUrl(fileName: "flickBothDirections.qml"));
7996 window->show();
7997 QVERIFY(QTest::qWaitForWindowActive(window));
7998
7999 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
8000 QVERIFY(listview);
8001
8002 if (!initValues) {
8003 listview->setOrientation(orientation);
8004 listview->setFlickableDirection(flickableDirection);
8005 if (contentWidth > 0)
8006 listview->setContentWidth(contentWidth);
8007 if (contentHeight > 0)
8008 listview->setContentHeight(contentHeight);
8009 }
8010
8011 flick(window, from: QPoint(100, 100), to: QPoint(25, 25), duration: 50);
8012 QVERIFY(listview->isMoving());
8013 QTRY_VERIFY(!listview->isMoving());
8014 QCOMPARE(listview->contentX(), targetPos.x());
8015 QCOMPARE(listview->contentY(), targetPos.y());
8016}
8017
8018void tst_QQuickListView::flickBothDirections_data()
8019{
8020 QTest::addColumn<bool>(name: "initValues");
8021 QTest::addColumn<QQuickListView::Orientation>(name: "orientation");
8022 QTest::addColumn<QQuickFlickable::FlickableDirection>(name: "flickableDirection");
8023 QTest::addColumn<qreal>(name: "contentWidth");
8024 QTest::addColumn<qreal>(name: "contentHeight");
8025 QTest::addColumn<QPointF>(name: "targetPos");
8026
8027 // model: 20
8028 // listview: 100x100
8029 // vertical delegate: 120x20 -> contentHeight: 20x20=400
8030 // horizontal delegate: 10x110 -> contentWidth: 20x10=200
8031
8032 QTest::newRow(dataTag: "init:vertical,-1") << true << QQuickListView::Vertical << QQuickFlickable::VerticalFlick << -1. << -1. << QPointF(0, 300);
8033 QTest::newRow(dataTag: "init:vertical,120") << true << QQuickListView::Vertical << QQuickFlickable::VerticalFlick<< 120. << -1. << QPointF(0, 300);
8034 QTest::newRow(dataTag: "init:vertical,auto,-1") << true << QQuickListView::Vertical << QQuickFlickable::AutoFlickDirection << -1. << -1. << QPointF(0, 300);
8035 QTest::newRow(dataTag: "init:vertical,auto,120") << true << QQuickListView::Vertical << QQuickFlickable::AutoFlickDirection << 120. << -1. << QPointF(20, 300);
8036
8037 QTest::newRow(dataTag: "completed:vertical,-1") << false << QQuickListView::Vertical << QQuickFlickable::VerticalFlick << -1. << -1. << QPointF(0, 300);
8038 QTest::newRow(dataTag: "completed:vertical,120") << false << QQuickListView::Vertical << QQuickFlickable::VerticalFlick << 120. << -1. << QPointF(0, 300);
8039 QTest::newRow(dataTag: "completed:vertical,auto,-1") << false << QQuickListView::Vertical << QQuickListView::AutoFlickDirection << -1. << -1. << QPointF(0, 300);
8040 QTest::newRow(dataTag: "completed:vertical,auto,120") << false << QQuickListView::Vertical << QQuickListView::AutoFlickDirection << 120. << -1. << QPointF(20, 300);
8041
8042 QTest::newRow(dataTag: "init:horizontal,-1") << true << QQuickListView::Horizontal << QQuickFlickable::HorizontalFlick << -1. << -1. << QPointF(100, 0);
8043 QTest::newRow(dataTag: "init:horizontal,110") << true << QQuickListView::Horizontal << QQuickFlickable::HorizontalFlick <<-1. << 110. << QPointF(100, 0);
8044 QTest::newRow(dataTag: "init:horizontal,auto,-1") << true << QQuickListView::Horizontal << QQuickListView::AutoFlickDirection << -1. << -1. << QPointF(100, 0);
8045 QTest::newRow(dataTag: "init:horizontal,auto,110") << true << QQuickListView::Horizontal << QQuickListView::AutoFlickDirection << -1. << 110. << QPointF(100, 10);
8046
8047 QTest::newRow(dataTag: "completed:horizontal,-1") << false << QQuickListView::Horizontal << QQuickFlickable::HorizontalFlick << -1. << -1. << QPointF(100, 0);
8048 QTest::newRow(dataTag: "completed:horizontal,110") << false << QQuickListView::Horizontal << QQuickFlickable::HorizontalFlick << -1. << 110. << QPointF(100, 0);
8049 QTest::newRow(dataTag: "completed:horizontal,auto,-1") << false << QQuickListView::Horizontal << QQuickListView::AutoFlickDirection << -1. << -1. << QPointF(100, 0);
8050 QTest::newRow(dataTag: "completed:horizontal,auto,110") << false << QQuickListView::Horizontal << QQuickListView::AutoFlickDirection << -1. << 110. << QPointF(100, 10);
8051}
8052
8053void tst_QQuickListView::destroyItemOnCreation()
8054{
8055 QaimModel model;
8056 QScopedPointer<QQuickView> window(createView());
8057 window->rootContext()->setContextProperty("testModel", &model);
8058
8059 window->setSource(testFileUrl(fileName: "destroyItemOnCreation.qml"));
8060 window->show();
8061 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8062
8063
8064 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
8065 QVERIFY(listview != nullptr);
8066
8067 QQuickItem *contentItem = listview->contentItem();
8068 QVERIFY(contentItem != nullptr);
8069 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
8070
8071 QCOMPARE(window->rootObject()->property("createdIndex").toInt(), -1);
8072 model.addItem(name: "new item", number: "");
8073 QTRY_COMPARE(window->rootObject()->property("createdIndex").toInt(), 0);
8074
8075 QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
8076 QCOMPARE(model.count(), 0);
8077}
8078
8079void tst_QQuickListView::parentBinding()
8080{
8081 QScopedPointer<QQuickView> window(createView());
8082 QQmlTestMessageHandler messageHandler;
8083
8084 window->setSource(testFileUrl(fileName: "parentBinding.qml"));
8085 window->show();
8086 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8087
8088 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
8089 QVERIFY(listview != nullptr);
8090
8091 QQuickItem *contentItem = listview->contentItem();
8092 QVERIFY(contentItem != nullptr);
8093 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
8094
8095 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: "wrapper", index: 0);
8096 QVERIFY(item);
8097 QCOMPARE(item->width(), listview->width());
8098 QCOMPARE(item->height(), listview->height()/12);
8099
8100 // there should be no transient binding error
8101 QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
8102}
8103
8104void tst_QQuickListView::defaultHighlightMoveDuration()
8105{
8106 QQmlEngine engine;
8107 QQmlComponent component(&engine);
8108 component.setData("import QtQuick 2.0; ListView {}", baseUrl: QUrl::fromLocalFile(localfile: ""));
8109
8110 QScopedPointer<QObject> obj(component.create());
8111 QVERIFY(obj);
8112
8113 QCOMPARE(obj->property("highlightMoveDuration").toInt(), -1);
8114}
8115
8116void tst_QQuickListView::accessEmptyCurrentItem_QTBUG_30227()
8117{
8118 QScopedPointer<QQuickView> window(createView());
8119 window->setSource(testFileUrl(fileName: "emptymodel.qml"));
8120
8121 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
8122 QTRY_VERIFY(listview != nullptr);
8123 listview->forceLayout();
8124
8125 QMetaObject::invokeMethod(obj: window->rootObject(), member: "remove");
8126 QVERIFY(window->rootObject()->property("isCurrentItemNull").toBool());
8127
8128 QMetaObject::invokeMethod(obj: window->rootObject(), member: "add");
8129 QVERIFY(!window->rootObject()->property("isCurrentItemNull").toBool());
8130}
8131
8132void tst_QQuickListView::delayedChanges_QTBUG_30555()
8133{
8134 QScopedPointer<QQuickView> window(createView());
8135 window->setSource(testFileUrl(fileName: "delayedChanges.qml"));
8136
8137 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
8138 QTRY_VERIFY(listview != nullptr);
8139
8140 QCOMPARE(listview->count(), 10);
8141
8142 //Takes two just like in the bug report
8143 QMetaObject::invokeMethod(obj: window->rootObject(), member: "takeTwo");
8144 QTRY_COMPARE(listview->count(), 8);
8145
8146 QMetaObject::invokeMethod(obj: window->rootObject(), member: "takeTwo_sync");
8147 QCOMPARE(listview->count(), 6);
8148}
8149
8150void tst_QQuickListView::outsideViewportChangeNotAffectingView()
8151{
8152 QScopedPointer<QQuickView> window(createView());
8153 QQuickViewTestUtil::moveMouseAway(window: window.data());
8154 window->setSource(testFileUrl(fileName: "outsideViewportChangeNotAffectingView.qml"));
8155
8156 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
8157 QTRY_VERIFY(listview != nullptr);
8158
8159 window->show();
8160 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8161
8162 listview->setContentY(1250);
8163
8164 QTRY_COMPARE(listview->indexAt(0, listview->contentY()), 4);
8165 QTRY_COMPARE(listview->itemAt(0, listview->contentY())->y(), 1200.);
8166
8167 QMetaObject::invokeMethod(obj: window->rootObject(), member: "resizeThirdItem", Q_ARG(QVariant, 290));
8168 QTRY_COMPARE(listview->indexAt(0, listview->contentY()), 4);
8169 QTRY_COMPARE(listview->itemAt(0, listview->contentY())->y(), 1200.);
8170
8171 QMetaObject::invokeMethod(obj: window->rootObject(), member: "resizeThirdItem", Q_ARG(QVariant, 300));
8172 QTRY_COMPARE(listview->indexAt(0, listview->contentY()), 4);
8173 QTRY_COMPARE(listview->itemAt(0, listview->contentY())->y(), 1200.);
8174
8175 QMetaObject::invokeMethod(obj: window->rootObject(), member: "resizeThirdItem", Q_ARG(QVariant, 310));
8176 QTRY_COMPARE(listview->indexAt(0, listview->contentY()), 4);
8177 QTRY_COMPARE(listview->itemAt(0, listview->contentY())->y(), 1200.);
8178
8179 QMetaObject::invokeMethod(obj: window->rootObject(), member: "resizeThirdItem", Q_ARG(QVariant, 400));
8180 QTRY_COMPARE(listview->indexAt(0, listview->contentY()), 4);
8181 QTRY_COMPARE(listview->itemAt(0, listview->contentY())->y(), 1200.);
8182}
8183
8184void tst_QQuickListView::testProxyModelChangedAfterMove()
8185{
8186 QScopedPointer<QQuickView> window(createView());
8187 QQuickViewTestUtil::moveMouseAway(window: window.data());
8188 window->setSource(testFileUrl(fileName: "proxytest.qml"));
8189
8190 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
8191 QTRY_VERIFY(listview != nullptr);
8192
8193 window->show();
8194 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8195
8196 QTRY_COMPARE(listview->count(), 3);
8197}
8198
8199void tst_QQuickListView::typedModel()
8200{
8201 QQmlEngine engine;
8202 QQmlComponent component(&engine, testFileUrl(fileName: "typedModel.qml"));
8203
8204 QScopedPointer<QObject> object(component.create());
8205
8206 QQuickListView *listview = qobject_cast<QQuickListView *>(object: object.data());
8207 QVERIFY(listview);
8208
8209 QCOMPARE(listview->count(), 6);
8210
8211 QQmlListModel *listModel = nullptr;
8212
8213 listview->setModel(QVariant::fromValue(value: listModel));
8214 QCOMPARE(listview->count(), 0);
8215}
8216
8217void tst_QQuickListView::displayMargin()
8218{
8219 QScopedPointer<QQuickView> window(createView());
8220 window->setSource(testFileUrl(fileName: "displayMargin.qml"));
8221 window->show();
8222 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8223
8224 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
8225 QVERIFY(listview != nullptr);
8226
8227 QQuickItem *content = listview->contentItem();
8228 QVERIFY(content != nullptr);
8229
8230 QQuickItem *item0 = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 0);
8231 QVERIFY(item0);
8232 QCOMPARE(delegateVisible(item0), true);
8233
8234 // the 14th item should be within the end margin
8235
8236 QQuickItem *item14 = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 13);
8237 QVERIFY(item14);
8238 QCOMPARE(delegateVisible(item14), true);
8239
8240 // the 15th item should be outside the end margin
8241 QVERIFY(findItem<QQuickItem>(content, "delegate", 14) == nullptr);
8242
8243 // the first delegate should still be within the begin margin
8244 listview->positionViewAtIndex(index: 3, mode: QQuickListView::Beginning);
8245 QCOMPARE(delegateVisible(item0), true);
8246
8247 // the first delegate should now be outside the begin margin
8248 listview->positionViewAtIndex(index: 4, mode: QQuickListView::Beginning);
8249 QCOMPARE(delegateVisible(item0), false);
8250}
8251
8252void tst_QQuickListView::negativeDisplayMargin()
8253{
8254 QScopedPointer<QQuickView> window(createView());
8255 window->setSource(testFileUrl(fileName: "negativeDisplayMargin.qml"));
8256 window->show();
8257 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8258
8259 QQuickItem *listview = window->rootObject();
8260 QQuickListView *innerList = findItem<QQuickListView>(parent: window->rootObject(), objectName: "innerList");
8261 QVERIFY(innerList != nullptr);
8262
8263 QTRY_COMPARE(innerList->property("createdItems").toInt(), 11);
8264 QCOMPARE(innerList->property("destroyedItem").toInt(), 0);
8265
8266 QQuickItem *content = innerList->contentItem();
8267 QVERIFY(content != nullptr);
8268
8269 QQuickItem *item = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 0);
8270 QVERIFY(item);
8271 QCOMPARE(delegateVisible(item), true);
8272
8273 item = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 7);
8274 QVERIFY(item);
8275 QCOMPARE(delegateVisible(item), true);
8276
8277 item = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 8);
8278 QVERIFY(item);
8279 QCOMPARE(delegateVisible(item), false);
8280
8281 // Flick until contentY means that delegate8 should be visible
8282 listview->setProperty(name: "contentY", value: 500);
8283 item = findItem<QQuickItem>(parent: content, objectName: "delegate", index: 8);
8284 QVERIFY(item);
8285 QTRY_COMPARE(delegateVisible(item), true);
8286
8287 listview->setProperty(name: "contentY", value: 1000);
8288 QTRY_VERIFY((item = findItem<QQuickItem>(content, "delegate", 14)));
8289 QTRY_COMPARE(delegateVisible(item), true);
8290
8291 listview->setProperty(name: "contentY", value: 0);
8292 QTRY_VERIFY(item = findItem<QQuickItem>(content, "delegate", 4));
8293 QTRY_COMPARE(delegateVisible(item), true);
8294}
8295
8296void tst_QQuickListView::highlightItemGeometryChanges()
8297{
8298 QScopedPointer<QQuickView> window(createView());
8299 window->setSource(testFileUrl(fileName: "HighlightResize.qml"));
8300 window->show();
8301 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8302
8303 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8304 QVERIFY(listview);
8305
8306 QCOMPARE(listview->count(), 5);
8307
8308 for (int i = 0; i < listview->count(); ++i) {
8309 listview->setCurrentIndex(i);
8310 QTRY_COMPARE(listview->highlightItem()->width(), qreal(100 + i * 20));
8311 QTRY_COMPARE(listview->highlightItem()->height(), qreal(100 + i * 10));
8312 }
8313}
8314
8315void tst_QQuickListView::QTBUG_36481()
8316{
8317 QQmlEngine engine;
8318 QQmlComponent component(&engine, testFileUrl(fileName: "headerCrash.qml"));
8319
8320 // just testing that we don't crash when creating
8321 QScopedPointer<QObject> object(component.create());
8322}
8323
8324void tst_QQuickListView::QTBUG_35920()
8325{
8326 QScopedPointer<QQuickView> window(createView());
8327 window->setSource(testFileUrl(fileName: "qtbug35920.qml"));
8328 window->show();
8329 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8330
8331 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8332 QVERIFY(listview);
8333
8334 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,0));
8335 for (int i = 0; i < 100; ++i) {
8336 QTest::mouseMove(window: window.data(), pos: QPoint(10,i));
8337 if (listview->isMoving()) {
8338 // do not fixup() the position while in movement to avoid flicker
8339 const qreal contentY = listview->contentY();
8340 listview->setPreferredHighlightBegin(i);
8341 QCOMPARE(listview->contentY(), contentY);
8342 listview->resetPreferredHighlightBegin();
8343 QCOMPARE(listview->contentY(), contentY);
8344
8345 listview->setPreferredHighlightEnd(i+10);
8346 QCOMPARE(listview->contentY(), contentY);
8347 listview->resetPreferredHighlightEnd();
8348 QCOMPARE(listview->contentY(), contentY);
8349 }
8350 }
8351 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,100));
8352}
8353
8354Q_DECLARE_METATYPE(Qt::Orientation)
8355
8356void tst_QQuickListView::stickyPositioning()
8357{
8358 QFETCH(QString, fileName);
8359
8360 QFETCH(Qt::Orientation, orientation);
8361 QFETCH(Qt::LayoutDirection, layoutDirection);
8362 QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
8363
8364 QFETCH(int, positionIndex);
8365 QFETCH(QQuickItemView::PositionMode, positionMode);
8366 QFETCH(QList<QPointF>, movement);
8367
8368 QFETCH(QPointF, headerPos);
8369 QFETCH(QPointF, footerPos);
8370
8371 QQuickView *window = getView();
8372
8373 QaimModel model;
8374 for (int i = 0; i < 20; i++)
8375 model.addItem(name: QString::number(i), number: QString::number(i/10));
8376
8377 QQmlContext *ctxt = window->rootContext();
8378 ctxt->setContextProperty("testModel", &model);
8379 ctxt->setContextProperty("testOrientation", orientation);
8380 ctxt->setContextProperty("testLayoutDirection", layoutDirection);
8381 ctxt->setContextProperty("testVerticalLayoutDirection", verticalLayoutDirection);
8382
8383 window->setSource(testFileUrl(fileName));
8384 window->show();
8385 QVERIFY(QTest::qWaitForWindowExposed(window));
8386
8387 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
8388 QVERIFY(listview);
8389
8390 QQuickItem *contentItem = listview->contentItem();
8391 QVERIFY(contentItem);
8392
8393 listview->positionViewAtIndex(index: positionIndex, mode: positionMode);
8394
8395 foreach (const QPointF &offset, movement) {
8396 listview->setContentX(listview->contentX() + offset.x());
8397 listview->setContentY(listview->contentY() + offset.y());
8398 }
8399
8400 if (listview->header()) {
8401 QQuickItem *headerItem = listview->headerItem();
8402 QVERIFY(headerItem);
8403 QPointF actualPos = listview->mapFromItem(item: contentItem, point: headerItem->position());
8404 QCOMPARE(actualPos, headerPos);
8405 }
8406
8407 if (listview->footer()) {
8408 QQuickItem *footerItem = listview->footerItem();
8409 QVERIFY(footerItem);
8410 QPointF actualPos = listview->mapFromItem(item: contentItem, point: footerItem->position());
8411 QCOMPARE(actualPos, footerPos);
8412 }
8413
8414 releaseView(view: window);
8415}
8416
8417void tst_QQuickListView::stickyPositioning_data()
8418{
8419 qRegisterMetaType<Qt::Orientation>();
8420 qRegisterMetaType<Qt::LayoutDirection>();
8421 qRegisterMetaType<QQuickItemView::VerticalLayoutDirection>();
8422 qRegisterMetaType<QQuickItemView::PositionMode>();
8423
8424 QTest::addColumn<QString>(name: "fileName");
8425
8426 QTest::addColumn<Qt::Orientation>(name: "orientation");
8427 QTest::addColumn<Qt::LayoutDirection>(name: "layoutDirection");
8428 QTest::addColumn<QQuickItemView::VerticalLayoutDirection>(name: "verticalLayoutDirection");
8429
8430 QTest::addColumn<int>(name: "positionIndex");
8431 QTest::addColumn<QQuickItemView::PositionMode>(name: "positionMode");
8432 QTest::addColumn<QList<QPointF> >(name: "movement");
8433
8434 QTest::addColumn<QPointF>(name: "headerPos");
8435 QTest::addColumn<QPointF>(name: "footerPos");
8436
8437 // header at the top
8438 QTest::newRow(dataTag: "top header") << "stickyPositioning-header.qml"
8439 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8440 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8441 << QPointF(0,0) << QPointF();
8442
8443 QTest::newRow(dataTag: "top header: 1/2 up") << "stickyPositioning-header.qml"
8444 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8445 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-5))
8446 << QPointF(0,-5) << QPointF();
8447
8448 QTest::newRow(dataTag: "top header: up") << "stickyPositioning-header.qml"
8449 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8450 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15))
8451 << QPointF(0,0) << QPointF();
8452
8453 QTest::newRow(dataTag: "top header: 1/2 down") << "stickyPositioning-header.qml"
8454 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8455 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
8456 << QPointF(0,-5) << QPointF();
8457
8458 QTest::newRow(dataTag: "top header: down") << "stickyPositioning-header.qml"
8459 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8460 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
8461 << QPointF(0,-10) << QPointF();
8462
8463
8464 // footer at the top
8465 QTest::newRow(dataTag: "top footer") << "stickyPositioning-footer.qml"
8466 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8467 << 19 << QQuickItemView::End << QList<QPointF>()
8468 << QPointF() << QPointF(0,0);
8469
8470 QTest::newRow(dataTag: "top footer: 1/2 up") << "stickyPositioning-footer.qml"
8471 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8472 << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-5))
8473 << QPointF() << QPointF(0,-5);
8474
8475 QTest::newRow(dataTag: "top footer: up") << "stickyPositioning-footer.qml"
8476 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8477 << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15))
8478 << QPointF() << QPointF(0,0);
8479
8480 QTest::newRow(dataTag: "top footer: 1/2 down") << "stickyPositioning-footer.qml"
8481 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8482 << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
8483 << QPointF() << QPointF(0,-5);
8484
8485 QTest::newRow(dataTag: "top footer: down") << "stickyPositioning-footer.qml"
8486 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8487 << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
8488 << QPointF() << QPointF(0,-10);
8489
8490
8491 // header at the bottom
8492 QTest::newRow(dataTag: "bottom header") << "stickyPositioning-header.qml"
8493 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8494 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8495 << QPointF(0,90) << QPointF();
8496
8497 QTest::newRow(dataTag: "bottom header: 1/2 down") << "stickyPositioning-header.qml"
8498 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8499 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,5))
8500 << QPointF(0,95) << QPointF();
8501
8502 QTest::newRow(dataTag: "bottom header: down") << "stickyPositioning-header.qml"
8503 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8504 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15))
8505 << QPointF(0,90) << QPointF();
8506
8507 QTest::newRow(dataTag: "bottom header: 1/2 up") << "stickyPositioning-header.qml"
8508 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8509 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
8510 << QPointF(0,95) << QPointF();
8511
8512 QTest::newRow(dataTag: "bottom header: up") << "stickyPositioning-header.qml"
8513 << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
8514 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
8515 << QPointF(0,100) << QPointF();
8516
8517
8518 // footer at the bottom
8519 QTest::newRow(dataTag: "bottom footer") << "stickyPositioning-footer.qml"
8520 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8521 << 19 << QQuickItemView::End << QList<QPointF>()
8522 << QPointF() << QPointF(0,90);
8523
8524 QTest::newRow(dataTag: "bottom footer: 1/2 down") << "stickyPositioning-footer.qml"
8525 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8526 << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,5))
8527 << QPointF() << QPointF(0,95);
8528
8529 QTest::newRow(dataTag: "bottom footer: down") << "stickyPositioning-footer.qml"
8530 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8531 << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15))
8532 << QPointF() << QPointF(0,90);
8533
8534 QTest::newRow(dataTag: "bottom footer: 1/2 up") << "stickyPositioning-footer.qml"
8535 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8536 << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
8537 << QPointF() << QPointF(0,95);
8538
8539 QTest::newRow(dataTag: "bottom footer: up") << "stickyPositioning-footer.qml"
8540 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8541 << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
8542 << QPointF() << QPointF(0,100);
8543
8544
8545 // header at the top (& footer at the bottom)
8546 QTest::newRow(dataTag: "top header & bottom footer") << "stickyPositioning-both.qml"
8547 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8548 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8549 << QPointF(0,0) << QPointF(0,100);
8550
8551 QTest::newRow(dataTag: "top header & bottom footer: 1/2 up") << "stickyPositioning-both.qml"
8552 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8553 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-5))
8554 << QPointF(0,-5) << QPointF(0,95);
8555
8556 QTest::newRow(dataTag: "top header & bottom footer: up") << "stickyPositioning-both.qml"
8557 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8558 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15))
8559 << QPointF(0,0) << QPointF(0,100);
8560
8561 QTest::newRow(dataTag: "top header & bottom footer: 1/2 down") << "stickyPositioning-both.qml"
8562 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8563 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
8564 << QPointF(0,-5) << QPointF(0,95);
8565
8566 QTest::newRow(dataTag: "top header & bottom footer: down") << "stickyPositioning-both.qml"
8567 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8568 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
8569 << QPointF(0,-10) << QPointF(0,90);
8570
8571
8572 // footer at the bottom (& header at the top)
8573 QTest::newRow(dataTag: "bottom footer & top header") << "stickyPositioning-both.qml"
8574 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8575 << 1 << QQuickItemView::Beginning << QList<QPointF>()
8576 << QPointF(0,-10) << QPointF(0,90);
8577
8578 QTest::newRow(dataTag: "bottom footer & top header: 1/2 down") << "stickyPositioning-both.qml"
8579 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8580 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,5))
8581 << QPointF(0,-10) << QPointF(0,90);
8582
8583 QTest::newRow(dataTag: "bottom footer & top header: down") << "stickyPositioning-both.qml"
8584 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8585 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15))
8586 << QPointF(0,-10) << QPointF(0,90);
8587
8588 QTest::newRow(dataTag: "bottom footer & top header: 1/2 up") << "stickyPositioning-both.qml"
8589 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8590 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
8591 << QPointF(0,-5) << QPointF(0,95);
8592
8593 QTest::newRow(dataTag: "bottom footer & top header: up") << "stickyPositioning-both.qml"
8594 << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
8595 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
8596 << QPointF(0,0) << QPointF(0,100);
8597
8598 // header on the left
8599 QTest::newRow(dataTag: "left header") << "stickyPositioning-header.qml"
8600 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8601 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8602 << QPointF(0,0) << QPointF();
8603
8604 QTest::newRow(dataTag: "left header: 1/2 left") << "stickyPositioning-header.qml"
8605 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8606 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-5,0))
8607 << QPointF(-5,0) << QPointF();
8608
8609 QTest::newRow(dataTag: "left header: left") << "stickyPositioning-header.qml"
8610 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8611 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0))
8612 << QPointF(0,0) << QPointF();
8613
8614 QTest::newRow(dataTag: "left header: 1/2 right") << "stickyPositioning-header.qml"
8615 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8616 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
8617 << QPointF(-5,0) << QPointF();
8618
8619 QTest::newRow(dataTag: "left header: right") << "stickyPositioning-header.qml"
8620 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8621 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
8622 << QPointF(-10,0) << QPointF();
8623
8624
8625 // footer on the left
8626 QTest::newRow(dataTag: "left footer") << "stickyPositioning-footer.qml"
8627 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8628 << 19 << QQuickItemView::End << QList<QPointF>()
8629 << QPointF() << QPointF(0,0);
8630
8631 QTest::newRow(dataTag: "left footer: 1/2 left") << "stickyPositioning-footer.qml"
8632 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8633 << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(-5,0))
8634 << QPointF() << QPointF(-5,0);
8635
8636 QTest::newRow(dataTag: "left footer: left") << "stickyPositioning-footer.qml"
8637 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8638 << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0))
8639 << QPointF() << QPointF(0,0);
8640
8641 QTest::newRow(dataTag: "left footer: 1/2 right") << "stickyPositioning-footer.qml"
8642 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8643 << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
8644 << QPointF() << QPointF(-5,0);
8645
8646 QTest::newRow(dataTag: "left footer: right") << "stickyPositioning-footer.qml"
8647 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8648 << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
8649 << QPointF() << QPointF(-10,0);
8650
8651
8652 // header on the right
8653 QTest::newRow(dataTag: "right header") << "stickyPositioning-header.qml"
8654 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8655 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8656 << QPointF(90,0) << QPointF();
8657
8658 QTest::newRow(dataTag: "right header: 1/2 right") << "stickyPositioning-header.qml"
8659 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8660 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(5,0))
8661 << QPointF(95,0) << QPointF();
8662
8663 QTest::newRow(dataTag: "right header: right") << "stickyPositioning-header.qml"
8664 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8665 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0))
8666 << QPointF(90,0) << QPointF();
8667
8668 QTest::newRow(dataTag: "right header: 1/2 left") << "stickyPositioning-header.qml"
8669 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8670 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
8671 << QPointF(95,0) << QPointF();
8672
8673 QTest::newRow(dataTag: "right header: left") << "stickyPositioning-header.qml"
8674 << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
8675 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
8676 << QPointF(100,0) << QPointF();
8677
8678
8679 // footer on the right
8680 QTest::newRow(dataTag: "right footer") << "stickyPositioning-footer.qml"
8681 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8682 << 19 << QQuickItemView::End << QList<QPointF>()
8683 << QPointF() << QPointF(90,0);
8684
8685 QTest::newRow(dataTag: "right footer: 1/2 right") << "stickyPositioning-footer.qml"
8686 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8687 << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(5,0))
8688 << QPointF() << QPointF(95,0);
8689
8690 QTest::newRow(dataTag: "right footer: right") << "stickyPositioning-footer.qml"
8691 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8692 << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0))
8693 << QPointF() << QPointF(90,0);
8694
8695 QTest::newRow(dataTag: "right footer: 1/2 left") << "stickyPositioning-footer.qml"
8696 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8697 << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
8698 << QPointF() << QPointF(95,0);
8699
8700 QTest::newRow(dataTag: "right footer: left") << "stickyPositioning-footer.qml"
8701 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8702 << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
8703 << QPointF() << QPointF(100,0);
8704
8705
8706 // header on the left (& footer on the right)
8707 QTest::newRow(dataTag: "left header & right footer") << "stickyPositioning-both.qml"
8708 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8709 << 0 << QQuickItemView::Beginning << QList<QPointF>()
8710 << QPointF(0,0) << QPointF(100,0);
8711
8712 QTest::newRow(dataTag: "left header & right footer: 1/2 left") << "stickyPositioning-both.qml"
8713 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8714 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-5,0))
8715 << QPointF(-5,0) << QPointF(95,0);
8716
8717 QTest::newRow(dataTag: "left header & right footer: left") << "stickyPositioning-both.qml"
8718 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8719 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0))
8720 << QPointF(0,0) << QPointF(100,0);
8721
8722 QTest::newRow(dataTag: "left header & right footer: 1/2 right") << "stickyPositioning-both.qml"
8723 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8724 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
8725 << QPointF(-5,0) << QPointF(95,0);
8726
8727 QTest::newRow(dataTag: "left header & right footer: right") << "stickyPositioning-both.qml"
8728 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8729 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
8730 << QPointF(-10,0) << QPointF(90,0);
8731
8732
8733 // footer on the right (& header on the left)
8734 QTest::newRow(dataTag: "right footer & left header") << "stickyPositioning-both.qml"
8735 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8736 << 1 << QQuickItemView::Beginning << QList<QPointF>()
8737 << QPointF(-10,0) << QPointF(90,0);
8738
8739 QTest::newRow(dataTag: "right footer & left header: 1/2 right") << "stickyPositioning-both.qml"
8740 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8741 << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(5,0))
8742 << QPointF(-10,0) << QPointF(90,0);
8743
8744 QTest::newRow(dataTag: "right footer & left header: right") << "stickyPositioning-both.qml"
8745 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8746 << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0))
8747 << QPointF(-10,0) << QPointF(90,0);
8748
8749 QTest::newRow(dataTag: "right footer & left header: 1/2 left") << "stickyPositioning-both.qml"
8750 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8751 << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
8752 << QPointF(-5,0) << QPointF(95,0);
8753
8754 QTest::newRow(dataTag: "right footer & left header: left") << "stickyPositioning-both.qml"
8755 << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
8756 << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
8757 << QPointF(0,0) << QPointF(100,0);
8758}
8759
8760void tst_QQuickListView::roundingErrors()
8761{
8762 QFETCH(bool, pixelAligned);
8763
8764 QScopedPointer<QQuickView> window(createView());
8765 window->setSource(testFileUrl(fileName: "roundingErrors.qml"));
8766 window->show();
8767 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8768
8769 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8770 QVERIFY(listview);
8771 listview->setPixelAligned(pixelAligned);
8772 listview->positionViewAtIndex(index: 20, mode: QQuickListView::Beginning);
8773
8774 QQuickItem *content = listview->contentItem();
8775 QVERIFY(content);
8776
8777 const QPoint viewPos(150, 36);
8778 const QPointF contentPos = content->mapFromItem(item: listview, point: viewPos);
8779
8780 QPointer<QQuickItem> item = listview->itemAt(x: contentPos.x(), y: contentPos.y());
8781 QVERIFY(item);
8782
8783 // QTBUG-37339: drag an item and verify that it doesn't
8784 // get prematurely released due to rounding errors
8785 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: viewPos);
8786 for (int i = 0; i < 150; i += 5) {
8787 QTest::mouseMove(window: window.data(), pos: viewPos - QPoint(i, 0));
8788 QVERIFY(item);
8789 }
8790 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(0, 36));
8791
8792 // maintain position relative to the right edge
8793 listview->setLayoutDirection(Qt::RightToLeft);
8794 const qreal contentX = listview->contentX();
8795 listview->setContentX(contentX + 0.2);
8796 QCOMPARE(listview->contentX(), pixelAligned ? qRound(contentX + 0.2) : contentX + 0.2);
8797 listview->setWidth(listview->width() - 0.2);
8798 QCOMPARE(listview->contentX(), pixelAligned ? qRound(contentX + 0.2) : contentX + 0.2);
8799
8800 // maintain position relative to the bottom edge
8801 listview->setOrientation(QQuickListView::Vertical);
8802 listview->setVerticalLayoutDirection(QQuickListView::BottomToTop);
8803 const qreal contentY = listview->contentY();
8804 listview->setContentY(contentY + 0.2);
8805 QCOMPARE(listview->contentY(), pixelAligned ? qRound(contentY + 0.2) : contentY + 0.2);
8806 listview->setHeight(listview->height() - 0.2);
8807 QCOMPARE(listview->contentY(), pixelAligned ? qRound(contentY + 0.2) : contentY + 0.2);
8808}
8809
8810void tst_QQuickListView::roundingErrors_data()
8811{
8812 QTest::addColumn<bool>(name: "pixelAligned");
8813 QTest::newRow(dataTag: "pixelAligned=true") << true;
8814 QTest::newRow(dataTag: "pixelAligned=false") << false;
8815}
8816
8817void tst_QQuickListView::QTBUG_38209()
8818{
8819 QScopedPointer<QQuickView> window(createView());
8820 window->setSource(testFileUrl(fileName: "simplelistview.qml"));
8821 window->show();
8822 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8823
8824 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8825 QVERIFY(listview);
8826
8827 // simulate mouse flick
8828 flick(window: window.data(), from: QPoint(200, 200), to: QPoint(200, 50), duration: 100);
8829 QTRY_VERIFY(!listview->isMoving());
8830 qreal contentY = listview->contentY();
8831
8832 // flick down
8833 listview->flick(xVelocity: 0, yVelocity: 1000);
8834
8835 // ensure we move more than just a couple pixels
8836 QTRY_VERIFY(contentY - listview->contentY() > qreal(100.0));
8837}
8838
8839void tst_QQuickListView::programmaticFlickAtBounds()
8840{
8841 QSKIP("Disabled due to false negatives (QTBUG-41228)");
8842
8843 QScopedPointer<QQuickView> window(createView());
8844 window->setSource(testFileUrl(fileName: "simplelistview.qml"));
8845 window->show();
8846 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8847
8848 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8849 QVERIFY(listview);
8850 QSignalSpy spy(listview, SIGNAL(contentYChanged()));
8851
8852 // flick down
8853 listview->flick(xVelocity: 0, yVelocity: 1000);
8854
8855 // verify that there is movement beyond bounds
8856 QVERIFY(spy.wait(100));
8857
8858 // reset, and test with StopAtBounds
8859 listview->cancelFlick();
8860 listview->returnToBounds();
8861 QTRY_COMPARE(listview->contentY(), qreal(0.0));
8862 listview->setBoundsBehavior(QQuickFlickable::StopAtBounds);
8863
8864 // flick down
8865 listview->flick(xVelocity: 0, yVelocity: 1000);
8866
8867 // verify that there is no movement beyond bounds
8868 QVERIFY(!spy.wait(100));
8869}
8870
8871void tst_QQuickListView::programmaticFlickAtBounds2()
8872{
8873 QScopedPointer<QQuickView> window(createView());
8874 window->setSource(testFileUrl(fileName: "programmaticFlickAtBounds2.qml"));
8875 window->show();
8876 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8877
8878 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8879 QVERIFY(listview);
8880
8881 // move exactly one item
8882 qreal velocity = -qSqrt(v: 2 * 50 * listview->flickDeceleration());
8883
8884 // flick down
8885 listview->flick(xVelocity: 0, yVelocity: velocity);
8886
8887 QTRY_COMPARE(listview->contentY(), qreal(50.0));
8888
8889 // flick down
8890 listview->flick(xVelocity: 0, yVelocity: velocity);
8891
8892 QTRY_COMPARE(listview->contentY(), qreal(100.0));
8893}
8894
8895void tst_QQuickListView::programmaticFlickAtBounds3()
8896{
8897 QScopedPointer<QQuickView> window(createView());
8898 window->setSource(testFileUrl(fileName: "programmaticFlickAtBounds3.qml"));
8899 window->show();
8900 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8901
8902 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
8903 QVERIFY(listview);
8904
8905 // flick down
8906 listview->flick(xVelocity: 0, yVelocity: 2000);
8907
8908 // verify scope of the movement
8909 QTRY_VERIFY(listview->property("minOvershoot").toReal() < qreal(-50.0));
8910
8911 // reset, and test a second time
8912 listview->cancelFlick();
8913 listview->returnToBounds();
8914 QTRY_COMPARE(listview->contentY(), qreal(0.0));
8915 listview->setProperty(name: "minOvershoot", value: qreal(0.0));
8916
8917 // flick down
8918 listview->flick(xVelocity: 0, yVelocity: 2000);
8919
8920 // verify scope of the movement is the same
8921 QTRY_VERIFY(listview->property("minOvershoot").toReal() < qreal(-50.0));
8922}
8923
8924void tst_QQuickListView::layoutChange()
8925{
8926 QScopedPointer<RandomSortModel> model(new RandomSortModel);
8927 QScopedPointer<QSortFilterProxyModel> sortModel(new QSortFilterProxyModel);
8928 sortModel->setSourceModel(model.data());
8929 sortModel->setSortRole(Qt::UserRole);
8930 sortModel->setDynamicSortFilter(true);
8931 sortModel->sort(column: 0);
8932
8933 QScopedPointer<QQuickView> window(createView());
8934 window->rootContext()->setContextProperty("testModel", QVariant::fromValue(value: sortModel.data()));
8935 window->setSource(testFileUrl(fileName: "layoutChangeSort.qml"));
8936 window->show();
8937 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
8938
8939 QQuickListView *listview = window->rootObject()->findChild<QQuickListView *>(aName: "listView");
8940 QVERIFY(listview);
8941
8942 for (int iter = 0; iter < 100; iter++) {
8943 for (int i = 0; i < sortModel->rowCount(); ++i) {
8944 QQuickItem *delegateItem = listview->itemAt(x: 10, y: 10 + 2 * i * 20 + 20); // item + group
8945 QVERIFY(delegateItem);
8946 QQuickItem *delegateText = delegateItem->findChild<QQuickItem *>(aName: "delegateText");
8947 QVERIFY(delegateText);
8948
8949 QCOMPARE(delegateText->property("text").toString(),
8950 sortModel->index(i, 0, QModelIndex()).data().toString());
8951 }
8952
8953 model->randomize();
8954 listview->forceLayout();
8955 QTest::qWait(ms: 5); // give view a chance to update
8956 }
8957}
8958
8959void tst_QQuickListView::QTBUG_39492_data()
8960{
8961 QStandardItemModel *sourceModel = new QStandardItemModel(this);
8962 for (int i = 0; i < 5; ++i) {
8963 QStandardItem *item = new QStandardItem(QString::number(i));
8964 for (int j = 0; j < 5; ++j) {
8965 QStandardItem *subItem = new QStandardItem(QString("%1-%2").arg(a: i).arg(a: j));
8966 item->appendRow(aitem: subItem);
8967 }
8968 sourceModel->appendRow(aitem: item);
8969 }
8970
8971 QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(this);
8972 sortModel->setSourceModel(sourceModel);
8973
8974 QTest::addColumn<QSortFilterProxyModel*>(name: "model");
8975 QTest::addColumn<QPersistentModelIndex>(name: "rootIndex");
8976
8977 QTest::newRow(dataTag: "invalid rootIndex")
8978 << sortModel
8979 << QPersistentModelIndex();
8980
8981 QTest::newRow(dataTag: "rootIndex 1")
8982 << sortModel
8983 << QPersistentModelIndex(sortModel->index(row: 1, column: 0));
8984
8985 QTest::newRow(dataTag: "rootIndex 3")
8986 << sortModel
8987 << QPersistentModelIndex(sortModel->index(row: 3, column: 0));
8988
8989 const QModelIndex rootIndex2 = sortModel->index(row: 2, column: 0);
8990 QTest::newRow(dataTag: "rootIndex 2-1")
8991 << sortModel
8992 << QPersistentModelIndex(sortModel->index(row: 1, column: 0, parent: rootIndex2));
8993}
8994
8995void tst_QQuickListView::QTBUG_39492()
8996{
8997 QFETCH(QSortFilterProxyModel*, model);
8998 QFETCH(QPersistentModelIndex, rootIndex);
8999
9000 QQuickView *window = getView();
9001 window->rootContext()->setContextProperty("testModel", QVariant::fromValue(value: model));
9002 window->setSource(testFileUrl(fileName: "qtbug39492.qml"));
9003
9004 QQuickListView *listview = window->rootObject()->findChild<QQuickListView *>(aName: "listView");
9005 QVERIFY(listview);
9006
9007 QQmlDelegateModel *delegateModel = window->rootObject()->findChild<QQmlDelegateModel *>(aName: "delegateModel");
9008 QVERIFY(delegateModel);
9009
9010 delegateModel->setRootIndex(QVariant::fromValue(value: QModelIndex(rootIndex)));
9011 model->sort(column: 0, order: Qt::AscendingOrder);
9012 listview->forceLayout();
9013
9014 for (int i = 0; i < model->rowCount(parent: rootIndex); ++i) {
9015 QQuickItem *delegateItem = listview->itemAt(x: 10, y: 10 + i * 20);
9016 QVERIFY(delegateItem);
9017 QQuickItem *delegateText = delegateItem->findChild<QQuickItem *>(aName: "delegateText");
9018 QVERIFY(delegateText);
9019 QCOMPARE(delegateText->property("text").toString(),
9020 model->index(i, 0, rootIndex).data().toString());
9021 }
9022
9023 model->sort(column: 0, order: Qt::DescendingOrder);
9024 listview->forceLayout();
9025
9026 for (int i = 0; i < model->rowCount(parent: rootIndex); ++i) {
9027 QQuickItem *delegateItem = listview->itemAt(x: 10, y: 10 + i * 20);
9028 QVERIFY(delegateItem);
9029 QQuickItem *delegateText = delegateItem->findChild<QQuickItem *>(aName: "delegateText");
9030 QVERIFY(delegateText);
9031 QCOMPARE(delegateText->property("text").toString(),
9032 model->index(i, 0, rootIndex).data().toString());
9033 }
9034
9035 releaseView(view: window);
9036}
9037
9038void tst_QQuickListView::jsArrayChange()
9039{
9040 QQmlEngine engine;
9041 QQmlComponent component(&engine);
9042 component.setData("import QtQuick 2.4; ListView {}", baseUrl: QUrl());
9043
9044 QScopedPointer<QQuickListView> view(qobject_cast<QQuickListView *>(object: component.create()));
9045 QVERIFY(!view.isNull());
9046
9047 QSignalSpy spy(view.data(), SIGNAL(modelChanged()));
9048 QVERIFY(spy.isValid());
9049
9050 QJSValue array1 = engine.newArray(length: 3);
9051 QJSValue array2 = engine.newArray(length: 3);
9052 for (int i = 0; i < 3; ++i) {
9053 array1.setProperty(arrayIndex: i, value: i);
9054 array2.setProperty(arrayIndex: i, value: i);
9055 }
9056
9057 view->setModel(QVariant::fromValue(value: array1));
9058 QCOMPARE(spy.count(), 1);
9059
9060 // no change
9061 view->setModel(QVariant::fromValue(value: array2));
9062 QCOMPARE(spy.count(), 1);
9063}
9064
9065static bool compareObjectModel(QQuickListView *listview, QQmlObjectModel *model)
9066{
9067 if (listview->count() != model->count())
9068 return false;
9069 for (int i = 0; i < listview->count(); ++i) {
9070 listview->setCurrentIndex(i);
9071 if (listview->currentItem() != model->get(index: i))
9072 return false;
9073 }
9074 return true;
9075}
9076
9077void tst_QQuickListView::objectModel()
9078{
9079 QQmlEngine engine;
9080 QQmlComponent component(&engine, testFileUrl(fileName: "objectmodel.qml"));
9081
9082 QScopedPointer<QObject> obj(component.create());
9083 QQuickListView *listview = qobject_cast<QQuickListView *>(object: obj.data());
9084 QVERIFY(listview);
9085
9086 QQmlObjectModel *model = listview->model().value<QQmlObjectModel *>();
9087 QVERIFY(model);
9088
9089 listview->setCurrentIndex(0);
9090 QVERIFY(listview->currentItem());
9091 QCOMPARE(listview->currentItem()->property("color").toString(), QColor("red").name());
9092
9093 listview->setCurrentIndex(1);
9094 QVERIFY(listview->currentItem());
9095 QCOMPARE(listview->currentItem()->property("color").toString(), QColor("green").name());
9096
9097 listview->setCurrentIndex(2);
9098 QVERIFY(listview->currentItem());
9099 QCOMPARE(listview->currentItem()->property("color").toString(), QColor("blue").name());
9100
9101 QQuickItem *item0 = new QQuickItem(listview);
9102 item0->setSize(QSizeF(20, 20));
9103 model->append(object: item0);
9104 QCOMPARE(model->count(), 4);
9105 QVERIFY(compareObjectModel(listview, model));
9106
9107 QQuickItem *item1 = new QQuickItem(listview);
9108 item1->setSize(QSizeF(20, 20));
9109 model->insert(index: 0, object: item1);
9110 QCOMPARE(model->count(), 5);
9111 QVERIFY(compareObjectModel(listview, model));
9112
9113 model->move(from: 1, to: 2, n: 3);
9114 QVERIFY(compareObjectModel(listview, model));
9115
9116 model->remove(index: 2, n: 2);
9117 QCOMPARE(model->count(), 3);
9118 QVERIFY(compareObjectModel(listview, model));
9119
9120 model->clear();
9121 QCOMPARE(model->count(), 0);
9122 QCOMPARE(listview->count(), 0);
9123}
9124
9125void tst_QQuickListView::contentHeightWithDelayRemove_data()
9126{
9127 QTest::addColumn<bool>(name: "useDelayRemove");
9128 QTest::addColumn<QByteArray>(name: "removeFunc");
9129 QTest::addColumn<int>(name: "countDelta");
9130 QTest::addColumn<qreal>(name: "contentHeightDelta");
9131
9132 QTest::newRow(dataTag: "remove without delayRemove")
9133 << false
9134 << QByteArray("takeOne")
9135 << -1
9136 << qreal(-1 * 100.0);
9137
9138 QTest::newRow(dataTag: "remove with delayRemove")
9139 << true
9140 << QByteArray("takeOne")
9141 << -1
9142 << qreal(-1 * 100.0);
9143
9144 QTest::newRow(dataTag: "remove with multiple delayRemove")
9145 << true
9146 << QByteArray("takeThree")
9147 << -3
9148 << qreal(-3 * 100.0);
9149
9150 QTest::newRow(dataTag: "clear with delayRemove")
9151 << true
9152 << QByteArray("takeAll")
9153 << -5
9154 << qreal(-5 * 100.0);
9155}
9156
9157void tst_QQuickListView::contentHeightWithDelayRemove()
9158{
9159 QFETCH(bool, useDelayRemove);
9160 QFETCH(QByteArray, removeFunc);
9161 QFETCH(int, countDelta);
9162 QFETCH(qreal, contentHeightDelta);
9163
9164 QScopedPointer<QQuickView> window(createView());
9165 window->setSource(testFileUrl(fileName: "contentHeightWithDelayRemove.qml"));
9166 window->show();
9167 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9168
9169 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
9170 QTRY_VERIFY(listview != nullptr);
9171
9172 const int initialCount(listview->count());
9173 const int eventualCount(initialCount + countDelta);
9174
9175 const qreal initialContentHeight(listview->contentHeight());
9176 const int eventualContentHeight(qRound(d: initialContentHeight + contentHeightDelta));
9177
9178 listview->setProperty(name: "useDelayRemove", value: useDelayRemove);
9179 QMetaObject::invokeMethod(obj: window->rootObject(), member: removeFunc.constData());
9180 QTest::qWait(ms: 50);
9181 QCOMPARE(listview->count(), eventualCount);
9182
9183 if (useDelayRemove) {
9184 QCOMPARE(qRound(listview->contentHeight()), qRound(initialContentHeight));
9185 QTRY_COMPARE(qRound(listview->contentHeight()), eventualContentHeight);
9186 } else {
9187 QCOMPARE(qRound(listview->contentHeight()), eventualContentHeight);
9188 }
9189}
9190
9191void tst_QQuickListView::QTBUG_48044_currentItemNotVisibleAfterTransition()
9192{
9193 QScopedPointer<QQuickView> window(createView());
9194 window->setSource(testFileUrl(fileName: "qtbug48044.qml"));
9195 window->show();
9196 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9197
9198 QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
9199 QTRY_VERIFY(listview != nullptr);
9200
9201 // Expand 2nd header
9202 listview->setProperty(name: "transitionsDone", value: QVariant(false));
9203 QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(window->width() / 2, 75));
9204 QTRY_VERIFY(listview->property("transitionsDone").toBool());
9205
9206 // Flick listview to the bottom
9207 flick(window: window.data(), from: QPoint(window->width() / 2, 400), to: QPoint(window->width() / 2, 0), duration: 100);
9208 QTRY_VERIFY(!listview->isMoving());
9209
9210 // Expand 3rd header
9211 listview->setProperty(name: "transitionsDone", value: QVariant(false));
9212 QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(window->width() / 2, window->height() - 25));
9213 QTRY_VERIFY(listview->property("transitionsDone").toBool());
9214
9215 // Check current item is what we expect
9216 QCOMPARE(listview->currentIndex(), 2);
9217 QQuickItem *currentItem = listview->currentItem();
9218 QVERIFY(currentItem);
9219 QVERIFY(currentItem->isVisible());
9220
9221 // This is the actual test
9222 QQuickItemPrivate *currentPriv = QQuickItemPrivate::get(item: currentItem);
9223 QVERIFY(!currentPriv->culled);
9224}
9225
9226void tst_QQuickListView::keyNavigationEnabled()
9227{
9228 QScopedPointer<QQuickView> window(createView());
9229 window->setSource(testFileUrl(fileName: "keyNavigationEnabled.qml"));
9230 window->show();
9231 window->requestActivate();
9232 QVERIFY(QTest::qWaitForWindowActive(window.data()));
9233
9234 QQuickListView *listView = qobject_cast<QQuickListView *>(object: window->rootObject());
9235 QVERIFY(listView);
9236 QCOMPARE(listView->isKeyNavigationEnabled(), true);
9237
9238 listView->setFocus(true);
9239 QVERIFY(listView->hasActiveFocus());
9240
9241 listView->setHighlightMoveDuration(0);
9242
9243 // If keyNavigationEnabled is not explicitly set to true, respect the original behavior
9244 // of disabling both mouse and keyboard interaction.
9245 QSignalSpy enabledSpy(listView, SIGNAL(keyNavigationEnabledChanged()));
9246 listView->setInteractive(false);
9247 QCOMPARE(enabledSpy.count(), 1);
9248 QCOMPARE(listView->isKeyNavigationEnabled(), false);
9249
9250 flick(window: window.data(), from: QPoint(200, 200), to: QPoint(200, 50), duration: 100);
9251 QVERIFY(!listView->isMoving());
9252 QCOMPARE(listView->contentY(), 0.0);
9253 QCOMPARE(listView->currentIndex(), 0);
9254
9255 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
9256 QCOMPARE(listView->currentIndex(), 0);
9257
9258 // Check that isKeyNavigationEnabled implicitly follows the value of interactive.
9259 listView->setInteractive(true);
9260 QCOMPARE(enabledSpy.count(), 2);
9261 QCOMPARE(listView->isKeyNavigationEnabled(), true);
9262
9263 // Change it back again for the next check.
9264 listView->setInteractive(false);
9265 QCOMPARE(enabledSpy.count(), 3);
9266 QCOMPARE(listView->isKeyNavigationEnabled(), false);
9267
9268 // Setting keyNavigationEnabled to true shouldn't enable mouse interaction.
9269 listView->setKeyNavigationEnabled(true);
9270 QCOMPARE(enabledSpy.count(), 4);
9271 flick(window: window.data(), from: QPoint(200, 200), to: QPoint(200, 50), duration: 100);
9272 QVERIFY(!listView->isMoving());
9273 QCOMPARE(listView->contentY(), 0.0);
9274 QCOMPARE(listView->currentIndex(), 0);
9275
9276 // Should now work.
9277 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
9278 QCOMPARE(listView->currentIndex(), 1);
9279 // contentY won't change for one index change in a view this high.
9280
9281 // Changing interactive now shouldn't result in keyNavigationEnabled changing,
9282 // since we broke the "binding".
9283 listView->setInteractive(true);
9284 QCOMPARE(enabledSpy.count(), 4);
9285
9286 // Keyboard interaction shouldn't work now.
9287 listView->setKeyNavigationEnabled(false);
9288 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
9289 QCOMPARE(listView->currentIndex(), 1);
9290}
9291
9292void tst_QQuickListView::QTBUG_61269_appendDuringScrollDown_data()
9293{
9294 QTest::addColumn<QQuickListView::SnapMode>(name: "snapMode");
9295
9296 QTest::newRow(dataTag: "NoSnap") << QQuickListView::NoSnap;
9297 QTest::newRow(dataTag: "SnapToItem") << QQuickListView::SnapToItem;
9298 QTest::newRow(dataTag: "SnapOneItem") << QQuickListView::SnapOneItem;
9299}
9300
9301void tst_QQuickListView::QTBUG_61269_appendDuringScrollDown() // AKA QTBUG-62864
9302{
9303 QFETCH(QQuickListView::SnapMode, snapMode);
9304
9305 QScopedPointer<QQuickView> window(createView());
9306 window->setSource(testFileUrl(fileName: "appendDuringScrollDown.qml"));
9307 window->show();
9308 window->requestActivate();
9309 QVERIFY(QTest::qWaitForWindowActive(window.data()));
9310
9311 QQuickListView *listView = qobject_cast<QQuickListView *>(object: window->rootObject());
9312 listView->setSnapMode(snapMode);
9313 QQuickItem *highlightItem = listView->highlightItem();
9314 QVERIFY(listView);
9315 QCOMPARE(listView->isKeyNavigationEnabled(), true);
9316 listView->setHighlightMoveVelocity(400);
9317 listView->setHighlightMoveDuration(-1); // let it animate
9318 listView->setFocus(true);
9319 QVERIFY(listView->hasActiveFocus());
9320 qreal highlightYLimit = listView->height() - highlightItem->height(); // should be 200
9321
9322 for (int i = 1; i < 15; ++i) {
9323 QTest::keyClick(window: window.data(), key: Qt::Key_Down);
9324
9325 // Wait for the highlight movement animation to finish.
9326 QTRY_COMPARE(highlightItem->y(), 40.0 * i);
9327
9328 // As we scroll down, the QML will append rows to its own model.
9329 // Make sure the highlighted row and highlight item stay within the view.
9330 // In QTBUG-62864 and QTBUG-61269, it would go off the bottom.
9331 QVERIFY(highlightItem->y() - listView->contentY() <= highlightYLimit);
9332 }
9333}
9334
9335void tst_QQuickListView::QTBUG_48870_fastModelUpdates()
9336{
9337 StressTestModel model;
9338
9339 QScopedPointer<QQuickView> window(createView());
9340 QQmlContext *ctxt = window->rootContext();
9341 ctxt->setContextProperty("testModel", &model);
9342
9343 window->setSource(testFileUrl(fileName: "qtbug48870.qml"));
9344 window->show();
9345 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9346
9347 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
9348 QTRY_VERIFY(listview != nullptr);
9349
9350 QQuickItemViewPrivate *priv = QQuickItemViewPrivate::get(o: listview);
9351 bool nonUnique;
9352 FxViewItem *item = nullptr;
9353 int expectedIdx;
9354 QVERIFY(testVisibleItems(priv, &nonUnique, &item, &expectedIdx));
9355
9356 for (int i = 0; i < 10; i++) {
9357 QTest::qWait(ms: 100);
9358 QVERIFY2(testVisibleItems(priv, &nonUnique, &item, &expectedIdx),
9359 qPrintable(!item ? QString("Unexpected null item")
9360 : nonUnique ? QString("Non-unique item at %1 and %2").arg(item->index).arg(expectedIdx)
9361 : QString("Found index %1, expected index is %3").arg(item->index).arg(expectedIdx)));
9362 if (i % 3 != 0) {
9363 if (i & 1)
9364 flick(window: window.data(), from: QPoint(100, 200), to: QPoint(100, 0), duration: 100);
9365 else
9366 flick(window: window.data(), from: QPoint(100, 200), to: QPoint(100, 400), duration: 100);
9367 }
9368 }
9369}
9370
9371// infinite loop in overlay header positioning due to undesired rounding in QQuickFlickablePrivate::fixup()
9372void tst_QQuickListView::QTBUG_50105()
9373{
9374 QQmlEngine engine;
9375 QQmlComponent component(&engine);
9376 component.loadUrl(url: testFileUrl(fileName: "qtbug50105.qml"));
9377
9378 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
9379 QVERIFY(window.data());
9380 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9381}
9382
9383void tst_QQuickListView::QTBUG_50097_stickyHeader_positionViewAtIndex()
9384{
9385 QScopedPointer<QQuickView> window(createView());
9386 window->setSource(testFileUrl(fileName: "qtbug50097.qml"));
9387 window->show();
9388 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9389
9390 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
9391 QVERIFY(listview != nullptr);
9392 QTRY_COMPARE(listview->contentY(), -100.0); // the header size, since the header is overlaid
9393 listview->setProperty(name: "currentPage", value: 2);
9394 QTRY_COMPARE(listview->contentY(), 400.0); // a full page of items down, sans the original negative header position
9395 listview->setProperty(name: "currentPage", value: 1);
9396 QTRY_COMPARE(listview->contentY(), -100.0); // back to the same position: header visible, items not under the header.
9397}
9398
9399void tst_QQuickListView::QTBUG_63974_stickyHeader_positionViewAtIndex_Contain()
9400{
9401 QScopedPointer<QQuickView> window(createView());
9402 window->setSource(testFileUrl(fileName: "qtbug63974.qml"));
9403 window->show();
9404 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9405
9406 QQuickListView *listview = qobject_cast<QQuickListView*>(object: window->rootObject());
9407 QVERIFY(listview != nullptr);
9408
9409 const qreal headerSize = 20;
9410 const qreal footerSize = 20;
9411 const qreal itemSize = 20;
9412 const int itemCount = 30;
9413 const qreal contentHeight = itemCount * itemSize;
9414
9415 const qreal initialY = listview->contentY();
9416 const qreal endPosition = contentHeight + footerSize - listview->height();
9417
9418 QVERIFY(qFuzzyCompare(initialY, -headerSize));
9419
9420 listview->positionViewAtIndex(index: itemCount - 1, mode: QQuickListView::Contain);
9421 QTRY_COMPARE(listview->contentY(), endPosition);
9422
9423 listview->positionViewAtIndex(index: 0, mode: QQuickListView::Contain);
9424 QTRY_COMPARE(listview->contentY(), -headerSize);
9425
9426 listview->positionViewAtIndex(index: itemCount - 1, mode: QQuickListView::Visible);
9427 QTRY_COMPARE(listview->contentY(), endPosition);
9428
9429 listview->positionViewAtIndex(index: 0, mode: QQuickListView::Visible);
9430 QTRY_COMPARE(listview->contentY(), -headerSize);
9431
9432 listview->positionViewAtIndex(index: itemCount - 1, mode: QQuickListView::SnapPosition);
9433 QTRY_COMPARE(listview->contentY(), endPosition);
9434
9435 listview->positionViewAtIndex(index: 0, mode: QQuickListView::SnapPosition);
9436 QTRY_COMPARE(listview->contentY(), -headerSize);
9437}
9438
9439void tst_QQuickListView::QTBUG_66163_setModelViewPortSizeChange()
9440{
9441 QScopedPointer<QQuickView> window(createView());
9442 QQmlComponent comp(window->engine());
9443 comp.setData(R"(
9444 import QtQuick 2.0
9445
9446 Item {
9447 id: root
9448 width: 400
9449 height: 400
9450
9451 ListView {
9452 id: view
9453 objectName: "view"
9454 anchors.fill: parent
9455
9456 model: 4
9457 highlightRangeMode: ListView.StrictlyEnforceRange
9458
9459 delegate: Rectangle {
9460 color: index % 2 ? "green" : "orange"
9461 width: parent.width
9462 height: 50
9463 }
9464
9465 populate: Transition {
9466 SequentialAnimation {
9467 NumberAnimation { property: "y"; from: 100; duration: 1000 }
9468 }
9469 }
9470 }
9471 }
9472 )", baseUrl: QUrl("testData"));
9473 auto root {qobject_cast<QQuickItem*>(object: comp.create())};
9474 QVERIFY(root);
9475 window->setContent(url: QUrl(), component: &comp, item: root);
9476 window->show();
9477 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9478 auto view = root->findChild<QQuickListView *>(aName: "view");
9479 QVERIFY(view);
9480 QVERIFY(QQuickTest::qWaitForItemPolished(view));
9481 QSignalSpy spy(view, &QQuickListView::contentYChanged);
9482 auto transition = view->property(name: "populate").value<QQuickTransition*>();
9483 QVERIFY(transition);
9484 QQmlProperty model(view, "model");
9485 QVERIFY(model.isValid());
9486 model.write(5);
9487 // Animations inside a Transition do not emit a finished signal
9488 // so we cannot wait for them in that way
9489 QTest::qWait(ms: 1100); // animation takes 1000ms, + 10% extra delay
9490 /* the viewport should not have changed, thus there should not have
9491 been any contentYChanged signal*/
9492 QCOMPARE(spy.count(), 0);
9493}
9494
9495void tst_QQuickListView::itemFiltered()
9496{
9497 QStringListModel model(QStringList() << "one" << "two" << "three" << "four" << "five" << "six");
9498 QSortFilterProxyModel proxy1;
9499 proxy1.setSourceModel(&model);
9500 proxy1.setSortRole(Qt::DisplayRole);
9501 proxy1.setDynamicSortFilter(true);
9502 proxy1.sort(column: 0);
9503
9504 QSortFilterProxyModel proxy2;
9505 proxy2.setSourceModel(&proxy1);
9506 proxy2.setFilterRole(Qt::DisplayRole);
9507 proxy2.setFilterRegExp("^[^ ]*$");
9508 proxy2.setDynamicSortFilter(true);
9509
9510 QScopedPointer<QQuickView> window(createView());
9511 window->engine()->rootContext()->setContextProperty("_model", &proxy2);
9512 QQmlComponent component(window->engine());
9513 component.setData("import QtQuick 2.4; ListView { "
9514 "anchors.fill: parent; model: _model; delegate: Text { width: parent.width;"
9515 "text: model.display; } }",
9516 baseUrl: QUrl());
9517 window->setContent(url: QUrl(), component: &component, item: component.create());
9518
9519 window->show();
9520 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9521
9522 // this should not crash
9523 model.setData(index: model.index(row: 2), QStringLiteral("modified three"), role: Qt::DisplayRole);
9524}
9525
9526void tst_QQuickListView::releaseItems()
9527{
9528 QScopedPointer<QQuickView> view(createView());
9529 view->setSource(testFileUrl(fileName: "releaseItems.qml"));
9530
9531 QQuickListView *listview = qobject_cast<QQuickListView *>(object: view->rootObject());
9532 QVERIFY(listview);
9533
9534 // don't crash (QTBUG-61294)
9535 listview->setModel(123);
9536}
9537
9538void tst_QQuickListView::QTBUG_34576_velocityZero()
9539{
9540 QScopedPointer<QQuickView> window(new QQuickView(nullptr));
9541 window->setGeometry(posx: 0,posy: 0,w: 240,h: 320);
9542
9543 QString filename(testFile(fileName: "qtbug34576.qml"));
9544 window->setSource(QUrl::fromLocalFile(localfile: filename));
9545 window->show();
9546 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9547
9548 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
9549 QVERIFY(listview);
9550 QQuickItem *contentItem = listview->contentItem();
9551 QVERIFY(contentItem);
9552 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
9553
9554 QSignalSpy horizontalVelocitySpy(listview, SIGNAL(horizontalVelocityChanged()));
9555
9556 // currentIndex is initialized to 0
9557 QCOMPARE(listview->currentIndex(), 0);
9558
9559 // set currentIndex to last item currently visible item
9560 window->rootObject()->setProperty(name: "horizontalVelocityZeroCount", value: QVariant(0));
9561 listview->setCurrentIndex(2);
9562 QTRY_COMPARE(window->rootObject()->property("current").toInt(), 2);
9563 QCOMPARE(horizontalVelocitySpy.count(), 0);
9564 QCOMPARE(window->rootObject()->property("horizontalVelocityZeroCount").toInt(), 0);
9565
9566 QSignalSpy currentIndexChangedSpy(listview, SIGNAL(currentIndexChanged()));
9567
9568 // click button which increases currentIndex
9569 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(295,215));
9570 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(295,215));
9571
9572 // verify that currentIndexChanged is triggered
9573 QTRY_VERIFY(currentIndexChangedSpy.count() > 0);
9574
9575 // since we have set currentIndex to an item out of view, the listview will scroll
9576 QTRY_COMPARE(window->rootObject()->property("current").toInt(), 3);
9577 QTRY_VERIFY(horizontalVelocitySpy.count() > 0);
9578
9579 // velocity should be always > 0.0
9580 QTRY_COMPARE(window->rootObject()->property("horizontalVelocityZeroCount").toInt(), 0);
9581}
9582
9583void tst_QQuickListView::QTBUG_61537_modelChangesAsync()
9584{
9585 // The purpose of this test if to check that any model changes that happens
9586 // during start-up, while a loader higher up in the chain is still incubating
9587 // async, will not fail.
9588 QQuickView window;
9589 window.setGeometry(posx: 0,posy: 0,w: 640,h: 480);
9590
9591 QString filename(testFile(fileName: "qtbug61537_modelChangesAsync.qml"));
9592 window.setSource(QUrl::fromLocalFile(localfile: filename));
9593 window.show();
9594 QVERIFY(QTest::qWaitForWindowExposed(&window));
9595
9596 // The qml file will assign the listview to the 'listView' property once the
9597 // loader is ready with async incubation. So we need to wait for it.
9598 QObject *root = window.rootObject();
9599 QTRY_VERIFY(root->property("listView").value<QQuickListView *>());
9600 QQuickListView *listView = root->property(name: "listView").value<QQuickListView *>();
9601 QVERIFY(listView);
9602
9603 // Check that the number of delegates we expect to be visible in
9604 // the listview matches the number of items we find if we count.
9605 int reportedCount = listView->count();
9606 int actualCount = findItems<QQuickItem>(parent: listView, objectName: "delegate").count();
9607 QCOMPARE(reportedCount, actualCount);
9608}
9609
9610void tst_QQuickListView::addOnCompleted()
9611{
9612 QScopedPointer<QQuickView> window(createView());
9613 window->setSource(testFileUrl(fileName: "addoncompleted.qml"));
9614 window->show();
9615 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9616
9617 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "view");
9618 QTRY_VERIFY(listview != nullptr);
9619
9620 QQuickItem *contentItem = listview->contentItem();
9621 QTRY_VERIFY(contentItem != nullptr);
9622
9623 qreal y = -1;
9624 for (char name = 'a'; name <= 'j'; ++name) {
9625 for (int num = 9; num >= 0; --num) {
9626 const QString objName = QString::fromLatin1(str: "%1%2").arg(a: name).arg(a: num);
9627 QQuickItem *item = findItem<QQuickItem>(parent: contentItem, objectName: objName);
9628 if (!item) {
9629 QVERIFY(name >= 'd');
9630 y = 9999999;
9631 } else {
9632 const qreal newY = item->y();
9633 QVERIFY(newY != 9999999); // once we could not find an item, we shouldn' find any further ones
9634 QVERIFY2(newY > y, objName.toUtf8().constData());
9635 y = newY;
9636 }
9637 }
9638 }
9639}
9640
9641void tst_QQuickListView::setPositionOnLayout()
9642{
9643 // Make sure we don't trigger a crash by removing items during layout from setPosition().
9644 QScopedPointer<QQuickView> window(createView());
9645 window->setSource(testFileUrl(fileName: "setpositiononlayout.qml"));
9646 window->show();
9647 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9648 window->requestActivate();
9649 QVERIFY(QTest::qWaitForWindowActive(window.data()));
9650 for (int i = 0; i < 1000; ++i) {
9651 QTest::keyPress(window: window.data(), key: Qt::Key_Down);
9652 QTest::qWait(ms: 1);
9653 QTest::keyRelease(window: window.data(), key: Qt::Key_Down);
9654 }
9655}
9656
9657void tst_QQuickListView::useDelegateChooserWithoutDefault()
9658{
9659 // Check that the application doesn't crash
9660 // if the delegate chooser doesn't cover all cells
9661 QScopedPointer<QQuickView> window(createView());
9662 window->setSource(testFileUrl(fileName: "usechooserwithoutdefault.qml"));
9663 window->show();
9664};
9665
9666void tst_QQuickListView::touchCancel() // QTBUG-74679
9667{
9668 QScopedPointer<QQuickView> window(createView());
9669 window->setSource(testFileUrl(fileName: "delegateWithMouseArea.qml"));
9670 window->show();
9671 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9672
9673 QQuickListView *listview = qobject_cast<QQuickListView *>(object: window->rootObject());
9674 QVERIFY(listview);
9675 QQuickMouseArea *mouseArea = listview->currentItem()->findChild<QQuickMouseArea *>();
9676 QVERIFY(mouseArea);
9677
9678 QPoint p1(300, 300);
9679 QTest::touchEvent(window: window.data(), device: touchDevice).press(touchId: 0, pt: p1, window: window.data());
9680 QQuickTouchUtils::flush(window: window.data());
9681 QTRY_VERIFY(mouseArea->pressed());
9682 // and because Flickable filtered it, QQuickFlickablePrivate::pressed
9683 // should be true, but it's not easily tested here
9684
9685 QTouchEvent cancelEvent(QEvent::TouchCancel);
9686 cancelEvent.setDevice(touchDevice);
9687 QCoreApplication::sendEvent(receiver: window.data(), event: &cancelEvent);
9688 // now QQuickWindowPrivate::sendUngrabEvent() will be called, Flickable will filter it,
9689 // QQuickFlickablePrivate::pressed will be set to false, and that will allow setCurrentIndex() to make it move
9690 QQuickTouchUtils::flush(window: window.data());
9691
9692 listview->setCurrentIndex(1);
9693 // ensure that it actually moves (animates) to the second delegate
9694 QTRY_COMPARE(listview->contentY(), 500.0);
9695}
9696
9697void tst_QQuickListView::resizeAfterComponentComplete() // QTBUG-76487
9698{
9699 QScopedPointer<QQuickView> window(createView());
9700 window->setSource(testFileUrl(fileName: "resizeAfterComponentComplete.qml"));
9701 window->resize(w: 640, h: 480);
9702 window->show();
9703 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9704
9705 QObject *listView = window->rootObject();
9706 QVERIFY(listView);
9707
9708 QObject *lastItem = qvariant_cast<QObject *>(v: listView->property(name: "lastItem"));
9709 QTRY_COMPARE(lastItem->property("y").toInt(), 9 * lastItem->property("height").toInt());
9710}
9711
9712class Animal
9713{
9714public:
9715 Animal(const int cost, const QString &name) {m_name = name; m_cost = cost;}
9716
9717 int cost() const {return m_cost;}
9718 QString name() const {return m_name;}
9719
9720 QString m_name;
9721 int m_cost;
9722};
9723
9724class FruitModel : public QAbstractListModel
9725{
9726 Q_OBJECT
9727public:
9728 enum AnimalRoles {
9729 NameRole = Qt::UserRole + 1,
9730 CostRole
9731 };
9732
9733 FruitModel(QObject* = nullptr) {
9734 m_animals.push_back(t: Animal {4, QLatin1String("Melon")});
9735 m_animals.push_back(t: Animal {5, QLatin1String("Cherry")});
9736 }
9737
9738 int rowCount(const QModelIndex & = QModelIndex()) const override {return m_animals.count();}
9739
9740 QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override {
9741 if (!checkIndex(index))
9742 return {};
9743 const Animal &animal = m_animals[index.row()];
9744 if (role == CostRole)
9745 return animal.cost();
9746 else if (role == NameRole)
9747 return animal.name();
9748 return QVariant();
9749 }
9750
9751protected:
9752 QHash<int, QByteArray> roleNames() const override {
9753 QHash<int, QByteArray> roles;
9754 roles[CostRole] = "cost";
9755 roles[NameRole] = "name";
9756 return roles;
9757 }
9758private:
9759 QList<Animal> m_animals;
9760};
9761
9762void tst_QQuickListView::delegateWithRequiredProperties()
9763{
9764 FruitModel myModel;
9765 qmlRegisterSingletonInstance(uri: "Qt.fruit", versionMajor: 1, versionMinor: 0, typeName: "FruitModelCpp", cppObject: &myModel);
9766 {
9767 // ListModel
9768 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "Apple2");
9769 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "Orange3");
9770 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "Banana1");
9771 QScopedPointer<QQuickView> window(createView());
9772 window->setInitialProperties({{QLatin1String("useCpp"), false}});
9773 window->setSource(testFileUrl(fileName: "delegatesWithRequiredProperties.qml"));
9774 window->show();
9775 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9776
9777 QObject *listView = window->rootObject();
9778 QVERIFY(listView);
9779 }
9780 {
9781 // C++ model
9782 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "Melon4");
9783 QTest::ignoreMessage(type: QtMsgType::QtDebugMsg, message: "Cherry5");
9784 QScopedPointer<QQuickView> window(createView());
9785 window->setInitialProperties({{QLatin1String("useCpp"), true}});
9786 window->setSource(testFileUrl(fileName: "delegatesWithRequiredProperties.qml"));
9787
9788 window->show();
9789 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9790
9791 QObject *listView = window->rootObject();
9792 QVERIFY(listView);
9793 }
9794}
9795
9796void tst_QQuickListView::reuse_reuseIsOffByDefault()
9797{
9798 // Check that delegate recycling is off by default. The reason is that
9799 // ListView needs to be backwards compatible with legacy applications. And
9800 // when using delegate recycling, there are certain differences, like that
9801 // a delegates Component.onCompleted will just be called the first time the
9802 // item is created, and not when it's reused.
9803 QScopedPointer<QQuickView> window(createView());
9804 window->setSource(testFileUrl(fileName: "listviewtest.qml"));
9805 window->resize(w: 640, h: 480);
9806 window->show();
9807 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9808
9809 QQuickListView *listView = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
9810 QVERIFY(listView != nullptr);
9811 QVERIFY(!listView->reuseItems());
9812}
9813
9814void tst_QQuickListView::reuse_checkThatItemsAreReused()
9815{
9816 // Flick up and down one page of items. Check that this results in the
9817 // delegate items being reused once.
9818 // Note that this is slightly different from tableview, which will reuse the items
9819 // twice during a similar down-then-up flick. The reason is that listview fills up
9820 // free space in the view with items _before_ it release old items that have been
9821 // flicked out. But changing this will break other auto tests (and perhaps legacy
9822 // apps), so we have chosen to stick with this behavior for now.
9823 QScopedPointer<QQuickView> window(createView());
9824
9825 ReuseModel model(100);
9826 QQmlContext *ctxt = window->rootContext();
9827 ctxt->setContextProperty("reuseModel", &model);
9828
9829 window->setSource(testFileUrl(fileName: "reusedelegateitems.qml"));
9830 window->resize(w: 640, h: 480);
9831 window->show();
9832 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9833 QVERIFY(window->rootObject() != nullptr);
9834
9835 QQuickListView *listView = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
9836 QTRY_VERIFY(listView != nullptr);
9837 const auto itemView_d = QQuickItemViewPrivate::get(o: listView);
9838
9839 QVERIFY(listView->reuseItems());
9840
9841 auto items = findItems<QQuickItem>(parent: listView, objectName: "delegate");
9842 const int initialItemCount = items.count();
9843 QVERIFY(initialItemCount > 0);
9844
9845 // Sanity check that the size of the initial list of items match the count we tracked from QML
9846 QCOMPARE(listView->property("delegatesCreatedCount").toInt(), initialItemCount);
9847
9848 // Go through all the initial items and check that they have not been reused yet
9849 for (const auto item : qAsConst(t&: items))
9850 QCOMPARE(item->property("reusedCount").toInt(), 0);
9851
9852 // Flick one page down and count how many items we have created thus
9853 // far. We expect this number to be twice as high as the initial count
9854 // since we flicked one whole page.
9855 const qreal delegateHeight = items.at(i: 0)->height();
9856 const qreal flickDistance = (initialItemCount * delegateHeight) + 1;
9857 listView->setContentY(flickDistance);
9858 QVERIFY(QQuickTest::qWaitForItemPolished(listView));
9859 const int countAfterDownFlick = listView->property(name: "delegatesCreatedCount").toInt();
9860 QCOMPARE(countAfterDownFlick, initialItemCount * 2);
9861
9862 // Check that the reuse pool is now populated. We expect all initial items to be pooled,
9863 // except model index 0, which was never reused or released, since it's ListView.currentItem.
9864 const int poolSizeAfterDownFlick = itemView_d->model->poolSize();
9865 QCOMPARE(poolSizeAfterDownFlick, initialItemCount - 1);
9866
9867 // Go through all items and check that all model data inside the delegate
9868 // have values updated according to their model index. Since model roles
9869 // like 'display' are injected into the context in a special way by the
9870 // QML model classes, we need to catch it through a binding instead (which is
9871 // OK, since then we can also check that bindings are updated when reused).
9872 items = findItems<QQuickItem>(parent: listView, objectName: "delegate");
9873 for (const auto item : qAsConst(t&: items)) {
9874 const QString display = item->property(name: "displayBinding").toString();
9875 const int modelIndex = item->property(name: "modelIndex").toInt();
9876 QVERIFY(modelIndex >= initialItemCount);
9877 QCOMPARE(display, model.displayStringForRow(modelIndex));
9878 }
9879
9880 // Flick one page up. This time there shouldn't be any new items created, so
9881 // delegatesCreatedCount should remain unchanged. But while we reuse all the items
9882 // in the pool during the flick, we also fill it up again with all the items that
9883 // were inside the page that was flicked out.
9884 listView->setContentY(0);
9885 QVERIFY(QQuickTest::qWaitForItemPolished(listView));
9886 const int countAfterUpFlick = listView->property(name: "delegatesCreatedCount").toInt();
9887 const int poolSizeAfterUpFlick = itemView_d->model->poolSize();
9888 QCOMPARE(countAfterUpFlick, countAfterDownFlick);
9889 QCOMPARE(poolSizeAfterUpFlick, initialItemCount);
9890
9891 // Go through all items and check that they have been reused exactly once
9892 // (except for ListView.currentItem, which was never released).
9893 const auto listViewCurrentItem = listView->currentItem();
9894 items = findItems<QQuickItem>(parent: listView, objectName: "delegate");
9895 for (const auto item : qAsConst(t&: items)) {
9896 const int reusedCount = item->property(name: "reusedCount").toInt();
9897 if (item == listViewCurrentItem)
9898 QCOMPARE(reusedCount, 0);
9899 else
9900 QCOMPARE(reusedCount, 1);
9901 }
9902
9903 // Go through all items again and check that all model data inside the delegate
9904 // have correct values now that they have been reused.
9905 items = findItems<QQuickItem>(parent: listView, objectName: "delegate");
9906 for (const auto item : qAsConst(t&: items)) {
9907 const QString display = item->property(name: "displayBinding").toString();
9908 const int modelIndex = item->property(name: "modelIndex").toInt();
9909 QVERIFY(modelIndex < initialItemCount);
9910 QCOMPARE(display, model.displayStringForRow(modelIndex));
9911 }
9912}
9913
9914void tst_QQuickListView::dragOverFloatingHeaderOrFooter() // QTBUG-74046
9915{
9916 QQuickView *window = getView();
9917 QQuickViewTestUtil::moveMouseAway(window);
9918 window->setSource(testFileUrl(fileName: "qtbug63974.qml"));
9919 window->show();
9920 QVERIFY(QTest::qWaitForWindowExposed(window));
9921
9922 QQuickListView *listview = qmlobject_cast<QQuickListView *>(object: window->rootObject());
9923 QVERIFY(listview);
9924 QCOMPARE(listview->contentY(), -20);
9925
9926 // Drag downwards from the header: the list shouldn't move
9927 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,10));
9928 for (int i = 0; i < 10; ++i)
9929 QTest::mouseMove(window, pos: QPoint(10, 10 + i * 10));
9930 QCOMPARE(listview->isMoving(), false);
9931 QCOMPARE(listview->contentY(), -20);
9932 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier);
9933
9934 // Drag upwards from the footer: the list shouldn't move
9935 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,190));
9936 for (int i = 0; i < 10; ++i)
9937 QTest::mouseMove(window, pos: QPoint(10, 190 - i * 10));
9938 QCOMPARE(listview->isMoving(), false);
9939 QCOMPARE(listview->contentY(), -20);
9940 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier);
9941
9942 // Drag upwards from the middle: the list should move
9943 QTest::mousePress(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,100));
9944 for (int i = 0; i < 10 && listview->contentY() == -20; ++i)
9945 QTest::mouseMove(window, pos: QPoint(10, 100 - i * 10));
9946 QVERIFY(listview->isMoving());
9947 QVERIFY(listview->contentY() > -20);
9948 QTest::mouseRelease(window, button: Qt::LeftButton, stateKey: Qt::NoModifier);
9949
9950 releaseView(view: window);
9951}
9952
9953void tst_QQuickListView::moveObjectModelItemToAnotherObjectModel()
9954{
9955 QScopedPointer<QQuickView> window(createView());
9956 window->setSource(testFileUrl(fileName: "moveObjectModelItemToAnotherObjectModel.qml"));
9957 QCOMPARE(window->status(), QQuickView::Ready);
9958 window->show();
9959 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9960
9961 QObject *root = window->rootObject();
9962 QVERIFY(root);
9963
9964 const QQuickListView *listView1 = root->property(name: "listView1").value<QQuickListView*>();
9965 QVERIFY(listView1);
9966
9967 const QQuickListView *listView2 = root->property(name: "listView2").value<QQuickListView*>();
9968 QVERIFY(listView2);
9969
9970 const QQuickItem *redRect = listView1->itemAtIndex(index: 0);
9971 QVERIFY(redRect);
9972 QCOMPARE(redRect->objectName(), QString::fromLatin1("redRect"));
9973
9974 QVERIFY(QMetaObject::invokeMethod(root, "moveRedRectToModel2"));
9975 QVERIFY(QQuickTest::qIsPolishScheduled(listView2));
9976 QVERIFY(QQuickTest::qWaitForItemPolished(listView2));
9977 QVERIFY(redRect->isVisible());
9978 QVERIFY(!QQuickItemPrivate::get(redRect)->culled);
9979
9980 QVERIFY(QMetaObject::invokeMethod(root, "moveRedRectToModel1"));
9981 QVERIFY(QQuickTest::qIsPolishScheduled(listView1));
9982 QVERIFY(QQuickTest::qWaitForItemPolished(listView1));
9983 QVERIFY(redRect->isVisible());
9984 QVERIFY(!QQuickItemPrivate::get(redRect)->culled);
9985}
9986
9987void tst_QQuickListView::changeModelAndDestroyTheOldOne() // QTBUG-80203
9988{
9989 QScopedPointer<QQuickView> window(createView());
9990 window->setSource(testFileUrl(fileName: "changeModelAndDestroyTheOldOne.qml"));
9991 window->resize(w: 640, h: 480);
9992 window->show();
9993 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
9994
9995 QQuickItem *root = window->rootObject();
9996 QVERIFY(root);
9997
9998 QVERIFY(QQuickTest::qWaitForItemPolished(root));
9999 // no crash
10000}
10001
10002void tst_QQuickListView::objectModelCulling()
10003{
10004 QScopedPointer<QQuickView> window(createView());
10005 window->setSource(testFileUrl(fileName: "objectModelCulling.qml"));
10006 window->resize(w: 640, h: 480);
10007 window->show();
10008 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
10009 QObject *root = window->rootObject();
10010 QVERIFY(root);
10011 auto listView = root->findChild<QQuickListView *>(aName: "lv");
10012 QVERIFY(listView);
10013 auto model1 = root->findChild<QObject *>(aName: "model1");
10014 QVERIFY(model1);
10015 auto model2 = root->findChild<QObject *>(aName: "model2");
10016 QVERIFY(model2);
10017 auto redRect = root->findChild<QQuickItem *>(aName: "redRect");
10018 QVERIFY(redRect);
10019 auto redRectPriv = QQuickItemPrivate::get(item: redRect);
10020 listView->setModel(QVariant::fromValue(value: model1));
10021 QVERIFY(!redRectPriv->culled);
10022 listView->setModel(QVariant::fromValue(value: model2));
10023 QTRY_VERIFY(redRectPriv->culled);
10024}
10025
10026class DataObject : public QObject
10027{
10028 Q_OBJECT
10029 Q_PROPERTY(QString name READ name CONSTANT)
10030 Q_PROPERTY(QString color READ color CONSTANT)
10031
10032public:
10033 DataObject(QObject *parent = nullptr) : QObject(parent) {}
10034 DataObject(const QString &name, const QString &color, QObject *parent = nullptr)
10035 : QObject(parent), m_name(name), m_color(color) {}
10036
10037 QString name() const { return m_name; }
10038 QString color() const { return m_color; }
10039
10040private:
10041 QString m_name;
10042 QString m_color;
10043};
10044
10045void tst_QQuickListView::requiredObjectListModel()
10046{
10047 QList<QObject *> dataList = {
10048 new DataObject("Item 1", "red", this),
10049 new DataObject("Item 2", "green", this),
10050 new DataObject("Item 3", "blue", this),
10051 new DataObject("Item 4", "yellow", this)
10052 };
10053
10054 const auto deleter = qScopeGuard(f: [&](){ qDeleteAll(c: dataList); });
10055 Q_UNUSED(deleter);
10056
10057 QQuickView view;
10058 view.setInitialProperties({{ "model", QVariant::fromValue(value: dataList) }});
10059 view.setSource(testFileUrl(fileName: "requiredObjectListModel.qml"));
10060 view.show();
10061
10062 QVERIFY(QTest::qWaitForWindowExposed(&view));
10063
10064 const auto *root = qobject_cast<QQuickListView *>(object: view.rootObject());
10065 QVERIFY(root);
10066
10067 QCOMPARE(root->count(), dataList.count());
10068
10069 for (int i = 0, end = dataList.count(); i != end; ++i) {
10070 const auto *rect = qobject_cast<QQuickRectangle *>(object: root->itemAtIndex(index: i));
10071 QVERIFY(rect);
10072 const auto *data = qobject_cast<DataObject *>(object: dataList.at(i));
10073 QVERIFY(data);
10074
10075 QCOMPARE(rect->color(), QColor(data->color()));
10076 QCOMPARE(rect->property("name").toString(), data->name());
10077 }
10078}
10079
10080void tst_QQuickListView::clickHeaderAndFooterWhenClip() // QTBUG-85302
10081{
10082 QScopedPointer<QQuickView> window(createView());
10083 window->setSource(testFileUrl(fileName: "clickHeaderAndFooterWhenClip.qml"));
10084 window->resize(w: 640, h: 480);
10085 window->show();
10086
10087 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
10088 auto *root = window->rootObject();
10089 QVERIFY(root);
10090
10091 auto *header = root->findChild<QQuickItem *>(aName: "header");
10092 QVERIFY(header);
10093
10094 auto *footer = root->findChild<QQuickItem *>(aName: "footer");
10095 QVERIFY(footer);
10096
10097 QVERIFY(root->property("headerPressed").isValid() && root->property("headerPressed").canConvert<bool>() && !root->property("headerPressed").toBool());
10098 QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: header->mapToItem(item: root, point: QPoint(header->width() / 2, header->height() / 2)).toPoint());
10099 QVERIFY(root->property("headerPressed").toBool());
10100
10101 QVERIFY(root->property("footerPressed").isValid() && root->property("footerPressed").canConvert<bool>() && !root->property("footerPressed").toBool());
10102 QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: footer->mapToItem(item: root, point: QPoint(footer->width() / 2, footer->height() / 2)).toPoint());
10103 QVERIFY(root->property("footerPressed").toBool());
10104}
10105
10106void tst_QQuickListView::animatedDelegate()
10107{
10108 // QTBUG-86567: Should not crash
10109 QScopedPointer<QQuickView> window(createView());
10110 window->setSource(testFileUrl(fileName: "animatedDelegate.qml"));
10111 window->show();
10112 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
10113
10114 for (int i = 0; i < 100; ++i) {
10115 QMetaObject::invokeMethod(obj: window->rootObject(), member: "refreshModel");
10116 QTest::qWait(ms: 10);
10117 }
10118}
10119
10120static void dragListView(QWindow *window, QPoint *startPos, const QPoint &delta)
10121{
10122 auto drag_helper = [&](QWindow *window, QPoint *startPos, const QPoint &d) {
10123 QPoint pos = *startPos;
10124 const int dragDistance = d.manhattanLength();
10125 const QPoint unitVector(qBound(min: -1, val: d.x(), max: 1), qBound(min: -1, val: d.y(), max: 1));
10126 for (int i = 0; i < dragDistance; ++i) {
10127 QTest::mouseMove(window, pos);
10128 pos += unitVector;
10129 }
10130 // Move to the final position
10131 pos = *startPos + d;
10132 QTest::mouseMove(window, pos);
10133 *startPos = pos;
10134 };
10135
10136 if (delta.manhattanLength() == 0)
10137 return;
10138 const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
10139 const QPoint unitVector(qBound(min: -1, val: delta.x(), max: 1), qBound(min: -1, val: delta.y(), max: 1));
10140 // go just beyond the drag theshold
10141 drag_helper(window, startPos, unitVector * (dragThreshold + 1));
10142 drag_helper(window, startPos, unitVector);
10143
10144 // next drag will actually scroll the listview
10145 drag_helper(window, startPos, delta);
10146}
10147
10148void tst_QQuickListView::dragDelegateWithMouseArea()
10149{
10150 QFETCH(QQuickItemView::LayoutDirection, layoutDirection);
10151
10152 QScopedPointer<QQuickView> window(createView());
10153 QVERIFY(window);
10154 window->setSource(testFileUrl(fileName: "delegateWithMouseArea2.qml"));
10155 window->show();
10156 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
10157
10158 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
10159 QVERIFY(listview != nullptr);
10160
10161 const bool horizontal = layoutDirection < QQuickItemView::VerticalTopToBottom;
10162 listview->setOrientation(horizontal ? QQuickListView::Horizontal : QQuickListView::Vertical);
10163
10164 if (horizontal)
10165 listview->setLayoutDirection(static_cast<Qt::LayoutDirection>(layoutDirection));
10166 else
10167 listview->setVerticalLayoutDirection(static_cast<QQuickItemView::VerticalLayoutDirection>(layoutDirection));
10168
10169 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10170
10171 auto contentPosition = [&](QQuickListView *listview) {
10172 return (listview->orientation() == QQuickListView::Horizontal ? listview->contentX(): listview->contentY());
10173 };
10174
10175 qreal expectedContentPosition = contentPosition(listview);
10176 QPoint startPos = (QPointF(listview->width(), listview->height())/2).toPoint();
10177 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: startPos, delay: 200);
10178
10179 QPoint dragDelta(0, -10);
10180
10181 if (layoutDirection == QQuickItemView::RightToLeft || layoutDirection == QQuickItemView::VerticalBottomToTop)
10182 dragDelta = -dragDelta;
10183 expectedContentPosition -= dragDelta.y();
10184 if (horizontal)
10185 dragDelta = dragDelta.transposed();
10186
10187 dragListView(window: window.data(), startPos: &startPos, delta: dragDelta);
10188
10189 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: startPos, delay: 200); // Wait 200 ms before we release to avoid trigger a flick
10190
10191 // wait for the "fixup" animation to finish
10192 QVERIFY(QTest::qWaitFor([&]()
10193 { return !listview->isMoving();}
10194 ));
10195
10196 QCOMPARE(contentPosition(listview), expectedContentPosition);
10197}
10198
10199void tst_QQuickListView::dragDelegateWithMouseArea_data()
10200{
10201 QTest::addColumn<QQuickItemView::LayoutDirection>(name: "layoutDirection");
10202
10203 for (int layDir = QQuickItemView::LeftToRight; layDir <= (int)QQuickItemView::VerticalBottomToTop; layDir++) {
10204 const char *enumValueName = QMetaEnum::fromType<QQuickItemView::LayoutDirection>().valueToKey(value: layDir);
10205 QTest::newRow(dataTag: enumValueName) << static_cast<QQuickItemView::LayoutDirection>(layDir);
10206 }
10207}
10208
10209class SingletonModel : public QStringListModel
10210{
10211 Q_OBJECT
10212public:
10213 SingletonModel(QObject* parent = nullptr) : QStringListModel(parent) { }
10214};
10215
10216void tst_QQuickListView::singletonModelLifetime()
10217{
10218 // this does not really test any functionality of listview, but we do not have a good way
10219 // to unit test QQmlAdaptorModel in isolation.
10220 qmlRegisterSingletonType<SingletonModel>(uri: "test", versionMajor: 1, versionMinor: 0, typeName: "SingletonModel",
10221 callback: [](QQmlEngine* , QJSEngine*) -> QObject* { return new SingletonModel; });
10222
10223 QQmlApplicationEngine engine(testFile(fileName: "singletonModelLifetime.qml"));
10224 // needs event loop iteration for callLater to execute
10225 QTRY_VERIFY(engine.rootObjects().first()->property("alive").toBool());
10226}
10227
10228void tst_QQuickListView::QTBUG_92809()
10229{
10230 QScopedPointer<QQuickView> window(createView());
10231 QTRY_VERIFY(window);
10232 window->setSource(testFileUrl(fileName: "qtbug_92809.qml"));
10233 window->show();
10234 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
10235
10236 QQuickListView *listview = findItem<QQuickListView>(parent: window->rootObject(), objectName: "list");
10237 QTRY_VERIFY(listview != nullptr);
10238 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10239 listview->setCurrentIndex(1);
10240 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10241 listview->setCurrentIndex(2);
10242 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10243 listview->setCurrentIndex(3);
10244 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10245 QTest::qWait(ms: 500);
10246 listview->setCurrentIndex(10);
10247 QVERIFY(QQuickTest::qWaitForItemPolished(listview));
10248 QTest::qWait(ms: 500);
10249 int currentIndex = listview->currentIndex();
10250 QTRY_COMPARE(currentIndex, 9);
10251}
10252
10253QTEST_MAIN(tst_QQuickListView)
10254
10255#include "tst_qquicklistview.moc"
10256

source code of qtdeclarative/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp