Additional ListView section header placement options

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>
This commit is contained in:
Martin Jones 2011-09-20 15:58:05 +10:00 committed by Qt by Nokia
parent b4001eacd6
commit 7ecce2cc0e
7 changed files with 591 additions and 158 deletions

View 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

View File

@ -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() }
}
}

View File

@ -42,22 +42,33 @@
// 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
}
}
}
}

View File

@ -55,11 +55,132 @@
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;
}

View 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

View 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
}
}

View 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