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:
parent
b4001eacd6
commit
7ecce2cc0e
|
@ -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
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 §ion);
|
||||
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 §ion)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue