aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/items/qquickitemsmodule.cpp1
-rw-r--r--src/quick/items/qquicklistview.cpp163
-rw-r--r--src/quick/items/qquicklistview_p.h15
-rw-r--r--tests/auto/quick/qquicklistview/data/stickyPositioning-both.qml77
-rw-r--r--tests/auto/quick/qquicklistview/data/stickyPositioning-footer.qml70
-rw-r--r--tests/auto/quick/qquicklistview/data/stickyPositioning-header.qml70
-rw-r--r--tests/auto/quick/qquicklistview/tst_qquicklistview.cpp409
-rw-r--r--tests/testapplications/listview/sticky.qml250
8 files changed, 1031 insertions, 24 deletions
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index 2951fc70b9..d8671390af 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -270,6 +270,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
qmlRegisterType<QQuickImage, 1>(uri, 2, 3,"Image");
qmlRegisterType<QQuickItem, 2>(uri, 2, 4, "Item");
+ qmlRegisterType<QQuickListView, 2>(uri, 2, 4, "ListView");
qmlRegisterType<QQuickMouseArea, 1>(uri, 2, 4, "MouseArea");
qmlRegisterType<QQuickShaderEffect, 1>(uri, 2, 4, "ShaderEffect");
}
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index e8043804fb..1b268eccf3 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -125,6 +125,8 @@ public:
virtual bool showFooterForIndex(int index) const;
virtual void updateHeader();
virtual void updateFooter();
+ bool hasStickyHeader() const;
+ bool hasStickyFooter() const;
virtual void changedVisibleIndex(int newIndex);
virtual void initializeCurrentItem();
@@ -143,6 +145,9 @@ public:
qreal spacing;
QQuickListView::SnapMode snapMode;
+ QQuickListView::HeaderPositioning headerPositioning;
+ QQuickListView::FooterPositioning footerPositioning;
+
QSmoothedAnimation *highlightPosAnimator;
QSmoothedAnimation *highlightWidthAnimator;
QSmoothedAnimation *highlightHeightAnimator;
@@ -170,6 +175,8 @@ public:
, visiblePos(0)
, averageSize(100.0), spacing(0.0)
, snapMode(QQuickListView::NoSnap)
+ , headerPositioning(QQuickListView::InlineHeader)
+ , footerPositioning(QQuickListView::InlineFooter)
, highlightPosAnimator(0), highlightWidthAnimator(0), highlightHeightAnimator(0)
, highlightMoveVelocity(400), highlightResizeVelocity(400), highlightResizeDuration(-1)
, sectionCriteria(0), currentSectionItem(0), nextSectionItem(0)
@@ -1048,6 +1055,9 @@ void QQuickListViewPrivate::updateStickySections()
bool isFlowReversed = isContentFlowReversed();
qreal viewPos = isFlowReversed ? -position()-size() : position();
+ qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos;
+ qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size();
+
QQuickItem *sectionItem = 0;
QQuickItem *lastSectionItem = 0;
int index = 0;
@@ -1059,18 +1069,18 @@ void QQuickListViewPrivate::updateStickySections()
qreal sectionSize = orient == QQuickListView::Vertical ? section->height() : section->width();
bool visTop = true;
if (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart)
- visTop = isFlowReversed ? -sectionPos-sectionSize >= viewPos : sectionPos >= viewPos;
+ visTop = isFlowReversed ? -sectionPos-sectionSize >= startPos : sectionPos >= startPos;
bool visBot = true;
if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd)
- visBot = isFlowReversed ? -sectionPos <= viewPos + size() : sectionPos + sectionSize < viewPos + size();
+ visBot = isFlowReversed ? -sectionPos <= endPos : sectionPos + sectionSize < endPos;
section->setVisible(visBot && visTop);
if (visTop && !sectionItem)
sectionItem = section;
if (isFlowReversed) {
- if (-sectionPos <= viewPos + size())
+ if (-sectionPos <= endPos)
lastSectionItem = section;
} else {
- if (sectionPos + sectionSize < viewPos + size())
+ if (sectionPos + sectionSize < endPos)
lastSectionItem = section;
}
}
@@ -1092,14 +1102,14 @@ void QQuickListViewPrivate::updateStickySections()
qreal sectionSize = orient == QQuickListView::Vertical ? currentSectionItem->height() : currentSectionItem->width();
bool atBeginning = orient == QQuickListView::Vertical ? (isBottomToTop() ? vData.atEnd : vData.atBeginning) : (isRightToLeft() ? hData.atEnd : hData.atBeginning);
- currentSectionItem->setVisible(!atBeginning && (!header || header->endPosition() < viewPos));
- qreal pos = isFlowReversed ? position() + size() - sectionSize : viewPos;
+ currentSectionItem->setVisible(!atBeginning && (!header || hasStickyHeader() || header->endPosition() < viewPos));
+ qreal pos = isFlowReversed ? position() + size() - sectionSize : startPos;
+ if (header)
+ pos = isFlowReversed ? qMin(-header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos);
if (sectionItem) {
qreal sectionPos = orient == QQuickListView::Vertical ? sectionItem->y() : sectionItem->x();
pos = isFlowReversed ? qMax(pos, sectionPos + sectionSize) : qMin(pos, sectionPos - sectionSize);
}
- if (header)
- pos = isFlowReversed ? qMin(header->endPosition(), pos) : qMax(header->endPosition(), pos);
if (footer)
pos = isFlowReversed ? qMax(-footer->position(), pos) : qMin(footer->position() - sectionSize, pos);
if (orient == QQuickListView::Vertical)
@@ -1125,13 +1135,15 @@ void QQuickListViewPrivate::updateStickySections()
qreal sectionSize = orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
nextSectionItem->setVisible(!nextSection.isEmpty());
- qreal pos = isFlowReversed ? position() : viewPos + size() - sectionSize;
+ qreal pos = isFlowReversed ? position() : endPos - sectionSize;
+ if (footer)
+ pos = isFlowReversed ? qMax(-footer->position(), pos) : qMin(footer->position() - sectionSize, pos);
if (lastSectionItem) {
qreal sectionPos = orient == QQuickListView::Vertical ? lastSectionItem->y() : lastSectionItem->x();
pos = isFlowReversed ? qMin(pos, sectionPos - sectionSize) : qMax(pos, sectionPos + sectionSize);
}
if (header)
- pos = isFlowReversed ? qMin(header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos);
+ pos = isFlowReversed ? qMin(-header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos);
if (orient == QQuickListView::Vertical)
nextSectionItem->setY(pos);
else
@@ -1192,12 +1204,11 @@ void QQuickListViewPrivate::updateCurrentSection()
return;
}
bool inlineSections = sectionCriteria->labelPositioning() & QQuickViewSection::InlineLabels;
- qreal sectionThreshold = position();
- if (currentSectionItem && !inlineSections)
- sectionThreshold += orient == QQuickListView::Vertical ? currentSectionItem->height() : currentSectionItem->width();
+ qreal viewPos = isContentFlowReversed() ? -position()-size() : position();
+ qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos;
int index = 0;
int modelIndex = visibleIndex;
- while (index < visibleItems.count() && visibleItems.at(index)->endPosition() <= sectionThreshold) {
+ while (index < visibleItems.count() && visibleItems.at(index)->endPosition() <= startPos) {
if (visibleItems.at(index)->index != -1)
modelIndex = visibleItems.at(index)->index;
++index;
@@ -1220,7 +1231,7 @@ void QQuickListViewPrivate::updateCurrentSection()
// section when that changes. Clearing lastVisibleSection will also
// force searching.
QString lastSection = currentSection;
- qreal endPos = isContentFlowReversed() ? -position() : position() + size();
+ qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size();
if (nextSectionItem && !inlineSections)
endPos -= orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
while (index < visibleItems.count() && static_cast<FxListItemSG*>(visibleItems.at(index))->itemPosition() < endPos) {
@@ -1315,13 +1326,21 @@ void QQuickListViewPrivate::updateFooter()
FxListItemSG *listItem = static_cast<FxListItemSG*>(footer);
if (visibleItems.count()) {
- qreal endPos = lastPosition();
- if (findLastVisibleIndex() == model->count()-1) {
- listItem->setPosition(endPos);
+ if (footerPositioning == QQuickListView::OverlayFooter) {
+ listItem->setPosition(isContentFlowReversed() ? -position() - footerSize() : position() + size() - footerSize());
+ } else if (footerPositioning == QQuickListView::PullBackFooter) {
+ qreal viewPos = isContentFlowReversed() ? -position() : position() + size();
+ qreal clampedPos = qBound(originPosition() - footerSize() + size(), listItem->position(), lastPosition());
+ listItem->setPosition(qBound(viewPos - footerSize(), clampedPos, viewPos));
} else {
- qreal visiblePos = position() + q->height();
- if (endPos <= visiblePos || listItem->position() < endPos)
+ qreal endPos = lastPosition();
+ if (findLastVisibleIndex() == model->count()-1) {
listItem->setPosition(endPos);
+ } else {
+ qreal visiblePos = position() + q->height();
+ if (endPos <= visiblePos || listItem->position() < endPos)
+ listItem->setPosition(endPos);
+ }
}
} else {
listItem->setPosition(visiblePos);
@@ -1347,12 +1366,20 @@ void QQuickListViewPrivate::updateHeader()
FxListItemSG *listItem = static_cast<FxListItemSG*>(header);
if (listItem) {
if (visibleItems.count()) {
- qreal startPos = originPosition();
- if (visibleIndex == 0) {
- listItem->setPosition(startPos - headerSize());
+ if (headerPositioning == QQuickListView::OverlayHeader) {
+ listItem->setPosition(isContentFlowReversed() ? -position() - size() : position());
+ } else if (headerPositioning == QQuickListView::PullBackHeader) {
+ qreal viewPos = isContentFlowReversed() ? -position() - size() : position();
+ qreal clampedPos = qBound(originPosition() - headerSize(), listItem->position(), lastPosition() - headerSize() - size());
+ listItem->setPosition(qBound(viewPos - headerSize(), clampedPos, viewPos));
} else {
- if (position() <= startPos || listItem->position() > startPos - headerSize())
+ qreal startPos = originPosition();
+ if (visibleIndex == 0) {
listItem->setPosition(startPos - headerSize());
+ } else {
+ if (position() <= startPos || listItem->position() > startPos - headerSize())
+ listItem->setPosition(startPos - headerSize());
+ }
}
} else {
listItem->setPosition(-headerSize());
@@ -1363,6 +1390,16 @@ void QQuickListViewPrivate::updateHeader()
emit q->headerItemChanged();
}
+bool QQuickListViewPrivate::hasStickyHeader() const
+{
+ return header && headerPositioning != QQuickListView::InlineHeader;
+}
+
+bool QQuickListViewPrivate::hasStickyFooter() const
+{
+ return footer && footerPositioning != QQuickListView::InlineFooter;
+}
+
void QQuickListViewPrivate::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_Q(QQuickListView);
@@ -2443,6 +2480,80 @@ void QQuickListView::setSnapMode(SnapMode mode)
*/
/*!
+ \qmlproperty enumeration QtQuick::ListView::headerPositioning
+ \since Qt 5.4
+
+ This property determines the positioning of the \l{headerItem}{header item}.
+
+ The possible values are:
+ \list
+ \li ListView.InlineHeader (default) - the header is positioned in the beginning
+ of the content and moves together with the content like an ordinary item.
+ \li ListView.OverlayHeader - the header is positioned in the beginning of the view.
+ \li ListView.PullBackHeader - the header is positioned in the beginning of the view.
+ The header can be pushed away by moving the content forwards, and pulled back by
+ moving the content backwards.
+ \endlist
+*/
+QQuickListView::HeaderPositioning QQuickListView::headerPositioning() const
+{
+ Q_D(const QQuickListView);
+ return d->headerPositioning;
+}
+
+void QQuickListView::setHeaderPositioning(QQuickListView::HeaderPositioning positioning)
+{
+ Q_D(QQuickListView);
+ if (d->headerPositioning != positioning) {
+ d->applyPendingChanges();
+ d->headerPositioning = positioning;
+ if (isComponentComplete()) {
+ d->updateHeader();
+ d->updateViewport();
+ d->fixupPosition();
+ }
+ emit headerPositioningChanged();
+ }
+}
+
+/*!
+ \qmlproperty enumeration QtQuick::ListView::footerPositioning
+ \since Qt 5.4
+
+ This property determines the positioning of the \l{footerItem}{footer item}.
+
+ The possible values are:
+ \list
+ \li ListView.InlineFooter (default) - the footer is positioned in the end
+ of the content and moves together with the content like an ordinary item.
+ \li ListView.OverlayFooter - the footer is positioned in the end of the view.
+ \li ListView.PullBackFooter - the footer is positioned in the end of the view.
+ The footer can be pushed away by moving the content backwards, and pulled back by
+ moving the content forwards.
+ \endlist
+*/
+QQuickListView::FooterPositioning QQuickListView::footerPositioning() const
+{
+ Q_D(const QQuickListView);
+ return d->footerPositioning;
+}
+
+void QQuickListView::setFooterPositioning(QQuickListView::FooterPositioning positioning)
+{
+ Q_D(QQuickListView);
+ if (d->footerPositioning != positioning) {
+ d->applyPendingChanges();
+ d->footerPositioning = positioning;
+ if (isComponentComplete()) {
+ d->updateFooter();
+ d->updateViewport();
+ d->fixupPosition();
+ }
+ emit footerPositioningChanged();
+ }
+}
+
+/*!
\qmlproperty Transition QtQuick::ListView::populate
This property holds the transition to apply to the items that are initially created
@@ -2816,6 +2927,10 @@ void QQuickListView::viewportMoved(Qt::Orientations orient)
}
d->inFlickCorrection = false;
}
+ if (d->hasStickyHeader())
+ d->updateHeader();
+ if (d->hasStickyFooter())
+ d->updateFooter();
if (d->sectionCriteria) {
d->updateCurrentSection();
d->updateStickySections();
diff --git a/src/quick/items/qquicklistview_p.h b/src/quick/items/qquicklistview_p.h
index 2494e101cd..3e799b05e3 100644
--- a/src/quick/items/qquicklistview_p.h
+++ b/src/quick/items/qquicklistview_p.h
@@ -111,8 +111,13 @@ class Q_AUTOTEST_EXPORT QQuickListView : public QQuickItemView
Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged)
+ Q_PROPERTY(HeaderPositioning headerPositioning READ headerPositioning WRITE setHeaderPositioning NOTIFY headerPositioningChanged REVISION 2)
+ Q_PROPERTY(FooterPositioning footerPositioning READ footerPositioning WRITE setFooterPositioning NOTIFY footerPositioningChanged REVISION 2)
+
Q_ENUMS(Orientation)
Q_ENUMS(SnapMode)
+ Q_ENUMS(HeaderPositioning)
+ Q_ENUMS(FooterPositioning)
Q_CLASSINFO("DefaultProperty", "data")
public:
@@ -146,6 +151,14 @@ public:
SnapMode snapMode() const;
void setSnapMode(SnapMode mode);
+ enum HeaderPositioning { InlineHeader, OverlayHeader, PullBackHeader };
+ HeaderPositioning headerPositioning() const;
+ void setHeaderPositioning(HeaderPositioning positioning);
+
+ enum FooterPositioning { InlineFooter, OverlayFooter, PullBackFooter };
+ FooterPositioning footerPositioning() const;
+ void setFooterPositioning(FooterPositioning positioning);
+
static QQuickListViewAttached *qmlAttachedProperties(QObject *);
public Q_SLOTS:
@@ -160,6 +173,8 @@ Q_SIGNALS:
void highlightResizeVelocityChanged();
void highlightResizeDurationChanged();
void snapModeChanged();
+ Q_REVISION(2) void headerPositioningChanged();
+ Q_REVISION(2) void footerPositioningChanged();
protected:
virtual void viewportMoved(Qt::Orientations orient);
diff --git a/tests/auto/quick/qquicklistview/data/stickyPositioning-both.qml b/tests/auto/quick/qquicklistview/data/stickyPositioning-both.qml
new file mode 100644
index 0000000000..4bbe7a0053
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/stickyPositioning-both.qml
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.4
+
+Rectangle {
+ width: 240
+ height: 320
+ ListView {
+ id: list
+ objectName: "list"
+ width: 100
+ height: 100
+ cacheBuffer: 0
+ anchors.centerIn: parent
+ model: testModel
+ orientation: testOrientation
+ layoutDirection: testLayoutDirection
+ verticalLayoutDirection: testVerticalLayoutDirection
+ delegate: Rectangle {
+ width: 10
+ height: 10
+ border.width: 1
+ border.color: "gray"
+ }
+ headerPositioning: ListView.PullBackHeader
+ header: Rectangle {
+ width: 10
+ height: 10
+ color: "red"
+ objectName: "header"
+ }
+ footerPositioning: ListView.PullBackFooter
+ footer: Rectangle {
+ width: 10
+ height: 10
+ color: "blue"
+ objectName: "footer"
+ }
+ }
+}
diff --git a/tests/auto/quick/qquicklistview/data/stickyPositioning-footer.qml b/tests/auto/quick/qquicklistview/data/stickyPositioning-footer.qml
new file mode 100644
index 0000000000..57773df37e
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/stickyPositioning-footer.qml
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.4
+
+Rectangle {
+ width: 240
+ height: 320
+ ListView {
+ id: list
+ objectName: "list"
+ width: 100
+ height: 100
+ cacheBuffer: 0
+ anchors.centerIn: parent
+ model: testModel
+ orientation: testOrientation
+ layoutDirection: testLayoutDirection
+ verticalLayoutDirection: testVerticalLayoutDirection
+ delegate: Rectangle {
+ width: 10
+ height: 10
+ border.width: 1
+ border.color: "gray"
+ }
+ footerPositioning: ListView.PullBackFooter
+ footer: Rectangle {
+ width: 10
+ height: 10
+ color: "blue"
+ objectName: "footer"
+ }
+ }
+}
diff --git a/tests/auto/quick/qquicklistview/data/stickyPositioning-header.qml b/tests/auto/quick/qquicklistview/data/stickyPositioning-header.qml
new file mode 100644
index 0000000000..1ca9813baa
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/stickyPositioning-header.qml
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.4
+
+Rectangle {
+ width: 240
+ height: 320
+ ListView {
+ id: list
+ objectName: "list"
+ width: 100
+ height: 100
+ cacheBuffer: 0
+ anchors.centerIn: parent
+ model: testModel
+ orientation: testOrientation
+ layoutDirection: testLayoutDirection
+ verticalLayoutDirection: testVerticalLayoutDirection
+ delegate: Rectangle {
+ width: 10
+ height: 10
+ border.width: 1
+ border.color: "gray"
+ }
+ headerPositioning: ListView.PullBackHeader
+ header: Rectangle {
+ width: 10
+ height: 10
+ color: "red"
+ objectName: "header"
+ }
+ }
+}
diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
index c8ab62cbbc..39610e57f7 100644
--- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
@@ -228,6 +228,9 @@ private slots:
void QTBUG_36481();
void QTBUG_35920();
+ void stickyPositioning();
+ void stickyPositioning_data();
+
void roundingErrors();
void roundingErrors_data();
@@ -7309,6 +7312,412 @@ void tst_QQuickListView::QTBUG_35920()
QTest::mouseRelease(window.data(), Qt::LeftButton, 0, QPoint(10,100));
}
+Q_DECLARE_METATYPE(Qt::Orientation)
+
+void tst_QQuickListView::stickyPositioning()
+{
+ QFETCH(QString, fileName);
+
+ QFETCH(Qt::Orientation, orientation);
+ QFETCH(Qt::LayoutDirection, layoutDirection);
+ QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
+
+ QFETCH(int, positionIndex);
+ QFETCH(QQuickItemView::PositionMode, positionMode);
+ QFETCH(QList<QPointF>, movement);
+
+ QFETCH(QPointF, headerPos);
+ QFETCH(QPointF, footerPos);
+
+ QQuickView *window = createView();
+
+ QaimModel model;
+ for (int i = 0; i < 20; i++)
+ model.addItem(QString::number(i), QString::number(i/10));
+
+ QQmlContext *ctxt = window->rootContext();
+ ctxt->setContextProperty("testModel", &model);
+ ctxt->setContextProperty("testOrientation", orientation);
+ ctxt->setContextProperty("testLayoutDirection", layoutDirection);
+ ctxt->setContextProperty("testVerticalLayoutDirection", verticalLayoutDirection);
+
+ window->setSource(testFileUrl(fileName));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QVERIFY(listview);
+
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem);
+
+ listview->positionViewAtIndex(positionIndex, positionMode);
+
+ foreach (const QPointF &offset, movement) {
+ listview->setContentX(listview->contentX() + offset.x());
+ listview->setContentY(listview->contentY() + offset.y());
+ }
+
+ if (listview->header()) {
+ QQuickItem *headerItem = listview->headerItem();
+ QVERIFY(headerItem);
+ QPointF actualPos = listview->mapFromItem(contentItem, headerItem->position());
+ QCOMPARE(actualPos, headerPos);
+ }
+
+ if (listview->footer()) {
+ QQuickItem *footerItem = listview->footerItem();
+ QVERIFY(footerItem);
+ QPointF actualPos = listview->mapFromItem(contentItem, footerItem->position());
+ QCOMPARE(actualPos, footerPos);
+ }
+
+ delete window;
+}
+
+void tst_QQuickListView::stickyPositioning_data()
+{
+ qRegisterMetaType<Qt::Orientation>();
+ qRegisterMetaType<Qt::LayoutDirection>();
+ qRegisterMetaType<QQuickItemView::VerticalLayoutDirection>();
+ qRegisterMetaType<QQuickItemView::PositionMode>();
+
+ QTest::addColumn<QString>("fileName");
+
+ QTest::addColumn<Qt::Orientation>("orientation");
+ QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+ QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
+
+ QTest::addColumn<int>("positionIndex");
+ QTest::addColumn<QQuickItemView::PositionMode>("positionMode");
+ QTest::addColumn<QList<QPointF> >("movement");
+
+ QTest::addColumn<QPointF>("headerPos");
+ QTest::addColumn<QPointF>("footerPos");
+
+ // header at the top
+ QTest::newRow("top header") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(0,-10) << QPointF();
+
+ QTest::newRow("top header: 1/2 up") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-5))
+ << QPointF(0,-5) << QPointF();
+
+ QTest::newRow("top header: up") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15))
+ << QPointF(0,0) << QPointF();
+
+ QTest::newRow("top header: 1/2 down") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
+ << QPointF(0,-5) << QPointF();
+
+ QTest::newRow("top header: down") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
+ << QPointF(0,-10) << QPointF();
+
+
+ // footer at the top
+ QTest::newRow("top footer") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 19 << QQuickItemView::End << QList<QPointF>()
+ << QPointF() << QPointF(0,-10);
+
+ QTest::newRow("top footer: 1/2 up") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-5))
+ << QPointF() << QPointF(0,-5);
+
+ QTest::newRow("top footer: up") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15))
+ << QPointF() << QPointF(0,0);
+
+ QTest::newRow("top footer: 1/2 down") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
+ << QPointF() << QPointF(0,-5);
+
+ QTest::newRow("top footer: down") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
+ << QPointF() << QPointF(0,-10);
+
+
+ // header at the bottom
+ QTest::newRow("bottom header") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(0,100) << QPointF();
+
+ QTest::newRow("bottom header: 1/2 down") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,5))
+ << QPointF(0,95) << QPointF();
+
+ QTest::newRow("bottom header: down") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15))
+ << QPointF(0,90) << QPointF();
+
+ QTest::newRow("bottom header: 1/2 up") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
+ << QPointF(0,95) << QPointF();
+
+ QTest::newRow("bottom header: up") << "stickyPositioning-header.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::BottomToTop
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
+ << QPointF(0,100) << QPointF();
+
+
+ // footer at the bottom
+ QTest::newRow("bottom footer") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 19 << QQuickItemView::End << QList<QPointF>()
+ << QPointF() << QPointF(0,100);
+
+ QTest::newRow("bottom footer: 1/2 down") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,5))
+ << QPointF() << QPointF(0,95);
+
+ QTest::newRow("bottom footer: down") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15))
+ << QPointF() << QPointF(0,90);
+
+ QTest::newRow("bottom footer: 1/2 up") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
+ << QPointF() << QPointF(0,95);
+
+ QTest::newRow("bottom footer: up") << "stickyPositioning-footer.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
+ << QPointF() << QPointF(0,100);
+
+
+ // header at the top (& footer at the bottom)
+ QTest::newRow("top header & bottom footer") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(0,-10) << QPointF(0,90);
+
+ QTest::newRow("top header & bottom footer: 1/2 up") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-5))
+ << QPointF(0,-5) << QPointF(0,95);
+
+ QTest::newRow("top header & bottom footer: up") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15))
+ << QPointF(0,0) << QPointF(0,100);
+
+ QTest::newRow("top header & bottom footer: 1/2 down") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,5))
+ << QPointF(0,-5) << QPointF(0,95);
+
+ QTest::newRow("top header & bottom footer: down") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,-15) << QPointF(0,10))
+ << QPointF(0,-10) << QPointF(0,90);
+
+
+ // footer at the bottom (& header at the top)
+ QTest::newRow("bottom footer & top header") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(0,-10) << QPointF(0,90);
+
+ QTest::newRow("bottom footer & top header: 1/2 down") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,5))
+ << QPointF(0,-10) << QPointF(0,90);
+
+ QTest::newRow("bottom footer & top header: down") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15))
+ << QPointF(0,-10) << QPointF(0,90);
+
+ QTest::newRow("bottom footer & top header: 1/2 up") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-5))
+ << QPointF(0,-5) << QPointF(0,95);
+
+ QTest::newRow("bottom footer & top header: up") << "stickyPositioning-both.qml"
+ << Qt::Vertical << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(0,15) << QPointF(0,-10))
+ << QPointF(0,0) << QPointF(0,100);
+
+ // header on the left
+ QTest::newRow("left header") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(-10,0) << QPointF();
+
+ QTest::newRow("left header: 1/2 left") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-5,0))
+ << QPointF(-5,0) << QPointF();
+
+ QTest::newRow("left header: left") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0))
+ << QPointF(0,0) << QPointF();
+
+ QTest::newRow("left header: 1/2 right") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
+ << QPointF(-5,0) << QPointF();
+
+ QTest::newRow("left header: right") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
+ << QPointF(-10,0) << QPointF();
+
+
+ // footer on the left
+ QTest::newRow("left footer") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 19 << QQuickItemView::End << QList<QPointF>()
+ << QPointF() << QPointF(-10,0);
+
+ QTest::newRow("left footer: 1/2 left") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(-5,0))
+ << QPointF() << QPointF(-5,0);
+
+ QTest::newRow("left footer: left") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0))
+ << QPointF() << QPointF(0,0);
+
+ QTest::newRow("left footer: 1/2 right") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
+ << QPointF() << QPointF(-5,0);
+
+ QTest::newRow("left footer: right") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
+ << QPointF() << QPointF(-10,0);
+
+
+ // header on the right
+ QTest::newRow("right header") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(100,0) << QPointF();
+
+ QTest::newRow("right header: 1/2 right") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(5,0))
+ << QPointF(95,0) << QPointF();
+
+ QTest::newRow("right header: right") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0))
+ << QPointF(90,0) << QPointF();
+
+ QTest::newRow("right header: 1/2 left") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
+ << QPointF(95,0) << QPointF();
+
+ QTest::newRow("right header: left") << "stickyPositioning-header.qml"
+ << Qt::Horizontal << Qt::RightToLeft << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
+ << QPointF(100,0) << QPointF();
+
+
+ // footer on the right
+ QTest::newRow("right footer") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 19 << QQuickItemView::End << QList<QPointF>()
+ << QPointF() << QPointF(100,0);
+
+ QTest::newRow("right footer: 1/2 right") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 18 << QQuickItemView::End << (QList<QPointF>() << QPointF(5,0))
+ << QPointF() << QPointF(95,0);
+
+ QTest::newRow("right footer: right") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 17 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0))
+ << QPointF() << QPointF(90,0);
+
+ QTest::newRow("right footer: 1/2 left") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 16 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
+ << QPointF() << QPointF(95,0);
+
+ QTest::newRow("right footer: left") << "stickyPositioning-footer.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 15 << QQuickItemView::End << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
+ << QPointF() << QPointF(100,0);
+
+
+ // header on the left (& footer on the right)
+ QTest::newRow("left header & right footer") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 0 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(-10,0) << QPointF(90,0);
+
+ QTest::newRow("left header & right footer: 1/2 left") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-5,0))
+ << QPointF(-5,0) << QPointF(95,0);
+
+ QTest::newRow("left header & right footer: left") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0))
+ << QPointF(0,0) << QPointF(100,0);
+
+ QTest::newRow("left header & right footer: 1/2 right") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(5,0))
+ << QPointF(-5,0) << QPointF(95,0);
+
+ QTest::newRow("left header & right footer: right") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(-15,0) << QPointF(10,0))
+ << QPointF(-10,0) << QPointF(90,0);
+
+
+ // footer on the right (& header on the left)
+ QTest::newRow("right footer & left header") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << QList<QPointF>()
+ << QPointF(-10,0) << QPointF(90,0);
+
+ QTest::newRow("right footer & left header: 1/2 right") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 1 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(5,0))
+ << QPointF(-10,0) << QPointF(90,0);
+
+ QTest::newRow("right footer & left header: right") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 2 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0))
+ << QPointF(-10,0) << QPointF(90,0);
+
+ QTest::newRow("right footer & left header: 1/2 left") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 3 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-5,0))
+ << QPointF(-5,0) << QPointF(95,0);
+
+ QTest::newRow("right footer & left header: left") << "stickyPositioning-both.qml"
+ << Qt::Horizontal << Qt::LeftToRight << QQuickListView::TopToBottom
+ << 4 << QQuickItemView::Beginning << (QList<QPointF>() << QPointF(15,0) << QPointF(-10,0))
+ << QPointF(0,0) << QPointF(100,0);
+}
+
void tst_QQuickListView::roundingErrors()
{
QFETCH(bool, pixelAligned);
diff --git a/tests/testapplications/listview/sticky.qml b/tests/testapplications/listview/sticky.qml
new file mode 100644
index 0000000000..6dcdf6b57e
--- /dev/null
+++ b/tests/testapplications/listview/sticky.qml
@@ -0,0 +1,250 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
+** of its contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtQuick.Window 2.1
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import Qt.labs.settings 1.0
+
+ApplicationWindow {
+ id: window
+
+ width: 640
+ height: 480
+ visible: true
+
+ property int hspacing: 10
+ property int vspacing: 35
+
+ Settings {
+ property alias windowX: window.x
+ property alias windowY: window.y
+ property alias windowWidth: window.width
+ property alias windowHeight: window.height
+ property alias orientation: oriItem.currentIndex
+ property alias layoutDirection: ldItem.currentIndex
+ property alias verticalLayoutDirection: vldItem.currentIndex
+ property alias hasHeader: hItem.checked
+ property alias headerPositioning: shItem.currentIndex
+ property alias hasFooter: fItem.checked
+ property alias footerPositioning: sfItem.currentIndex
+ property alias clipEnabled: clipItem.checked
+ property alias filterEnabled: filterItem.checked
+ property alias opacityEnabled: opacityItem.checked
+ property alias inlineSections: isItem.checked
+ property alias stickyCurrentSection: scsItem.checked
+ property alias stickyNextSection: snsItem.checked
+ }
+
+ toolBar: GridLayout {
+ rows: 3
+ flow: GridLayout.TopToBottom
+
+ anchors.margins: hspacing
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ ComboBox { id: oriItem; currentIndex: 1; model: ["Horizontal", "Vertical"] }
+ ComboBox { id: ldItem; model: ["LeftToRight", "RightToLeft"] }
+ ComboBox { id: vldItem; model: ["TopToBottom", "BottomToTop"] }
+
+ CheckBox { id: clipItem; text: "Clip"; checked: true }
+ CheckBox { id: opacityItem; text: "Opaque"; checked: true }
+ CheckBox { id: filterItem; text: "Filter" }
+
+ CheckBox { id: hItem; text: "Header:" }
+ CheckBox { id: fItem; text: "Footer:" }
+ Item { width: 1; height: 1 }
+
+ ComboBox { id: shItem; model: shModel; textRole: "name"; enabled: hItem.checked }
+ ComboBox { id: sfItem; model: sfModel; textRole: "name"; enabled: fItem.checked }
+ Item { width: 1; height: 1 }
+
+ CheckBox { id: scsItem; text: "Sticky current section" }
+ CheckBox { id: snsItem; text: "Sticky next section" }
+ CheckBox { id: isItem; text: "Inline sections" }
+
+ Button { text: "Beginning"; onClicked: listview.positionViewAtBeginning() }
+ Button { text: "Middle"; onClicked: listview.positionViewAtIndex(50, ListView.Center) }
+ Button { text: "End"; onClicked: listview.positionViewAtEnd() }
+ }
+
+ ListModel {
+ id: shModel
+ ListElement { name: "Inline"; value: ListView.InlineHeader }
+ ListElement { name: "Overlay"; value: ListView.OverlayHeader }
+ ListElement { name: "PullBack"; value: ListView.PullBackHeader }
+ }
+
+ ListModel {
+ id: sfModel
+ ListElement { name: "Inline"; value: ListView.InlineFooter }
+ ListElement { name: "Overlay"; value: ListView.OverlayFooter }
+ ListElement { name: "PullBack"; value: ListView.PullBackFooter }
+ }
+
+ statusBar: RowLayout {
+ anchors.margins: window.hspacing
+ anchors.left: parent.left
+ anchors.right: parent.right
+ Text {
+ anchors.left: parent.left
+ text: listview.currentSection ? "#" + listview.currentSection : ""
+ visible: scsItem.checked || snsItem.checked || isItem.checked
+ }
+ Text {
+ anchors.right: parent.right
+ property string pos: listview.isVertical ? listview.contentY.toFixed(2) : listview.contentX.toFixed(2)
+ property string size: listview.isVertical ? listview.contentHeight.toFixed(2) : listview.contentWidth.toFixed(2)
+ function padded(str) {
+ return String(" " + str).slice(-8)
+ }
+ text: padded(pos) + " /" + padded(size)
+ }
+ }
+
+ ListView {
+ id: listview
+
+ property bool isVertical: orientation == ListView.Vertical
+
+ anchors.fill: parent
+ anchors.leftMargin: window.hspacing
+ anchors.rightMargin: window.hspacing
+ anchors.topMargin: window.vspacing
+ anchors.bottomMargin: window.vspacing
+
+ clip: clipItem.checked
+
+ orientation: oriItem.currentIndex === 0 ? ListView.Horizontal : ListView.Vertical
+ layoutDirection: ldItem.currentIndex === 0 ? ListView.LeftToRight : ListView.RightToLeft
+ verticalLayoutDirection: vldItem.currentIndex === 0 ? ListView.TopToBottom : ListView.BottomToTop
+
+ model: ListModel {
+ Component.onCompleted: {
+ for (var i = 0; i < 100; ++i)
+ append({section: Math.floor(i / 10)})
+ }
+ }
+
+ delegate: Rectangle {
+ clip: true
+ property bool filterOut: filterItem.checked && index % 5
+ visible: !filterOut
+ width: filterOut ? 0 : parent && listview.isVertical ? parent.width : label.implicitHeight
+ height: filterOut ? 0 : !parent || listview.isVertical ? label.implicitHeight : parent.height
+ color: index % 2 ? "#ffffff" : "#f3f3f3"
+ Text {
+ id: label
+ anchors.centerIn: parent
+ rotation: listview.isVertical ? 0 : -90
+ text: index
+ }
+ }
+
+ section.property: "section"
+ section.delegate: Rectangle {
+ width: parent && listview.orientation == ListView.Vertical ? parent.width : sectionLabel.implicitHeight
+ height: !parent || listview.orientation == ListView.Vertical ? sectionLabel.implicitHeight : parent.height
+ color: "darkgray"
+ opacity: opacityItem.checked ? 1.0 : 0.8
+ Text {
+ id: sectionLabel
+ anchors.centerIn: parent
+ rotation: listview.isVertical ? 0 : -90
+ text: "#" + section
+ }
+ }
+ section.labelPositioning: (isItem.checked ? ViewSection.InlineLabels : 0) |
+ (scsItem.checked ? ViewSection.CurrentLabelAtStart : 0) |
+ (snsItem.checked ? ViewSection.NextLabelAtEnd : 0)
+
+ headerPositioning: shModel.get(shItem.currentIndex).value
+ header: hItem.checked ? headerComponent : null
+
+ footerPositioning: sfModel.get(sfItem.currentIndex).value
+ footer: fItem.checked ? footerComponent : null
+
+ Rectangle {
+ border.width: 1
+ anchors.fill: parent
+ color: "transparent"
+ border.color: "darkgray"
+ }
+
+ Component {
+ id: headerComponent
+ Rectangle {
+ z: 3
+ width: parent && listview.orientation == ListView.Vertical ? parent.width : headerLabel.implicitHeight * 2
+ height: !parent || listview.orientation == ListView.Vertical ? headerLabel.implicitHeight * 2 : parent.height
+ color: "steelblue"
+ opacity: opacityItem.checked ? 1.0 : 0.8
+ Text {
+ id: headerLabel
+ text: "Header"
+ font.pointSize: 12
+ anchors.centerIn: parent
+ rotation: listview.isVertical ? 0 : -90
+ }
+ }
+ }
+
+ Component {
+ id: footerComponent
+ Rectangle {
+ z: 3
+ width: parent && listview.orientation == ListView.Vertical ? parent.width : footerLabel.implicitHeight * 2
+ height: !parent || listview.orientation == ListView.Vertical ? footerLabel.implicitHeight * 2 : parent.height
+ color: "steelblue"
+ opacity: opacityItem.checked ? 1.0 : 0.8
+ Text {
+ id: footerLabel
+ text: "Footer"
+ font.pointSize: 10
+ anchors.centerIn: parent
+ rotation: listview.isVertical ? 0 : -90
+ }
+ }
+ }
+ }
+}