@@ -18,11 +18,44 @@
1818
1919#include <QDebug>
2020#include <qqmlinfo.h>
21+#include <qqmlengine.h>
2122#include <private/qquickvisualdatamodel_p.h>
2223#include <private/qqmlglobal_p.h>
2324#include <private/qquickitem_p.h>
2425// #include <private/qquickrectangle_p.h>
2526
27+qreal ListViewWithPageHeader::ListItem::height() const
28+{
29+ return m_item->height() + (m_sectionItem ? m_sectionItem->height() : 0);
30+}
31+
32+qreal ListViewWithPageHeader::ListItem::y() const
33+{
34+ return m_item->y() - (m_sectionItem ? m_sectionItem->height() : 0);
35+}
36+
37+void ListViewWithPageHeader::ListItem::setY(qreal newY)
38+{
39+ if (m_sectionItem) {
40+ m_sectionItem->setY(newY);
41+ m_item->setY(newY + m_sectionItem->height());
42+ } else {
43+ m_item->setY(newY);
44+ }
45+}
46+
47+bool ListViewWithPageHeader::ListItem::culled() const
48+{
49+ return QQuickItemPrivate::get(m_item)->culled;
50+}
51+
52+void ListViewWithPageHeader::ListItem::setCulled(bool culled)
53+{
54+ QQuickItemPrivate::get(m_item)->setCulled(culled);
55+ if (m_sectionItem)
56+ QQuickItemPrivate::get(m_sectionItem)->setCulled(culled);
57+}
58+
2659ListViewWithPageHeader::ListViewWithPageHeader()
2760 : m_delegateModel(nullptr)
2861 , m_delegateValidated(false)
@@ -32,6 +65,8 @@ ListViewWithPageHeader::ListViewWithPageHeader()
3265 , m_headerItem(nullptr)
3366 , m_previousContentY(0)
3467 , m_headerItemShownHeight(0)
68+ , m_sectionDelegate(nullptr)
69+ , m_topSectionItem(nullptr)
3570{
3671 m_clipItem = new QQuickItem(contentItem());
3772// m_clipItem = new QQuickRectangle(contentItem());
@@ -80,7 +115,7 @@ void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
80115 }
81116
82117 // Cleanup the existing items
83− foreach(QQuickItem *item, m_visibleItems)
118+ foreach(ListItem *item, m_visibleItems)
84119 releaseItem(item);
85120 m_visibleItems.clear();
86121 m_firstVisibleIndex = -1;
@@ -110,6 +145,7 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
110145 oldHeaderHeight = m_headerItem->height();
111146 oldHeaderY = m_headerItem->y();
112147 QQuickItemPrivate::get(m_headerItem)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
148+ m_headerItem->setParentItem(nullptr);
113149 }
114150 m_headerItem = headerItem;
115151 if (m_headerItem) {
@@ -127,6 +163,46 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
127163 }
128164}
129165
166+QQmlComponent *ListViewWithPageHeader::sectionDelegate() const
167+{
168+ return m_sectionDelegate;
169+}
170+
171+void ListViewWithPageHeader::setSectionDelegate(QQmlComponent *delegate)
172+{
173+ if (delegate != m_sectionDelegate) {
174+ // TODO clean existing sections
175+
176+ m_sectionDelegate = delegate;
177+
178+ m_topSectionItem = getSectionItem(QString());
179+ m_topSectionItem->setZ(3);
180+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
181+
182+ // TODO create sections for existing items
183+
184+ Q_EMIT sectionDelegateChanged();
185+ }
186+}
187+
188+QString ListViewWithPageHeader::sectionProperty() const
189+{
190+ return m_sectionProperty;
191+}
192+
193+void ListViewWithPageHeader::setSectionProperty(const QString &property)
194+{
195+ if (property != m_sectionProperty) {
196+ m_sectionProperty = property;
197+
198+ updateWatchedRoles();
199+
200+ // TODO recreate sections
201+
202+ Q_EMIT sectionPropertyChanged();
203+ }
204+}
205+
130206void ListViewWithPageHeader::positionAtBeginning()
131207{
132208 qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
@@ -134,14 +210,14 @@ void ListViewWithPageHeader::positionAtBeginning()
134210 // This could be optimized by trying to reuse the interesection
135211 // of items that may end up intersecting between the existing
136212 // m_visibleItems and the items we are creating in the next loop
137− foreach(QQuickItem *item, m_visibleItems)
213+ foreach(ListItem *item, m_visibleItems)
138214 releaseItem(item);
139215 m_visibleItems.clear();
140216 m_firstVisibleIndex = -1;
141217
142218 // Create the item 0, it will be already correctly positioned at createItem()
143219 m_clipItem->setY(0);
144− QQuickItem *item = createItem(0, false);
220+ ListItem *item = createItem(0, false);
145221 // Create the subsequent items
146222 int modelIndex = 1;
147223 qreal pos = item->y() + item->height();
@@ -221,9 +297,10 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
221297 }
222298 if (!m_visibleItems.isEmpty()) {
223299 updateClipItem();
224− QQuickItem *firstItem = m_visibleItems.first();
300+ ListItem *firstItem = m_visibleItems.first();
225301 firstItem->setY(firstItem->y() + diff);
226302 }
303+
227304 m_previousContentY = contentY();
228305 polish();
229306}
@@ -239,6 +316,7 @@ void ListViewWithPageHeader::createDelegateModel()
239316 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
240317 if (isComponentComplete())
241318 m_delegateModel->componentComplete();
319+ updateWatchedRoles();
242320}
243321
244322void ListViewWithPageHeader::refill()
@@ -271,7 +349,7 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
271349 if (m_delegateModel->count() == 0)
272350 return false;
273351
274− QQuickItem *item;
352+ ListItem *item;
275353// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
276354
277355 int modelIndex = 0;
@@ -311,14 +389,68 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
311389 return changed;
312390}
313391
314−void ListViewWithPageHeader::releaseItem(QQuickItem *item)
392+void ListViewWithPageHeader::releaseItem(ListItem *listItem)
315393{
394+ QQuickItem *item = listItem->m_item;
316395 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
317396 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
318397 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
319398 if (flags & QQuickVisualModel::Destroyed) {
320399 item->setParentItem(nullptr);
321400 }
401+ delete listItem->m_sectionItem;
402+ delete listItem;
403+}
404+
405+void ListViewWithPageHeader::updateWatchedRoles()
406+{
407+ if (m_delegateModel) {
408+ QList<QByteArray> roles;
409+ if (!m_sectionProperty.isEmpty())
410+ roles << m_sectionProperty.toUtf8();
411+ m_delegateModel->setWatchedRoles(roles);
412+ }
413+}
414+
415+QQuickItem *ListViewWithPageHeader::getSectionItem(int modelIndex)
416+{
417+ if (!m_sectionDelegate)
418+ return nullptr;
419+
420+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
421+ if (modelIndex > 0) {
422+ const QString prevSection = m_delegateModel->stringValue(modelIndex - 1, m_sectionProperty);
423+ if (section == prevSection)
424+ return nullptr;
425+ }
426+
427+ return getSectionItem(section);
428+}
429+
430+QQuickItem *ListViewWithPageHeader::getSectionItem(const QString §ionText)
431+{
432+ QQuickItem *sectionItem = nullptr;
433+
434+ QQmlContext *creationContext = m_sectionDelegate->creationContext();
435+ QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this));
436+ context->setContextProperty(QLatin1String("section"), sectionText);
437+ QObject *nobj = m_sectionDelegate->beginCreate(context);
438+ if (nobj) {
439+ QQml_setParent_noEvent(context, nobj);
440+ sectionItem = qobject_cast<QQuickItem *>(nobj);
441+ if (!sectionItem) {
442+ delete nobj;
443+ } else {
444+ sectionItem->setZ(2);
445+ QQml_setParent_noEvent(sectionItem, m_clipItem);
446+ sectionItem->setParentItem(m_clipItem);
447+ }
448+ } else {
449+ delete context;
450+ }
451+ m_sectionDelegate->completeCreate();
452+
453+ return sectionItem;
322454}
323455
324456bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
@@ -330,7 +462,7 @@ bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal buffe
330462 int i = 0;
331463 int removedItems = 0;
332464 while (i < m_visibleItems.count()) {
333− QQuickItem *item = m_visibleItems[i];
465+ ListItem *item = m_visibleItems[i];
334466 const qreal pos = item->y() + m_clipItem->y();
335467// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
336468 if (pos + item->height() < bufferFrom || pos > bufferTo) {
@@ -352,7 +484,7 @@ bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal buffe
352484 return changed;
353485}
354486
355−QQuickItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
487+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
356488{
357489// qDebug() << "CREATE ITEM" << modelIndex;
358490 if (asynchronous && m_asyncRequestedIndexes.contains(modelIndex))
@@ -365,29 +497,32 @@ QQuickItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous
365497 return 0;
366498 } else {
367499// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
500+ ListItem *listItem = new ListItem;
501+ listItem->m_item = item;
502+ listItem->m_sectionItem = getSectionItem(modelIndex);
368503 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
369− QQuickItem *prevItem = itemAtIndex(modelIndex - 1);
504+ ListItem *prevItem = itemAtIndex(modelIndex - 1);
370505 if (prevItem) {
371− item->setY(prevItem->y() + prevItem->height());
506+ listItem->setY(prevItem->y() + prevItem->height());
372507 } else {
373− QQuickItem *nextItem = itemAtIndex(modelIndex + 1);
508+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
374509 if (nextItem) {
375− item->setY(nextItem->y() - item->height());
510+ listItem->setY(nextItem->y() - listItem ->height());
376511 } else if (modelIndex == 0 && m_headerItem) {
377− item->setY(m_headerItem->height());
512+ listItem->setY(m_headerItem->height());
378513 }
379514 }
380− QQuickItemPrivate::get(item)->setCulled(item->y() + item->height() + m_clipItem->y() < contentY() || item->y() + m_clipItem->y() >= contentY() + height());
515+ listItem->setCulled(listItem->y() + listItem->height() + m_clipItem->y() < contentY() || listItem->y() + m_clipItem->y() >= contentY() + height());
381516 if (m_visibleItems.isEmpty()) {
382− m_visibleItems << item;
517+ m_visibleItems << listItem;
383518 } else {
384− m_visibleItems.insert(modelIndex - m_firstVisibleIndex, item);
519+ m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
385520 }
386521 if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
387522 m_firstVisibleIndex = modelIndex;
388523 adjustMinYExtent();
389524 }
390− return item;
525+ return listItem;
391526 }
392527}
393528
@@ -431,15 +566,15 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
431566 bool growDown = true;
432567 for (int i = 0; growDown && i < remove.count; ++i) {
433568 const int modelIndex = remove.index + i;
434− QQuickItem *item = itemAtIndex(modelIndex);
435− if (item && !QQuickItemPrivate::get(item)->culled) {
569+ ListItem *item = itemAtIndex(modelIndex);
570+ if (item && !item->culled()) {
436571 growDown = false;
437572 }
438573 }
439574 for (int i = remove.count - 1; i >= 0; --i) {
440575 const int visibleIndex = remove.index + i - m_firstVisibleIndex;
441576 if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
442− QQuickItem *item = m_visibleItems[visibleIndex];
577+ ListItem *item = m_visibleItems[visibleIndex];
443578 releaseItem(item);
444579 m_visibleItems.removeAt(visibleIndex);
445580 }
@@ -467,7 +602,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
467602 bool growUp = false;
468603 if (!firstItemWithViewOnTop) {
469604 for (int i = 0; i < m_visibleItems.count(); ++i) {
470− if (!QQuickItemPrivate::get(m_visibleItems[i])->culled) {
605+ if (!m_visibleItems[i]->culled()) {
471606 if (insert.index <= m_firstVisibleIndex + i) {
472607 growUp = true;
473608 }
@@ -479,15 +614,15 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
479614 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
480615 for (int i = insert.count - 1; i >= 0; --i) {
481616 const int modelIndex = insert.index + i;
482− QQuickItem *item = createItem(modelIndex, false);
617+ ListItem *item = createItem(modelIndex, false);
483618 if (growUp) {
484− QQuickItem *firstItem = m_visibleItems.first();
619+ ListItem *firstItem = m_visibleItems.first();
485620 firstItem->setY(firstItem->y() - item->height());
486621 adjustMinYExtent();
487622 }
488623 }
489624 if (firstItemWithViewOnTop) {
490− QQuickItem *firstItem = m_visibleItems.first();
625+ ListItem *firstItem = m_visibleItems.first();
491626 firstItem->setY(oldFirstValidIndexPos);
492627 }
493628 } else if (insert.index <= m_firstVisibleIndex) {
@@ -507,7 +642,7 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem *item, const QRectF
507642 headerHeightChanged(newGeometry.height(), oldGeometry.height(), oldGeometry.y());
508643 } else {
509644 if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
510− QQuickItem *firstItem = m_visibleItems.first();
645+ ListItem *firstItem = m_visibleItems.first();
511646 firstItem->setY(firstItem->y() - heightDiff);
512647 adjustMinYExtent();
513648 }
@@ -531,7 +666,7 @@ void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal ol
531666 if (oldHeaderY + oldHeaderHeight > contentY()) {
532667 // If the header is shown because its position
533668 // Change its size
534− QQuickItem *firstItem = m_visibleItems.first();
669+ ListItem *firstItem = m_visibleItems.first();
535670 firstItem->setY(firstItem->y() + heightDiff);
536671 } else {
537672 // If the header is not on screen, just change the start of the list
@@ -551,7 +686,7 @@ void ListViewWithPageHeader::adjustMinYExtent()
551686 }
552687}
553688
554−QQuickItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
689+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
555690{
556691 const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
557692 if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
@@ -572,11 +707,52 @@ void ListViewWithPageHeader::updatePolish()
572707 qreal pos = m_visibleItems.first()->y();
573708
574709// qDebug() << "ListViewWithPageHeader::updatePolish Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
575− foreach(QQuickItem *item, m_visibleItems) {
576− QQuickItemPrivate::get(item)->setCulled(pos + item->height() < visibleFrom || pos >= visibleTo);
710+ int firstReallyVisibleItem = -1;
711+ int modelIndex = m_firstVisibleIndex;
712+ foreach(ListItem *item, m_visibleItems) {
713+ const bool cull = pos + item->height() < visibleFrom || pos >= visibleTo;
714+ item->setCulled(cull);
577715 item->setY(pos);
716+ if (!cull && firstReallyVisibleItem == -1) {
717+ firstReallyVisibleItem = modelIndex;
718+ if (m_topSectionItem) {
719+ // Update the top sticky section header
720+ const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
721+ QQmlContext *context = QQmlEngine::contextForObject(m_topSectionItem)->parentContext();
722+ context->setContextProperty(QLatin1String("section"), section);
723+
724+
725+ // Positing the top section sticky item is a two step process
726+ // First we set it either we cull it (because it doesn't need to be sticked to the top)
727+ // or stick it to the top
728+ // Then after the loop we'll make sure that if there's another section just below it
729+ // pushed the sticky section up to make it disappear
730+ const qreal topSectionStickPos = m_headerItemShownHeight + contentY() - m_clipItem->y();
731+ if (topSectionStickPos <= pos) {
732+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
733+ } else {
734+ QQuickItemPrivate::get(m_topSectionItem)->setCulled(false);
735+ m_topSectionItem->setY(topSectionStickPos);
736+ }
737+ }
738+ }
578739// qDebug() << "ListViewWithPageHeader::updatePolish" << /*index << */pos + m_clipItem->y() << /*QQuickItemPrivate::get(item)->culled <<*/ (pos + item->height() < visibleFrom) << (pos > visibleTo);
579740 pos += item->height();
741+ ++modelIndex;
742+ }
743+
744+ // Second step of section sticky item positioning
745+ // Look at the next section header, check if it's pushing up the sticky one
746+ if (m_topSectionItem) {
747+ for (int i = firstReallyVisibleItem - m_firstVisibleIndex + 1; i < m_visibleItems.count(); ++i) {
748+ ListItem *item = m_visibleItems[i];
749+ if (item->m_sectionItem) {
750+ if (m_topSectionItem->y() + m_topSectionItem->height() > item->y()) {
751+ m_topSectionItem->setY(item->y() - m_topSectionItem->height());
752+ }
753+ break;
754+ }
755+ }
580756 }
581757 }
582758
@@ -588,7 +764,7 @@ void ListViewWithPageHeader::updatePolish()
588764 const int visibleItems = m_visibleItems.count();
589765 const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
590766 if (lastValidIndex == modelCount - 1) {
591− QQuickItem *item = m_visibleItems.last();
767+ ListItem *item = m_visibleItems.last();
592768 contentHeight = item->y() + item->height() + m_clipItem->y();
593769 if (m_firstVisibleIndex != 0) {
594770 // Make sure that if we are shrinking we tell the view we still fit
@@ -597,7 +773,7 @@ void ListViewWithPageHeader::updatePolish()
597773 } else {
598774 contentHeight = m_headerItem ? m_headerItem->height() : 0;
599775 if (!m_visibleItems.isEmpty()) {
600− foreach(QQuickItem *item, m_visibleItems) {
776+ foreach(ListItem *item, m_visibleItems) {
601777 contentHeight += item->height();
602778 }
603779 const int unknownSizes = modelCount - visibleItems;
@@ -610,3 +786,5 @@ void ListViewWithPageHeader::updatePolish()
610786 }
611787
612788}
789+
790+#include "moc_listviewwithpageheader.cpp"