From c0fc795feb06a8e2f813690e47b2cb981c67546c Mon Sep 17 00:00:00 2001 From: Bea Lam Date: Thu, 14 Jul 2011 13:55:20 +1000 Subject: [PATCH] Always position headers at a negative position 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 Reviewed-by: Martin Jones --- src/declarative/items/qsggridview.cpp | 50 ++-- src/declarative/items/qsgitemview.cpp | 12 +- src/declarative/items/qsgitemview_p_p.h | 1 + src/declarative/items/qsglistview.cpp | 12 +- tests/auto/declarative/qsggridview/data/footer.qml | 12 +- tests/auto/declarative/qsggridview/data/header.qml | 4 +- .../declarative/qsggridview/tst_qsggridview.cpp | 205 +++++++++++++-- tests/auto/declarative/qsglistview/data/footer.qml | 16 +- tests/auto/declarative/qsglistview/data/header.qml | 7 +- .../declarative/qsglistview/data/headerfooter.qml | 2 +- .../declarative/qsglistview/tst_qsglistview.cpp | 291 ++++++++++++++++----- 11 files changed, 478 insertions(+), 134 deletions(-) diff --git a/src/declarative/items/qsggridview.cpp b/src/declarative/items/qsggridview.cpp index 24fc708..ef660c0 100644 --- a/src/declarative/items/qsggridview.cpp +++ b/src/declarative/items/qsggridview.cpp @@ -292,10 +292,8 @@ qreal QSGGridViewPrivate::colPosAt(int modelIndex) const int count = columns - 1 - (modelIndex - visibleItems.last()->index - 1) % columns; return static_cast(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(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(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(); } diff --git a/src/declarative/items/qsgitemview.cpp b/src/declarative/items/qsgitemview.cpp index e1788ca..df8de86 100644 --- a/src/declarative/items/qsgitemview.cpp +++ b/src/declarative/items/qsgitemview.cpp @@ -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; } diff --git a/src/declarative/items/qsgitemview_p_p.h b/src/declarative/items/qsgitemview_p_p.h index 78f0e14..bc2e45f 100644 --- a/src/declarative/items/qsgitemview_p_p.h +++ b/src/declarative/items/qsgitemview_p_p.h @@ -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; diff --git a/src/declarative/items/qsglistview.cpp b/src/declarative/items/qsglistview.cpp index e9a5922..4502dc8 100644 --- a/src/declarative/items/qsglistview.cpp +++ b/src/declarative/items/qsglistview.cpp @@ -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 { diff --git a/tests/auto/declarative/qsggridview/data/footer.qml b/tests/auto/declarative/qsggridview/data/footer.qml index b0d1117..9083f9f 100644 --- a/tests/auto/declarative/qsggridview/data/footer.qml +++ b/tests/auto/declarative/qsggridview/data/footer.qml @@ -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 } } } diff --git a/tests/auto/declarative/qsggridview/data/header.qml b/tests/auto/declarative/qsggridview/data/header.qml index f725b68..5978317 100644 --- a/tests/auto/declarative/qsggridview/data/header.qml +++ b/tests/auto/declarative/qsggridview/data/header.qml @@ -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 } } } diff --git a/tests/auto/declarative/qsggridview/tst_qsggridview.cpp b/tests/auto/declarative/qsggridview/tst_qsggridview.cpp index 39359d1..c97a42b 100644 --- a/tests/auto/declarative/qsggridview/tst_qsggridview.cpp +++ b/tests/auto/declarative/qsggridview/tst_qsggridview.cpp @@ -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(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(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(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(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(contentItem, "footer"); @@ -1859,15 +1911,76 @@ void tst_QSGGridView::footer() footer = findItem(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(contentItem, "wrapper", 0); + QVERIFY(item); + QCOMPARE(item->pos(), firstDelegatePos); delete canvas; } +void tst_QSGGridView::footer_data() +{ + QTest::addColumn("flow"); + QTest::addColumn("layoutDirection"); + QTest::addColumn("initialFooterPos"); + QTest::addColumn("changedFooterPos"); + QTest::addColumn("initialContentPos"); + QTest::addColumn("changedContentPos"); + QTest::addColumn("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(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(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(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(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(contentItem, "wrapper", 0); + QVERIFY(item); + QCOMPARE(item->pos(), firstDelegatePos); delete canvas; } +void tst_QSGGridView::header_data() +{ + QTest::addColumn("flow"); + QTest::addColumn("layoutDirection"); + QTest::addColumn("initialHeaderPos"); + QTest::addColumn("changedHeaderPos"); + QTest::addColumn("initialContentPos"); + QTest::addColumn("changedContentPos"); + QTest::addColumn("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(); diff --git a/tests/auto/declarative/qsglistview/data/footer.qml b/tests/auto/declarative/qsglistview/data/footer.qml index 49e1944..2a56199 100644 --- a/tests/auto/declarative/qsglistview/data/footer.qml +++ b/tests/auto/declarative/qsglistview/data/footer.qml @@ -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 } } } diff --git a/tests/auto/declarative/qsglistview/data/header.qml b/tests/auto/declarative/qsglistview/data/header.qml index 455159f..b073146 100644 --- a/tests/auto/declarative/qsglistview/data/header.qml +++ b/tests/auto/declarative/qsglistview/data/header.qml @@ -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 } } + } diff --git a/tests/auto/declarative/qsglistview/data/headerfooter.qml b/tests/auto/declarative/qsglistview/data/headerfooter.qml index 30b7199..8e8463d 100644 --- a/tests/auto/declarative/qsglistview/data/headerfooter.qml +++ b/tests/auto/declarative/qsglistview/data/headerfooter.qml @@ -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 } diff --git a/tests/auto/declarative/qsglistview/tst_qsglistview.cpp b/tests/auto/declarative/qsglistview/tst_qsglistview.cpp index 3abca71..75b68e4 100644 --- a/tests/auto/declarative/qsglistview/tst_qsglistview.cpp +++ b/tests/auto/declarative/qsglistview/tst_qsglistview.cpp @@ -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(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(canvas->rootObject(), "list"); + QTRY_VERIFY(listview != 0); + listview->setOrientation(orientation); + listview->setLayoutDirection(layoutDirection); - QSGText *header = findItem(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(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(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(contentItem, "header"); - QVERIFY(!header); - header = findItem(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(contentItem, "header"); + QVERIFY(!header); + header = findItem(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(canvas->rootObject(), "list"); - QTRY_VERIFY(listview != 0); +void tst_QSGListView::header_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("layoutDirection"); + QTest::addColumn("initialHeaderPos"); + QTest::addColumn("changedHeaderPos"); + QTest::addColumn("initialContentPos"); + QTest::addColumn("changedContentPos"); + QTest::addColumn("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(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(canvas->rootObject(), "list"); + QTRY_VERIFY(listview != 0); - delete canvas; - } + QSGItem *contentItem = listview->contentItem(); + QTRY_VERIFY(contentItem != 0); + + QSGText *header = findItem(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(canvas->rootObject(), "list"); QTRY_VERIFY(listview != 0); + listview->setOrientation(orientation); + listview->setLayoutDirection(layoutDirection); QSGItem *contentItem = listview->contentItem(); QTRY_VERIFY(contentItem != 0); QSGText *footer = findItem(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(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(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(contentItem, "wrapper", 0); + QVERIFY(item); + QCOMPARE(item->pos(), firstDelegatePos); delete canvas; } +void tst_QSGListView::footer_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("layoutDirection"); + QTest::addColumn("initialFooterPos"); + QTest::addColumn("changedFooterPos"); + QTest::addColumn("initialContentPos"); + QTest::addColumn("changedContentPos"); + QTest::addColumn("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(contentItem, "header"); QVERIFY(header); - QCOMPARE(header->y(), 0.0); + QCOMPARE(header->y(), -header->height()); QSGItem *footer = findItem(contentItem, "footer"); QVERIFY(footer); - QCOMPARE(footer->y(), 20.0); + QCOMPARE(footer->y(), 0.); QVERIFY(static_cast(listview)->minY() == 0); QVERIFY(static_cast(listview)->maxY() == 0); @@ -2067,11 +2234,11 @@ void tst_QSGListView::headerFooter() QSGItem *header = findItem(contentItem, "header"); QVERIFY(header); - QCOMPARE(header->x(), 0.0); + QCOMPARE(header->x(), -header->width()); QSGItem *footer = findItem(contentItem, "footer"); QVERIFY(footer); - QCOMPARE(footer->x(), 20.0); + QCOMPARE(footer->x(), 0.); QVERIFY(static_cast(listview)->minX() == 0); QVERIFY(static_cast(listview)->maxX() == 0); @@ -2099,11 +2266,11 @@ void tst_QSGListView::headerFooter() QSGItem *header = findItem(contentItem, "header"); QVERIFY(header); - QCOMPARE(header->x(), -20.0); + QCOMPARE(header->x(), 0.); QSGItem *footer = findItem(contentItem, "footer"); QVERIFY(footer); - QCOMPARE(footer->x(), -50.0); + QCOMPARE(footer->x(), -footer->width()); QCOMPARE(static_cast(listview)->minX(), 240.); QCOMPARE(static_cast(listview)->maxX(), 240.); -- 2.7.4