Additional ListView section header placement options
authorMartin Jones <martin.jones@nokia.com>
Tue, 20 Sep 2011 05:58:05 +0000 (15:58 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 21 Sep 2011 22:23:56 +0000 (00:23 +0200)
Add a section.labelPositioning property which can be a combination
of:

- ViewSection.InlineLabels - section labels are shown inline between
  the item delegates separating sections (default).
- ViewSection.CurrentLabelAtStart - the current section label sticks to
  the start of the view as it is moved.
- ViewSection.NextLabelAtEnd - the next section label (beyond all visible
  sections) sticks to the end of the view as it is moved.

Task-number: QTBUG-12880
Change-Id: I4601828337412bd3a83769c9b8df3f6d4d7474b8
Reviewed-on: http://codereview.qt-project.org/5192
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Bea Lam <bea.lam@nokia.com>
doc/src/declarative/whatsnew.qdoc
examples/declarative/modelviews/listview/content/ToggleButton.qml [new file with mode: 0644]
examples/declarative/modelviews/listview/sections.qml
src/declarative/items/qsglistview.cpp
src/declarative/items/qsglistview_p.h
tests/auto/declarative/qsglistview/data/listview-sections_delegate.qml
tests/auto/declarative/qsglistview/tst_qsglistview.cpp

index d4addf8..da00160 100644 (file)
@@ -114,6 +114,10 @@ PathView now has a \c currentItem property
 ListView and GridView now have headerItem and footerItem properties (the instantiated
 header and footer items).
 
+ListView section.labelPositioning property added to allow keeping the current section label
+at the start and/or next section label at the end of the view.
+
+
 \section2 QtQuick 1 is now a separate library and module
 
 Writing C++ applications using QtQuick 1 specific API, i.e. QDeclarativeView or QDeclarativeItem
diff --git a/examples/declarative/modelviews/listview/content/ToggleButton.qml b/examples/declarative/modelviews/listview/content/ToggleButton.qml
new file mode 100644 (file)
index 0000000..2d8221e
--- /dev/null
@@ -0,0 +1,18 @@
+import QtQuick 2.0
+
+Rectangle {
+    id: root
+    property alias label: text.text
+    property bool active: false
+    signal toggled
+    width: 149
+    height: 30
+    radius: 3
+    color: active ? "green" : "lightgray"
+    border.width: 1
+    Text { id: text; anchors.centerIn: parent; font.pixelSize: 14 }
+    MouseArea {
+        anchors.fill: parent
+        onClicked: { active = !active; root.toggled() }
+    }
+}
index 09e58be..4496567 100644 (file)
 // the ListView.section attached property.
 
 import QtQuick 2.0
+import "content"
 
-//! [0]
 Rectangle {
     id: container
-    width: 200
-    height: 250
+    width: 300
+    height: 360
 
     ListModel {
         id: animalsModel
+        ListElement { name: "Ant"; size: "Tiny" }
+        ListElement { name: "Flea"; size: "Tiny" }
         ListElement { name: "Parrot"; size: "Small" }
         ListElement { name: "Guinea pig"; size: "Small" }
+        ListElement { name: "Rat"; size: "Small" }
+        ListElement { name: "Butterfly"; size: "Small" }
         ListElement { name: "Dog"; size: "Medium" }
         ListElement { name: "Cat"; size: "Medium" }
-        ListElement { name: "Elephant"; size: "Large" }
+        ListElement { name: "Pony"; size: "Medium" }
+        ListElement { name: "Koala"; size: "Medium" }
+        ListElement { name: "Horse"; size: "Large" }
+        ListElement { name: "Tiger"; size: "Large" }
+        ListElement { name: "Giraffe"; size: "Large" }
+        ListElement { name: "Elephant"; size: "Huge" }
+        ListElement { name: "Whale"; size: "Huge" }
     }
 
+//! [0]
     // The delegate for each section header
     Component { 
         id: sectionHeading
@@ -69,19 +80,48 @@ Rectangle {
             Text {
                 text: section
                 font.bold: true
+                font.pixelSize: 20
             }
         }
     }
 
     ListView {
-        anchors.fill: parent
+        id: view
+        anchors.top: parent.top
+        anchors.bottom: buttonBar.top
+        width: parent.width
         model: animalsModel
-        delegate: Text { text: name }
+        delegate: Text { text: name; font.pixelSize: 18 }
 
         section.property: "size"
         section.criteria: ViewSection.FullString
         section.delegate: sectionHeading
     }
-}
 //! [0]
 
+    Row {
+        id: buttonBar
+        anchors.bottom: parent.bottom
+        anchors.bottomMargin: 1
+        spacing: 1
+        ToggleButton {
+            label: "CurrentLabelAtStart"
+            onToggled: {
+                if (active)
+                    view.section.labelPositioning |= ViewSection.CurrentLabelAtStart
+                else
+                    view.section.labelPositioning &= ~ViewSection.CurrentLabelAtStart
+            }
+        }
+        ToggleButton {
+            label: "NextLabelAtEnd"
+            onToggled: {
+                if (active)
+                    view.section.labelPositioning |= ViewSection.NextLabelAtEnd
+                else
+                    view.section.labelPositioning &= ~ViewSection.NextLabelAtEnd
+            }
+        }
+    }
+}
+
index e8a6bf2..f4d1392 100644 (file)
 
 QT_BEGIN_NAMESPACE
 
+class FxListItemSG;
+
+class QSGListViewPrivate : public QSGItemViewPrivate
+{
+    Q_DECLARE_PUBLIC(QSGListView)
+public:
+    static QSGListViewPrivate* get(QSGListView *item) { return item->d_func(); }
+
+    virtual Qt::Orientation layoutOrientation() const;
+    virtual bool isContentFlowReversed() const;
+    bool isRightToLeft() const;
+
+    virtual qreal positionAt(int index) const;
+    virtual qreal endPositionAt(int index) const;
+    virtual qreal originPosition() const;
+    virtual qreal lastPosition() const;
+
+    FxViewItem *nextVisibleItem() const;
+    FxViewItem *itemBefore(int modelIndex) const;
+    QString sectionAt(int modelIndex);
+    qreal snapPosAt(qreal pos);
+    FxViewItem *snapItemAt(qreal pos);
+
+    virtual void init();
+    virtual void clear();
+
+    virtual bool addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer);
+    virtual bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
+    virtual void visibleItemsChanged();
+
+    virtual FxViewItem *newViewItem(int index, QSGItem *item);
+    virtual void initializeViewItem(FxViewItem *item);
+    virtual void releaseItem(FxViewItem *item);
+    virtual void repositionPackageItemAt(QSGItem *item, int index);
+    virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem);
+    virtual void resetFirstItemPosition();
+    virtual void moveItemBy(FxViewItem *item, const QList<FxViewItem *> &items, const QList<FxViewItem *> &movedBackwards);
+
+    virtual void createHighlight();
+    virtual void updateHighlight();
+    virtual void resetHighlightPosition();
+
+    virtual void setPosition(qreal pos);
+    virtual void layoutVisibleItems();
+    bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, QList<FxViewItem *> *, QList<FxViewItem *> *, FxViewItem *firstVisible);
+
+    virtual void updateSections();
+    QSGItem *getSectionItem(const QString &section);
+    void releaseSectionItem(QSGItem *item);
+    void updateInlineSection(FxListItemSG *);
+    void updateCurrentSection();
+    void updateStickySections();
+
+    virtual qreal headerSize() const;
+    virtual qreal footerSize() const;
+    virtual bool showHeaderForIndex(int index) const;
+    virtual bool showFooterForIndex(int index) const;
+    virtual void updateHeader();
+    virtual void updateFooter();
+
+    virtual void changedVisibleIndex(int newIndex);
+    virtual void initializeCurrentItem();
+
+    void updateAverage();
+
+    void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry);
+    virtual void fixupPosition();
+    virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent);
+    virtual void flick(QSGItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
+                        QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity);
+
+    QSGListView::Orientation orient;
+    qreal visiblePos;
+    qreal averageSize;
+    qreal spacing;
+    QSGListView::SnapMode snapMode;
+
+    QSmoothedAnimation *highlightPosAnimator;
+    QSmoothedAnimation *highlightSizeAnimator;
+    qreal highlightMoveSpeed;
+    qreal highlightResizeSpeed;
+    int highlightResizeDuration;
+
+    QSGViewSection *sectionCriteria;
+    QString currentSection;
+    static const int sectionCacheSize = 5;
+    QSGItem *sectionCache[sectionCacheSize];
+    QSGItem *currentSectionItem;
+    QString currentStickySection;
+    QSGItem *nextSectionItem;
+    QString nextStickySection;
+    QString lastVisibleSection;
+    QString nextSection;
+
+    qreal overshootDist;
+    bool correctFlick : 1;
+    bool inFlickCorrection : 1;
+
+    QSGListViewPrivate()
+        : orient(QSGListView::Vertical)
+        , visiblePos(0)
+        , averageSize(100.0), spacing(0.0)
+        , snapMode(QSGListView::NoSnap)
+        , highlightPosAnimator(0), highlightSizeAnimator(0)
+        , highlightMoveSpeed(400), highlightResizeSpeed(400), highlightResizeDuration(-1)
+        , sectionCriteria(0), currentSectionItem(0), nextSectionItem(0)
+        , overshootDist(0.0), correctFlick(false), inFlickCorrection(false)
+    {}
+
+    friend class QSGViewSection;
+};
+
+//----------------------------------------------------------------------------
+
+QSGViewSection::QSGViewSection(QSGListView *parent)
+    : QObject(parent), m_criteria(FullString), m_delegate(0), m_labelPositioning(InlineLabels)
+    , m_view(QSGListViewPrivate::get(parent))
+{
+}
+
 void QSGViewSection::setProperty(const QString &property)
 {
     if (property != m_property) {
         m_property = property;
         emit propertyChanged();
+        m_view->updateSections();
     }
 }
 
@@ -68,6 +189,7 @@ void QSGViewSection::setCriteria(QSGViewSection::SectionCriteria criteria)
     if (criteria != m_criteria) {
         m_criteria = criteria;
         emit criteriaChanged();
+        m_view->updateSections();
     }
 }
 
@@ -76,6 +198,7 @@ void QSGViewSection::setDelegate(QDeclarativeComponent *delegate)
     if (delegate != m_delegate) {
         m_delegate = delegate;
         emit delegateChanged();
+        m_view->updateSections();
     }
 }
 
@@ -87,6 +210,15 @@ QString QSGViewSection::sectionString(const QString &value)
         return value;
 }
 
+void QSGViewSection::setLabelPositioning(int l)
+{
+    if (m_labelPositioning != l) {
+        m_labelPositioning = l;
+        emit labelPositioningChanged();
+        m_view->updateSections();
+    }
+}
+
 //----------------------------------------------------------------------------
 
 class FxListItemSG : public FxViewItem
@@ -179,103 +311,6 @@ public:
 
 //----------------------------------------------------------------------------
 
-class QSGListViewPrivate : public QSGItemViewPrivate
-{
-    Q_DECLARE_PUBLIC(QSGListView)
-public:
-    virtual Qt::Orientation layoutOrientation() const;
-    virtual bool isContentFlowReversed() const;
-    bool isRightToLeft() const;
-
-    virtual qreal positionAt(int index) const;
-    virtual qreal endPositionAt(int index) const;
-    virtual qreal originPosition() const;
-    virtual qreal lastPosition() const;
-
-    FxViewItem *nextVisibleItem() const;
-    FxViewItem *itemBefore(int modelIndex) const;
-    QString sectionAt(int modelIndex);
-    qreal snapPosAt(qreal pos);
-    FxViewItem *snapItemAt(qreal pos);
-
-    virtual void init();
-    virtual void clear();
-
-    virtual bool addVisibleItems(qreal fillFrom, qreal fillTo, bool doBuffer);
-    virtual bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
-    virtual void visibleItemsChanged();
-
-    virtual FxViewItem *newViewItem(int index, QSGItem *item);
-    virtual void initializeViewItem(FxViewItem *item);
-    virtual void releaseItem(FxViewItem *item);
-    virtual void repositionPackageItemAt(QSGItem *item, int index);
-    virtual void resetItemPosition(FxViewItem *item, FxViewItem *toItem);
-    virtual void resetFirstItemPosition();
-    virtual void moveItemBy(FxViewItem *item, const QList<FxViewItem *> &items, const QList<FxViewItem *> &movedBackwards);
-
-    virtual void createHighlight();
-    virtual void updateHighlight();
-    virtual void resetHighlightPosition();
-
-    virtual void setPosition(qreal pos);
-    virtual void layoutVisibleItems();
-    bool applyInsertionChange(const QDeclarativeChangeSet::Insert &, QList<FxViewItem *> *, QList<FxViewItem *> *, FxViewItem *firstVisible);
-
-    virtual void updateSections();
-    void createSection(FxListItemSG *);
-    void updateCurrentSection();
-
-    virtual qreal headerSize() const;
-    virtual qreal footerSize() const;
-    virtual bool showHeaderForIndex(int index) const;
-    virtual bool showFooterForIndex(int index) const;
-    virtual void updateHeader();
-    virtual void updateFooter();
-
-    virtual void changedVisibleIndex(int newIndex);
-    virtual void initializeCurrentItem();
-
-    void updateAverage();
-
-    void itemGeometryChanged(QSGItem *item, const QRectF &newGeometry, const QRectF &oldGeometry);
-    virtual void fixupPosition();
-    virtual void fixup(AxisData &data, qreal minExtent, qreal maxExtent);
-    virtual void flick(QSGItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
-                        QDeclarativeTimeLineCallback::Callback fixupCallback, qreal velocity);
-
-    QSGListView::Orientation orient;
-    qreal visiblePos;
-    qreal averageSize;
-    qreal spacing;
-    QSGListView::SnapMode snapMode;
-
-    QSmoothedAnimation *highlightPosAnimator;
-    QSmoothedAnimation *highlightSizeAnimator;
-    qreal highlightMoveSpeed;
-    qreal highlightResizeSpeed;
-    int highlightResizeDuration;
-
-    QSGViewSection *sectionCriteria;
-    QString currentSection;
-    static const int sectionCacheSize = 4;
-    QSGItem *sectionCache[sectionCacheSize];
-
-    qreal overshootDist;
-    bool correctFlick : 1;
-    bool inFlickCorrection : 1;
-
-    QSGListViewPrivate()
-        : orient(QSGListView::Vertical)
-        , visiblePos(0)
-        , averageSize(100.0), spacing(0.0)
-        , snapMode(QSGListView::NoSnap)
-        , highlightPosAnimator(0), highlightSizeAnimator(0)
-        , highlightMoveSpeed(400), highlightResizeSpeed(400), highlightResizeDuration(-1)
-        , sectionCriteria(0)
-        , overshootDist(0.0), correctFlick(false), inFlickCorrection(false)
-    {}
-};
-
 bool QSGListViewPrivate::isContentFlowReversed() const
 {
     return isRightToLeft();
@@ -474,6 +509,9 @@ void QSGListViewPrivate::clear()
         sectionCache[i] = 0;
     }
     visiblePos = 0;
+    currentSectionItem = 0;
+    nextSectionItem = 0;
+    lastVisibleSection = QString();
     QSGItemViewPrivate::clear();
 }
 
@@ -514,7 +552,7 @@ void QSGListViewPrivate::initializeViewItem(FxViewItem *item)
 
     if (sectionCriteria && sectionCriteria->delegate()) {
         if (item->attached->m_prevSection != item->attached->m_section)
-            createSection(static_cast<FxListItemSG*>(item));
+            updateInlineSection(static_cast<FxListItemSG*>(item));
     }
 }
 
@@ -790,42 +828,66 @@ void QSGListViewPrivate::resetHighlightPosition()
         static_cast<FxListItemSG*>(highlight)->setPosition(static_cast<FxListItemSG*>(currentItem)->itemPosition());
 }
 
-void QSGListViewPrivate::createSection(FxListItemSG *listItem)
+QSGItem * QSGListViewPrivate::getSectionItem(const QString &section)
 {
     Q_Q(QSGListView);
+    QSGItem *sectionItem = 0;
+    int i = sectionCacheSize-1;
+    while (i >= 0 && !sectionCache[i])
+        --i;
+    if (i >= 0) {
+        sectionItem = sectionCache[i];
+        sectionCache[i] = 0;
+        sectionItem->setVisible(true);
+        QDeclarativeContext *context = QDeclarativeEngine::contextForObject(sectionItem)->parentContext();
+        context->setContextProperty(QLatin1String("section"), section);
+    } else {
+        QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q));
+        context->setContextProperty(QLatin1String("section"), section);
+        QObject *nobj = sectionCriteria->delegate()->beginCreate(context);
+        if (nobj) {
+            QDeclarative_setParent_noEvent(context, nobj);
+            sectionItem = qobject_cast<QSGItem *>(nobj);
+            if (!sectionItem) {
+                delete nobj;
+            } else {
+                sectionItem->setZ(2);
+                QDeclarative_setParent_noEvent(sectionItem, contentItem);
+                sectionItem->setParentItem(contentItem);
+            }
+        } else {
+            delete context;
+        }
+        sectionCriteria->delegate()->completeCreate();
+    }
+
+    return sectionItem;
+}
+
+void QSGListViewPrivate::releaseSectionItem(QSGItem *item)
+{
+    int i = 0;
+    do {
+        if (!sectionCache[i]) {
+            sectionCache[i] = item;
+            sectionCache[i]->setVisible(false);
+            return;
+        }
+        ++i;
+    } while (i < sectionCacheSize);
+    delete item;
+}
+
+void QSGListViewPrivate::updateInlineSection(FxListItemSG *listItem)
+{
     if (!sectionCriteria || !sectionCriteria->delegate())
         return;
-    if (listItem->attached->m_prevSection != listItem->attached->m_section) {
+    if (listItem->attached->m_prevSection != listItem->attached->m_section
+            && (sectionCriteria->labelPositioning() & QSGViewSection::InlineLabels
+                || (listItem->index == 0 && sectionCriteria->labelPositioning() & QSGViewSection::CurrentLabelAtStart))) {
         if (!listItem->section) {
             qreal pos = listItem->position();
-            int i = sectionCacheSize-1;
-            while (i >= 0 && !sectionCache[i])
-                --i;
-            if (i >= 0) {
-                listItem->section = sectionCache[i];
-                sectionCache[i] = 0;
-                listItem->section->setVisible(true);
-                QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext();
-                context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
-            } else {
-                QDeclarativeContext *context = new QDeclarativeContext(qmlContext(q));
-                context->setContextProperty(QLatin1String("section"), listItem->attached->m_section);
-                QObject *nobj = sectionCriteria->delegate()->beginCreate(context);
-                if (nobj) {
-                    QDeclarative_setParent_noEvent(context, nobj);
-                    listItem->section = qobject_cast<QSGItem *>(nobj);
-                    if (!listItem->section) {
-                        delete nobj;
-                    } else {
-                        listItem->section->setZ(1);
-                        QDeclarative_setParent_noEvent(listItem->section, q->contentItem());
-                        listItem->section->setParentItem(q->contentItem());
-                    }
-                } else {
-                    delete context;
-                }
-                sectionCriteria->delegate()->completeCreate();
-            }
+            listItem->section = getSectionItem(listItem->attached->m_section);
             listItem->setPosition(pos);
         } else {
             QDeclarativeContext *context = QDeclarativeEngine::contextForObject(listItem->section)->parentContext();
@@ -833,24 +895,119 @@ void QSGListViewPrivate::createSection(FxListItemSG *listItem)
         }
     } else if (listItem->section) {
         qreal pos = listItem->position();
-        int i = 0;
-        do {
-            if (!sectionCache[i]) {
-                sectionCache[i] = listItem->section;
-                sectionCache[i]->setVisible(false);
-                listItem->section = 0;
-                return;
-            }
-            ++i;
-        } while (i < sectionCacheSize);
-        delete listItem->section;
+        releaseSectionItem(listItem->section);
         listItem->section = 0;
         listItem->setPosition(pos);
     }
 }
 
+void QSGListViewPrivate::updateStickySections()
+{
+    if (!sectionCriteria || visibleItems.isEmpty()
+            || (!sectionCriteria->labelPositioning() && !currentSectionItem && !nextSectionItem))
+        return;
+
+    bool isRtl = isRightToLeft();
+    qreal viewPos = isRightToLeft() ? -position()-size() : position();
+    QSGItem *sectionItem = 0;
+    QSGItem *lastSectionItem = 0;
+    int index = 0;
+    while (index < visibleItems.count()) {
+        if (QSGItem *section = static_cast<FxListItemSG *>(visibleItems.at(index))->section) {
+            // Find the current section header and last visible section header
+            // and hide them if they will overlap a static section header.
+            qreal sectionPos = orient == QSGListView::Vertical ? section->y() : section->x();
+            qreal sectionSize = orient == QSGListView::Vertical ? section->height() : section->width();
+            bool visTop = true;
+            if (sectionCriteria->labelPositioning() & QSGViewSection::CurrentLabelAtStart)
+                visTop = isRtl ? -sectionPos-sectionSize >= viewPos : sectionPos >= viewPos;
+            bool visBot = true;
+            if (sectionCriteria->labelPositioning() & QSGViewSection::NextLabelAtEnd)
+                visBot = isRtl ? -sectionPos <= viewPos + size() : sectionPos + sectionSize < viewPos + size();
+            section->setVisible(visBot && visTop);
+            if (visTop && !sectionItem)
+                sectionItem = section;
+            if (isRtl) {
+               if (-sectionPos <= viewPos + size())
+                    lastSectionItem = section;
+            } else {
+                if (sectionPos + sectionSize < viewPos + size())
+                    lastSectionItem = section;
+            }
+        }
+        ++index;
+    }
+
+    // Current section header
+    if (sectionCriteria->labelPositioning() & QSGViewSection::CurrentLabelAtStart) {
+        if (!currentSectionItem) {
+            currentSectionItem = getSectionItem(currentSection);
+        } else if (currentStickySection != currentSection) {
+            QDeclarativeContext *context = QDeclarativeEngine::contextForObject(currentSectionItem)->parentContext();
+            context->setContextProperty(QLatin1String("section"), currentSection);
+        }
+        currentStickySection = currentSection;
+        if (!currentSectionItem)
+            return;
+
+        qreal sectionSize = orient == QSGListView::Vertical ? currentSectionItem->height() : currentSectionItem->width();
+        bool atBeginning = orient == QSGListView::Vertical ? vData.atBeginning : (isRightToLeft() ? hData.atEnd : hData.atBeginning);
+        currentSectionItem->setVisible(!atBeginning && (!header || header->endPosition() < viewPos));
+        qreal pos = isRtl ? position() + size() - sectionSize : viewPos;
+        if (sectionItem) {
+            qreal sectionPos = orient == QSGListView::Vertical ? sectionItem->y() : sectionItem->x();
+            pos = isRtl ? qMax(pos, sectionPos + sectionSize) : qMin(pos, sectionPos - sectionSize);
+        }
+        if (header)
+            pos = isRtl ? qMin(header->endPosition(), pos) : qMax(header->endPosition(), pos);
+        if (footer)
+            pos = isRtl ? qMax(-footer->position(), pos) : qMin(footer->position() - sectionSize, pos);
+        if (orient == QSGListView::Vertical)
+            currentSectionItem->setY(pos);
+        else
+            currentSectionItem->setX(pos);
+    } else if (currentSectionItem) {
+        releaseSectionItem(currentSectionItem);
+        currentSectionItem = 0;
+    }
+
+    // Next section footer
+    if (sectionCriteria->labelPositioning() & QSGViewSection::NextLabelAtEnd) {
+        if (!nextSectionItem) {
+            nextSectionItem = getSectionItem(nextSection);
+        } else if (nextStickySection != nextSection) {
+            QDeclarativeContext *context = QDeclarativeEngine::contextForObject(nextSectionItem)->parentContext();
+            context->setContextProperty(QLatin1String("section"), nextSection);
+        }
+        nextStickySection = nextSection;
+        if (!nextSectionItem)
+            return;
+
+        qreal sectionSize = orient == QSGListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
+        nextSectionItem->setVisible(!nextSection.isEmpty());
+        qreal pos = isRtl ? position() : viewPos + size() - sectionSize;
+        if (lastSectionItem) {
+            qreal sectionPos = orient == QSGListView::Vertical ? lastSectionItem->y() : lastSectionItem->x();
+            pos = isRtl ? qMin(pos, sectionPos - sectionSize) : qMax(pos, sectionPos + sectionSize);
+        }
+        if (header)
+            pos = isRtl ? qMin(header->endPosition() - sectionSize, pos) : qMax(header->endPosition(), pos);
+        if (orient == QSGListView::Vertical)
+            nextSectionItem->setY(pos);
+        else
+            nextSectionItem->setX(pos);
+    } else if (nextSectionItem) {
+        releaseSectionItem(nextSectionItem);
+        nextSectionItem = 0;
+    }
+}
+
 void QSGListViewPrivate::updateSections()
 {
+    Q_Q(QSGListView);
+    if (!q->isComponentComplete())
+        return;
+
     QSGItemViewPrivate::updateSections();
 
     if (sectionCriteria && !visibleItems.isEmpty()) {
@@ -867,7 +1024,7 @@ void QSGListViewPrivate::updateSections()
                 attached->setSection(sectionCriteria->sectionString(propValue));
                 idx = visibleItems.at(i)->index;
             }
-            createSection(static_cast<FxListItemSG*>(visibleItems.at(i)));
+            updateInlineSection(static_cast<FxListItemSG*>(visibleItems.at(i)));
             if (prevAtt)
                 prevAtt->setNextSection(attached->section());
             prevSection = attached->section();
@@ -880,6 +1037,10 @@ void QSGListViewPrivate::updateSections()
                 prevAtt->setNextSection(QString());
         }
     }
+
+    lastVisibleSection = QString();
+    updateCurrentSection();
+    updateStickySections();
 }
 
 void QSGListViewPrivate::updateCurrentSection()
@@ -892,9 +1053,17 @@ void QSGListViewPrivate::updateCurrentSection()
         }
         return;
     }
+    bool inlineSections = sectionCriteria->labelPositioning() & QSGViewSection::InlineLabels;
+    qreal sectionThreshold = position();
+    if (currentSectionItem && !inlineSections)
+        sectionThreshold += orient == QSGListView::Vertical ? currentSectionItem->height() : currentSectionItem->width();
     int index = 0;
-    while (index < visibleItems.count() && visibleItems.at(index)->endPosition() <= position())
+    int modelIndex = visibleIndex;
+    while (index < visibleItems.count() && visibleItems.at(index)->endPosition() <= sectionThreshold) {
+        if (visibleItems.at(index)->index != -1)
+            modelIndex = visibleItems.at(index)->index;
         ++index;
+    }
 
     QString newSection = currentSection;
     if (index < visibleItems.count())
@@ -903,8 +1072,39 @@ void QSGListViewPrivate::updateCurrentSection()
         newSection = (*visibleItems.constBegin())->attached->section();
     if (newSection != currentSection) {
         currentSection = newSection;
+        updateStickySections();
         emit q->currentSectionChanged();
     }
+
+    if (sectionCriteria->labelPositioning() & QSGViewSection::NextLabelAtEnd) {
+        // Don't want to scan for next section on every movement, so remember
+        // the last section in the visible area and only scan for the next
+        // section when that changes.  Clearing lastVisibleSection will also
+        // force searching.
+        QString lastSection = currentSection;
+        qreal endPos = isRightToLeft() ? -position() : position() + size();
+        if (nextSectionItem && !inlineSections)
+            endPos -= orient == QSGListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
+        while (index < visibleItems.count() && static_cast<FxListItemSG*>(visibleItems.at(index))->itemPosition() < endPos) {
+            if (visibleItems.at(index)->index != -1)
+                modelIndex = visibleItems.at(index)->index;
+            lastSection = visibleItems.at(index)->attached->section();
+            ++index;
+        }
+
+        if (lastVisibleSection != lastSection) {
+            nextSection = QString();
+            lastVisibleSection = lastSection;
+            for (int i = modelIndex; i < itemCount; ++i) {
+                QString section = sectionAt(i);
+                if (section != lastSection) {
+                    nextSection = section;
+                    updateStickySections();
+                    break;
+                }
+            }
+        }
+    }
 }
 
 void QSGListViewPrivate::initializeCurrentItem()
@@ -1708,12 +1908,10 @@ void QSGListView::setOrientation(QSGListView::Orientation orientation)
     \qmlproperty string QtQuick2::ListView::section.property
     \qmlproperty enumeration QtQuick2::ListView::section.criteria
     \qmlproperty Component QtQuick2::ListView::section.delegate
+    \qmlproperty enumeration QtQuick2::ListView::section.labelPositioning
 
-    These properties hold the expression to be evaluated for the \l section attached property.
-
-    The \l section attached property enables a ListView to be visually
-    separated into different parts. These properties determine how sections
-    are created.
+    These properties determine the expression to be evaluated and appearance
+    of the section labels.
 
     \c section.property holds the name of the property that is the basis
     of each section.
@@ -1731,9 +1929,23 @@ void QSGListView::setOrientation(QSGListView::Orientation orientation)
 
     \c section.delegate holds the delegate component for each section.
 
+    \c section.labelPositioning determines whether the current and/or
+    next section labels stick to the start/end of the view, and whether
+    the labels are shown inline.  This value can be a combination of:
+
+    \list
+    \o ViewSection.InlineLabels - section labels are shown inline between
+    the item delegates separating sections (default).
+    \o ViewSection.CurrentLabelAtStart - the current section label sticks to the
+    start of the view as it is moved.
+    \o ViewSection.NextLabelAtEnd - the next section label (beyond all visible
+    sections) sticks to the end of the view as it is moved. \note Enabling
+    \c ViewSection.NextLabelAtEnd requires the view to scan ahead for the next
+    section, which has performance implications, especially for slower models.
+    \endlist
+
     Each item in the list has attached properties named \c ListView.section,
-    \c ListView.previousSection and \c ListView.nextSection.  These may be
-    used to place a section header for related items.
+    \c ListView.previousSection and \c ListView.nextSection.
 
     For example, here is a ListView that displays a list of animals, separated
     into sections. Each item in the ListView is placed in a different section
@@ -2000,6 +2212,10 @@ void QSGListView::viewportMoved()
         }
         d->inFlickCorrection = false;
     }
+    if (d->sectionCriteria) {
+        d->updateCurrentSection();
+        d->updateStickySections();
+    }
     d->inViewportMoved = false;
 }
 
index 8ff4b05..a3c31df 100644 (file)
@@ -53,15 +53,19 @@ QT_BEGIN_NAMESPACE
 
 QT_MODULE(Declarative)
 
+class QSGListView;
+class QSGListViewPrivate;
 class Q_AUTOTEST_EXPORT QSGViewSection : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(QString property READ property WRITE setProperty NOTIFY propertyChanged)
     Q_PROPERTY(SectionCriteria criteria READ criteria WRITE setCriteria NOTIFY criteriaChanged)
     Q_PROPERTY(QDeclarativeComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+    Q_PROPERTY(int labelPositioning READ labelPositioning WRITE setLabelPositioning NOTIFY labelPositioningChanged)
     Q_ENUMS(SectionCriteria)
+    Q_ENUMS(LabelPositioning)
 public:
-    QSGViewSection(QObject *parent=0) : QObject(parent), m_criteria(FullString), m_delegate(0) {}
+    QSGViewSection(QSGListView *parent=0);
 
     QString property() const { return m_property; }
     void setProperty(const QString &);
@@ -75,21 +79,27 @@ public:
 
     QString sectionString(const QString &value);
 
+    enum LabelPositioning { InlineLabels = 0x01, CurrentLabelAtStart = 0x02, NextLabelAtEnd = 0x04 };
+    int labelPositioning() { return m_labelPositioning; }
+    void setLabelPositioning(int pos);
+
 Q_SIGNALS:
     void propertyChanged();
     void criteriaChanged();
     void delegateChanged();
+    void labelPositioningChanged();
 
 private:
     QString m_property;
     SectionCriteria m_criteria;
     QDeclarativeComponent *m_delegate;
+    int m_labelPositioning;
+    QSGListViewPrivate *m_view;
 };
 
 
 class QSGVisualModel;
 class QSGListViewAttached;
-class QSGListViewPrivate;
 class Q_AUTOTEST_EXPORT QSGListView : public QSGItemView
 {
     Q_OBJECT
index 82f332c..496d8d7 100644 (file)
@@ -2,6 +2,7 @@ import QtQuick 2.0
 
 Rectangle {
     property string sectionProperty: "number"
+    property int sectionPositioning: ViewSection.InlineLabels
     width: 240
     height: 320
     color: "#ffffff"
@@ -63,7 +64,8 @@ Rectangle {
             color: "#99bb99"
             height: 20
             width: list.width
-            Text { text: section }
+            Text { text: section + ",   " + parent.y + ",    " + parent.objectName }
         }
+        section.labelPositioning: sectionPositioning
     }
 }
index cbd1b01..ce1587d 100644 (file)
@@ -105,6 +105,7 @@ private slots:
     void enforceRange_withoutHighlight();
     void spacing();
     void sections();
+    void sectionsPositioning();
     void sectionsDelegate();
     void cacheBuffer();
     void positionViewAtIndex();
@@ -144,6 +145,7 @@ private:
     template <class T> void moved();
     template <class T> void clear();
     QSGView *createView();
+    QSGItem *findVisibleChild(QSGItem *parent, const QString &objectName);
     template<typename T>
     T *findItem(QSGItem *parent, const QString &id, int index=-1);
     template<typename T>
@@ -1701,6 +1703,135 @@ void tst_QSGListView::sectionsDelegate()
     delete canvas;
 }
 
+void tst_QSGListView::sectionsPositioning()
+{
+    QSGView *canvas = createView();
+    canvas->show();
+
+    TestModel model;
+    for (int i = 0; i < 30; i++)
+        model.addItem("Item" + QString::number(i), QString::number(i/5));
+
+    QDeclarativeContext *ctxt = canvas->rootContext();
+    ctxt->setContextProperty("testModel", &model);
+
+    canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listview-sections_delegate.qml"));
+    qApp->processEvents();
+    canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart | QSGViewSection::NextLabelAtEnd)));
+
+    QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
+    QTRY_VERIFY(listview != 0);
+
+    QSGItem *contentItem = listview->contentItem();
+    QTRY_VERIFY(contentItem != 0);
+
+    for (int i = 0; i < 3; ++i) {
+        QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
+        QVERIFY(item);
+        QTRY_COMPARE(item->y(), qreal(i*20*6));
+    }
+
+    QSGItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
+    QVERIFY(topItem);
+    QCOMPARE(topItem->y(), 0.);
+
+    QSGItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
+    QVERIFY(bottomItem);
+    QCOMPARE(bottomItem->y(), 300.);
+
+    // move down a little and check that section header is at top
+    listview->setContentY(10);
+    QCOMPARE(topItem->y(), 0.);
+
+    // push the top header up
+    listview->setContentY(110);
+    topItem = findVisibleChild(contentItem, "sect_0"); // section header
+    QVERIFY(topItem);
+    QCOMPARE(topItem->y(), 100.);
+
+    QSGItem *item = findVisibleChild(contentItem, "sect_1");
+    QVERIFY(item);
+    QCOMPARE(item->y(), 120.);
+
+    bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
+    QVERIFY(bottomItem);
+    QCOMPARE(bottomItem->y(), 410.);
+
+    // Move past section 0
+    listview->setContentY(120);
+    topItem = findVisibleChild(contentItem, "sect_0"); // section header
+    QVERIFY(!topItem);
+
+    // Push section footer down
+    listview->setContentY(70);
+    bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
+    QVERIFY(bottomItem);
+    QCOMPARE(bottomItem->y(), 380.);
+
+    // Change current section
+    listview->setContentY(10);
+    model.modifyItem(0, "One", "aaa");
+    model.modifyItem(1, "Two", "aaa");
+    model.modifyItem(2, "Three", "aaa");
+    model.modifyItem(3, "Four", "aaa");
+    model.modifyItem(4, "Five", "aaa");
+    QTest::qWait(300);
+
+    QTRY_COMPARE(listview->currentSection(), QString("aaa"));
+
+    for (int i = 0; i < 3; ++i) {
+        QSGItem *item = findItem<QSGItem>(contentItem,
+                "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
+        QVERIFY(item);
+        QTRY_COMPARE(item->y(), qreal(i*20*6));
+    }
+
+    topItem = findVisibleChild(contentItem, "sect_aaa"); // section header
+    QVERIFY(topItem);
+    QCOMPARE(topItem->y(), 10.);
+
+    // remove section boundary
+    listview->setContentY(120);
+    model.removeItem(5);
+    QTRY_COMPARE(listview->count(), model.count());
+    for (int i = 0; i < 3; ++i) {
+        QSGItem *item = findItem<QSGItem>(contentItem,
+                "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
+        QVERIFY(item);
+        QTRY_COMPARE(item->y(), qreal(i*20*6));
+    }
+
+    QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
+    QCOMPARE(topItem->y(), 120.);
+    QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
+    QCOMPARE(topItem->y(), 140.);
+
+    // Change the next section
+    listview->setContentY(0);
+    bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
+    QVERIFY(bottomItem);
+    QCOMPARE(bottomItem->y(), 320.);
+
+    model.modifyItem(14, "New", "new");
+
+    QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
+    QCOMPARE(bottomItem->y(), 320.);
+
+    // Turn sticky footer off
+    listview->setContentY(50);
+    canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart)));
+    item = findVisibleChild(contentItem, "sect_new"); // inline label restored
+    QCOMPARE(item->y(), 360.);
+
+    // Turn sticky header off
+    listview->setContentY(50);
+    canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels)));
+    item = findVisibleChild(contentItem, "sect_aaa"); // inline label restored
+    QCOMPARE(item->y(), 20.);
+
+    delete canvas;
+}
+
 void tst_QSGListView::currentIndex()
 {
     TestModel model;
@@ -3491,6 +3622,18 @@ QSGView *tst_QSGListView::createView()
     return canvas;
 }
 
+QSGItem *tst_QSGListView::findVisibleChild(QSGItem *parent, const QString &objectName)
+{
+    QSGItem *item = 0;
+    QList<QSGItem*> items = parent->findChildren<QSGItem*>(objectName);
+    for (int i = 0; i < items.count(); ++i) {
+        if (items.at(i)->isVisible()) {
+            item = items.at(i);
+            break;
+        }
+    }
+    return item;
+}
 /*
    Find an item with the specified objectName.  If index is supplied then the
    item must also evaluate the {index} expression equal to index