1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtTest/QtTest>
43 #include <QtWidgets/QStringListModel>
44 #include <QtDeclarative/qsgview.h>
45 #include <QtDeclarative/qdeclarativeengine.h>
46 #include <QtDeclarative/qdeclarativecontext.h>
47 #include <QtDeclarative/qdeclarativeexpression.h>
48 #include <QtDeclarative/private/qsgitem_p.h>
49 #include <QtDeclarative/private/qsglistview_p.h>
50 #include <QtDeclarative/private/qsgtext_p.h>
51 #include <QtDeclarative/private/qsgvisualitemmodel_p.h>
52 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
53 #include <QtDeclarative/private/qlistmodelinterface_p.h>
54 #include <QtDeclarative/private/qdeclarativechangeset_p.h>
55 #include "../shared/util.h"
56 #include "incrementalmodel.h"
59 Q_DECLARE_METATYPE(Qt::LayoutDirection)
60 Q_DECLARE_METATYPE(QSGListView::Orientation)
62 class tst_QSGListView : public QObject
70 void cleanupTestCase();
71 // Test both QListModelInterface and QAbstractItemModel model types
72 void qListModelInterface_items();
73 void qAbstractItemModel_items();
75 void qListModelInterface_changed();
76 void qAbstractItemModel_changed();
78 void qListModelInterface_inserted();
79 void qListModelInterface_inserted_more();
80 void qListModelInterface_inserted_more_data();
81 void qAbstractItemModel_inserted();
82 void qAbstractItemModel_inserted_more();
83 void qAbstractItemModel_inserted_more_data();
85 void qListModelInterface_removed();
86 void qAbstractItemModel_removed();
88 void qListModelInterface_moved();
89 void qListModelInterface_moved_data();
90 void qAbstractItemModel_moved();
91 void qAbstractItemModel_moved_data();
93 void multipleChanges();
94 void multipleChanges_data();
96 void qListModelInterface_clear();
97 void qAbstractItemModel_clear();
99 void swapWithFirstItem();
101 void currentIndex_delayedItemCreation();
102 void currentIndex_delayedItemCreation_data();
104 void noCurrentIndex();
106 void enforceRange_withoutHighlight();
109 void sectionsPositioning();
110 void sectionsDelegate();
112 void positionViewAtIndex();
114 void propertyChanges();
115 void componentChanges();
117 void manualHighlight();
120 void header_delayItemCreation();
125 void resizeViewAndRepaint();
126 void sizeLessThan1();
128 void resizeDelegate();
129 void resizeFirstDelegate();
132 void incrementalModel();
136 void onRemove_data();
138 void test_mirroring();
140 void creationContext();
141 void snapToItem_data();
149 template <class T> void items();
150 template <class T> void changed();
151 template <class T> void inserted();
152 template <class T> void inserted_more();
153 template <class T> void removed(bool animated);
154 template <class T> void moved();
155 template <class T> void clear();
156 QSGView *createView();
157 void flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration);
158 QSGItem *findVisibleChild(QSGItem *parent, const QString &objectName);
160 T *findItem(QSGItem *parent, const QString &id, int index=-1);
162 QList<T*> findItems(QSGItem *parent, const QString &objectName);
163 void dumpTree(QSGItem *parent, int depth = 0);
165 void inserted_more_data();
169 void tst_QSGListView::initTestCase()
173 void tst_QSGListView::cleanupTestCase()
177 class TestObject : public QObject
181 Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
182 Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
183 Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
184 Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
187 TestObject(QObject *parent = 0)
188 : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
191 bool error() const { return mError; }
192 void setError(bool err) { mError = err; emit changedError(); }
194 bool animate() const { return mAnimate; }
195 void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
197 bool invalidHighlight() const { return mInvalidHighlight; }
198 void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
200 int cacheBuffer() const { return mCacheBuffer; }
201 void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
207 void changedCacheBuffer();
212 bool mInvalidHighlight;
217 void tst_qsglistview_move(int from, int to, int n, T *items)
220 // Only move forwards - flip if backwards moving
228 items->move(from, to);
232 typename T::ConstIterator it=items->begin(); it += from+n;
233 for (; i<to-from; ++i,++it)
234 replaced.append(*it);
236 it=items->begin(); it += from;
237 for (; i<n; ++i,++it)
238 replaced.append(*it);
239 typename T::ConstIterator f=replaced.begin();
240 typename T::Iterator t=items->begin(); t += from;
241 for (; f != replaced.end(); ++f, ++t)
246 class TestModel : public QListModelInterface
250 TestModel(QObject *parent = 0) : QListModelInterface(parent) {}
253 enum Roles { Name, Number };
255 QString name(int index) const { return list.at(index).first; }
256 QString number(int index) const { return list.at(index).second; }
258 int count() const { return list.count(); }
260 QList<int> roles() const { return QList<int>() << Name << Number; }
261 QString toString(int role) const {
272 QVariant data(int index, int role) const
275 return list.at(index).first;
277 return list.at(index).second;
280 QHash<int, QVariant> data(int index, const QList<int> &roles) const {
281 QHash<int,QVariant> returnHash;
283 for (int i = 0; i < roles.size(); ++i) {
284 int role = roles.at(i);
288 info = list.at(index).first;
291 info = list.at(index).second;
296 returnHash.insert(role, info);
301 void addItem(const QString &name, const QString &number) {
302 list.append(QPair<QString,QString>(name, number));
303 emit itemsInserted(list.count()-1, 1);
306 void insertItem(int index, const QString &name, const QString &number) {
307 list.insert(index, QPair<QString,QString>(name, number));
308 emit itemsInserted(index, 1);
311 void insertItems(int index, const QList<QPair<QString, QString> > &items) {
312 for (int i=0; i<items.count(); i++)
313 list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
314 emit itemsInserted(index, items.count());
317 void removeItem(int index) {
318 list.removeAt(index);
319 emit itemsRemoved(index, 1);
322 void removeItems(int index, int count) {
325 list.removeAt(index);
326 emit itemsRemoved(index, count);
329 void moveItem(int from, int to) {
331 emit itemsMoved(from, to, 1);
334 void moveItems(int from, int to, int count) {
335 tst_qsglistview_move(from, to, count, &list);
336 emit itemsMoved(from, to, count);
339 void modifyItem(int index, const QString &name, const QString &number) {
340 list[index] = QPair<QString,QString>(name, number);
341 emit itemsChanged(index, 1, roles());
345 int count = list.count();
347 emit itemsRemoved(0, count);
351 QList<QPair<QString,QString> > list;
355 class TestModel2 : public QAbstractListModel
358 enum Roles { Name = Qt::UserRole+1, Number = Qt::UserRole+2 };
360 TestModel2(QObject *parent=0) : QAbstractListModel(parent) {
361 QHash<int, QByteArray> roles;
362 roles[Name] = "name";
363 roles[Number] = "number";
367 int rowCount(const QModelIndex &parent=QModelIndex()) const { Q_UNUSED(parent); return list.count(); }
368 QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const {
371 rv = list.at(index.row()).first;
372 else if (role == Number)
373 rv = list.at(index.row()).second;
378 int count() const { return rowCount(); }
379 QString name(int index) const { return list.at(index).first; }
380 QString number(int index) const { return list.at(index).second; }
382 void addItem(const QString &name, const QString &number) {
383 emit beginInsertRows(QModelIndex(), list.count(), list.count());
384 list.append(QPair<QString,QString>(name, number));
385 emit endInsertRows();
388 void addItems(const QList<QPair<QString, QString> > &items) {
389 emit beginInsertRows(QModelIndex(), list.count(), list.count()+items.count()-1);
390 for (int i=0; i<items.count(); i++)
391 list.append(QPair<QString,QString>(items[i].first, items[i].second));
392 emit endInsertRows();
395 void insertItem(int index, const QString &name, const QString &number) {
396 emit beginInsertRows(QModelIndex(), index, index);
397 list.insert(index, QPair<QString,QString>(name, number));
398 emit endInsertRows();
401 void insertItems(int index, const QList<QPair<QString, QString> > &items) {
402 emit beginInsertRows(QModelIndex(), index, index+items.count()-1);
403 for (int i=0; i<items.count(); i++)
404 list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
405 emit endInsertRows();
408 void removeItem(int index) {
409 emit beginRemoveRows(QModelIndex(), index, index);
410 list.removeAt(index);
411 emit endRemoveRows();
414 void removeItems(int index, int count) {
415 emit beginRemoveRows(QModelIndex(), index, index+count-1);
417 list.removeAt(index);
418 emit endRemoveRows();
421 void moveItem(int from, int to) {
422 emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
427 void moveItems(int from, int to, int count) {
428 emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
429 tst_qsglistview_move(from, to, count, &list);
433 void modifyItem(int idx, const QString &name, const QString &number) {
434 list[idx] = QPair<QString,QString>(name, number);
435 emit dataChanged(index(idx,0), index(idx,0));
439 int count = list.count();
440 emit beginRemoveRows(QModelIndex(), 0, count-1);
442 emit endRemoveRows();
446 QList<QPair<QString,QString> > list;
449 tst_QSGListView::tst_QSGListView()
454 void tst_QSGListView::items()
456 QSGView *canvas = createView();
459 model.addItem("Fred", "12345");
460 model.addItem("John", "2345");
461 model.addItem("Bob", "54321");
463 QDeclarativeContext *ctxt = canvas->rootContext();
464 ctxt->setContextProperty("testModel", &model);
466 TestObject *testObject = new TestObject;
467 ctxt->setContextProperty("testObject", testObject);
469 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
470 qApp->processEvents();
472 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
473 QTRY_VERIFY(listview != 0);
475 QSGItem *contentItem = listview->contentItem();
476 QTRY_VERIFY(contentItem != 0);
478 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
479 QTRY_VERIFY(testObject->error() == false);
481 QTRY_VERIFY(listview->highlightItem() != 0);
482 QTRY_COMPARE(listview->count(), model.count());
483 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
484 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
486 // current item should be first item
487 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
489 for (int i = 0; i < model.count(); ++i) {
490 QSGText *name = findItem<QSGText>(contentItem, "textName", i);
491 QTRY_VERIFY(name != 0);
492 QTRY_COMPARE(name->text(), model.name(i));
493 QSGText *number = findItem<QSGText>(contentItem, "textNumber", i);
494 QTRY_VERIFY(number != 0);
495 QTRY_COMPARE(number->text(), model.number(i));
498 // switch to other delegate
499 testObject->setAnimate(true);
500 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
501 QTRY_VERIFY(testObject->error() == false);
502 QTRY_VERIFY(listview->currentItem());
504 // set invalid highlight
505 testObject->setInvalidHighlight(true);
506 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
507 QTRY_VERIFY(testObject->error() == false);
508 QTRY_VERIFY(listview->currentItem());
509 QTRY_VERIFY(listview->highlightItem() == 0);
511 // back to normal highlight
512 testObject->setInvalidHighlight(false);
513 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
514 QTRY_VERIFY(testObject->error() == false);
515 QTRY_VERIFY(listview->currentItem());
516 QTRY_VERIFY(listview->highlightItem() != 0);
518 // set an empty model and confirm that items are destroyed
520 ctxt->setContextProperty("testModel", &model2);
522 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
523 QTRY_VERIFY(itemCount == 0);
525 QTRY_COMPARE(listview->highlightResizeSpeed(), 1000.0);
526 QTRY_COMPARE(listview->highlightMoveSpeed(), 1000.0);
534 void tst_QSGListView::changed()
536 QSGView *canvas = createView();
539 model.addItem("Fred", "12345");
540 model.addItem("John", "2345");
541 model.addItem("Bob", "54321");
543 QDeclarativeContext *ctxt = canvas->rootContext();
544 ctxt->setContextProperty("testModel", &model);
546 TestObject *testObject = new TestObject;
547 ctxt->setContextProperty("testObject", testObject);
549 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
550 qApp->processEvents();
552 QSGFlickable *listview = findItem<QSGFlickable>(canvas->rootObject(), "list");
553 QTRY_VERIFY(listview != 0);
555 QSGItem *contentItem = listview->contentItem();
556 QTRY_VERIFY(contentItem != 0);
558 model.modifyItem(1, "Will", "9876");
559 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
560 QTRY_VERIFY(name != 0);
561 QTRY_COMPARE(name->text(), model.name(1));
562 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
563 QTRY_VERIFY(number != 0);
564 QTRY_COMPARE(number->text(), model.number(1));
571 void tst_QSGListView::inserted()
573 QSGView *canvas = createView();
577 model.addItem("Fred", "12345");
578 model.addItem("John", "2345");
579 model.addItem("Bob", "54321");
581 QDeclarativeContext *ctxt = canvas->rootContext();
582 ctxt->setContextProperty("testModel", &model);
584 TestObject *testObject = new TestObject;
585 ctxt->setContextProperty("testObject", testObject);
587 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
588 qApp->processEvents();
590 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
591 QTRY_VERIFY(listview != 0);
593 QSGItem *contentItem = listview->contentItem();
594 QTRY_VERIFY(contentItem != 0);
596 model.insertItem(1, "Will", "9876");
598 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
599 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
601 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
602 QTRY_VERIFY(name != 0);
603 QTRY_COMPARE(name->text(), model.name(1));
604 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
605 QTRY_VERIFY(number != 0);
606 QTRY_COMPARE(number->text(), model.number(1));
608 // Confirm items positioned correctly
609 for (int i = 0; i < model.count(); ++i) {
610 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
611 QTRY_COMPARE(item->y(), i*20.0);
614 model.insertItem(0, "Foo", "1111"); // zero index, and current item
616 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
617 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
619 name = findItem<QSGText>(contentItem, "textName", 0);
620 QTRY_VERIFY(name != 0);
621 QTRY_COMPARE(name->text(), model.name(0));
622 number = findItem<QSGText>(contentItem, "textNumber", 0);
623 QTRY_VERIFY(number != 0);
624 QTRY_COMPARE(number->text(), model.number(0));
626 QTRY_COMPARE(listview->currentIndex(), 1);
628 // Confirm items positioned correctly
629 for (int i = 0; i < model.count(); ++i) {
630 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
631 QTRY_COMPARE(item->y(), i*20.0);
634 for (int i = model.count(); i < 30; ++i)
635 model.insertItem(i, "Hello", QString::number(i));
637 listview->setContentY(80);
639 // Insert item outside visible area
640 model.insertItem(1, "Hello", "1324");
642 QTRY_VERIFY(listview->contentY() == 80);
644 // Confirm items positioned correctly
645 for (int i = 5; i < 5+15; ++i) {
646 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
647 if (!item) qWarning() << "Item" << i << "not found";
649 QTRY_COMPARE(item->y(), i*20.0 - 20.0);
652 // QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
656 model.insertItem(0, "Hello", "1234");
657 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
659 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
661 QCOMPARE(item->y(), 0.);
662 QVERIFY(listview->contentY() == 0);
669 void tst_QSGListView::inserted_more()
671 QFETCH(qreal, contentY);
672 QFETCH(int, insertIndex);
673 QFETCH(int, insertCount);
674 QFETCH(qreal, itemsOffsetAfterMove);
678 QSGView *canvas = createView();
682 for (int i = 0; i < 30; i++)
683 model.addItem("Item" + QString::number(i), "");
685 QDeclarativeContext *ctxt = canvas->rootContext();
686 ctxt->setContextProperty("testModel", &model);
688 TestObject *testObject = new TestObject;
689 ctxt->setContextProperty("testObject", testObject);
691 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
692 qApp->processEvents();
694 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
695 QTRY_VERIFY(listview != 0);
696 QSGItem *contentItem = listview->contentItem();
697 QTRY_VERIFY(contentItem != 0);
699 listview->setContentY(contentY);
701 QList<QPair<QString, QString> > newData;
702 for (int i=0; i<insertCount; i++)
703 newData << qMakePair(QString("value %1").arg(i), QString::number(i));
704 model.insertItems(insertIndex, newData);
705 QTRY_COMPARE(listview->property("count").toInt(), model.count());
707 // check visibleItems.first() is in correct position
708 QSGItem *item0 = findItem<QSGItem>(contentItem, "wrapper", 0);
710 QCOMPARE(item0->y(), itemsOffsetAfterMove);
712 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
713 int firstVisibleIndex = -1;
714 for (int i=0; i<items.count(); i++) {
715 if (items[i]->y() >= contentY) {
716 QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
717 firstVisibleIndex = e.evaluate().toInt();
721 QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
723 // Confirm items positioned correctly and indexes correct
724 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
725 for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
726 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
727 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
728 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
729 name = findItem<QSGText>(contentItem, "textName", i);
731 QTRY_COMPARE(name->text(), model.name(i));
732 number = findItem<QSGText>(contentItem, "textNumber", i);
733 QVERIFY(number != 0);
734 QTRY_COMPARE(number->text(), model.number(i));
741 void tst_QSGListView::inserted_more_data()
743 QTest::addColumn<qreal>("contentY");
744 QTest::addColumn<int>("insertIndex");
745 QTest::addColumn<int>("insertCount");
746 QTest::addColumn<qreal>("itemsOffsetAfterMove");
748 QTest::newRow("add 1, before visible items")
751 << -20.0; // insert above first visible i.e. 0 is at -20, first visible should not move
753 QTest::newRow("add multiple, before visible")
756 << -20.0 * 3; // again first visible should not move
758 QTest::newRow("add 1, at start of visible, content at start")
763 QTest::newRow("add multiple, start of visible, content at start")
768 QTest::newRow("add 1, at start of visible, content not at start")
773 QTest::newRow("add multiple, at start of visible, content not at start")
779 QTest::newRow("add 1, at end of visible, content at start")
784 QTest::newRow("add 1, at end of visible, content at start")
789 QTest::newRow("add 1, at end of visible, content not at start")
794 QTest::newRow("add multiple, at end of visible, content not at start")
800 QTest::newRow("add 1, after visible, content at start")
805 QTest::newRow("add 1, after visible, content at start")
810 QTest::newRow("add 1, after visible, content not at start")
815 QTest::newRow("add multiple, after visible, content not at start")
822 void tst_QSGListView::removed(bool animated)
824 QSGView *canvas = createView();
827 for (int i = 0; i < 50; i++)
828 model.addItem("Item" + QString::number(i), "");
830 QDeclarativeContext *ctxt = canvas->rootContext();
831 ctxt->setContextProperty("testModel", &model);
833 TestObject *testObject = new TestObject;
834 ctxt->setContextProperty("testObject", testObject);
836 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
838 qApp->processEvents();
840 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
841 QTRY_VERIFY(listview != 0);
843 QSGItem *contentItem = listview->contentItem();
844 QTRY_VERIFY(contentItem != 0);
847 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
849 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
850 QTRY_VERIFY(name != 0);
851 QTRY_COMPARE(name->text(), model.name(1));
852 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
853 QTRY_VERIFY(number != 0);
854 QTRY_COMPARE(number->text(), model.number(1));
856 // Confirm items positioned correctly
857 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
858 for (int i = 0; i < model.count() && i < itemCount; ++i) {
859 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
860 if (!item) qWarning() << "Item" << i << "not found";
862 QTRY_VERIFY(item->y() == i*20);
865 // Remove first item (which is the current item);
866 model.removeItem(0); // post: top item starts at 20
867 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
869 name = findItem<QSGText>(contentItem, "textName", 0);
870 QTRY_VERIFY(name != 0);
871 QTRY_COMPARE(name->text(), model.name(0));
872 number = findItem<QSGText>(contentItem, "textNumber", 0);
873 QTRY_VERIFY(number != 0);
874 QTRY_COMPARE(number->text(), model.number(0));
876 // Confirm items positioned correctly
877 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
878 for (int i = 0; i < model.count() && i < itemCount; ++i) {
879 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
880 if (!item) qWarning() << "Item" << i << "not found";
882 QTRY_COMPARE(item->y(),i*20.0 + 20.0);
885 // Remove items not visible
886 model.removeItem(18);
887 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
889 // Confirm items positioned correctly
890 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
891 for (int i = 0; i < model.count() && i < itemCount; ++i) {
892 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
893 if (!item) qWarning() << "Item" << i << "not found";
895 QTRY_COMPARE(item->y(),i*20.0+20.0);
898 // Remove items before visible
899 listview->setContentY(80);
900 listview->setCurrentIndex(10);
902 model.removeItem(1); // post: top item will be at 40
903 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
905 // Confirm items positioned correctly
906 for (int i = 2; i < 18; ++i) {
907 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
908 if (!item) qWarning() << "Item" << i << "not found";
910 QTRY_COMPARE(item->y(),40+i*20.0);
913 // Remove current index
914 QTRY_VERIFY(listview->currentIndex() == 9);
915 QSGItem *oldCurrent = listview->currentItem();
918 QTRY_COMPARE(listview->currentIndex(), 9);
919 QTRY_VERIFY(listview->currentItem() != oldCurrent);
921 listview->setContentY(40); // That's the top now
922 // let transitions settle.
925 // Confirm items positioned correctly
926 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
927 for (int i = 0; i < model.count() && i < itemCount; ++i) {
928 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
929 if (!item) qWarning() << "Item" << i << "not found";
931 QTRY_COMPARE(item->y(),40+i*20.0);
934 // remove current item beyond visible items.
935 listview->setCurrentIndex(20);
936 listview->setContentY(40);
937 model.removeItem(20);
939 QTRY_COMPARE(listview->currentIndex(), 20);
940 QTRY_VERIFY(listview->currentItem() != 0);
942 // remove item before current, but visible
943 listview->setCurrentIndex(8);
944 oldCurrent = listview->currentItem();
947 QTRY_COMPARE(listview->currentIndex(), 7);
948 QTRY_VERIFY(listview->currentItem() == oldCurrent);
950 listview->setContentY(80);
953 // remove all visible items
954 model.removeItems(1, 18);
955 QTRY_COMPARE(listview->count() , model.count());
957 // Confirm items positioned correctly
958 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
959 for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
960 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i+2);
961 if (!item) qWarning() << "Item" << i+2 << "not found";
963 QTRY_COMPARE(item->y(),80+i*20.0);
966 model.removeItems(1, 17);
967 QTRY_COMPARE(listview->count() , model.count());
969 model.removeItems(2, 1);
970 QTRY_COMPARE(listview->count() , model.count());
972 model.addItem("New", "1");
973 QTRY_COMPARE(listview->count() , model.count());
975 QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", model.count()-1));
976 QCOMPARE(name->text(), QString("New"));
978 // Add some more items so that we don't run out
980 for (int i = 0; i < 50; i++)
981 model.addItem("Item" + QString::number(i), "");
984 listview->setCurrentIndex(0);
985 listview->setContentY(30);
987 QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", 0));
989 // QTBUG-19198 move to end and remove all visible items one at a time.
990 listview->positionViewAtEnd();
991 for (int i = 0; i < 18; ++i)
992 model.removeItems(model.count() - 1, 1);
993 QTRY_VERIFY(findItems<QSGItem>(contentItem, "wrapper").count() > 16);
1000 void tst_QSGListView::clear()
1002 QSGView *canvas = createView();
1005 for (int i = 0; i < 30; i++)
1006 model.addItem("Item" + QString::number(i), "");
1008 QDeclarativeContext *ctxt = canvas->rootContext();
1009 ctxt->setContextProperty("testModel", &model);
1011 TestObject *testObject = new TestObject;
1012 ctxt->setContextProperty("testObject", testObject);
1014 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1015 qApp->processEvents();
1017 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1018 QTRY_VERIFY(listview != 0);
1020 QSGItem *contentItem = listview->contentItem();
1021 QTRY_VERIFY(contentItem != 0);
1025 QTRY_VERIFY(listview->count() == 0);
1026 QTRY_VERIFY(listview->currentItem() == 0);
1027 QTRY_VERIFY(listview->contentY() == 0);
1028 QVERIFY(listview->currentIndex() == -1);
1030 // confirm sanity when adding an item to cleared list
1031 model.addItem("New", "1");
1032 QTRY_VERIFY(listview->count() == 1);
1033 QVERIFY(listview->currentItem() != 0);
1034 QVERIFY(listview->currentIndex() == 0);
1041 void tst_QSGListView::moved()
1043 QFETCH(qreal, contentY);
1047 QFETCH(qreal, itemsOffsetAfterMove);
1051 QSGView *canvas = createView();
1055 for (int i = 0; i < 30; i++)
1056 model.addItem("Item" + QString::number(i), "");
1058 QDeclarativeContext *ctxt = canvas->rootContext();
1059 ctxt->setContextProperty("testModel", &model);
1061 TestObject *testObject = new TestObject;
1062 ctxt->setContextProperty("testObject", testObject);
1064 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1065 qApp->processEvents();
1067 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1068 QTRY_VERIFY(listview != 0);
1070 QSGItem *contentItem = listview->contentItem();
1071 QTRY_VERIFY(contentItem != 0);
1073 QSGItem *currentItem = listview->currentItem();
1074 QTRY_VERIFY(currentItem != 0);
1076 listview->setContentY(contentY);
1077 model.moveItems(from, to, count);
1079 // wait for items to move
1082 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
1083 int firstVisibleIndex = -1;
1084 for (int i=0; i<items.count(); i++) {
1085 if (items[i]->y() >= contentY) {
1086 QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
1087 firstVisibleIndex = e.evaluate().toInt();
1091 QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
1093 // Confirm items positioned correctly and indexes correct
1094 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1095 for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
1096 if (i >= firstVisibleIndex + 16) // index has moved out of view
1098 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1099 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1100 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
1101 name = findItem<QSGText>(contentItem, "textName", i);
1103 QTRY_COMPARE(name->text(), model.name(i));
1104 number = findItem<QSGText>(contentItem, "textNumber", i);
1105 QVERIFY(number != 0);
1106 QTRY_COMPARE(number->text(), model.number(i));
1108 // current index should have been updated
1109 if (item == currentItem)
1110 QTRY_COMPARE(listview->currentIndex(), i);
1117 void tst_QSGListView::moved_data()
1119 QTest::addColumn<qreal>("contentY");
1120 QTest::addColumn<int>("from");
1121 QTest::addColumn<int>("to");
1122 QTest::addColumn<int>("count");
1123 QTest::addColumn<qreal>("itemsOffsetAfterMove");
1125 // model starts with 30 items, each 20px high, in area 320px high
1126 // 16 items should be visible at a time
1127 // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
1129 QTest::newRow("move 1 forwards, within visible items")
1134 QTest::newRow("move 1 forwards, from non-visible -> visible")
1135 << 80.0 // show 4-19
1137 << 20.0; // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement
1139 QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)")
1140 << 80.0 // show 4-19
1142 << 20.0; // first item has moved to below item4, everything drops down by size of 1 item
1144 QTest::newRow("move 1 forwards, from visible -> non-visible")
1149 QTest::newRow("move 1 forwards, from visible -> non-visible (move first item)")
1155 QTest::newRow("move 1 backwards, within visible items")
1160 QTest::newRow("move 1 backwards, within visible items (to first index)")
1165 QTest::newRow("move 1 backwards, from non-visible -> visible")
1170 QTest::newRow("move 1 backwards, from non-visible -> visible (move last item)")
1175 QTest::newRow("move 1 backwards, from visible -> non-visible")
1176 << 80.0 // show 4-19
1178 << -20.0; // to minimize movement, item 0 moves to -20, and other items do not move
1180 QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)")
1181 << 80.0 // show 4-19
1183 << -20.0; // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move
1186 QTest::newRow("move multiple forwards, within visible items")
1191 QTest::newRow("move multiple forwards, before visible items")
1192 << 140.0 // show 7-22
1193 << 4 << 5 << 3 // 4,5,6 move to below 7
1194 << 20.0 * 3; // 4,5,6 moved down
1196 QTest::newRow("move multiple forwards, from non-visible -> visible")
1197 << 80.0 // show 4-19
1199 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1201 QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)")
1202 << 80.0 // show 4-19
1204 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1206 QTest::newRow("move multiple forwards, from visible -> non-visible")
1211 QTest::newRow("move multiple forwards, from visible -> non-visible (move first item)")
1217 QTest::newRow("move multiple backwards, within visible items")
1222 QTest::newRow("move multiple backwards, from non-visible -> visible")
1227 QTest::newRow("move multiple backwards, from non-visible -> visible (move last item)")
1232 QTest::newRow("move multiple backwards, from visible -> non-visible")
1233 << 80.0 // show 4-19
1235 << -20.0 * 3; // to minimize movement, 0 moves by -60, and other items do not move
1237 QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)")
1238 << 80.0 // show 4-19
1240 << -20.0 * 3; // to minimize movement, 16,17,18 move to above item 0, and other items do not move
1245 enum { Inserted, Removed, Moved, SetCurrent } type;
1250 static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; }
1251 static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; }
1252 static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; }
1253 static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; }
1255 Q_DECLARE_METATYPE(QList<ListChange>)
1257 void tst_QSGListView::multipleChanges()
1259 QFETCH(int, startCount);
1260 QFETCH(QList<ListChange>, changes);
1261 QFETCH(int, newCount);
1262 QFETCH(int, newCurrentIndex);
1264 QSGView *canvas = createView();
1268 for (int i = 0; i < startCount; i++)
1269 model.addItem("Item" + QString::number(i), "");
1271 QDeclarativeContext *ctxt = canvas->rootContext();
1272 ctxt->setContextProperty("testModel", &model);
1274 TestObject *testObject = new TestObject;
1275 ctxt->setContextProperty("testObject", testObject);
1277 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1278 qApp->processEvents();
1280 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1281 QTRY_VERIFY(listview != 0);
1283 for (int i=0; i<changes.count(); i++) {
1284 switch (changes[i].type) {
1285 case ListChange::Inserted:
1287 QList<QPair<QString, QString> > items;
1288 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
1289 items << qMakePair(QString("new item " + j), QString::number(j));
1290 model.insertItems(changes[i].index, items);
1293 case ListChange::Removed:
1294 model.removeItems(changes[i].index, changes[i].count);
1296 case ListChange::Moved:
1297 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
1299 case ListChange::SetCurrent:
1300 listview->setCurrentIndex(changes[i].index);
1305 QTRY_COMPARE(listview->count(), newCount);
1306 QCOMPARE(listview->count(), model.count());
1307 QTRY_COMPARE(listview->currentIndex(), newCurrentIndex);
1311 QSGItem *contentItem = listview->contentItem();
1312 QTRY_VERIFY(contentItem != 0);
1313 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1314 for (int i=0; i < model.count() && i < itemCount; ++i) {
1315 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1316 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1317 name = findItem<QSGText>(contentItem, "textName", i);
1319 QTRY_COMPARE(name->text(), model.name(i));
1320 number = findItem<QSGText>(contentItem, "textNumber", i);
1321 QVERIFY(number != 0);
1322 QTRY_COMPARE(number->text(), model.number(i));
1329 void tst_QSGListView::multipleChanges_data()
1331 QTest::addColumn<int>("startCount");
1332 QTest::addColumn<QList<ListChange> >("changes");
1333 QTest::addColumn<int>("newCount");
1334 QTest::addColumn<int>("newCurrentIndex");
1336 QList<ListChange> changes;
1338 for (int i=1; i<30; i++)
1339 changes << ListChange::remove(0);
1340 QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0;
1342 changes << ListChange::remove(0);
1343 QTest::newRow("remove all") << 30 << changes << 0 << -1;
1346 changes << ListChange::setCurrent(29);
1347 for (int i=29; i>0; i--)
1348 changes << ListChange::remove(i);
1349 QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0;
1351 QTest::newRow("remove then insert at 0") << 10 << (QList<ListChange>()
1352 << ListChange::remove(0, 1)
1353 << ListChange::insert(0, 1)
1356 QTest::newRow("remove then insert at non-zero index") << 10 << (QList<ListChange>()
1357 << ListChange::setCurrent(2)
1358 << ListChange::remove(2, 1)
1359 << ListChange::insert(2, 1)
1362 QTest::newRow("remove current then insert below it") << 10 << (QList<ListChange>()
1363 << ListChange::setCurrent(1)
1364 << ListChange::remove(1, 3)
1365 << ListChange::insert(2, 2)
1368 QTest::newRow("remove current index then move it down") << 10 << (QList<ListChange>()
1369 << ListChange::setCurrent(2)
1370 << ListChange::remove(1, 3)
1371 << ListChange::move(1, 5, 1)
1374 QTest::newRow("remove current index then move it up") << 10 << (QList<ListChange>()
1375 << ListChange::setCurrent(5)
1376 << ListChange::remove(4, 3)
1377 << ListChange::move(4, 1, 1)
1381 QTest::newRow("insert multiple times") << 0 << (QList<ListChange>()
1382 << ListChange::insert(0, 2)
1383 << ListChange::insert(0, 4)
1384 << ListChange::insert(0, 6)
1387 QTest::newRow("insert multiple times with current index changes") << 0 << (QList<ListChange>()
1388 << ListChange::insert(0, 2)
1389 << ListChange::insert(0, 4)
1390 << ListChange::insert(0, 6)
1391 << ListChange::setCurrent(3)
1392 << ListChange::insert(3, 2)
1395 QTest::newRow("insert and remove all") << 0 << (QList<ListChange>()
1396 << ListChange::insert(0, 30)
1397 << ListChange::remove(0, 30)
1400 QTest::newRow("insert and remove current") << 30 << (QList<ListChange>()
1401 << ListChange::insert(1)
1402 << ListChange::setCurrent(1)
1403 << ListChange::remove(1)
1406 QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList<ListChange>()
1407 << ListChange::insert(0, 10)
1408 << ListChange::remove(5, 10)
1411 QTest::newRow("insert multiple, then move new items to end") << 10 << (QList<ListChange>()
1412 << ListChange::insert(0, 3)
1413 << ListChange::move(0, 10, 3)
1416 QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList<ListChange>()
1417 << ListChange::insert(0, 3)
1418 << ListChange::move(0, 8, 5)
1421 QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList<ListChange>()
1422 << ListChange::setCurrent(9)
1423 << ListChange::insert(10, 3)
1424 << ListChange::move(8, 0, 5)
1428 QTest::newRow("move back and forth to same index") << 10 << (QList<ListChange>()
1429 << ListChange::setCurrent(1)
1430 << ListChange::move(1, 2, 2)
1431 << ListChange::move(2, 1, 2)
1434 QTest::newRow("move forwards then back") << 10 << (QList<ListChange>()
1435 << ListChange::setCurrent(2)
1436 << ListChange::move(1, 2, 3)
1437 << ListChange::move(3, 0, 5)
1440 QTest::newRow("move current, then remove it") << 10 << (QList<ListChange>()
1441 << ListChange::setCurrent(5)
1442 << ListChange::move(5, 0, 1)
1443 << ListChange::remove(0)
1446 QTest::newRow("move current, then insert before it") << 10 << (QList<ListChange>()
1447 << ListChange::setCurrent(5)
1448 << ListChange::move(5, 0, 1)
1449 << ListChange::insert(0)
1452 QTest::newRow("move multiple, then remove them") << 10 << (QList<ListChange>()
1453 << ListChange::setCurrent(1)
1454 << ListChange::move(5, 1, 3)
1455 << ListChange::remove(1, 3)
1458 QTest::newRow("move multiple, then insert before them") << 10 << (QList<ListChange>()
1459 << ListChange::setCurrent(5)
1460 << ListChange::move(5, 1, 3)
1461 << ListChange::insert(1, 5)
1464 QTest::newRow("move multiple, then insert after them") << 10 << (QList<ListChange>()
1465 << ListChange::setCurrent(3)
1466 << ListChange::move(0, 1, 2)
1467 << ListChange::insert(3, 5)
1471 QTest::newRow("clear current") << 0 << (QList<ListChange>()
1472 << ListChange::insert(0, 5)
1473 << ListChange::setCurrent(-1)
1474 << ListChange::remove(0, 5)
1475 << ListChange::insert(0, 5)
1479 void tst_QSGListView::swapWithFirstItem()
1481 QSGView *canvas = createView();
1485 for (int i = 0; i < 30; i++)
1486 model.addItem("Item" + QString::number(i), "");
1488 QDeclarativeContext *ctxt = canvas->rootContext();
1489 ctxt->setContextProperty("testModel", &model);
1491 TestObject *testObject = new TestObject;
1492 ctxt->setContextProperty("testObject", testObject);
1494 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1495 qApp->processEvents();
1497 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1498 QTRY_VERIFY(listview != 0);
1500 // ensure content position is stable
1501 listview->setContentY(0);
1502 model.moveItem(1, 0);
1503 QTRY_VERIFY(listview->contentY() == 0);
1509 void tst_QSGListView::enforceRange()
1511 QSGView *canvas = createView();
1514 for (int i = 0; i < 30; i++)
1515 model.addItem("Item" + QString::number(i), "");
1517 QDeclarativeContext *ctxt = canvas->rootContext();
1518 ctxt->setContextProperty("testModel", &model);
1520 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange.qml")));
1521 qApp->processEvents();
1523 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1524 QTRY_VERIFY(listview != 0);
1526 QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1527 QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1528 QTRY_COMPARE(listview->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1530 QSGItem *contentItem = listview->contentItem();
1531 QTRY_VERIFY(contentItem != 0);
1533 // view should be positioned at the top of the range.
1534 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
1536 QTRY_COMPARE(listview->contentY(), -100.0);
1538 QSGText *name = findItem<QSGText>(contentItem, "textName", 0);
1539 QTRY_VERIFY(name != 0);
1540 QTRY_COMPARE(name->text(), model.name(0));
1541 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 0);
1542 QTRY_VERIFY(number != 0);
1543 QTRY_COMPARE(number->text(), model.number(0));
1545 // Check currentIndex is updated when contentItem moves
1546 listview->setContentY(20);
1548 QTRY_COMPARE(listview->currentIndex(), 6);
1552 for (int i = 0; i < 5; i++)
1553 model2.addItem("Item" + QString::number(i), "");
1555 ctxt->setContextProperty("testModel", &model2);
1556 QCOMPARE(listview->count(), 5);
1561 void tst_QSGListView::enforceRange_withoutHighlight()
1564 // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1565 // to the correct position (i.e. to the next/previous item, not next/previous section)
1566 // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1568 QSGView *canvas = createView();
1573 model.addItem("Item 0", "a");
1574 model.addItem("Item 1", "b");
1575 model.addItem("Item 2", "b");
1576 model.addItem("Item 3", "c");
1578 QDeclarativeContext *ctxt = canvas->rootContext();
1579 ctxt->setContextProperty("testModel", &model);
1581 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange-nohighlight.qml")));
1582 qApp->processEvents();
1584 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1585 QTRY_VERIFY(listview != 0);
1587 qreal expectedPos = -100.0;
1589 expectedPos += 10.0; // scroll past 1st section's delegate (10px height)
1590 QTRY_COMPARE(listview->contentY(), expectedPos);
1592 expectedPos += 20 + 10; // scroll past 1st section and section delegate of 2nd section
1593 QTest::keyClick(canvas, Qt::Key_Down);
1595 QTRY_COMPARE(listview->contentY(), expectedPos);
1597 expectedPos += 20; // scroll past 1st item of 2nd section
1598 QTest::keyClick(canvas, Qt::Key_Down);
1599 QTRY_COMPARE(listview->contentY(), expectedPos);
1601 expectedPos += 20 + 10; // scroll past 2nd item of 2nd section and section delegate of 3rd section
1602 QTest::keyClick(canvas, Qt::Key_Down);
1603 QTRY_COMPARE(listview->contentY(), expectedPos);
1608 void tst_QSGListView::spacing()
1610 QSGView *canvas = createView();
1614 for (int i = 0; i < 30; i++)
1615 model.addItem("Item" + QString::number(i), "");
1617 QDeclarativeContext *ctxt = canvas->rootContext();
1618 ctxt->setContextProperty("testModel", &model);
1620 TestObject *testObject = new TestObject;
1621 ctxt->setContextProperty("testObject", testObject);
1623 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1624 qApp->processEvents();
1626 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1627 QTRY_VERIFY(listview != 0);
1629 QSGItem *contentItem = listview->contentItem();
1630 QTRY_VERIFY(contentItem != 0);
1632 // Confirm items positioned correctly
1633 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1634 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1635 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1636 if (!item) qWarning() << "Item" << i << "not found";
1638 QTRY_VERIFY(item->y() == i*20);
1641 listview->setSpacing(10);
1642 QTRY_VERIFY(listview->spacing() == 10);
1644 // Confirm items positioned correctly
1645 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1646 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1647 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1648 if (!item) qWarning() << "Item" << i << "not found";
1650 QTRY_VERIFY(item->y() == i*30);
1653 listview->setSpacing(0);
1655 // Confirm items positioned correctly
1656 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1657 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1658 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1659 if (!item) qWarning() << "Item" << i << "not found";
1661 QTRY_COMPARE(item->y(), i*20.0);
1668 void tst_QSGListView::sections()
1670 QSGView *canvas = createView();
1674 for (int i = 0; i < 30; i++)
1675 model.addItem("Item" + QString::number(i), QString::number(i/5));
1677 QDeclarativeContext *ctxt = canvas->rootContext();
1678 ctxt->setContextProperty("testModel", &model);
1680 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections.qml")));
1681 qApp->processEvents();
1683 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1684 QTRY_VERIFY(listview != 0);
1686 QSGItem *contentItem = listview->contentItem();
1687 QTRY_VERIFY(contentItem != 0);
1689 // Confirm items positioned correctly
1690 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1691 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1692 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1694 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
1695 QSGText *next = findItem<QSGText>(item, "nextSection");
1696 QCOMPARE(next->text().toInt(), (i+1)/5);
1699 QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
1701 // Remove section boundary
1702 model.removeItem(5);
1703 QTRY_COMPARE(listview->count(), model.count());
1705 // New section header created
1706 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 5);
1708 QTRY_COMPARE(item->height(), 40.0);
1710 model.insertItem(3, "New Item", "0");
1711 QTRY_COMPARE(listview->count(), model.count());
1713 // Section header moved
1714 item = findItem<QSGItem>(contentItem, "wrapper", 5);
1716 QTRY_COMPARE(item->height(), 20.0);
1718 item = findItem<QSGItem>(contentItem, "wrapper", 6);
1720 QTRY_COMPARE(item->height(), 40.0);
1722 // insert item which will become a section header
1723 model.insertItem(6, "Replace header", "1");
1724 QTRY_COMPARE(listview->count(), model.count());
1726 item = findItem<QSGItem>(contentItem, "wrapper", 6);
1728 QTRY_COMPARE(item->height(), 40.0);
1730 item = findItem<QSGItem>(contentItem, "wrapper", 7);
1732 QTRY_COMPARE(item->height(), 20.0);
1734 QTRY_COMPARE(listview->currentSection(), QString("0"));
1736 listview->setContentY(140);
1737 QTRY_COMPARE(listview->currentSection(), QString("1"));
1739 QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
1741 listview->setContentY(20);
1742 QTRY_COMPARE(listview->currentSection(), QString("0"));
1744 QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
1746 item = findItem<QSGItem>(contentItem, "wrapper", 1);
1748 QTRY_COMPARE(item->height(), 20.0);
1750 // check that headers change when item changes
1751 listview->setContentY(0);
1752 model.modifyItem(0, "changed", "2");
1755 item = findItem<QSGItem>(contentItem, "wrapper", 1);
1757 QTRY_COMPARE(item->height(), 40.0);
1762 void tst_QSGListView::sectionsDelegate()
1764 QSGView *canvas = createView();
1768 for (int i = 0; i < 30; i++)
1769 model.addItem("Item" + QString::number(i), QString::number(i/5));
1771 QDeclarativeContext *ctxt = canvas->rootContext();
1772 ctxt->setContextProperty("testModel", &model);
1774 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1775 qApp->processEvents();
1777 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1778 QTRY_VERIFY(listview != 0);
1780 QSGItem *contentItem = listview->contentItem();
1781 QTRY_VERIFY(contentItem != 0);
1783 // Confirm items positioned correctly
1784 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1785 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1786 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1788 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
1789 QSGText *next = findItem<QSGText>(item, "nextSection");
1790 QCOMPARE(next->text().toInt(), (i+1)/5);
1793 for (int i = 0; i < 3; ++i) {
1794 QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1796 QTRY_COMPARE(item->y(), qreal(i*20*6));
1799 model.modifyItem(0, "One", "aaa");
1800 model.modifyItem(1, "Two", "aaa");
1801 model.modifyItem(2, "Three", "aaa");
1802 model.modifyItem(3, "Four", "aaa");
1803 model.modifyItem(4, "Five", "aaa");
1806 for (int i = 0; i < 3; ++i) {
1807 QSGItem *item = findItem<QSGItem>(contentItem,
1808 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1810 QTRY_COMPARE(item->y(), qreal(i*20*6));
1813 // remove section boundary
1814 model.removeItem(5);
1815 QTRY_COMPARE(listview->count(), model.count());
1816 for (int i = 0; i < 3; ++i) {
1817 QSGItem *item = findItem<QSGItem>(contentItem,
1818 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1823 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "sect_1");
1824 QCOMPARE(items.count(), 1);
1827 model.modifyItem(0, "One", "aaa");
1828 model.modifyItem(1, "One", "aaa");
1829 model.modifyItem(2, "One", "aaa");
1830 model.modifyItem(3, "Four", "aaa");
1831 model.modifyItem(4, "Four", "aaa");
1832 model.modifyItem(5, "Four", "aaa");
1833 model.modifyItem(6, "Five", "aaa");
1834 model.modifyItem(7, "Five", "aaa");
1835 model.modifyItem(8, "Five", "aaa");
1836 model.modifyItem(9, "Two", "aaa");
1837 model.modifyItem(10, "Two", "aaa");
1838 model.modifyItem(11, "Two", "aaa");
1839 QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_aaa").count(), 1);
1840 canvas->rootObject()->setProperty("sectionProperty", "name");
1841 // ensure view has settled.
1842 QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_Four").count(), 1);
1843 for (int i = 0; i < 4; ++i) {
1844 QSGItem *item = findItem<QSGItem>(contentItem,
1845 "sect_" + model.name(i*3));
1847 QTRY_COMPARE(item->y(), qreal(i*20*4));
1851 model.removeItems(10, 20);
1852 // ensure view has settled.
1853 QTRY_COMPARE(findItems<QSGItem>(contentItem, "wrapper").count(), 10);
1854 // Drag view up beyond bounds
1855 QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,20));
1857 QMouseEvent mv(QEvent::MouseMove, QPoint(20,0), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1858 QApplication::sendEvent(canvas, &mv);
1861 QMouseEvent mv(QEvent::MouseMove, QPoint(20,-50), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1862 QApplication::sendEvent(canvas, &mv);
1865 QMouseEvent mv(QEvent::MouseMove, QPoint(20,-200), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1866 QApplication::sendEvent(canvas, &mv);
1868 QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,-200));
1869 // view should settle back at 0
1870 QTRY_COMPARE(listview->contentY(), 0.0);
1875 void tst_QSGListView::sectionsPositioning()
1877 QSGView *canvas = createView();
1881 for (int i = 0; i < 30; i++)
1882 model.addItem("Item" + QString::number(i), QString::number(i/5));
1884 QDeclarativeContext *ctxt = canvas->rootContext();
1885 ctxt->setContextProperty("testModel", &model);
1887 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1888 qApp->processEvents();
1889 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart | QSGViewSection::NextLabelAtEnd)));
1891 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1892 QTRY_VERIFY(listview != 0);
1894 QSGItem *contentItem = listview->contentItem();
1895 QTRY_VERIFY(contentItem != 0);
1897 for (int i = 0; i < 3; ++i) {
1898 QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1900 QTRY_COMPARE(item->y(), qreal(i*20*6));
1903 QSGItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
1905 QCOMPARE(topItem->y(), 0.);
1907 QSGItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1908 QVERIFY(bottomItem);
1909 QCOMPARE(bottomItem->y(), 300.);
1911 // move down a little and check that section header is at top
1912 listview->setContentY(10);
1913 QCOMPARE(topItem->y(), 0.);
1915 // push the top header up
1916 listview->setContentY(110);
1917 topItem = findVisibleChild(contentItem, "sect_0"); // section header
1919 QCOMPARE(topItem->y(), 100.);
1921 QSGItem *item = findVisibleChild(contentItem, "sect_1");
1923 QCOMPARE(item->y(), 120.);
1925 bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1926 QVERIFY(bottomItem);
1927 QCOMPARE(bottomItem->y(), 410.);
1929 // Move past section 0
1930 listview->setContentY(120);
1931 topItem = findVisibleChild(contentItem, "sect_0"); // section header
1934 // Push section footer down
1935 listview->setContentY(70);
1936 bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1937 QVERIFY(bottomItem);
1938 QCOMPARE(bottomItem->y(), 380.);
1940 // Change current section
1941 listview->setContentY(10);
1942 model.modifyItem(0, "One", "aaa");
1943 model.modifyItem(1, "Two", "aaa");
1944 model.modifyItem(2, "Three", "aaa");
1945 model.modifyItem(3, "Four", "aaa");
1946 model.modifyItem(4, "Five", "aaa");
1949 QTRY_COMPARE(listview->currentSection(), QString("aaa"));
1951 for (int i = 0; i < 3; ++i) {
1952 QSGItem *item = findItem<QSGItem>(contentItem,
1953 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1955 QTRY_COMPARE(item->y(), qreal(i*20*6));
1958 topItem = findVisibleChild(contentItem, "sect_aaa"); // section header
1960 QCOMPARE(topItem->y(), 10.);
1962 // remove section boundary
1963 listview->setContentY(120);
1964 model.removeItem(5);
1965 QTRY_COMPARE(listview->count(), model.count());
1966 for (int i = 0; i < 3; ++i) {
1967 QSGItem *item = findItem<QSGItem>(contentItem,
1968 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1970 QTRY_COMPARE(item->y(), qreal(i*20*6));
1973 QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
1974 QCOMPARE(topItem->y(), 120.);
1975 QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
1976 QTRY_COMPARE(topItem->y(), 140.);
1978 // Change the next section
1979 listview->setContentY(0);
1980 bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1981 QVERIFY(bottomItem);
1982 QTRY_COMPARE(bottomItem->y(), 320.);
1984 model.modifyItem(14, "New", "new");
1986 QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
1987 QTRY_COMPARE(bottomItem->y(), 320.);
1989 // Turn sticky footer off
1990 listview->setContentY(50);
1991 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart)));
1992 item = findVisibleChild(contentItem, "sect_new"); // inline label restored
1993 QCOMPARE(item->y(), 360.);
1995 // Turn sticky header off
1996 listview->setContentY(50);
1997 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels)));
1998 item = findVisibleChild(contentItem, "sect_aaa"); // inline label restored
1999 QCOMPARE(item->y(), 20.);
2004 void tst_QSGListView::currentIndex_delayedItemCreation()
2006 QFETCH(bool, setCurrentToZero);
2008 QSGView *canvas = createView();
2012 // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2013 // (since the currentItem will have changed and that shares the same index)
2014 canvas->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2016 canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2017 qApp->processEvents();
2019 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2020 QTRY_VERIFY(listview != 0);
2022 QSGItem *contentItem = listview->contentItem();
2023 QTRY_VERIFY(contentItem != 0);
2025 QSignalSpy spy(listview, SIGNAL(currentIndexChanged()));
2026 QCOMPARE(listview->currentIndex(), 0);
2027 QTRY_COMPARE(spy.count(), 1);
2032 void tst_QSGListView::currentIndex_delayedItemCreation_data()
2034 QTest::addColumn<bool>("setCurrentToZero");
2036 QTest::newRow("set to 0") << true;
2037 QTest::newRow("don't set to 0") << false;
2040 void tst_QSGListView::currentIndex()
2043 for (int i = 0; i < 30; i++)
2044 model.addItem("Item" + QString::number(i), QString::number(i));
2046 QSGView *canvas = new QSGView(0);
2047 canvas->setGeometry(0,0,240,320);
2049 QDeclarativeContext *ctxt = canvas->rootContext();
2050 ctxt->setContextProperty("testModel", &model);
2051 ctxt->setContextProperty("testWrap", QVariant(false));
2053 QString filename(TESTDATA("listview-initCurrent.qml"));
2054 canvas->setSource(QUrl::fromLocalFile(filename));
2056 qApp->processEvents();
2058 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2059 QTRY_VERIFY(listview != 0);
2061 QSGItem *contentItem = listview->contentItem();
2062 QTRY_VERIFY(contentItem != 0);
2064 // current item should be 20th item at startup
2065 // and current item should be in view
2066 QCOMPARE(listview->currentIndex(), 20);
2067 QCOMPARE(listview->contentY(), 100.0);
2068 QCOMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 20));
2069 QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2072 listview->setCurrentIndex(0);
2073 QCOMPARE(listview->currentIndex(), 0);
2074 // confirm that the velocity is updated
2075 QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2077 listview->incrementCurrentIndex();
2078 QCOMPARE(listview->currentIndex(), 1);
2079 listview->decrementCurrentIndex();
2080 QCOMPARE(listview->currentIndex(), 0);
2082 listview->decrementCurrentIndex();
2083 QCOMPARE(listview->currentIndex(), 0);
2086 ctxt->setContextProperty("testWrap", QVariant(true));
2087 QVERIFY(listview->isWrapEnabled());
2089 listview->decrementCurrentIndex();
2090 QCOMPARE(listview->currentIndex(), model.count()-1);
2092 QTRY_COMPARE(listview->contentY(), 280.0);
2094 listview->incrementCurrentIndex();
2095 QCOMPARE(listview->currentIndex(), 0);
2097 QTRY_COMPARE(listview->contentY(), 0.0);
2100 // footer should become visible if it is out of view, and then current index is set to count-1
2101 canvas->rootObject()->setProperty("showFooter", true);
2102 QTRY_VERIFY(listview->footerItem());
2103 listview->setCurrentIndex(model.count()-2);
2104 QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2105 listview->setCurrentIndex(model.count()-1);
2106 QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2107 canvas->rootObject()->setProperty("showFooter", false);
2109 // header should become visible if it is out of view, and then current index is set to 0
2110 canvas->rootObject()->setProperty("showHeader", true);
2111 QTRY_VERIFY(listview->headerItem());
2112 listview->setCurrentIndex(1);
2113 QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2114 listview->setCurrentIndex(0);
2115 QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2116 canvas->rootObject()->setProperty("showHeader", false);
2121 canvas->requestActivateWindow();
2122 QTest::qWaitForWindowShown(canvas);
2123 QTRY_VERIFY(qGuiApp->focusWindow() == canvas);
2125 listview->setCurrentIndex(0);
2127 QTest::keyClick(canvas, Qt::Key_Down);
2128 QCOMPARE(listview->currentIndex(), 1);
2130 QTest::keyClick(canvas, Qt::Key_Up);
2131 QCOMPARE(listview->currentIndex(), 0);
2133 // hold down Key_Down
2134 for (int i=0; i<model.count()-1; i++) {
2135 QTest::simulateEvent(canvas, true, Qt::Key_Down, Qt::NoModifier, "", true);
2136 QTRY_COMPARE(listview->currentIndex(), i+1);
2138 QTest::keyRelease(canvas, Qt::Key_Down);
2139 QTRY_COMPARE(listview->currentIndex(), model.count()-1);
2140 QTRY_COMPARE(listview->contentY(), 280.0);
2143 for (int i=model.count()-1; i > 0; i--) {
2144 QTest::simulateEvent(canvas, true, Qt::Key_Up, Qt::NoModifier, "", true);
2145 QTRY_COMPARE(listview->currentIndex(), i-1);
2147 QTest::keyRelease(canvas, Qt::Key_Up);
2148 QTRY_COMPARE(listview->currentIndex(), 0);
2149 QTRY_COMPARE(listview->contentY(), 0.0);
2152 // turn off auto highlight
2153 listview->setHighlightFollowsCurrentItem(false);
2154 QVERIFY(listview->highlightFollowsCurrentItem() == false);
2156 QVERIFY(listview->highlightItem());
2157 qreal hlPos = listview->highlightItem()->y();
2159 listview->setCurrentIndex(4);
2160 QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2162 // insert item before currentIndex
2163 listview->setCurrentIndex(28);
2164 model.insertItem(0, "Foo", "1111");
2165 QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
2167 // check removing highlight by setting currentIndex to -1;
2168 listview->setCurrentIndex(-1);
2170 QCOMPARE(listview->currentIndex(), -1);
2171 QVERIFY(!listview->highlightItem());
2172 QVERIFY(!listview->currentItem());
2177 void tst_QSGListView::noCurrentIndex()
2180 for (int i = 0; i < 30; i++)
2181 model.addItem("Item" + QString::number(i), QString::number(i));
2183 QSGView *canvas = new QSGView(0);
2184 canvas->setGeometry(0,0,240,320);
2186 QDeclarativeContext *ctxt = canvas->rootContext();
2187 ctxt->setContextProperty("testModel", &model);
2189 QString filename(TESTDATA("listview-noCurrent.qml"));
2190 canvas->setSource(QUrl::fromLocalFile(filename));
2192 qApp->processEvents();
2194 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2195 QTRY_VERIFY(listview != 0);
2197 QSGItem *contentItem = listview->contentItem();
2198 QTRY_VERIFY(contentItem != 0);
2200 // current index should be -1 at startup
2201 // and we should not have a currentItem or highlightItem
2202 QCOMPARE(listview->currentIndex(), -1);
2203 QCOMPARE(listview->contentY(), 0.0);
2204 QVERIFY(!listview->highlightItem());
2205 QVERIFY(!listview->currentItem());
2207 listview->setCurrentIndex(2);
2208 QCOMPARE(listview->currentIndex(), 2);
2209 QVERIFY(listview->highlightItem());
2210 QVERIFY(listview->currentItem());
2215 void tst_QSGListView::itemList()
2217 QSGView *canvas = createView();
2219 canvas->setSource(QUrl::fromLocalFile(TESTDATA("itemlist.qml")));
2220 qApp->processEvents();
2222 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
2223 QTRY_VERIFY(listview != 0);
2225 QSGItem *contentItem = listview->contentItem();
2226 QTRY_VERIFY(contentItem != 0);
2228 QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
2229 QTRY_VERIFY(model != 0);
2231 QTRY_VERIFY(model->count() == 3);
2232 QTRY_COMPARE(listview->currentIndex(), 0);
2234 QSGItem *item = findItem<QSGItem>(contentItem, "item1");
2236 QTRY_COMPARE(item->x(), 0.0);
2237 QCOMPARE(item->height(), listview->height());
2239 QSGText *text = findItem<QSGText>(contentItem, "text1");
2241 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2243 listview->setCurrentIndex(2);
2245 item = findItem<QSGItem>(contentItem, "item3");
2247 QTRY_COMPARE(item->x(), 480.0);
2249 text = findItem<QSGText>(contentItem, "text3");
2251 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
2256 void tst_QSGListView::cacheBuffer()
2258 QSGView *canvas = createView();
2261 for (int i = 0; i < 30; i++)
2262 model.addItem("Item" + QString::number(i), "");
2264 QDeclarativeContext *ctxt = canvas->rootContext();
2265 ctxt->setContextProperty("testModel", &model);
2267 TestObject *testObject = new TestObject;
2268 ctxt->setContextProperty("testObject", testObject);
2270 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2271 qApp->processEvents();
2273 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2274 QTRY_VERIFY(listview != 0);
2276 QSGItem *contentItem = listview->contentItem();
2277 QTRY_VERIFY(contentItem != 0);
2278 QTRY_VERIFY(listview->delegate() != 0);
2279 QTRY_VERIFY(listview->model() != 0);
2280 QTRY_VERIFY(listview->highlight() != 0);
2282 // Confirm items positioned correctly
2283 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2284 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2285 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2286 if (!item) qWarning() << "Item" << i << "not found";
2288 QTRY_VERIFY(item->y() == i*20);
2291 testObject->setCacheBuffer(400);
2292 QTRY_VERIFY(listview->cacheBuffer() == 400);
2294 int newItemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2295 QTRY_VERIFY(newItemCount > itemCount);
2297 // Confirm items positioned correctly
2298 for (int i = 0; i < model.count() && i < newItemCount; ++i) {
2299 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2300 if (!item) qWarning() << "Item" << i << "not found";
2302 QTRY_VERIFY(item->y() == i*20);
2309 void tst_QSGListView::positionViewAtIndex()
2311 QSGView *canvas = createView();
2314 for (int i = 0; i < 40; i++)
2315 model.addItem("Item" + QString::number(i), "");
2317 QDeclarativeContext *ctxt = canvas->rootContext();
2318 ctxt->setContextProperty("testModel", &model);
2320 TestObject *testObject = new TestObject;
2321 ctxt->setContextProperty("testObject", testObject);
2323 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2324 qApp->processEvents();
2326 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2327 QTRY_VERIFY(listview != 0);
2329 QSGItem *contentItem = listview->contentItem();
2330 QTRY_VERIFY(contentItem != 0);
2332 // Confirm items positioned correctly
2333 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2334 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2335 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2336 if (!item) qWarning() << "Item" << i << "not found";
2338 QTRY_COMPARE(item->y(), i*20.);
2341 // Position on a currently visible item
2342 listview->positionViewAtIndex(3, QSGListView::Beginning);
2343 QTRY_COMPARE(listview->contentY(), 60.);
2345 // Confirm items positioned correctly
2346 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2347 for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
2348 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2349 if (!item) qWarning() << "Item" << i << "not found";
2351 QTRY_COMPARE(item->y(), i*20.);
2354 // Position on an item beyond the visible items
2355 listview->positionViewAtIndex(22, QSGListView::Beginning);
2356 QTRY_COMPARE(listview->contentY(), 440.);
2358 // Confirm items positioned correctly
2359 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2360 for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
2361 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2362 if (!item) qWarning() << "Item" << i << "not found";
2364 QTRY_COMPARE(item->y(), i*20.);
2367 // Position on an item that would leave empty space if positioned at the top
2368 listview->positionViewAtIndex(28, QSGListView::Beginning);
2369 QTRY_COMPARE(listview->contentY(), 480.);
2371 // Confirm items positioned correctly
2372 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2373 for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
2374 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2375 if (!item) qWarning() << "Item" << i << "not found";
2377 QTRY_COMPARE(item->y(), i*20.);
2380 // Position at the beginning again
2381 listview->positionViewAtIndex(0, QSGListView::Beginning);
2382 QTRY_COMPARE(listview->contentY(), 0.);
2384 // Confirm items positioned correctly
2385 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2386 for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
2387 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2388 if (!item) qWarning() << "Item" << i << "not found";
2390 QTRY_COMPARE(item->y(), i*20.);
2393 // Position at End using last index
2394 listview->positionViewAtIndex(model.count()-1, QSGListView::End);
2395 QTRY_COMPARE(listview->contentY(), 480.);
2397 // Confirm items positioned correctly
2398 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2399 for (int i = 24; i < model.count(); ++i) {
2400 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2401 if (!item) qWarning() << "Item" << i << "not found";
2403 QTRY_COMPARE(item->y(), i*20.);
2407 listview->positionViewAtIndex(20, QSGListView::End);
2408 QTRY_COMPARE(listview->contentY(), 100.);
2410 // Position in Center
2411 listview->positionViewAtIndex(15, QSGListView::Center);
2412 QTRY_COMPARE(listview->contentY(), 150.);
2414 // Ensure at least partially visible
2415 listview->positionViewAtIndex(15, QSGListView::Visible);
2416 QTRY_COMPARE(listview->contentY(), 150.);
2418 listview->setContentY(302);
2419 listview->positionViewAtIndex(15, QSGListView::Visible);
2420 QTRY_COMPARE(listview->contentY(), 302.);
2422 listview->setContentY(320);
2423 listview->positionViewAtIndex(15, QSGListView::Visible);
2424 QTRY_COMPARE(listview->contentY(), 300.);
2426 listview->setContentY(85);
2427 listview->positionViewAtIndex(20, QSGListView::Visible);
2428 QTRY_COMPARE(listview->contentY(), 85.);
2430 listview->setContentY(75);
2431 listview->positionViewAtIndex(20, QSGListView::Visible);
2432 QTRY_COMPARE(listview->contentY(), 100.);
2434 // Ensure completely visible
2435 listview->setContentY(120);
2436 listview->positionViewAtIndex(20, QSGListView::Contain);
2437 QTRY_COMPARE(listview->contentY(), 120.);
2439 listview->setContentY(302);
2440 listview->positionViewAtIndex(15, QSGListView::Contain);
2441 QTRY_COMPARE(listview->contentY(), 300.);
2443 listview->setContentY(85);
2444 listview->positionViewAtIndex(20, QSGListView::Contain);
2445 QTRY_COMPARE(listview->contentY(), 100.);
2447 // positionAtBeginnging
2448 listview->positionViewAtBeginning();
2449 QTRY_COMPARE(listview->contentY(), 0.);
2451 listview->setContentY(80);
2452 canvas->rootObject()->setProperty("showHeader", true);
2453 listview->positionViewAtBeginning();
2454 QTRY_COMPARE(listview->contentY(), -30.);
2457 listview->positionViewAtEnd();
2458 QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
2460 listview->setContentY(80);
2461 canvas->rootObject()->setProperty("showFooter", true);
2462 listview->positionViewAtEnd();
2463 QTRY_COMPARE(listview->contentY(), 510.);
2465 // set current item to outside visible view, position at beginning
2466 // and ensure highlight moves to current item
2467 listview->setCurrentIndex(1);
2468 listview->positionViewAtBeginning();
2469 QTRY_COMPARE(listview->contentY(), -30.);
2470 QVERIFY(listview->highlightItem());
2471 QCOMPARE(listview->highlightItem()->y(), 20.);
2477 void tst_QSGListView::resetModel()
2479 QSGView *canvas = createView();
2481 QStringList strings;
2482 strings << "one" << "two" << "three";
2483 QStringListModel model(strings);
2485 QDeclarativeContext *ctxt = canvas->rootContext();
2486 ctxt->setContextProperty("testModel", &model);
2488 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
2489 qApp->processEvents();
2491 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2492 QTRY_VERIFY(listview != 0);
2494 QSGItem *contentItem = listview->contentItem();
2495 QTRY_VERIFY(contentItem != 0);
2497 QTRY_COMPARE(listview->count(), model.rowCount());
2499 for (int i = 0; i < model.rowCount(); ++i) {
2500 QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2501 QTRY_VERIFY(display != 0);
2502 QTRY_COMPARE(display->text(), strings.at(i));
2506 strings << "four" << "five" << "six" << "seven";
2507 model.setStringList(strings);
2509 QTRY_COMPARE(listview->count(), model.rowCount());
2511 for (int i = 0; i < model.rowCount(); ++i) {
2512 QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2513 QTRY_VERIFY(display != 0);
2514 QTRY_COMPARE(display->text(), strings.at(i));
2520 void tst_QSGListView::propertyChanges()
2522 QSGView *canvas = createView();
2523 QTRY_VERIFY(canvas);
2524 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2526 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2527 QTRY_VERIFY(listView);
2529 QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
2530 QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
2531 QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
2532 QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
2533 QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
2534 QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
2535 QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
2537 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
2538 QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
2539 QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
2540 QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::ApplyRange);
2541 QTRY_COMPARE(listView->isWrapEnabled(), true);
2542 QTRY_COMPARE(listView->cacheBuffer(), 10);
2543 QTRY_COMPARE(listView->snapMode(), QSGListView::SnapToItem);
2545 listView->setHighlightFollowsCurrentItem(false);
2546 listView->setPreferredHighlightBegin(1.0);
2547 listView->setPreferredHighlightEnd(1.0);
2548 listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2549 listView->setWrapEnabled(false);
2550 listView->setCacheBuffer(3);
2551 listView->setSnapMode(QSGListView::SnapOneItem);
2553 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
2554 QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
2555 QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
2556 QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
2557 QTRY_COMPARE(listView->isWrapEnabled(), false);
2558 QTRY_COMPARE(listView->cacheBuffer(), 3);
2559 QTRY_COMPARE(listView->snapMode(), QSGListView::SnapOneItem);
2561 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2562 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2563 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2564 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2565 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2566 QTRY_COMPARE(cacheBufferSpy.count(),1);
2567 QTRY_COMPARE(snapModeSpy.count(),1);
2569 listView->setHighlightFollowsCurrentItem(false);
2570 listView->setPreferredHighlightBegin(1.0);
2571 listView->setPreferredHighlightEnd(1.0);
2572 listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2573 listView->setWrapEnabled(false);
2574 listView->setCacheBuffer(3);
2575 listView->setSnapMode(QSGListView::SnapOneItem);
2577 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2578 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2579 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2580 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2581 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2582 QTRY_COMPARE(cacheBufferSpy.count(),1);
2583 QTRY_COMPARE(snapModeSpy.count(),1);
2588 void tst_QSGListView::componentChanges()
2590 QSGView *canvas = createView();
2591 QTRY_VERIFY(canvas);
2592 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2594 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2595 QTRY_VERIFY(listView);
2597 QDeclarativeComponent component(canvas->engine());
2598 component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
2600 QDeclarativeComponent delegateComponent(canvas->engine());
2601 delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
2603 QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
2604 QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
2605 QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
2606 QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
2608 listView->setHighlight(&component);
2609 listView->setHeader(&component);
2610 listView->setFooter(&component);
2611 listView->setDelegate(&delegateComponent);
2613 QTRY_COMPARE(listView->highlight(), &component);
2614 QTRY_COMPARE(listView->header(), &component);
2615 QTRY_COMPARE(listView->footer(), &component);
2616 QTRY_COMPARE(listView->delegate(), &delegateComponent);
2618 QTRY_COMPARE(highlightSpy.count(),1);
2619 QTRY_COMPARE(delegateSpy.count(),1);
2620 QTRY_COMPARE(headerSpy.count(),1);
2621 QTRY_COMPARE(footerSpy.count(),1);
2623 listView->setHighlight(&component);
2624 listView->setHeader(&component);
2625 listView->setFooter(&component);
2626 listView->setDelegate(&delegateComponent);
2628 QTRY_COMPARE(highlightSpy.count(),1);
2629 QTRY_COMPARE(delegateSpy.count(),1);
2630 QTRY_COMPARE(headerSpy.count(),1);
2631 QTRY_COMPARE(footerSpy.count(),1);
2636 void tst_QSGListView::modelChanges()
2638 QSGView *canvas = createView();
2639 QTRY_VERIFY(canvas);
2640 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2642 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2643 QTRY_VERIFY(listView);
2645 QDeclarativeListModel *alternateModel = canvas->rootObject()->findChild<QDeclarativeListModel*>("alternateModel");
2646 QTRY_VERIFY(alternateModel);
2647 QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
2648 QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
2650 listView->setModel(modelVariant);
2651 QTRY_COMPARE(listView->model(), modelVariant);
2652 QTRY_COMPARE(modelSpy.count(),1);
2654 listView->setModel(modelVariant);
2655 QTRY_COMPARE(modelSpy.count(),1);
2657 listView->setModel(QVariant());
2658 QTRY_COMPARE(modelSpy.count(),2);
2663 void tst_QSGListView::QTBUG_9791()
2665 QSGView *canvas = createView();
2667 canvas->setSource(QUrl::fromLocalFile(TESTDATA("strictlyenforcerange.qml")));
2668 qApp->processEvents();
2670 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2671 QTRY_VERIFY(listview != 0);
2673 QSGItem *contentItem = listview->contentItem();
2674 QTRY_VERIFY(contentItem != 0);
2675 QTRY_VERIFY(listview->delegate() != 0);
2676 QTRY_VERIFY(listview->model() != 0);
2678 QMetaObject::invokeMethod(listview, "fillModel");
2679 qApp->processEvents();
2681 // Confirm items positioned correctly
2682 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2683 QCOMPARE(itemCount, 3);
2685 for (int i = 0; i < itemCount; ++i) {
2686 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2687 if (!item) qWarning() << "Item" << i << "not found";
2689 QTRY_COMPARE(item->x(), i*300.0);
2692 // check that view is positioned correctly
2693 QTRY_COMPARE(listview->contentX(), 590.0);
2698 void tst_QSGListView::manualHighlight()
2700 QSGView *canvas = new QSGView(0);
2701 canvas->setGeometry(0,0,240,320);
2703 QString filename(TESTDATA("manual-highlight.qml"));
2704 canvas->setSource(QUrl::fromLocalFile(filename));
2706 qApp->processEvents();
2708 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2709 QTRY_VERIFY(listview != 0);
2711 QSGItem *contentItem = listview->contentItem();
2712 QTRY_VERIFY(contentItem != 0);
2714 QTRY_COMPARE(listview->currentIndex(), 0);
2715 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
2716 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2718 listview->setCurrentIndex(2);
2720 QTRY_COMPARE(listview->currentIndex(), 2);
2721 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2722 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2725 listview->positionViewAtIndex(3, QSGListView::Contain);
2727 QTRY_COMPARE(listview->currentIndex(), 2);
2728 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2729 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2734 void tst_QSGListView::QTBUG_11105()
2736 QSGView *canvas = createView();
2739 for (int i = 0; i < 30; i++)
2740 model.addItem("Item" + QString::number(i), "");
2742 QDeclarativeContext *ctxt = canvas->rootContext();
2743 ctxt->setContextProperty("testModel", &model);
2745 TestObject *testObject = new TestObject;
2746 ctxt->setContextProperty("testObject", testObject);
2748 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2749 qApp->processEvents();
2751 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2752 QTRY_VERIFY(listview != 0);
2754 QSGItem *contentItem = listview->contentItem();
2755 QTRY_VERIFY(contentItem != 0);
2757 // Confirm items positioned correctly
2758 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2759 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2760 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2761 if (!item) qWarning() << "Item" << i << "not found";
2763 QTRY_VERIFY(item->y() == i*20);
2766 listview->positionViewAtIndex(20, QSGListView::Beginning);
2767 QCOMPARE(listview->contentY(), 280.);
2770 for (int i = 0; i < 5; i++)
2771 model2.addItem("Item" + QString::number(i), "");
2773 ctxt->setContextProperty("testModel", &model2);
2775 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2776 QCOMPARE(itemCount, 5);
2782 void tst_QSGListView::header()
2784 QFETCH(QSGListView::Orientation, orientation);
2785 QFETCH(Qt::LayoutDirection, layoutDirection);
2786 QFETCH(QPointF, initialHeaderPos);
2787 QFETCH(QPointF, firstDelegatePos);
2788 QFETCH(QPointF, initialContentPos);
2789 QFETCH(QPointF, changedHeaderPos);
2790 QFETCH(QPointF, changedContentPos);
2791 QFETCH(QPointF, resizeContentPos);
2794 for (int i = 0; i < 30; i++)
2795 model.addItem("Item" + QString::number(i), "");
2797 QSGView *canvas = createView();
2798 canvas->rootContext()->setContextProperty("testModel", &model);
2799 canvas->rootContext()->setContextProperty("initialViewWidth", 240);
2800 canvas->rootContext()->setContextProperty("initialViewHeight", 320);
2801 canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2803 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2804 QTRY_VERIFY(listview != 0);
2805 listview->setOrientation(orientation);
2806 listview->setLayoutDirection(layoutDirection);
2808 QSGItem *contentItem = listview->contentItem();
2809 QTRY_VERIFY(contentItem != 0);
2811 QSGText *header = findItem<QSGText>(contentItem, "header");
2814 QVERIFY(header == listview->headerItem());
2816 QCOMPARE(header->width(), 100.);
2817 QCOMPARE(header->height(), 30.);
2818 QCOMPARE(header->pos(), initialHeaderPos);
2819 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2821 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2823 QCOMPARE(item->pos(), firstDelegatePos);
2826 QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
2828 for (int i = 0; i < 30; i++)
2829 model.addItem("Item" + QString::number(i), "");
2831 QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
2832 QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
2834 QCOMPARE(headerItemSpy.count(), 1);
2836 header = findItem<QSGText>(contentItem, "header");
2838 header = findItem<QSGText>(contentItem, "header2");
2841 QVERIFY(header == listview->headerItem());
2843 QCOMPARE(header->pos(), changedHeaderPos);
2844 QCOMPARE(header->width(), 50.);
2845 QCOMPARE(header->height(), 20.);
2846 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2847 QCOMPARE(item->pos(), firstDelegatePos);
2852 // QTBUG-21207 header should become visible if view resizes from initial empty size
2854 canvas = createView();
2855 canvas->rootContext()->setContextProperty("testModel", &model);
2856 canvas->rootContext()->setContextProperty("initialViewWidth", 0.0);
2857 canvas->rootContext()->setContextProperty("initialViewHeight", 0.0);
2858 canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2860 listview = findItem<QSGListView>(canvas->rootObject(), "list");
2861 QTRY_VERIFY(listview != 0);
2862 listview->setOrientation(orientation);
2863 listview->setLayoutDirection(layoutDirection);
2865 listview->setWidth(240);
2866 listview->setHeight(320);
2867 QTRY_COMPARE(listview->headerItem()->pos(), initialHeaderPos);
2868 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2874 void tst_QSGListView::header_data()
2876 QTest::addColumn<QSGListView::Orientation>("orientation");
2877 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2878 QTest::addColumn<QPointF>("initialHeaderPos");
2879 QTest::addColumn<QPointF>("changedHeaderPos");
2880 QTest::addColumn<QPointF>("initialContentPos");
2881 QTest::addColumn<QPointF>("changedContentPos");
2882 QTest::addColumn<QPointF>("firstDelegatePos");
2883 QTest::addColumn<QPointF>("resizeContentPos");
2885 // header1 = 100 x 30
2886 // header2 = 50 x 20
2887 // delegates = 240 x 20
2890 // header above items, top left
2891 QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
2899 // header above items, top right
2900 QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2908 // header to left of items
2909 QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2917 // header to right of items
2918 QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2921 << QPointF(-240 + 100, 0)
2922 << QPointF(-240 + 50, 0)
2924 << QPointF(-240 + 40, 0);
2927 void tst_QSGListView::header_delayItemCreation()
2929 QSGView *canvas = createView();
2933 canvas->rootContext()->setContextProperty("setCurrentToZero", false);
2934 canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2935 qApp->processEvents();
2937 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2938 QTRY_VERIFY(listview != 0);
2940 QSGItem *contentItem = listview->contentItem();
2941 QTRY_VERIFY(contentItem != 0);
2943 QSGText *header = findItem<QSGText>(contentItem, "header");
2945 QCOMPARE(header->y(), -header->height());
2947 QCOMPARE(listview->contentY(), -header->height());
2950 QTRY_COMPARE(header->y(), -header->height());
2955 void tst_QSGListView::footer()
2957 QFETCH(QSGListView::Orientation, orientation);
2958 QFETCH(Qt::LayoutDirection, layoutDirection);
2959 QFETCH(QPointF, initialFooterPos);
2960 QFETCH(QPointF, firstDelegatePos);
2961 QFETCH(QPointF, initialContentPos);
2962 QFETCH(QPointF, changedFooterPos);
2963 QFETCH(QPointF, changedContentPos);
2964 QFETCH(QPointF, resizeContentPos);
2966 QSGView *canvas = createView();
2969 for (int i = 0; i < 3; i++)
2970 model.addItem("Item" + QString::number(i), "");
2972 QDeclarativeContext *ctxt = canvas->rootContext();
2973 ctxt->setContextProperty("testModel", &model);
2975 canvas->setSource(QUrl::fromLocalFile(TESTDATA("footer.qml")));
2977 qApp->processEvents();
2979 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2980 QTRY_VERIFY(listview != 0);
2981 listview->setOrientation(orientation);
2982 listview->setLayoutDirection(layoutDirection);
2984 QSGItem *contentItem = listview->contentItem();
2985 QTRY_VERIFY(contentItem != 0);
2987 QSGText *footer = findItem<QSGText>(contentItem, "footer");
2990 QVERIFY(footer == listview->footerItem());
2992 QCOMPARE(footer->pos(), initialFooterPos);
2993 QCOMPARE(footer->width(), 100.);
2994 QCOMPARE(footer->height(), 30.);
2995 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2997 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2999 QCOMPARE(item->pos(), firstDelegatePos);
3002 model.removeItem(1);
3004 if (orientation == QSGListView::Vertical) {
3005 QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20); // delegate height = 20
3007 QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
3008 initialFooterPos.x() - 40 : initialFooterPos.x() + 40); // delegate width = 40
3014 QPointF posWhenNoItems(0, 0);
3015 if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
3016 posWhenNoItems.setX(-100);
3017 QTRY_COMPARE(footer->pos(), posWhenNoItems);
3019 // if header is present, it's at a negative pos, so the footer should not move
3020 canvas->rootObject()->setProperty("showHeader", true);
3021 QTRY_COMPARE(footer->pos(), posWhenNoItems);
3022 canvas->rootObject()->setProperty("showHeader", false);
3025 for (int i = 0; i < 30; i++)
3026 model.addItem("Item" + QString::number(i), "");
3028 QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
3029 QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
3031 QCOMPARE(footerItemSpy.count(), 1);
3033 footer = findItem<QSGText>(contentItem, "footer");
3035 footer = findItem<QSGText>(contentItem, "footer2");
3038 QVERIFY(footer == listview->footerItem());
3040 QCOMPARE(footer->pos(), changedFooterPos);
3041 QCOMPARE(footer->width(), 50.);
3042 QCOMPARE(footer->height(), 20.);
3043 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3045 item = findItem<QSGItem>(contentItem, "wrapper", 0);
3047 QCOMPARE(item->pos(), firstDelegatePos);
3049 listview->positionViewAtEnd();
3050 footer->setHeight(10);
3051 footer->setWidth(40);
3052 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3057 void tst_QSGListView::footer_data()
3059 QTest::addColumn<QSGListView::Orientation>("orientation");
3060 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3061 QTest::addColumn<QPointF>("initialFooterPos");
3062 QTest::addColumn<QPointF>("changedFooterPos");
3063 QTest::addColumn<QPointF>("initialContentPos");
3064 QTest::addColumn<QPointF>("changedContentPos");
3065 QTest::addColumn<QPointF>("firstDelegatePos");
3066 QTest::addColumn<QPointF>("resizeContentPos");
3068 // footer1 = 100 x 30
3069 // footer2 = 50 x 20
3070 // delegates = 40 x 20
3072 // view height = 320
3074 // footer below items, bottom left
3075 QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
3076 << QPointF(0, 3 * 20)
3077 << QPointF(0, 30 * 20) // added 30 items
3081 << QPointF(0, 30 * 20 - 320 + 10);
3083 // footer below items, bottom right
3084 QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
3085 << QPointF(0, 3 * 20)
3086 << QPointF(0, 30 * 20)
3090 << QPointF(0, 30 * 20 - 320 + 10);
3092 // footer to right of items
3093 QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
3094 << QPointF(40 * 3, 0)
3095 << QPointF(40 * 30, 0)
3099 << QPointF(40 * 30 - 240 + 40, 0);
3101 // footer to left of items
3102 QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
3103 << QPointF(-(40 * 3) - 100, 0)
3104 << QPointF(-(40 * 30) - 50, 0) // 50 = new footer width
3108 << QPointF(-(40 * 30) - 40, 0);
3111 class LVAccessor : public QSGListView
3114 qreal minY() const { return minYExtent(); }
3115 qreal maxY() const { return maxYExtent(); }
3116 qreal minX() const { return minXExtent(); }
3117 qreal maxX() const { return maxXExtent(); }
3120 void tst_QSGListView::headerFooter()
3124 QSGView *canvas = createView();
3127 QDeclarativeContext *ctxt = canvas->rootContext();
3128 ctxt->setContextProperty("testModel", &model);
3130 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3131 qApp->processEvents();
3133 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3134 QTRY_VERIFY(listview != 0);
3136 QSGItem *contentItem = listview->contentItem();
3137 QTRY_VERIFY(contentItem != 0);
3139 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3141 QCOMPARE(header->y(), -header->height());
3143 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3145 QCOMPARE(footer->y(), 0.);
3147 QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), header->height());
3148 QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), header->height());
3154 QSGView *canvas = createView();
3157 QDeclarativeContext *ctxt = canvas->rootContext();
3158 ctxt->setContextProperty("testModel", &model);
3160 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3161 canvas->rootObject()->setProperty("horizontal", true);
3162 qApp->processEvents();
3164 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3165 QTRY_VERIFY(listview != 0);
3167 QSGItem *contentItem = listview->contentItem();
3168 QTRY_VERIFY(contentItem != 0);
3170 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3172 QCOMPARE(header->x(), -header->width());
3174 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3176 QCOMPARE(footer->x(), 0.);
3178 QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), header->width());
3179 QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), header->width());
3185 QSGView *canvas = createView();
3188 QDeclarativeContext *ctxt = canvas->rootContext();
3189 ctxt->setContextProperty("testModel", &model);
3191 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3192 canvas->rootObject()->setProperty("horizontal", true);
3193 canvas->rootObject()->setProperty("rtl", true);
3194 qApp->processEvents();
3196 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3197 QTRY_VERIFY(listview != 0);
3199 QSGItem *contentItem = listview->contentItem();
3200 QTRY_VERIFY(contentItem != 0);
3202 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3204 QCOMPARE(header->x(), 0.);
3206 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3208 QCOMPARE(footer->x(), -footer->width());
3210 QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240. - header->width());
3211 QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240. - header->width());
3217 void tst_QSGListView::resizeView()
3219 QSGView *canvas = createView();
3222 for (int i = 0; i < 40; i++)
3223 model.addItem("Item" + QString::number(i), "");
3225 QDeclarativeContext *ctxt = canvas->rootContext();
3226 ctxt->setContextProperty("testModel", &model);
3228 TestObject *testObject = new TestObject;
3229 ctxt->setContextProperty("testObject", testObject);
3231 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3232 qApp->processEvents();
3234 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3235 QTRY_VERIFY(listview != 0);
3237 QSGItem *contentItem = listview->contentItem();
3238 QTRY_VERIFY(contentItem != 0);
3240 // Confirm items positioned correctly
3241 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3242 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3243 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3244 if (!item) qWarning() << "Item" << i << "not found";
3246 QTRY_COMPARE(item->y(), i*20.);
3249 QVariant heightRatio;
3250 QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3251 QCOMPARE(heightRatio.toReal(), 0.4);
3253 listview->setHeight(200);
3255 QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3256 QCOMPARE(heightRatio.toReal(), 0.25);
3262 void tst_QSGListView::resizeViewAndRepaint()
3264 QSGView *canvas = createView();
3268 for (int i = 0; i < 40; i++)
3269 model.addItem("Item" + QString::number(i), "");
3271 QDeclarativeContext *ctxt = canvas->rootContext();
3272 ctxt->setContextProperty("testModel", &model);
3273 ctxt->setContextProperty("initialHeight", 100);
3275 canvas->setSource(QUrl::fromLocalFile(TESTDATA("resizeview.qml")));
3276 qApp->processEvents();
3278 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3279 QTRY_VERIFY(listview != 0);
3280 QSGItem *contentItem = listview->contentItem();
3281 QTRY_VERIFY(contentItem != 0);
3283 // item at index 10 should not be currently visible
3284 QVERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3286 listview->setHeight(320);
3287 QTRY_VERIFY(findItem<QSGItem>(contentItem, "wrapper", 10));
3289 listview->setHeight(100);
3290 QTRY_VERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3295 void tst_QSGListView::sizeLessThan1()
3297 QSGView *canvas = createView();
3300 for (int i = 0; i < 30; i++)
3301 model.addItem("Item" + QString::number(i), "");
3303 QDeclarativeContext *ctxt = canvas->rootContext();
3304 ctxt->setContextProperty("testModel", &model);
3306 TestObject *testObject = new TestObject;
3307 ctxt->setContextProperty("testObject", testObject);
3309 canvas->setSource(QUrl::fromLocalFile(TESTDATA("sizelessthan1.qml")));
3310 qApp->processEvents();
3312 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3313 QTRY_VERIFY(listview != 0);
3315 QSGItem *contentItem = listview->contentItem();
3316 QTRY_VERIFY(contentItem != 0);
3318 // Confirm items positioned correctly
3319 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3320 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3321 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3322 if (!item) qWarning() << "Item" << i << "not found";
3324 QTRY_COMPARE(item->y(), i*0.5);
3331 void tst_QSGListView::QTBUG_14821()
3333 QSGView *canvas = createView();
3335 canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug14821.qml")));
3336 qApp->processEvents();
3338 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3339 QVERIFY(listview != 0);
3341 QSGItem *contentItem = listview->contentItem();
3342 QVERIFY(contentItem != 0);
3344 listview->decrementCurrentIndex();
3345 QCOMPARE(listview->currentIndex(), 99);
3347 listview->incrementCurrentIndex();
3348 QCOMPARE(listview->currentIndex(), 0);
3353 void tst_QSGListView::resizeDelegate()
3355 QSGView *canvas = createView();
3358 QStringList strings;
3359 for (int i = 0; i < 30; ++i)
3360 strings << QString::number(i);
3361 QStringListModel model(strings);
3363 QDeclarativeContext *ctxt = canvas->rootContext();
3364 ctxt->setContextProperty("testModel", &model);
3366 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3367 qApp->processEvents();
3369 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3370 QVERIFY(listview != 0);
3372 QSGItem *contentItem = listview->contentItem();
3373 QVERIFY(contentItem != 0);
3375 QCOMPARE(listview->count(), model.rowCount());
3377 listview->setCurrentIndex(25);
3378 listview->setContentY(0);
3381 for (int i = 0; i < 16; ++i) {
3382 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3384 QCOMPARE(item->y(), i*20.0);
3387 QCOMPARE(listview->currentItem()->y(), 500.0);
3388 QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
3390 canvas->rootObject()->setProperty("delegateHeight", 30);
3393 for (int i = 0; i < 11; ++i) {
3394 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3396 QTRY_COMPARE(item->y(), i*30.0);
3399 QTRY_COMPARE(listview->currentItem()->y(), 750.0);
3400 QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
3402 listview->setCurrentIndex(1);
3403 listview->positionViewAtIndex(25, QSGListView::Beginning);
3404 listview->positionViewAtIndex(5, QSGListView::Beginning);
3406 for (int i = 5; i < 16; ++i) {
3407 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3409 QCOMPARE(item->y(), i*30.0);
3412 QTRY_COMPARE(listview->currentItem()->y(), 30.0);
3413 QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
3415 canvas->rootObject()->setProperty("delegateHeight", 20);
3418 for (int i = 5; i < 11; ++i) {
3419 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3421 QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
3424 QTRY_COMPARE(listview->currentItem()->y(), 70.0);
3425 QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
3430 void tst_QSGListView::resizeFirstDelegate()
3432 // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
3433 // and other delegates have height > 0
3435 QSGView *canvas = createView();
3438 // bug only occurs when all items in the model are visible
3440 for (int i = 0; i < 10; i++)
3441 model.addItem("Item" + QString::number(i), "");
3443 QDeclarativeContext *ctxt = canvas->rootContext();
3444 ctxt->setContextProperty("testModel", &model);
3446 TestObject *testObject = new TestObject;
3447 ctxt->setContextProperty("testObject", testObject);
3449 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3450 qApp->processEvents();
3452 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3453 QVERIFY(listview != 0);
3455 QSGItem *contentItem = listview->contentItem();
3456 QVERIFY(contentItem != 0);
3459 for (int i = 0; i < model.count(); ++i) {
3460 item = findItem<QSGItem>(contentItem, "wrapper", i);
3462 QCOMPARE(item->y(), i*20.0);
3465 item = findItem<QSGItem>(contentItem, "wrapper", 0);
3468 // check the content y has not jumped up and down
3469 QCOMPARE(listview->contentY(), 0.0);
3470 QSignalSpy spy(listview, SIGNAL(contentYChanged()));
3472 QCOMPARE(spy.count(), 0);
3474 for (int i = 1; i < model.count(); ++i) {
3475 item = findItem<QSGItem>(contentItem, "wrapper", i);
3477 QTRY_COMPARE(item->y(), (i-1)*20.0);
3484 void tst_QSGListView::QTBUG_16037()
3486 QSGView *canvas = createView();
3489 canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug16037.qml")));
3490 qApp->processEvents();
3492 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "listview");
3493 QTRY_VERIFY(listview != 0);
3495 QVERIFY(listview->contentHeight() <= 0.0);
3497 QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
3499 QTRY_COMPARE(listview->contentHeight(), 80.0);
3504 void tst_QSGListView::indexAt()
3506 QSGView *canvas = createView();
3509 for (int i = 0; i < 30; i++)
3510 model.addItem("Item" + QString::number(i), "");
3512 QDeclarativeContext *ctxt = canvas->rootContext();
3513 ctxt->setContextProperty("testModel", &model);
3515 TestObject *testObject = new TestObject;
3516 ctxt->setContextProperty("testObject", testObject);
3518 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3519 qApp->processEvents();
3521 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3522 QTRY_VERIFY(listview != 0);
3524 QSGItem *contentItem = listview->contentItem();
3525 QTRY_VERIFY(contentItem != 0);
3527 QCOMPARE(listview->indexAt(0,0), 0);
3528 QCOMPARE(listview->indexAt(0,19), 0);
3529 QCOMPARE(listview->indexAt(239,19), 0);
3530 QCOMPARE(listview->indexAt(0,20), 1);
3531 QCOMPARE(listview->indexAt(240,20), -1);
3537 void tst_QSGListView::incrementalModel()
3539 QSGView *canvas = createView();
3541 IncrementalModel model;
3542 QDeclarativeContext *ctxt = canvas->rootContext();
3543 ctxt->setContextProperty("testModel", &model);
3545 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3546 qApp->processEvents();
3548 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3549 QTRY_VERIFY(listview != 0);
3551 QSGItem *contentItem = listview->contentItem();
3552 QTRY_VERIFY(contentItem != 0);
3554 QTRY_COMPARE(listview->count(), 20);
3556 listview->positionViewAtIndex(10, QSGListView::Beginning);
3558 QTRY_COMPARE(listview->count(), 25);
3563 void tst_QSGListView::onAdd()
3565 QFETCH(int, initialItemCount);
3566 QFETCH(int, itemsToAdd);
3568 const int delegateHeight = 10;
3571 // these initial items should not trigger ListView.onAdd
3572 for (int i=0; i<initialItemCount; i++)
3573 model.addItem("dummy value", "dummy value");
3575 QSGView *canvas = createView();
3576 canvas->setGeometry(0,0,200, delegateHeight * (initialItemCount + itemsToAdd));
3577 QDeclarativeContext *ctxt = canvas->rootContext();
3578 ctxt->setContextProperty("testModel", &model);
3579 ctxt->setContextProperty("delegateHeight", delegateHeight);
3580 canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3582 QObject *object = canvas->rootObject();
3583 object->setProperty("width", canvas->width());
3584 object->setProperty("height", canvas->height());
3585 qApp->processEvents();
3587 QList<QPair<QString, QString> > items;
3588 for (int i=0; i<itemsToAdd; i++)
3589 items << qMakePair(QString("value %1").arg(i), QString::number(i));
3590 model.addItems(items);
3591 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3593 QVariantList result = object->property("addedDelegates").toList();
3594 QCOMPARE(result.count(), items.count());
3595 for (int i=0; i<items.count(); i++)
3596 QCOMPARE(result[i].toString(), items[i].first);
3601 void tst_QSGListView::onAdd_data()
3603 QTest::addColumn<int>("initialItemCount");
3604 QTest::addColumn<int>("itemsToAdd");
3606 QTest::newRow("0, add 1") << 0 << 1;
3607 QTest::newRow("0, add 2") << 0 << 2;
3608 QTest::newRow("0, add 10") << 0 << 10;
3610 QTest::newRow("1, add 1") << 1 << 1;
3611 QTest::newRow("1, add 2") << 1 << 2;
3612 QTest::newRow("1, add 10") << 1 << 10;
3614 QTest::newRow("5, add 1") << 5 << 1;
3615 QTest::newRow("5, add 2") << 5 << 2;
3616 QTest::newRow("5, add 10") << 5 << 10;
3619 void tst_QSGListView::onRemove()
3621 QFETCH(int, initialItemCount);
3622 QFETCH(int, indexToRemove);
3623 QFETCH(int, removeCount);
3625 const int delegateHeight = 10;
3627 for (int i=0; i<initialItemCount; i++)
3628 model.addItem(QString("value %1").arg(i), "dummy value");
3630 QSGView *canvas = createView();
3631 QDeclarativeContext *ctxt = canvas->rootContext();
3632 ctxt->setContextProperty("testModel", &model);
3633 ctxt->setContextProperty("delegateHeight", delegateHeight);
3634 canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3635 QObject *object = canvas->rootObject();
3637 model.removeItems(indexToRemove, removeCount);
3638 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3640 QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
3645 void tst_QSGListView::onRemove_data()
3647 QTest::addColumn<int>("initialItemCount");
3648 QTest::addColumn<int>("indexToRemove");
3649 QTest::addColumn<int>("removeCount");
3651 QTest::newRow("remove first") << 1 << 0 << 1;
3652 QTest::newRow("two items, remove first") << 2 << 0 << 1;
3653 QTest::newRow("two items, remove last") << 2 << 1 << 1;
3654 QTest::newRow("two items, remove all") << 2 << 0 << 2;
3656 QTest::newRow("four items, remove first") << 4 << 0 << 1;
3657 QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
3658 QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
3659 QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
3660 QTest::newRow("four items, remove last") << 4 << 3 << 1;
3661 QTest::newRow("four items, remove all") << 4 << 0 << 4;
3663 QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
3664 QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
3665 QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
3668 void tst_QSGListView::rightToLeft()
3670 QSGView *canvas = createView();
3671 canvas->setGeometry(0,0,640,320);
3672 canvas->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3673 qApp->processEvents();
3675 QVERIFY(canvas->rootObject() != 0);
3676 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
3677 QTRY_VERIFY(listview != 0);
3679 QSGItem *contentItem = listview->contentItem();
3680 QTRY_VERIFY(contentItem != 0);
3682 QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
3683 QTRY_VERIFY(model != 0);
3685 QTRY_VERIFY(model->count() == 3);
3686 QTRY_COMPARE(listview->currentIndex(), 0);
3688 // initial position at first item, right edge aligned
3689 QCOMPARE(listview->contentX(), -640.);
3691 QSGItem *item = findItem<QSGItem>(contentItem, "item1");
3693 QTRY_COMPARE(item->x(), -100.0);
3694 QCOMPARE(item->height(), listview->height());
3696 QSGText *text = findItem<QSGText>(contentItem, "text1");
3698 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
3700 listview->setCurrentIndex(2);
3702 item = findItem<QSGItem>(contentItem, "item3");
3704 QTRY_COMPARE(item->x(), -540.0);
3706 text = findItem<QSGText>(contentItem, "text3");
3708 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3710 QCOMPARE(listview->contentX(), -640.);
3712 // Ensure resizing maintains position relative to right edge
3713 qobject_cast<QSGItem*>(canvas->rootObject())->setWidth(600);
3714 QTRY_COMPARE(listview->contentX(), -600.);
3719 void tst_QSGListView::test_mirroring()
3721 QSGView *canvasA = createView();
3722 canvasA->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3723 QSGListView *listviewA = findItem<QSGListView>(canvasA->rootObject(), "view");
3724 QTRY_VERIFY(listviewA != 0);
3726 QSGView *canvasB = createView();
3727 canvasB->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3728 QSGListView *listviewB = findItem<QSGListView>(canvasB->rootObject(), "view");
3729 QTRY_VERIFY(listviewA != 0);
3730 qApp->processEvents();
3732 QList<QString> objectNames;
3733 objectNames << "item1" << "item2"; // << "item3"
3735 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3736 listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3737 QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
3740 foreach (const QString objectName, objectNames)
3741 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3743 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3744 listviewB->setProperty("layoutDirection", Qt::LeftToRight);
3747 foreach (const QString objectName, objectNames)
3748 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3750 QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
3751 QSGItemPrivate::get(listviewB)->setLayoutMirror(true);
3752 QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
3754 // LTR != LTR+mirror
3755 foreach (const QString objectName, objectNames)
3756 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3758 listviewA->setProperty("layoutDirection", Qt::RightToLeft);
3760 // RTL == LTR+mirror
3761 foreach (const QString objectName, objectNames)
3762 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3764 listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3766 // RTL != RTL+mirror
3767 foreach (const QString objectName, objectNames)
3768 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3770 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3772 // LTR == RTL+mirror
3773 foreach (const QString objectName, objectNames)
3774 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3780 void tst_QSGListView::margins()
3782 QSGView *canvas = createView();
3785 for (int i = 0; i < 50; i++)
3786 model.addItem("Item" + QString::number(i), "");
3788 QDeclarativeContext *ctxt = canvas->rootContext();
3789 ctxt->setContextProperty("testModel", &model);
3791 canvas->setSource(QUrl::fromLocalFile(TESTDATA("margins.qml")));
3793 qApp->processEvents();
3795 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3796 QTRY_VERIFY(listview != 0);
3798 QSGItem *contentItem = listview->contentItem();
3799 QTRY_VERIFY(contentItem != 0);
3801 QCOMPARE(listview->contentY(), -30.);
3802 QCOMPARE(listview->yOrigin(), 0.);
3805 listview->positionViewAtEnd();
3806 qreal pos = listview->contentY();
3807 listview->setContentY(pos + 80);
3808 listview->returnToBounds();
3809 QTRY_COMPARE(listview->contentY(), pos + 50);
3811 // remove item before visible and check that top margin is maintained
3812 // and yOrigin is updated
3813 listview->setContentY(100);
3814 model.removeItem(1);
3816 listview->setContentY(-50);
3817 listview->returnToBounds();
3818 QCOMPARE(listview->yOrigin(), 20.);
3819 QTRY_COMPARE(listview->contentY(), -10.);
3821 // reduce top margin
3822 listview->setTopMargin(20);
3823 QCOMPARE(listview->yOrigin(), 20.);
3824 QTRY_COMPARE(listview->contentY(), 0.);
3827 listview->positionViewAtEnd();
3828 pos = listview->contentY();
3829 listview->setContentY(pos + 80);
3830 listview->returnToBounds();
3831 QTRY_COMPARE(listview->contentY(), pos + 50);
3833 // reduce bottom margin
3834 pos = listview->contentY();
3835 listview->setBottomMargin(40);
3836 QCOMPARE(listview->yOrigin(), 20.);
3837 QTRY_COMPARE(listview->contentY(), pos-10);
3842 void tst_QSGListView::snapToItem_data()
3844 QTest::addColumn<QSGListView::Orientation>("orientation");
3845 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3846 QTest::addColumn<int>("highlightRangeMode");
3847 QTest::addColumn<QPoint>("flickStart");
3848 QTest::addColumn<QPoint>("flickEnd");
3849 QTest::addColumn<qreal>("snapAlignment");
3850 QTest::addColumn<qreal>("endExtent");
3851 QTest::addColumn<qreal>("startExtent");
3853 QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3854 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3856 QTest::newRow("horizontal, left to right") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3857 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3859 QTest::newRow("horizontal, right to left") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::NoHighlightRange)
3860 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 << -240.0;
3862 QTest::newRow("vertical, left to right, enforce range") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3863 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3865 QTest::newRow("horizontal, left to right, enforce range") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3866 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3868 QTest::newRow("horizontal, right to left, enforce range") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::StrictlyEnforceRange)
3869 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 - 140.0 << -220.0;
3872 void tst_QSGListView::snapToItem()
3874 QFETCH(QSGListView::Orientation, orientation);
3875 QFETCH(Qt::LayoutDirection, layoutDirection);
3876 QFETCH(int, highlightRangeMode);
3877 QFETCH(QPoint, flickStart);
3878 QFETCH(QPoint, flickEnd);
3879 QFETCH(qreal, snapAlignment);
3880 QFETCH(qreal, endExtent);
3881 QFETCH(qreal, startExtent);
3883 QSGView *canvas = createView();
3885 canvas->setSource(QUrl::fromLocalFile(TESTDATA("snapToItem.qml")));
3887 qApp->processEvents();
3889 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3890 QTRY_VERIFY(listview != 0);
3892 listview->setOrientation(orientation);
3893 listview->setLayoutDirection(layoutDirection);
3894 listview->setHighlightRangeMode(QSGItemView::HighlightRangeMode(highlightRangeMode));
3896 QSGItem *contentItem = listview->contentItem();
3897 QTRY_VERIFY(contentItem != 0);
3899 // confirm that a flick hits an item boundary
3900 flick(canvas, flickStart, flickEnd, 180);
3901 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3902 if (orientation == QSGListView::Vertical)
3903 QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
3905 QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
3909 flick(canvas, flickStart, flickEnd, 180);
3910 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3911 } while (orientation == QSGListView::Vertical
3912 ? !listview->isAtYEnd()
3913 : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
3915 if (orientation == QSGListView::Vertical)
3916 QCOMPARE(listview->contentY(), endExtent);
3918 QCOMPARE(listview->contentX(), endExtent);
3922 flick(canvas, flickEnd, flickStart, 180);
3923 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3924 } while (orientation == QSGListView::Vertical
3925 ? !listview->isAtYBeginning()
3926 : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
3928 if (orientation == QSGListView::Vertical)
3929 QCOMPARE(listview->contentY(), startExtent);
3931 QCOMPARE(listview->contentX(), startExtent);
3936 void tst_QSGListView::qListModelInterface_items()
3941 void tst_QSGListView::qAbstractItemModel_items()
3943 items<TestModel2>();
3946 void tst_QSGListView::qListModelInterface_changed()
3948 changed<TestModel>();
3951 void tst_QSGListView::qAbstractItemModel_changed()
3953 changed<TestModel2>();
3956 void tst_QSGListView::qListModelInterface_inserted()
3958 inserted<TestModel>();
3961 void tst_QSGListView::qListModelInterface_inserted_more()
3963 inserted_more<TestModel>();
3966 void tst_QSGListView::qListModelInterface_inserted_more_data()
3968 inserted_more_data();
3971 void tst_QSGListView::qAbstractItemModel_inserted()
3973 inserted<TestModel2>();
3976 void tst_QSGListView::qAbstractItemModel_inserted_more()
3978 inserted_more<TestModel2>();
3981 void tst_QSGListView::qAbstractItemModel_inserted_more_data()
3983 inserted_more_data();
3986 void tst_QSGListView::qListModelInterface_removed()
3988 removed<TestModel>(false);
3989 removed<TestModel>(true);
3992 void tst_QSGListView::qAbstractItemModel_removed()
3994 removed<TestModel2>(false);
3995 removed<TestModel2>(true);
3998 void tst_QSGListView::qListModelInterface_moved()
4003 void tst_QSGListView::qListModelInterface_moved_data()
4008 void tst_QSGListView::qAbstractItemModel_moved()
4010 moved<TestModel2>();
4013 void tst_QSGListView::qAbstractItemModel_moved_data()
4018 void tst_QSGListView::qListModelInterface_clear()
4023 void tst_QSGListView::qAbstractItemModel_clear()
4025 clear<TestModel2>();
4028 void tst_QSGListView::creationContext()
4031 canvas.setGeometry(0,0,240,320);
4032 canvas.setSource(QUrl::fromLocalFile(TESTDATA("creationContext.qml")));
4033 qApp->processEvents();
4035 QSGItem *rootItem = qobject_cast<QSGItem *>(canvas.rootObject());
4037 QVERIFY(rootItem->property("count").toInt() > 0);
4040 QVERIFY(item = rootItem->findChild<QSGItem *>("listItem"));
4041 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4042 QVERIFY(item = rootItem->findChild<QSGItem *>("header"));
4043 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4044 QVERIFY(item = rootItem->findChild<QSGItem *>("footer"));
4045 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4046 QVERIFY(item = rootItem->findChild<QSGItem *>("section"));
4047 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4050 void tst_QSGListView::QTBUG_21742()
4053 canvas.setGeometry(0,0,200,200);
4054 canvas.setSource(QUrl::fromLocalFile(TESTDATA("qtbug-21742.qml")));
4055 qApp->processEvents();
4057 QSGItem *rootItem = qobject_cast<QSGItem *>(canvas.rootObject());
4059 QCOMPARE(rootItem->property("count").toInt(), 1);
4062 QSGView *tst_QSGListView::createView()
4064 QSGView *canvas = new QSGView(0);
4065 canvas->setGeometry(0,0,240,320);
4070 void tst_QSGListView::flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration)
4072 const int pointCount = 5;
4073 QPoint diff = to - from;
4075 // send press, five equally spaced moves, and release.
4076 QTest::mousePress(canvas, Qt::LeftButton, 0, from);
4078 for (int i = 0; i < pointCount; ++i) {
4079 QMouseEvent mv(QEvent::MouseMove, from + (i+1)*diff/pointCount, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
4080 QApplication::sendEvent(canvas, &mv);
4081 QTest::qWait(duration/pointCount);
4082 QCoreApplication::processEvents();
4085 QTest::mouseRelease(canvas, Qt::LeftButton, 0, to);
4089 QSGItem *tst_QSGListView::findVisibleChild(QSGItem *parent, const QString &objectName)
4092 QList<QSGItem*> items = parent->findChildren<QSGItem*>(objectName);
4093 for (int i = 0; i < items.count(); ++i) {
4094 if (items.at(i)->isVisible()) {
4102 Find an item with the specified objectName. If index is supplied then the
4103 item must also evaluate the {index} expression equal to index
4105 template<typename T>
4106 T *tst_QSGListView::findItem(QSGItem *parent, const QString &objectName, int index)
4108 const QMetaObject &mo = T::staticMetaObject;
4109 //qDebug() << parent->childItems().count() << "children";
4110 for (int i = 0; i < parent->childItems().count(); ++i) {
4111 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4114 //qDebug() << "try" << item;
4115 if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
4117 QDeclarativeExpression e(qmlContext(item), item, "index");
4118 if (e.evaluate().toInt() == index)
4119 return static_cast<T*>(item);
4121 return static_cast<T*>(item);
4124 item = findItem<T>(item, objectName, index);
4126 return static_cast<T*>(item);
4132 template<typename T>
4133 QList<T*> tst_QSGListView::findItems(QSGItem *parent, const QString &objectName)
4136 const QMetaObject &mo = T::staticMetaObject;
4137 //qDebug() << parent->childItems().count() << "children";
4138 for (int i = 0; i < parent->childItems().count(); ++i) {
4139 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4140 if (!item || !item->isVisible())
4142 //qDebug() << "try" << item;
4143 if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
4144 items.append(static_cast<T*>(item));
4145 items += findItems<T>(item, objectName);
4151 void tst_QSGListView::dumpTree(QSGItem *parent, int depth)
4153 static QString padding(" ");
4154 for (int i = 0; i < parent->childItems().count(); ++i) {
4155 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4158 qDebug() << padding.left(depth*2) << item;
4159 dumpTree(item, depth+1);
4163 QTEST_MAIN(tst_QSGListView)
4165 #include "tst_qsglistview.moc"