Always position headers at a negative position
authorBea Lam <bea.lam@nokia.com>
Thu, 14 Jul 2011 03:55:20 +0000 (13:55 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 21 Jul 2011 04:23:11 +0000 (06:23 +0200)
Previously headers were either positioned at 0 if there were no items in
the view, or at (0 - headerSize) if items were present. With this fix
they are always positioned at (0 - headerSize) to be consistent.

Due to the change, setPosition(-headerSize()) is now called from
componentCompleted() to ensure the header is visible.

This change also fixes GridView's header and footer positioning in
TopToBottom + RightToLeft mode.

Also added extra tests for header and footer positioning and fixed
incorrect test value in tst_QSGGridView::positionViewAtIndex() (was 460
instead of 430 because previously rowPosAt() always added the header
size, so if positionViewAtIndex() jumped past all visible items and
caused them to be released, the new items started at +headerSize()
instead of 0).

Change-Id: I1015bed457d4ae964a7fb13702e2dfc470a168a9
Reviewed-on: http://codereview.qt.nokia.com/1618
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Martin Jones <martin.jones@nokia.com>

src/declarative/items/qsggridview.cpp
src/declarative/items/qsgitemview.cpp
src/declarative/items/qsgitemview_p_p.h
src/declarative/items/qsglistview.cpp
tests/auto/declarative/qsggridview/data/footer.qml
tests/auto/declarative/qsggridview/data/header.qml
tests/auto/declarative/qsggridview/tst_qsggridview.cpp
tests/auto/declarative/qsglistview/data/footer.qml
tests/auto/declarative/qsglistview/data/header.qml
tests/auto/declarative/qsglistview/data/headerfooter.qml
tests/auto/declarative/qsglistview/tst_qsglistview.cpp

index 24fc708..ef660c0 100644 (file)
@@ -292,10 +292,8 @@ qreal QSGGridViewPrivate::colPosAt(int modelIndex) const
             int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns;
             return static_cast<FxGridItemSG*>(visibleItems.last())->colPos() - count * colSize();
         }
-    } else {
-        return (modelIndex % columns) * colSize();
     }
-    return 0;
+    return (modelIndex % columns) * colSize();
 }
 
 qreal QSGGridViewPrivate::rowPosAt(int modelIndex) const
@@ -316,13 +314,8 @@ qreal QSGGridViewPrivate::rowPosAt(int modelIndex) const
             int rows = col / (columns * colSize());
             return lastItem->rowPos() + rows * rowSize();
         }
-    } else {
-        qreal pos = (modelIndex / columns) * rowSize();
-        if (header)
-            pos += headerSize();
-        return pos;
     }
-    return 0;
+    return (modelIndex / columns) * rowSize();
 }
 
 
@@ -659,13 +652,12 @@ void QSGGridViewPrivate::updateFooter()
 
     FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(footer);
     qreal colOffset = 0;
-    qreal rowOffset;
-    if (isRightToLeftTopToBottom()) {
-        rowOffset = gridItem->item->width()-cellWidth;
-    } else {
-        rowOffset = 0;
-        if (q->effectiveLayoutDirection() == Qt::RightToLeft)
-            colOffset = gridItem->item->width()-cellWidth;
+    qreal rowOffset = 0;
+    if (q->effectiveLayoutDirection() == Qt::RightToLeft) {
+        if (flow == QSGGridView::TopToBottom)
+            rowOffset = gridItem->item->width() - cellWidth;
+        else
+            colOffset = gridItem->item->width() - cellWidth;
     }
     if (visibleItems.count()) {
         qreal endPos = lastPosition() + 1;
@@ -677,11 +669,7 @@ void QSGGridViewPrivate::updateFooter()
                 gridItem->setPosition(colOffset, endPos + rowOffset);
         }
     } else {
-        qreal endPos = 0;
-        if (header) {
-            endPos += headerSize();
-        }
-        gridItem->setPosition(colOffset, endPos);
+        gridItem->setPosition(colOffset, rowOffset);
     }
 }
 
@@ -699,12 +687,11 @@ void QSGGridViewPrivate::updateHeader()
 
     FxGridItemSG *gridItem = static_cast<FxGridItemSG*>(header);
     qreal colOffset = 0;
-    qreal rowOffset;
-    if (isRightToLeftTopToBottom()) {
-        rowOffset = -cellWidth;
-    } else {
-        rowOffset = -headerSize();
-        if (q->effectiveLayoutDirection() == Qt::RightToLeft)
+    qreal rowOffset = -headerSize();
+    if (q->effectiveLayoutDirection() == Qt::RightToLeft) {
+        if (flow == QSGGridView::TopToBottom)
+            rowOffset += gridItem->item->width()-cellWidth;
+        else
             colOffset = gridItem->item->width()-cellWidth;
     }
     if (visibleItems.count()) {
@@ -718,7 +705,10 @@ void QSGGridViewPrivate::updateHeader()
                 gridItem->setPosition(colOffset, startPos + rowOffset);
         }
     } else {
-        gridItem->setPosition(colOffset, 0);
+        if (isRightToLeftTopToBottom())
+            gridItem->setPosition(colOffset, rowOffset);
+        else
+            gridItem->setPosition(colOffset, -headerSize());
     }
 }
 
@@ -1339,8 +1329,6 @@ void QSGGridView::itemsInserted(int modelIndex, int count)
                 rowPos += d->rowSize();
             }
         }
-    } else if (d->itemCount == 0 && d->header) {
-        rowPos = d->headerSize();
     }
 
     // Update the indexes of the following visible items.
@@ -1479,7 +1467,7 @@ void QSGGridView::itemsRemoved(int modelIndex, int count)
     if (removedVisible && d->visibleItems.isEmpty()) {
         d->timeline.clear();
         if (d->itemCount == 0) {
-            d->setPosition(0);
+            d->setPosition(d->contentStartPosition());
             d->updateHeader();
             d->updateFooter();
         }
index e1788ca..df8de86 100644 (file)
@@ -107,7 +107,7 @@ void QSGItemView::setModel(const QVariant &model)
     QSGVisualModel *oldModel = d->model;
 
     d->clear();
-    d->setPosition(0);
+    d->setPosition(d->contentStartPosition());
     d->model = 0;
     d->modelVariant = model;
 
@@ -955,6 +955,7 @@ void QSGItemView::componentComplete()
     d->updateHeader();
     d->updateFooter();
     d->updateViewport();
+    d->setPosition(d->contentStartPosition());
     if (d->isValid()) {
         d->refill();
         d->moveReason = QSGItemViewPrivate::SetIndex;
@@ -1022,6 +1023,11 @@ qreal QSGItemViewPrivate::endPosition() const
     return isContentFlowReversed() ? -originPosition()-1 : lastPosition();
 }
 
+qreal QSGItemViewPrivate::contentStartPosition() const
+{
+    return -headerSize();
+}
+
 int QSGItemViewPrivate::findLastVisibleIndex(int defaultValue) const
 {
     if (visibleItems.count()) {
@@ -1209,7 +1215,7 @@ void QSGItemViewPrivate::regenerate()
         updateFooter();
         clear();
         updateViewport();
-        setPosition(0);
+        setPosition(contentStartPosition());
         refill();
         updateCurrent(currentIndex);
     }
@@ -1241,7 +1247,7 @@ void QSGItemViewPrivate::layout()
     layoutScheduled = false;
     if (!isValid() && !visibleItems.count()) {
         clear();
-        setPosition(0);
+        setPosition(contentStartPosition());
         return;
     }
 
index 78f0e14..bc2e45f 100644 (file)
@@ -87,6 +87,7 @@ public:
     qreal size() const;
     qreal startPosition() const;
     qreal endPosition() const;
+    qreal contentStartPosition() const;
     int findLastVisibleIndex(int defaultValue = -1) const;
     FxViewItem *visibleItem(int modelIndex) const;
     FxViewItem *firstVisibleItem() const;
index e9a5922..4502dc8 100644 (file)
@@ -465,7 +465,7 @@ void QSGListViewPrivate::clear()
         delete sectionCache[i];
         sectionCache[i] = 0;
     }
-    visiblePos = header ? headerSize() : 0;
+    visiblePos = 0;
     QSGItemViewPrivate::clear();
 }
 
@@ -963,9 +963,7 @@ void QSGListViewPrivate::updateHeader()
                     listItem->setPosition(startPos - headerSize());
             }
         } else {
-            if (itemCount == 0)
-                visiblePos = headerSize();
-            listItem->setPosition(0);
+            listItem->setPosition(-headerSize());
         }
     }
 }
@@ -1607,8 +1605,6 @@ void QSGListView::itemsInserted(int modelIndex, int count)
     if (d->visibleItems.count()) {
         pos = index < d->visibleItems.count() ? d->visibleItems.at(index)->position()
                                                 : d->visibleItems.last()->endPosition()+d->spacing+1;
-    } else if (d->itemCount == 0 && d->header) {
-        pos = d->headerSize();
     }
 
     int initialPos = pos;
@@ -1781,8 +1777,8 @@ void QSGListView::itemsRemoved(int modelIndex, int count)
         d->timeline.clear();
         if (removedVisible && d->itemCount == 0) {
             d->visibleIndex = 0;
-            d->visiblePos = d->header ? d->headerSize() : 0;
-            d->setPosition(0);
+            d->visiblePos = 0;
+            d->setPosition(d->contentStartPosition());
             d->updateHeader();
             d->updateFooter();
         } else {
index b0d1117..9083f9f 100644 (file)
@@ -1,6 +1,8 @@
 import QtQuick 2.0
 
 Rectangle {
+    property bool showHeader: false
+
     function changeFooter() {
         grid.footer = footer2
     }
@@ -21,6 +23,11 @@ Rectangle {
             color: GridView.isCurrentItem ? "lightsteelblue" : "white"
         }
     }
+    Component {
+        id: headerComponent
+        Text { objectName: "header"; text: "Header " + x + "," + y; width: 100; height: 30 }
+    }
+
     GridView {
         id: grid
         objectName: "grid"
@@ -30,11 +37,12 @@ Rectangle {
         cellHeight: 60
         model: testModel
         delegate: myDelegate
-        footer: Text { objectName: "footer"; text: "Footer"; height: 30 }
+        header: parent.showHeader ? headerComponent : null
+        footer: Text { objectName: "footer"; text: "Footer " + x + "," + y; width: 100; height: 30 }
     }
 
     Component {
         id: footer2
-        Text { objectName: "footer2"; text: "Footer 2"; height: 20 }
+        Text { objectName: "footer2"; text: "Footer 2" + x + "," + y; width: 50; height: 20 }
     }
 }
index f725b68..5978317 100644 (file)
@@ -30,11 +30,11 @@ Rectangle {
         cellHeight: 60
         model: testModel
         delegate: myDelegate
-        header: Text { objectName: "header"; text: "Header"; height: 30 }
+        header: Text { objectName: "header"; text: "Header " + x + "," + y; width: 100; height: 30 }
     }
 
     Component {
         id: header2
-        Text { objectName: "header2"; text: "Header 2"; height: 20 }
+        Text { objectName: "header2"; text: "Header 2 " + x + "," + y; width: 50; height: 20 }
     }
 }
index 39359d1..c97a42b 100644 (file)
@@ -59,6 +59,9 @@
 #define SRCDIR "."
 #endif
 
+Q_DECLARE_METATYPE(Qt::LayoutDirection)
+Q_DECLARE_METATYPE(QSGGridView::Flow)
+
 class tst_QSGGridView : public QObject
 {
     Q_OBJECT
@@ -92,7 +95,9 @@ private slots:
     void QTBUG_8456();
     void manualHighlight();
     void footer();
+    void footer_data();
     void header();
+    void header_data();
     void indexAt();
     void onAdd();
     void onAdd_data();
@@ -1359,7 +1364,15 @@ void tst_QSGGridView::positionViewAtIndex()
     gridview->setContentX(80);
     canvas->rootObject()->setProperty("showFooter", true);
     gridview->positionViewAtEnd();
-    QTRY_COMPARE(gridview->contentX(), 460.);
+    QTRY_COMPARE(gridview->contentX(), 430.);
+
+    // set current item to outside visible view, position at beginning
+    // and ensure highlight moves to current item
+    gridview->setCurrentIndex(6);
+    gridview->positionViewAtBeginning();
+    QTRY_COMPARE(gridview->contentX(), -30.);
+    QVERIFY(gridview->highlightItem());
+    QCOMPARE(gridview->highlightItem()->x(), 80.);
 
     delete canvas;
 }
@@ -1816,8 +1829,17 @@ void tst_QSGGridView::manualHighlight()
     delete canvas;
 }
 
+
 void tst_QSGGridView::footer()
 {
+    QFETCH(QSGGridView::Flow, flow);
+    QFETCH(Qt::LayoutDirection, layoutDirection);
+    QFETCH(QPointF, initialFooterPos);
+    QFETCH(QPointF, changedFooterPos);
+    QFETCH(QPointF, initialContentPos);
+    QFETCH(QPointF, changedContentPos);
+    QFETCH(QPointF, firstDelegatePos);
+
     QSGView *canvas = createView();
     canvas->show();
 
@@ -1833,6 +1855,8 @@ void tst_QSGGridView::footer()
 
     QSGGridView *gridview = findItem<QSGGridView>(canvas->rootObject(), "grid");
     QTRY_VERIFY(gridview != 0);
+    gridview->setFlow(flow);
+    gridview->setLayoutDirection(layoutDirection);
 
     QSGItem *contentItem = gridview->contentItem();
     QTRY_VERIFY(contentItem != 0);
@@ -1840,18 +1864,46 @@ void tst_QSGGridView::footer()
     QSGText *footer = findItem<QSGText>(contentItem, "footer");
     QVERIFY(footer);
 
-    QCOMPARE(footer->y(), 180.0);
-    QCOMPARE(footer->height(), 30.0);
+    QCOMPARE(footer->pos(), initialFooterPos);
+    QCOMPARE(footer->width(), 100.);
+    QCOMPARE(footer->height(), 30.);
+    QCOMPARE(QPointF(gridview->contentX(), gridview->contentY()), initialContentPos);
 
-    model.removeItem(2);
-    QTRY_COMPARE(footer->y(), 120.0);
+    QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
+
+    if (flow == QSGGridView::LeftToRight) {
+        // shrink by one row
+        model.removeItem(2);
+        QTRY_COMPARE(footer->y(), initialFooterPos.y() - gridview->cellHeight());
+    } else {
+        // shrink by one column
+        model.removeItem(2);
+        model.removeItem(3);
+        if (layoutDirection == Qt::LeftToRight)
+            QTRY_COMPARE(footer->x(), initialFooterPos.x() - gridview->cellWidth());
+        else
+            QTRY_COMPARE(footer->x(), initialFooterPos.x() + gridview->cellWidth());
+    }
 
+    // remove all items
     model.clear();
-    QTRY_COMPARE(footer->y(), 0.0);
 
+    QPointF posWhenNoItems(0, 0);
+    if (layoutDirection == Qt::RightToLeft)
+        posWhenNoItems.setX(flow == QSGGridView::LeftToRight ? gridview->width() - footer->width() : -footer->width());
+    QTRY_COMPARE(footer->pos(), posWhenNoItems);
+
+    // if header is present, it's at a negative pos, so the footer should not move
+    canvas->rootObject()->setProperty("showHeader", true);
+    QVERIFY(findItem<QSGItem>(contentItem, "header") != 0);
+    QTRY_COMPARE(footer->pos(), posWhenNoItems);
+    canvas->rootObject()->setProperty("showHeader", false);
+
+    // add 30 items
     for (int i = 0; i < 30; i++)
         model.addItem("Item" + QString::number(i), "");
-
     QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
 
     footer = findItem<QSGText>(contentItem, "footer");
@@ -1859,15 +1911,76 @@ void tst_QSGGridView::footer()
     footer = findItem<QSGText>(contentItem, "footer2");
     QVERIFY(footer);
 
-    QCOMPARE(footer->y(), 600.0);
-    QCOMPARE(footer->height(), 20.0);
-    QCOMPARE(gridview->contentY(), 0.0);
+    QCOMPARE(footer->pos(), changedFooterPos);
+    QCOMPARE(footer->width(), 50.);
+    QCOMPARE(footer->height(), 20.);
+    QTRY_COMPARE(QPointF(gridview->contentX(), gridview->contentY()), changedContentPos);
+
+    item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
     delete canvas;
 }
 
+void tst_QSGGridView::footer_data()
+{
+    QTest::addColumn<QSGGridView::Flow>("flow");
+    QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+    QTest::addColumn<QPointF>("initialFooterPos");
+    QTest::addColumn<QPointF>("changedFooterPos");
+    QTest::addColumn<QPointF>("initialContentPos");
+    QTest::addColumn<QPointF>("changedContentPos");
+    QTest::addColumn<QPointF>("firstDelegatePos");
+
+    // footer1 = 100 x 30
+    // footer2 = 100 x 20
+    // cells = 80 * 60
+    // view width = 240
+
+    // footer below items, bottom left
+    QTest::newRow("flow left to right") << QSGGridView::LeftToRight << Qt::LeftToRight
+        << QPointF(0, 3 * 60)  // 180 = height of 3 rows (cell height is 60)
+        << QPointF(0, 10 * 60)  // 30 items = 10 rows
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0);
+
+    // footer below items, bottom right
+    QTest::newRow("flow left to right, layout right to left") << QSGGridView::LeftToRight << Qt::RightToLeft
+        << QPointF(240 - 100, 3 * 60)
+        << QPointF((240 - 100) + 50, 10 * 60)     // 50 = width diff between old and new footers
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(240 - 80, 0);
+
+    // footer to right of items
+    QTest::newRow("flow top to bottom, layout left to right") << QSGGridView::TopToBottom << Qt::LeftToRight
+        << QPointF(2 * 80, 0)      // 2 columns, cell width 80
+        << QPointF(6 * 80, 0)      // 30 items = 6 columns
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0);
+
+    // footer to left of items
+    QTest::newRow("flow top to bottom, layout right to left") << QSGGridView::TopToBottom << Qt::RightToLeft
+        << QPointF(-(2 * 80) - 100, 0)
+        << QPointF(-(6 * 80) - 50, 0)     // 50 = new footer width
+        << QPointF(-240, 0)
+        << QPointF(-240, 0)    // unchanged, footer change doesn't change content pos
+        << QPointF(-80, 0);
+}
+
 void tst_QSGGridView::header()
 {
+    QFETCH(QSGGridView::Flow, flow);
+    QFETCH(Qt::LayoutDirection, layoutDirection);
+    QFETCH(QPointF, initialHeaderPos);
+    QFETCH(QPointF, changedHeaderPos);
+    QFETCH(QPointF, initialContentPos);
+    QFETCH(QPointF, changedContentPos);
+    QFETCH(QPointF, firstDelegatePos);
+
     QSGView *canvas = createView();
 
     TestModel model;
@@ -1882,6 +1995,8 @@ void tst_QSGGridView::header()
 
     QSGGridView *gridview = findItem<QSGGridView>(canvas->rootObject(), "grid");
     QTRY_VERIFY(gridview != 0);
+    gridview->setFlow(flow);
+    gridview->setLayoutDirection(layoutDirection);
 
     QSGItem *contentItem = gridview->contentItem();
     QTRY_VERIFY(contentItem != 0);
@@ -1889,16 +2004,17 @@ void tst_QSGGridView::header()
     QSGText *header = findItem<QSGText>(contentItem, "header");
     QVERIFY(header);
 
-    QCOMPARE(header->y(), 0.0);
-    QCOMPARE(header->height(), 30.0);
-    QCOMPARE(gridview->contentY(), 0.0);
+    QCOMPARE(header->pos(), initialHeaderPos);
+    QCOMPARE(header->width(), 100.);
+    QCOMPARE(header->height(), 30.);
+    QCOMPARE(QPointF(gridview->contentX(), gridview->contentY()), initialContentPos);
 
     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
     QVERIFY(item);
-    QCOMPARE(item->y(), 30.0);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
     model.clear();
-    QTRY_COMPARE(header->y(), 0.0);
+    QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
 
     for (int i = 0; i < 30; i++)
         model.addItem("Item" + QString::number(i), "");
@@ -1910,13 +2026,66 @@ void tst_QSGGridView::header()
     header = findItem<QSGText>(contentItem, "header2");
     QVERIFY(header);
 
-    QCOMPARE(header->y(), 10.0);
-    QCOMPARE(header->height(), 20.0);
-    QCOMPARE(gridview->contentY(), 10.0);
+    QCOMPARE(header->pos(), changedHeaderPos);
+    QCOMPARE(header->width(), 50.);
+    QCOMPARE(header->height(), 20.);
+    QTRY_COMPARE(QPointF(gridview->contentX(), gridview->contentY()), changedContentPos);
+
+    item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
     delete canvas;
 }
 
+void tst_QSGGridView::header_data()
+{
+    QTest::addColumn<QSGGridView::Flow>("flow");
+    QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+    QTest::addColumn<QPointF>("initialHeaderPos");
+    QTest::addColumn<QPointF>("changedHeaderPos");
+    QTest::addColumn<QPointF>("initialContentPos");
+    QTest::addColumn<QPointF>("changedContentPos");
+    QTest::addColumn<QPointF>("firstDelegatePos");
+
+    // header1 = 100 x 30
+    // header2 = 100 x 20
+    // cells = 80 x 60
+    // view width = 240
+
+    // header above items, top left
+    QTest::newRow("flow left to right") << QSGGridView::LeftToRight << Qt::LeftToRight
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, 0);
+
+    // header above items, top right
+    QTest::newRow("flow left to right, layout right to left") << QSGGridView::LeftToRight << Qt::RightToLeft
+        << QPointF(240 - 100, -30)
+        << QPointF((240 - 100) + 50, -20)     // 50 = width diff between old and new headers
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(160, 0);
+
+    // header to left of items
+    QTest::newRow("flow top to bottom, layout left to right") << QSGGridView::TopToBottom << Qt::LeftToRight
+        << QPointF(-100, 0)
+        << QPointF(-50, 0)
+        << QPointF(-100, 0)
+        << QPointF(-50, 0)
+        << QPointF(0, 0);
+
+    // header to right of items
+    QTest::newRow("flow top to bottom, layout right to left") << QSGGridView::TopToBottom << Qt::RightToLeft
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(-(240 - 100), 0)
+        << QPointF(-(240 - 50), 0)
+        << QPointF(-80, 0);
+}
+
 void tst_QSGGridView::indexAt()
 {
     QSGView *canvas = createView();
index 49e1944..2a56199 100644 (file)
@@ -1,6 +1,8 @@
 import QtQuick 2.0
 
 Rectangle {
+    property bool showHeader: false
+
     function changeFooter() {
         list.footer = footer2
     }
@@ -13,13 +15,18 @@ Rectangle {
             id: wrapper
             objectName: "wrapper"
             height: 20
-            width: 240
+            width: 40
             Text {
-                text: index
+                text: index + " " + x + "," + y
             }
             color: ListView.isCurrentItem ? "lightsteelblue" : "white"
         }
     }
+    Component {
+        id: headerComponent
+        Text { objectName: "header"; text: "Header " + x + "," + y; width: 100; height: 30 }
+    }
+
     ListView {
         id: list
         objectName: "list"
@@ -28,11 +35,12 @@ Rectangle {
         height: 320
         model: testModel
         delegate: myDelegate
-        footer: Text { objectName: "footer"; text: "Footer"; height: 30 }
+        header: parent.showHeader ? headerComponent : null
+        footer: Text { objectName: "footer"; text: "Footer " + x + "," + y; width: 100; height: 30 }
     }
 
     Component {
         id: footer2
-        Text { objectName: "footer2"; text: "Footer 2"; height: 20 }
+        Text { objectName: "footer2"; text: "Footer 2 " + x + "," + y; width: 50; height: 20 }
     }
 }
index 455159f..b073146 100644 (file)
@@ -15,7 +15,7 @@ Rectangle {
             height: 30
             width: 240
             Text {
-                text: index
+                text: index + " " + x + "," + y
             }
             color: ListView.isCurrentItem ? "lightsteelblue" : "white"
         }
@@ -29,10 +29,11 @@ Rectangle {
         snapMode: ListView.SnapToItem
         model: testModel
         delegate: myDelegate
-        header: Text { objectName: "header"; text: "Header"; height: 20 }
+        header: Text { objectName: "header"; text: "Header " + x + "," + y; width: 100; height: 30 }
     }
     Component {
         id: header2
-        Text { objectName: "header2"; text: "Header 2"; height: 10 }
+        Text { objectName: "header2"; text: "Header " + x + "," + y; width: 50; height: 20 }
     }
+
 }
index 30b7199..8e8463d 100644 (file)
@@ -20,7 +20,7 @@ ListView {
         height: horizontal ? view.height : 30
         color: "blue"
     }
-//    model: testModel
+
     delegate: Text { width: 30; height: 30; text: index + "(" + x + ")" }
     layoutDirection: rtl ? Qt.RightToLeft : Qt.LeftToRight
 }
index 3abca71..75b68e4 100644 (file)
@@ -60,6 +60,9 @@
 #define SRCDIR "."
 #endif
 
+Q_DECLARE_METATYPE(Qt::LayoutDirection)
+Q_DECLARE_METATYPE(QSGListView::Orientation)
+
 class tst_QSGListView : public QObject
 {
     Q_OBJECT
@@ -105,7 +108,10 @@ private slots:
     void manualHighlight();
     void QTBUG_11105();
     void header();
+    void header_data();
+    void header_delayItemCreation();
     void footer();
+    void footer_data();
     void headerFooter();
     void resizeView();
     void sizeLessThan1();
@@ -1574,6 +1580,14 @@ void tst_QSGListView::positionViewAtIndex()
     listview->positionViewAtEnd();
     QTRY_COMPARE(listview->contentY(), 510.);
 
+    // set current item to outside visible view, position at beginning
+    // and ensure highlight moves to current item
+    listview->setCurrentIndex(1);
+    listview->positionViewAtBeginning();
+    QTRY_COMPARE(listview->contentY(), -30.);
+    QVERIFY(listview->highlightItem());
+    QCOMPARE(listview->highlightItem()->y(), 20.);
+
     delete canvas;
     delete testObject;
 }
@@ -1885,80 +1899,153 @@ void tst_QSGListView::QTBUG_11105()
 
 void tst_QSGListView::header()
 {
-    {
-        QSGView *canvas = createView();
+    QFETCH(QSGListView::Orientation, orientation);
+    QFETCH(Qt::LayoutDirection, layoutDirection);
+    QFETCH(QPointF, initialHeaderPos);
+    QFETCH(QPointF, firstDelegatePos);
+    QFETCH(QPointF, initialContentPos);
+    QFETCH(QPointF, changedHeaderPos);
+    QFETCH(QPointF, changedContentPos);
 
-        TestModel model;
-        for (int i = 0; i < 30; i++)
-            model.addItem("Item" + QString::number(i), "");
+    QSGView *canvas = createView();
 
-        QDeclarativeContext *ctxt = canvas->rootContext();
-        ctxt->setContextProperty("testModel", &model);
+    TestModel model;
+    for (int i = 0; i < 30; i++)
+        model.addItem("Item" + QString::number(i), "");
 
-        canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header.qml"));
-        qApp->processEvents();
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
 
-        QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
-        QTRY_VERIFY(listview != 0);
+    canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header.qml"));
+    qApp->processEvents();
 
-        QSGItem *contentItem = listview->contentItem();
-        QTRY_VERIFY(contentItem != 0);
+    QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+    listview->setOrientation(orientation);
+    listview->setLayoutDirection(layoutDirection);
 
-        QSGText *header = findItem<QSGText>(contentItem, "header");
-        QVERIFY(header);
-        QCOMPARE(header->y(), 0.0);
-        QCOMPARE(header->height(), 20.0);
+    QSGItem *contentItem = listview->contentItem();
+    QTRY_VERIFY(contentItem != 0);
 
-        QCOMPARE(listview->contentY(), 0.0);
+    QSGText *header = findItem<QSGText>(contentItem, "header");
+    QVERIFY(header);
 
-        model.clear();
-        QTRY_COMPARE(header->y(), 0.0);
+    QCOMPARE(header->width(), 100.);
+    QCOMPARE(header->height(), 30.);
+    QCOMPARE(header->pos(), initialHeaderPos);
+    QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
 
-        for (int i = 0; i < 30; i++)
-            model.addItem("Item" + QString::number(i), "");
+    QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
-        QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
+    model.clear();
+    QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
 
-        header = findItem<QSGText>(contentItem, "header");
-        QVERIFY(!header);
-        header = findItem<QSGText>(contentItem, "header2");
-        QVERIFY(header);
+    for (int i = 0; i < 30; i++)
+        model.addItem("Item" + QString::number(i), "");
 
-        QCOMPARE(header->y(), 10.0);
-        QCOMPARE(header->height(), 10.0);
-        QCOMPARE(listview->contentY(), 10.0);
+    QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
 
-        delete canvas;
-    }
-    {
-        QSGView *canvas = createView();
+    header = findItem<QSGText>(contentItem, "header");
+    QVERIFY(!header);
+    header = findItem<QSGText>(contentItem, "header2");
+    QVERIFY(header);
 
-        TestModel model;
+    QCOMPARE(header->pos(), changedHeaderPos);
+    QCOMPARE(header->width(), 50.);
+    QCOMPARE(header->height(), 20.);
+    QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
-        canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header1.qml"));
-        qApp->processEvents();
+    delete canvas;
+}
 
-        QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
-        QTRY_VERIFY(listview != 0);
+void tst_QSGListView::header_data()
+{
+    QTest::addColumn<QSGListView::Orientation>("orientation");
+    QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+    QTest::addColumn<QPointF>("initialHeaderPos");
+    QTest::addColumn<QPointF>("changedHeaderPos");
+    QTest::addColumn<QPointF>("initialContentPos");
+    QTest::addColumn<QPointF>("changedContentPos");
+    QTest::addColumn<QPointF>("firstDelegatePos");
+
+    // header1 = 100 x 30
+    // header2 = 50 x 20
+    // delegates = 240 x 20
+    // view width = 240
+
+    // header above items, top left
+    QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, 0);
+
+    // header above items, top right
+    QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, -30)
+        << QPointF(0, -20)
+        << QPointF(0, 0);
+
+    // header to left of items
+    QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
+        << QPointF(-100, 0)
+        << QPointF(-50, 0)
+        << QPointF(-100, 0)
+        << QPointF(-50, 0)
+        << QPointF(0, 0);
+
+    // header to right of items
+    QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(-240 + 100, 0)
+        << QPointF(-240 + 50, 0)
+        << QPointF(-240, 0);
+}
 
-        QSGItem *contentItem = listview->contentItem();
-        QTRY_VERIFY(contentItem != 0);
+void tst_QSGListView::header_delayItemCreation()
+{
+    QSGView *canvas = createView();
 
-        QSGText *header = findItem<QSGText>(contentItem, "header");
-        QVERIFY(header);
-        QCOMPARE(header->y(), 0.0);
+    TestModel model;
 
-        QCOMPARE(listview->contentY(), 0.0);
+    canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header1.qml"));
+    qApp->processEvents();
 
-        model.clear();
-        QTRY_COMPARE(header->y(), 0.0);
+    QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
 
-        delete canvas;
-    }
+    QSGItem *contentItem = listview->contentItem();
+    QTRY_VERIFY(contentItem != 0);
+
+    QSGText *header = findItem<QSGText>(contentItem, "header");
+    QVERIFY(header);
+    QCOMPARE(header->y(), -header->height());
+
+    QCOMPARE(listview->contentY(), -header->height());
+
+    model.clear();
+    QTRY_COMPARE(header->y(), -header->height());
+
+    delete canvas;
 }
 
 void tst_QSGListView::footer()
 {
+    QFETCH(QSGListView::Orientation, orientation);
+    QFETCH(Qt::LayoutDirection, layoutDirection);
+    QFETCH(QPointF, initialFooterPos);
+    QFETCH(QPointF, firstDelegatePos);
+    QFETCH(QPointF, initialContentPos);
+    QFETCH(QPointF, changedFooterPos);
+    QFETCH(QPointF, changedContentPos);
+
     QSGView *canvas = createView();
 
     TestModel model;
@@ -1974,21 +2061,48 @@ void tst_QSGListView::footer()
 
     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->setOrientation(orientation);
+    listview->setLayoutDirection(layoutDirection);
 
     QSGItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
     QSGText *footer = findItem<QSGText>(contentItem, "footer");
     QVERIFY(footer);
-    QCOMPARE(footer->y(), 60.0);
-    QCOMPARE(footer->height(), 30.0);
 
+    QCOMPARE(footer->pos(), initialFooterPos);
+    QCOMPARE(footer->width(), 100.);
+    QCOMPARE(footer->height(), 30.);
+    QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
+
+    QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
+
+    // remove one item
     model.removeItem(1);
-    QTRY_COMPARE(footer->y(), 40.0);
 
+    if (orientation == QSGListView::Vertical) {
+        QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20);   // delegate height = 20
+    } else {
+        QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
+                initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
+    }
+
+    // remove all items
     model.clear();
-    QTRY_COMPARE(footer->y(), 0.0);
 
+    QPointF posWhenNoItems(0, 0);
+    if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
+        posWhenNoItems.setX(-100);
+    QTRY_COMPARE(footer->pos(), posWhenNoItems);
+
+    // if header is present, it's at a negative pos, so the footer should not move
+    canvas->rootObject()->setProperty("showHeader", true);
+    QTRY_COMPARE(footer->pos(), posWhenNoItems);
+    canvas->rootObject()->setProperty("showHeader", false);
+
+    // add 30 items
     for (int i = 0; i < 30; i++)
         model.addItem("Item" + QString::number(i), "");
 
@@ -1999,13 +2113,66 @@ void tst_QSGListView::footer()
     footer = findItem<QSGText>(contentItem, "footer2");
     QVERIFY(footer);
 
-    QCOMPARE(footer->y(), 600.0);
-    QCOMPARE(footer->height(), 20.0);
-    QCOMPARE(listview->contentY(), 0.0);
+    QCOMPARE(footer->pos(), changedFooterPos);
+    QCOMPARE(footer->width(), 50.);
+    QCOMPARE(footer->height(), 20.);
+    QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
+
+    item = findItem<QSGItem>(contentItem, "wrapper", 0);
+    QVERIFY(item);
+    QCOMPARE(item->pos(), firstDelegatePos);
 
     delete canvas;
 }
 
+void tst_QSGListView::footer_data()
+{
+    QTest::addColumn<QSGListView::Orientation>("orientation");
+    QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+    QTest::addColumn<QPointF>("initialFooterPos");
+    QTest::addColumn<QPointF>("changedFooterPos");
+    QTest::addColumn<QPointF>("initialContentPos");
+    QTest::addColumn<QPointF>("changedContentPos");
+    QTest::addColumn<QPointF>("firstDelegatePos");
+
+    // footer1 = 100 x 30
+    // footer2 = 100 x 20
+    // delegates = 40 x 20
+    // view width = 240
+
+    // footer below items, bottom left
+    QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
+        << QPointF(0, 3 * 20)
+        << QPointF(0, 30 * 20)  // added 30 items
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0);
+
+    // footer below items, bottom right
+    QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
+        << QPointF(0, 3 * 20)
+        << QPointF(0, 30 * 20)
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0);
+
+    // footer to right of items
+    QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
+        << QPointF(40 * 3, 0)
+        << QPointF(40 * 30, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0)
+        << QPointF(0, 0);
+
+    // footer to left of items
+    QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
+        << QPointF(-(40 * 3) - 100, 0)
+        << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
+        << QPointF(-240, 0)
+        << QPointF(-240, 0)
+        << QPointF(-40, 0);
+}
+
 class LVAccessor : public QSGListView
 {
 public:
@@ -2036,11 +2203,11 @@ void tst_QSGListView::headerFooter()
 
         QSGItem *header = findItem<QSGItem>(contentItem, "header");
         QVERIFY(header);
-        QCOMPARE(header->y(), 0.0);
+        QCOMPARE(header->y(), -header->height());
 
         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
         QVERIFY(footer);
-        QCOMPARE(footer->y(), 20.0);
+        QCOMPARE(footer->y(), 0.);
 
         QVERIFY(static_cast<LVAccessor*>(listview)->minY() == 0);
         QVERIFY(static_cast<LVAccessor*>(listview)->maxY() == 0);
@@ -2067,11 +2234,11 @@ void tst_QSGListView::headerFooter()
 
         QSGItem *header = findItem<QSGItem>(contentItem, "header");
         QVERIFY(header);
-        QCOMPARE(header->x(), 0.0);
+        QCOMPARE(header->x(), -header->width());
 
         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
         QVERIFY(footer);
-        QCOMPARE(footer->x(), 20.0);
+        QCOMPARE(footer->x(), 0.);
 
         QVERIFY(static_cast<LVAccessor*>(listview)->minX() == 0);
         QVERIFY(static_cast<LVAccessor*>(listview)->maxX() == 0);
@@ -2099,11 +2266,11 @@ void tst_QSGListView::headerFooter()
 
         QSGItem *header = findItem<QSGItem>(contentItem, "header");
         QVERIFY(header);
-        QCOMPARE(header->x(), -20.0);
+        QCOMPARE(header->x(), 0.);
 
         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
         QVERIFY(footer);
-        QCOMPARE(footer->x(), -50.0);
+        QCOMPARE(footer->x(), -footer->width());
 
         QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240.);
         QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240.);