on xenial help sign in
Bring the section headers
bb95d96Albert Astals
Dash/DashApps.qml+5−5
@@ -168,11 +168,11 @@ LensView {
168 }
168 }
169 }
169 }
170
170
171−// sectionProperty: "category"
171+ sectionProperty: "category"
172−// sectionDelegate: Header {
172+ sectionDelegate: Header {
173−// width: categoryView.width
173+ width: categoryView.width
174−// text: categoryListModel.getCategory(section)
174+ text: categoryListModel.getCategory(section)
175−// }
175+ }
176 pageHeader: PageHeader {
176 pageHeader: PageHeader {
177 id: pageHeader
177 id: pageHeader
178 width: categoryView.width
178 width: categoryView.width
Dash/DashHome.qml+5−5
@@ -188,11 +188,11 @@ LensView {
188 }
188 }
189 }
189 }
190
190
191−// sectionProperty: "category"
191+ sectionProperty: "category"
192−// sectionDelegate: Header {
192+ sectionDelegate: Header {
193−// width: listView.width
193+ width: listView.width
194−// text: listView.model.getCategory(section)
194+ text: listView.model.getCategory(section)
195−// }
195+ }
196 pageHeader: PageHeader {
196 pageHeader: PageHeader {
197 width: listView.width
197 width: listView.width
198 text: i18n.tr("Home")
198 text: i18n.tr("Home")
Dash/DashMusic.qml+5−5
@@ -89,11 +89,11 @@ LensView {
89 }
89 }
90 }
90 }
91
91
92−// sectionProperty: "name"
92+ sectionProperty: "name"
93−// sectionDelegate: ListItems.Header {
93+ sectionDelegate: ListItems.Header {
94−// width: categoryView.width
94+ width: categoryView.width
95−// text: i18n.tr(section)
95+ text: i18n.tr(section)
96−// }
96+ }
97 pageHeader: PageHeader {
97 pageHeader: PageHeader {
98 id: pageHeader
98 id: pageHeader
99 width: categoryView.width
99 width: categoryView.width
Dash/DashPeople.qml+5−5
@@ -113,11 +113,11 @@ LensView {
113 }
113 }
114 }
114 }
115
115
116−// sectionProperty: "name"
116+ sectionProperty: "name"
117−// sectionDelegate: ListItems.Header {
117+ sectionDelegate: ListItems.Header {
118−// width: categoryView.width
118+ width: categoryView.width
119−// text: i18n.tr(section)
119+ text: i18n.tr(section)
120−// }
120+ }
121 pageHeader: PageHeader {
121 pageHeader: PageHeader {
122 id: pageHeader
122 id: pageHeader
123 width: categoryView.width
123 width: categoryView.width
Dash/DashVideos.qml+5−5
@@ -144,11 +144,11 @@ LensView {
144 }
144 }
145 }
145 }
146
146
147−// sectionProperty: "name"
147+ sectionProperty: "name"
148−// sectionDelegate: ListItems.Header {
148+ sectionDelegate: ListItems.Header {
149−// width: categoryView.width
149+ width: categoryView.width
150−// text: i18n.tr(section)
150+ text: i18n.tr(section)
151−// }
151+ }
152
152
153 pageHeader: PageHeader {
153 pageHeader: PageHeader {
154 id: pageHeader
154 id: pageHeader
Dash/GenericLensView.qml+5−5
@@ -80,11 +80,11 @@ LensView {
80 }
80 }
81 }
81 }
82
82
83−// sectionProperty: "name"
83+ sectionProperty: "name"
84−// sectionDelegate: ListItems.Header {
84+ sectionDelegate: ListItems.Header {
85−// width: categoryView.width
85+ width: categoryView.width
86−// text: section
86+ text: section
87−// }
87+ }
88 pageHeader: PageHeader {
88 pageHeader: PageHeader {
89 id: pageHeader
89 id: pageHeader
90 objectName: "pageHeader"
90 objectName: "pageHeader"
plugins/ListViewWithPageHeader/listviewwithpageheader.cpp+209−31
@@ -18,11 +18,44 @@
18
18
19#include <QDebug>
19#include <QDebug>
20#include <qqmlinfo.h>
20#include <qqmlinfo.h>
21+#include <qqmlengine.h>
21#include <private/qquickvisualdatamodel_p.h>
22#include <private/qquickvisualdatamodel_p.h>
22#include <private/qqmlglobal_p.h>
23#include <private/qqmlglobal_p.h>
23#include <private/qquickitem_p.h>
24#include <private/qquickitem_p.h>
24// #include <private/qquickrectangle_p.h>
25// #include <private/qquickrectangle_p.h>
25
26
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+
26ListViewWithPageHeader::ListViewWithPageHeader()
59ListViewWithPageHeader::ListViewWithPageHeader()
27 : m_delegateModel(nullptr)
60 : m_delegateModel(nullptr)
28 , m_delegateValidated(false)
61 , m_delegateValidated(false)
@@ -32,6 +65,8 @@ ListViewWithPageHeader::ListViewWithPageHeader()
32 , m_headerItem(nullptr)
65 , m_headerItem(nullptr)
33 , m_previousContentY(0)
66 , m_previousContentY(0)
34 , m_headerItemShownHeight(0)
67 , m_headerItemShownHeight(0)
68+ , m_sectionDelegate(nullptr)
69+ , m_topSectionItem(nullptr)
35{
70{
36 m_clipItem = new QQuickItem(contentItem());
71 m_clipItem = new QQuickItem(contentItem());
37// m_clipItem = new QQuickRectangle(contentItem());
72// m_clipItem = new QQuickRectangle(contentItem());
@@ -80,7 +115,7 @@ void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
80 }
115 }
81
116
82 // Cleanup the existing items
117 // Cleanup the existing items
83− foreach(QQuickItem *item, m_visibleItems)
118+ foreach(ListItem *item, m_visibleItems)
84 releaseItem(item);
119 releaseItem(item);
85 m_visibleItems.clear();
120 m_visibleItems.clear();
86 m_firstVisibleIndex = -1;
121 m_firstVisibleIndex = -1;
@@ -110,6 +145,7 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
110 oldHeaderHeight = m_headerItem->height();
145 oldHeaderHeight = m_headerItem->height();
111 oldHeaderY = m_headerItem->y();
146 oldHeaderY = m_headerItem->y();
112 QQuickItemPrivate::get(m_headerItem)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
147 QQuickItemPrivate::get(m_headerItem)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
148+ m_headerItem->setParentItem(nullptr);
113 }
149 }
114 m_headerItem = headerItem;
150 m_headerItem = headerItem;
115 if (m_headerItem) {
151 if (m_headerItem) {
@@ -127,6 +163,46 @@ void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
127 }
163 }
128}
164}
129
165
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+
130void ListViewWithPageHeader::positionAtBeginning()
206void ListViewWithPageHeader::positionAtBeginning()
131{
207{
132 qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
208 qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
@@ -134,14 +210,14 @@ void ListViewWithPageHeader::positionAtBeginning()
134 // This could be optimized by trying to reuse the interesection
210 // This could be optimized by trying to reuse the interesection
135 // of items that may end up intersecting between the existing
211 // of items that may end up intersecting between the existing
136 // m_visibleItems and the items we are creating in the next loop
212 // m_visibleItems and the items we are creating in the next loop
137− foreach(QQuickItem *item, m_visibleItems)
213+ foreach(ListItem *item, m_visibleItems)
138 releaseItem(item);
214 releaseItem(item);
139 m_visibleItems.clear();
215 m_visibleItems.clear();
140 m_firstVisibleIndex = -1;
216 m_firstVisibleIndex = -1;
141
217
142 // Create the item 0, it will be already correctly positioned at createItem()
218 // Create the item 0, it will be already correctly positioned at createItem()
143 m_clipItem->setY(0);
219 m_clipItem->setY(0);
144− QQuickItem *item = createItem(0, false);
220+ ListItem *item = createItem(0, false);
145 // Create the subsequent items
221 // Create the subsequent items
146 int modelIndex = 1;
222 int modelIndex = 1;
147 qreal pos = item->y() + item->height();
223 qreal pos = item->y() + item->height();
@@ -221,9 +297,10 @@ void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
221 }
297 }
222 if (!m_visibleItems.isEmpty()) {
298 if (!m_visibleItems.isEmpty()) {
223 updateClipItem();
299 updateClipItem();
224− QQuickItem *firstItem = m_visibleItems.first();
300+ ListItem *firstItem = m_visibleItems.first();
225 firstItem->setY(firstItem->y() + diff);
301 firstItem->setY(firstItem->y() + diff);
226 }
302 }
303+
227 m_previousContentY = contentY();
304 m_previousContentY = contentY();
228 polish();
305 polish();
229}
306}
@@ -239,6 +316,7 @@ void ListViewWithPageHeader::createDelegateModel()
239 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
316 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
240 if (isComponentComplete())
317 if (isComponentComplete())
241 m_delegateModel->componentComplete();
318 m_delegateModel->componentComplete();
319+ updateWatchedRoles();
242}
320}
243
321
244void ListViewWithPageHeader::refill()
322void ListViewWithPageHeader::refill()
@@ -271,7 +349,7 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
271 if (m_delegateModel->count() == 0)
349 if (m_delegateModel->count() == 0)
272 return false;
350 return false;
273
351
274− QQuickItem *item;
352+ ListItem *item;
275// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
353// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
276
354
277 int modelIndex = 0;
355 int modelIndex = 0;
@@ -311,14 +389,68 @@ bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool
311 return changed;
389 return changed;
312}
390}
313
391
314−void ListViewWithPageHeader::releaseItem(QQuickItem *item)
392+void ListViewWithPageHeader::releaseItem(ListItem *listItem)
315{
393{
394+ QQuickItem *item = listItem->m_item;
316 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
395 QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
317 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
396 itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
318 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
397 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
319 if (flags & QQuickVisualModel::Destroyed) {
398 if (flags & QQuickVisualModel::Destroyed) {
320 item->setParentItem(nullptr);
399 item->setParentItem(nullptr);
321 }
400 }
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 &sectionText)
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;
322}
454}
323
455
324bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
456bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
@@ -330,7 +462,7 @@ bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal buffe
330 int i = 0;
462 int i = 0;
331 int removedItems = 0;
463 int removedItems = 0;
332 while (i < m_visibleItems.count()) {
464 while (i < m_visibleItems.count()) {
333− QQuickItem *item = m_visibleItems[i];
465+ ListItem *item = m_visibleItems[i];
334 const qreal pos = item->y() + m_clipItem->y();
466 const qreal pos = item->y() + m_clipItem->y();
335// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
467// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
336 if (pos + item->height() < bufferFrom || pos > bufferTo) {
468 if (pos + item->height() < bufferFrom || pos > bufferTo) {
@@ -352,7 +484,7 @@ bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal buffe
352 return changed;
484 return changed;
353}
485}
354
486
355−QQuickItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
487+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
356{
488{
357// qDebug() << "CREATE ITEM" << modelIndex;
489// qDebug() << "CREATE ITEM" << modelIndex;
358 if (asynchronous && m_asyncRequestedIndexes.contains(modelIndex))
490 if (asynchronous && m_asyncRequestedIndexes.contains(modelIndex))
@@ -365,29 +497,32 @@ QQuickItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous
365 return 0;
497 return 0;
366 } else {
498 } else {
367// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
499// 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);
368 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
503 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
369− QQuickItem *prevItem = itemAtIndex(modelIndex - 1);
504+ ListItem *prevItem = itemAtIndex(modelIndex - 1);
370 if (prevItem) {
505 if (prevItem) {
371− item->setY(prevItem->y() + prevItem->height());
506+ listItem->setY(prevItem->y() + prevItem->height());
372 } else {
507 } else {
373− QQuickItem *nextItem = itemAtIndex(modelIndex + 1);
508+ ListItem *nextItem = itemAtIndex(modelIndex + 1);
374 if (nextItem) {
509 if (nextItem) {
375− item->setY(nextItem->y() - item->height());
510+ listItem->setY(nextItem->y() - listItem ->height());
376 } else if (modelIndex == 0 && m_headerItem) {
511 } else if (modelIndex == 0 && m_headerItem) {
377− item->setY(m_headerItem->height());
512+ listItem->setY(m_headerItem->height());
378 }
513 }
379 }
514 }
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());
381 if (m_visibleItems.isEmpty()) {
516 if (m_visibleItems.isEmpty()) {
382− m_visibleItems << item;
517+ m_visibleItems << listItem;
383 } else {
518 } else {
384− m_visibleItems.insert(modelIndex - m_firstVisibleIndex, item);
519+ m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
385 }
520 }
386 if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
521 if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
387 m_firstVisibleIndex = modelIndex;
522 m_firstVisibleIndex = modelIndex;
388 adjustMinYExtent();
523 adjustMinYExtent();
389 }
524 }
390− return item;
525+ return listItem;
391 }
526 }
392}
527}
393
528
@@ -431,15 +566,15 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
431 bool growDown = true;
566 bool growDown = true;
432 for (int i = 0; growDown && i < remove.count; ++i) {
567 for (int i = 0; growDown && i < remove.count; ++i) {
433 const int modelIndex = remove.index + i;
568 const int modelIndex = remove.index + i;
434− QQuickItem *item = itemAtIndex(modelIndex);
569+ ListItem *item = itemAtIndex(modelIndex);
435− if (item && !QQuickItemPrivate::get(item)->culled) {
570+ if (item && !item->culled()) {
436 growDown = false;
571 growDown = false;
437 }
572 }
438 }
573 }
439 for (int i = remove.count - 1; i >= 0; --i) {
574 for (int i = remove.count - 1; i >= 0; --i) {
440 const int visibleIndex = remove.index + i - m_firstVisibleIndex;
575 const int visibleIndex = remove.index + i - m_firstVisibleIndex;
441 if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
576 if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
442− QQuickItem *item = m_visibleItems[visibleIndex];
577+ ListItem *item = m_visibleItems[visibleIndex];
443 releaseItem(item);
578 releaseItem(item);
444 m_visibleItems.removeAt(visibleIndex);
579 m_visibleItems.removeAt(visibleIndex);
445 }
580 }
@@ -467,7 +602,7 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
467 bool growUp = false;
602 bool growUp = false;
468 if (!firstItemWithViewOnTop) {
603 if (!firstItemWithViewOnTop) {
469 for (int i = 0; i < m_visibleItems.count(); ++i) {
604 for (int i = 0; i < m_visibleItems.count(); ++i) {
470− if (!QQuickItemPrivate::get(m_visibleItems[i])->culled) {
605+ if (!m_visibleItems[i]->culled()) {
471 if (insert.index <= m_firstVisibleIndex + i) {
606 if (insert.index <= m_firstVisibleIndex + i) {
472 growUp = true;
607 growUp = true;
473 }
608 }
@@ -479,15 +614,15 @@ void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bo
479 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
614 const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
480 for (int i = insert.count - 1; i >= 0; --i) {
615 for (int i = insert.count - 1; i >= 0; --i) {
481 const int modelIndex = insert.index + i;
616 const int modelIndex = insert.index + i;
482− QQuickItem *item = createItem(modelIndex, false);
617+ ListItem *item = createItem(modelIndex, false);
483 if (growUp) {
618 if (growUp) {
484− QQuickItem *firstItem = m_visibleItems.first();
619+ ListItem *firstItem = m_visibleItems.first();
485 firstItem->setY(firstItem->y() - item->height());
620 firstItem->setY(firstItem->y() - item->height());
486 adjustMinYExtent();
621 adjustMinYExtent();
487 }
622 }
488 }
623 }
489 if (firstItemWithViewOnTop) {
624 if (firstItemWithViewOnTop) {
490− QQuickItem *firstItem = m_visibleItems.first();
625+ ListItem *firstItem = m_visibleItems.first();
491 firstItem->setY(oldFirstValidIndexPos);
626 firstItem->setY(oldFirstValidIndexPos);
492 }
627 }
493 } else if (insert.index <= m_firstVisibleIndex) {
628 } else if (insert.index <= m_firstVisibleIndex) {
@@ -507,7 +642,7 @@ void ListViewWithPageHeader::itemGeometryChanged(QQuickItem *item, const QRectF
507 headerHeightChanged(newGeometry.height(), oldGeometry.height(), oldGeometry.y());
642 headerHeightChanged(newGeometry.height(), oldGeometry.height(), oldGeometry.y());
508 } else {
643 } else {
509 if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
644 if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
510− QQuickItem *firstItem = m_visibleItems.first();
645+ ListItem *firstItem = m_visibleItems.first();
511 firstItem->setY(firstItem->y() - heightDiff);
646 firstItem->setY(firstItem->y() - heightDiff);
512 adjustMinYExtent();
647 adjustMinYExtent();
513 }
648 }
@@ -531,7 +666,7 @@ void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal ol
531 if (oldHeaderY + oldHeaderHeight > contentY()) {
666 if (oldHeaderY + oldHeaderHeight > contentY()) {
532 // If the header is shown because its position
667 // If the header is shown because its position
533 // Change its size
668 // Change its size
534− QQuickItem *firstItem = m_visibleItems.first();
669+ ListItem *firstItem = m_visibleItems.first();
535 firstItem->setY(firstItem->y() + heightDiff);
670 firstItem->setY(firstItem->y() + heightDiff);
536 } else {
671 } else {
537 // If the header is not on screen, just change the start of the list
672 // If the header is not on screen, just change the start of the list
@@ -551,7 +686,7 @@ void ListViewWithPageHeader::adjustMinYExtent()
551 }
686 }
552}
687}
553
688
554−QQuickItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
689+ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
555{
690{
556 const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
691 const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
557 if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
692 if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
@@ -572,11 +707,52 @@ void ListViewWithPageHeader::updatePolish()
572 qreal pos = m_visibleItems.first()->y();
707 qreal pos = m_visibleItems.first()->y();
573
708
574// qDebug() << "ListViewWithPageHeader::updatePolish Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
709// qDebug() << "ListViewWithPageHeader::updatePolish Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
710+ int firstReallyVisibleItem = -1;
711+ int modelIndex = m_firstVisibleIndex;
575− foreach(QQuickItem *item, m_visibleItems) {
712+ foreach(ListItem *item, m_visibleItems) {
576− QQuickItemPrivate::get(item)->setCulled(pos + item->height() < visibleFrom || pos >= visibleTo);
713+ const bool cull = pos + item->height() < visibleFrom || pos >= visibleTo;
714+ item->setCulled(cull);
577 item->setY(pos);
715 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+ }
578// qDebug() << "ListViewWithPageHeader::updatePolish" << /*index << */pos + m_clipItem->y() << /*QQuickItemPrivate::get(item)->culled <<*/ (pos + item->height() < visibleFrom) << (pos > visibleTo);
739// qDebug() << "ListViewWithPageHeader::updatePolish" << /*index << */pos + m_clipItem->y() << /*QQuickItemPrivate::get(item)->culled <<*/ (pos + item->height() < visibleFrom) << (pos > visibleTo);
579 pos += item->height();
740 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+ }
580 }
756 }
581 }
757 }
582
758
@@ -588,7 +764,7 @@ void ListViewWithPageHeader::updatePolish()
588 const int visibleItems = m_visibleItems.count();
764 const int visibleItems = m_visibleItems.count();
589 const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
765 const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
590 if (lastValidIndex == modelCount - 1) {
766 if (lastValidIndex == modelCount - 1) {
591− QQuickItem *item = m_visibleItems.last();
767+ ListItem *item = m_visibleItems.last();
592 contentHeight = item->y() + item->height() + m_clipItem->y();
768 contentHeight = item->y() + item->height() + m_clipItem->y();
593 if (m_firstVisibleIndex != 0) {
769 if (m_firstVisibleIndex != 0) {
594 // Make sure that if we are shrinking we tell the view we still fit
770 // Make sure that if we are shrinking we tell the view we still fit
@@ -597,7 +773,7 @@ void ListViewWithPageHeader::updatePolish()
597 } else {
773 } else {
598 contentHeight = m_headerItem ? m_headerItem->height() : 0;
774 contentHeight = m_headerItem ? m_headerItem->height() : 0;
599 if (!m_visibleItems.isEmpty()) {
775 if (!m_visibleItems.isEmpty()) {
600− foreach(QQuickItem *item, m_visibleItems) {
776+ foreach(ListItem *item, m_visibleItems) {
601 contentHeight += item->height();
777 contentHeight += item->height();
602 }
778 }
603 const int unknownSizes = modelCount - visibleItems;
779 const int unknownSizes = modelCount - visibleItems;
@@ -610,3 +786,5 @@ void ListViewWithPageHeader::updatePolish()
610 }
786 }
611
787
612}
788}
789+
790+#include "moc_listviewwithpageheader.cpp"
plugins/ListViewWithPageHeader/listviewwithpageheader.h+37−4
@@ -30,6 +30,8 @@ class ListViewWithPageHeader : public QQuickFlickable, public QQuickItemChangeLi
30 Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
30 Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelChanged)
31 Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
31 Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
32 Q_PROPERTY(QQuickItem *pageHeader READ header WRITE setHeader NOTIFY headerChanged)
32 Q_PROPERTY(QQuickItem *pageHeader READ header WRITE setHeader NOTIFY headerChanged)
33+ Q_PROPERTY(QQmlComponent *sectionDelegate READ sectionDelegate WRITE setSectionDelegate NOTIFY sectionDelegateChanged)
34+ Q_PROPERTY(QString sectionProperty READ sectionProperty WRITE setSectionProperty NOTIFY sectionPropertyChanged)
33
35
34 friend class ListViewWithPageHeaderTest;
36 friend class ListViewWithPageHeaderTest;
35
37
@@ -46,6 +48,12 @@ public:
46 QQuickItem *header() const;
48 QQuickItem *header() const;
47 void setHeader(QQuickItem *header);
49 void setHeader(QQuickItem *header);
48
50
51+ QQmlComponent *sectionDelegate() const;
52+ void setSectionDelegate(QQmlComponent *delegate);
53+
54+ QString sectionProperty() const;
55+ void setSectionProperty(const QString &property);
56+
49 Q_INVOKABLE void positionAtBeginning();
57 Q_INVOKABLE void positionAtBeginning();
50 Q_INVOKABLE void showHeader();
58 Q_INVOKABLE void showHeader();
51
59
@@ -53,6 +61,8 @@ Q_SIGNALS:
53 void modelChanged();
61 void modelChanged();
54 void delegateChanged();
62 void delegateChanged();
55 void headerChanged();
63 void headerChanged();
64+ void sectionDelegateChanged();
65+ void sectionPropertyChanged();
56
66
57protected:
67protected:
58 void componentComplete();
68 void componentComplete();
@@ -68,6 +78,22 @@ private Q_SLOTS:
68 void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
78 void onModelUpdated(const QQuickChangeSet &changeSet, bool reset);
69
79
70private:
80private:
81+ class ListItem
82+ {
83+ public:
84+ qreal height() const;
85+
86+ qreal y() const;
87+ void setY(qreal newY);
88+
89+ bool culled() const;
90+ void setCulled(bool culled);
91+
92+// private:
93+ QQuickItem *m_item;
94+ QQuickItem *m_sectionItem;
95+ };
96+
71 void setContentY(qreal pos);
97 void setContentY(qreal pos);
72
98
73 void createDelegateModel();
99 void createDelegateModel();
@@ -75,13 +101,16 @@ private:
75 void refill();
101 void refill();
76 bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
102 bool addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous);
77 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
103 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo);
78− QQuickItem *createItem(int modelIndex, bool asynchronous);
104+ ListItem *createItem(int modelIndex, bool asynchronous);
79
105
80 void adjustMinYExtent();
106 void adjustMinYExtent();
81 void updateClipItem();
107 void updateClipItem();
82 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
108 void headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY);
83− QQuickItem *itemAtIndex(int modelIndex) const; // Returns the item at modelIndex if has been created
109+ ListItem *itemAtIndex(int modelIndex) const; // Returns the item at modelIndex if has been created
84− void releaseItem(QQuickItem *item);
110+ void releaseItem(ListItem *item);
111+ void updateWatchedRoles();
112+ QQuickItem *getSectionItem(int modelIndex);
113+ QQuickItem *getSectionItem(const QString &sectionText);
85
114
86 QQuickVisualDataModel *m_delegateModel;
115 QQuickVisualDataModel *m_delegateModel;
87
116
@@ -92,7 +121,7 @@ private:
92 bool m_delegateValidated;
121 bool m_delegateValidated;
93
122
94 // Visible indexes, [0] is m_firstValidIndex, [0+1] is m_firstValidIndex +1, ...
123 // Visible indexes, [0] is m_firstValidIndex, [0+1] is m_firstValidIndex +1, ...
95− QList<QQuickItem *> m_visibleItems;
124+ QList<ListItem *> m_visibleItems;
96 int m_firstVisibleIndex;
125 int m_firstVisibleIndex;
97
126
98 qreal m_minYExtent;
127 qreal m_minYExtent;
@@ -107,6 +136,10 @@ private:
107 qreal m_previousContentY;
136 qreal m_previousContentY;
108 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
137 qreal m_headerItemShownHeight; // The height of header shown when the header is shown outside its topmost position
109 // i.e. it's being shown after dragging down in the middle of the list
138 // i.e. it's being shown after dragging down in the middle of the list
139+
140+ QQmlComponent *m_sectionDelegate;
141+ QString m_sectionProperty;
142+ QQuickItem *m_topSectionItem;
110};
143};
111
144
112
145
run_on_device+1−1
@@ -65,7 +65,7 @@ install_dependencies() {
65sync_code() {
65sync_code() {
66 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
66 [ -e .bzr ] && bzr export --uncommitted --format=dir /tmp/$CODE_DIR
67 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
67 [ -e .git ] && git checkout-index -a -f --prefix=/tmp/$CODE_DIR/
68− rsync -crlOzv -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
68+ rsync -crlOzv --delete --exclude builddir -e "ssh -p $TARGET_SSH_PORT -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" /tmp/$CODE_DIR/ $USER@$TARGET_IP:$CODE_DIR/
69 rm -rf /tmp/$CODE_DIR
69 rm -rf /tmp/$CODE_DIR
70}
70}
71
71
tests/plugins/ListViewWithPageHeader/listviewwithpageheadertest.cpp+2−2
@@ -59,10 +59,10 @@ private Q_SLOTS:
59
59
60 void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled)
60 void verifyItem(int visibleIndex, qreal pos, qreal height, bool culled)
61 {
61 {
62− QQuickItem *item = lvwph->m_visibleItems[visibleIndex];
62+ ListViewWithPageHeader::ListItem *item = lvwph->m_visibleItems[visibleIndex];
63 QTRY_COMPARE(item->y(), pos);
63 QTRY_COMPARE(item->y(), pos);
64 QTRY_COMPARE(item->height(), height);
64 QTRY_COMPARE(item->height(), height);
65− QCOMPARE(QQuickItemPrivate::get(item)->culled, culled);
65+ QCOMPARE(QQuickItemPrivate::get(item->m_item)->culled, culled);
66 }
66 }
67
67
68 void changeContentY(qreal change)
68 void changeContentY(qreal change)