@@ -0,0 +1,613 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "listviewwithpageheader.h"
18+
19+#include <QDebug>
20+#include <qqmlinfo.h>
21+#include <private/qquickvisualdatamodel_p.h>
22+#include <private/qqmlglobal_p.h>
23+#include <private/qquickitem_p.h>
24+// #include <private/qquickrectangle_p.h>
25+
26+ListViewWithPageHeader::ListViewWithPageHeader()
27+ : m_delegateModel(nullptr)
28+ , m_delegateValidated(false)
29+ , m_firstVisibleIndex(-1)
30+ , m_minYExtent(0)
31+ , m_contentHeightDirty(false)
32+ , m_headerItem(nullptr)
33+ , m_previousContentY(0)
34+ , m_headerItemShownHeight(0)
35+{
36+ m_clipItem = new QQuickItem(contentItem());
37+// m_clipItem = new QQuickRectangle(contentItem());
38+// ((QQuickRectangle*)m_clipItem)->setColor(Qt::gray);
39+
40+ connect(this, SIGNAL(contentWidthChanged()), this, SLOT(onContentWidthChanged()));
41+ connect(this, SIGNAL(contentHeightChanged()), this, SLOT(onContentHeightChanged()));
42+}
43+
44+ListViewWithPageHeader::~ListViewWithPageHeader()
45+{
46+}
47+
48+QAbstractItemModel *ListViewWithPageHeader::model() const
49+{
50+ return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
51+}
52+
53+void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
54+{
55+ if (model != this->model()) {
56+ if (!m_delegateModel) {
57+ createDelegateModel();
58+ } else {
59+ disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
60+ }
61+ m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
62+ connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
63+ Q_EMIT modelChanged();
64+ // TODO?
65+// Q_EMIT contentHeightChanged();
66+// Q_EMIT contentYChanged();
67+ }
68+}
69+
70+QQmlComponent *ListViewWithPageHeader::delegate() const
71+{
72+ return m_delegateModel ? m_delegateModel->delegate() : nullptr;
73+}
74+
75+void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
76+{
77+ if (delegate != this->delegate()) {
78+ if (!m_delegateModel) {
79+ createDelegateModel();
80+ }
81+
82+ // Cleanup the existing items
83+ foreach(QQuickItem *item, m_visibleItems)
84+ releaseItem(item);
85+ m_visibleItems.clear();
86+ m_firstVisibleIndex = -1;
87+ QQuickFlickable::setContentY(0);
88+ m_clipItem->setY(0);
89+
90+ m_delegateModel->setDelegate(delegate);
91+
92+ Q_EMIT delegateChanged();
93+ m_delegateValidated = false;
94+ m_contentHeightDirty = true;
95+ polish();
96+ }
97+}
98+
99+QQuickItem *ListViewWithPageHeader::header() const
100+{
101+ return m_headerItem;
102+}
103+
104+void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
105+{
106+ if (m_headerItem != headerItem) {
107+ qreal oldHeaderHeight = 0;
108+ qreal oldHeaderY = 0;
109+ if (m_headerItem) {
110+ oldHeaderHeight = m_headerItem->height();
111+ oldHeaderY = m_headerItem->y();
112+ QQuickItemPrivate::get(m_headerItem)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
113+ }
114+ m_headerItem = headerItem;
115+ if (m_headerItem) {
116+ m_headerItem->setParentItem(contentItem());
117+ m_headerItem->setZ(1);
118+ QQuickItemPrivate::get(m_headerItem)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
119+ }
120+ qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
121+ if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
122+ headerHeightChanged(newHeaderHeight, oldHeaderHeight, oldHeaderY);
123+ polish();
124+ m_contentHeightDirty = true;
125+ }
126+ Q_EMIT headerChanged();
127+ }
128+}
129+
130+void ListViewWithPageHeader::positionAtBeginning()
131+{
132+ qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
133+ if (m_firstVisibleIndex != 0) {
134+ // This could be optimized by trying to reuse the interesection
135+ // of items that may end up intersecting between the existing
136+ // m_visibleItems and the items we are creating in the next loop
137+ foreach(QQuickItem *item, m_visibleItems)
138+ releaseItem(item);
139+ m_visibleItems.clear();
140+ m_firstVisibleIndex = -1;
141+
142+ // Create the item 0, it will be already correctly positioned at createItem()
143+ m_clipItem->setY(0);
144+ QQuickItem *item = createItem(0, false);
145+ // Create the subsequent items
146+ int modelIndex = 1;
147+ qreal pos = item->y() + item->height();
148+ const qreal buffer = height() / 2;
149+ const qreal bufferTo = height() + buffer;
150+ while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
151+ if (!(item = createItem(modelIndex, false)))
152+ break;
153+ pos += item->height();
154+ ++modelIndex;
155+ }
156+
157+ m_previousContentY = m_visibleItems.first()->y() - headerHeight;
158+ }
159+ QQuickFlickable::setContentY(m_visibleItems.first()->y() + m_clipItem->y() - headerHeight);
160+}
161+
162+void ListViewWithPageHeader::showHeader()
163+{
164+ // TODO
165+}
166+
167+qreal ListViewWithPageHeader::minYExtent() const
168+{
169+// qDebug() << "ListViewWithPageHeader::minYExtent" << m_minYExtent;
170+ return m_minYExtent;
171+}
172+
173+void ListViewWithPageHeader::componentComplete()
174+{
175+ if (m_delegateModel)
176+ m_delegateModel->componentComplete();
177+
178+ QQuickFlickable::componentComplete();
179+
180+ polish();
181+}
182+
183+void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
184+{
185+ QQuickFlickable::viewportMoved(orient);
186+// qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
187+ qreal diff = m_previousContentY - contentY();
188+ if (m_headerItem) {
189+ auto oldHeaderItemShownHeight = m_headerItemShownHeight;
190+ if (contentY() < -m_minYExtent) {
191+ // Stick the header item to the top when dragging down
192+ m_headerItem->setY(contentY());
193+ } else {
194+ // We are going down (but it's not because of the rebound at the end)
195+ // (but the header was not shown by it's own position)
196+ // or the header is partially shown
197+ const bool scrolledUp = m_previousContentY > contentY();
198+ const bool notRebounding = contentY() + height() < contentHeight();
199+ const bool notShownByItsOwn = contentY() + diff > m_headerItem->y() + m_headerItem->height();
200+
201+ if (!scrolledUp && contentY() == -m_minYExtent) {
202+ m_headerItemShownHeight = 0;
203+ m_headerItem->setY(contentY());
204+ } else if ((scrolledUp && notRebounding && notShownByItsOwn) || (m_headerItemShownHeight > 0)) {
205+ m_headerItemShownHeight += diff;
206+ if (contentY() == -m_minYExtent) {
207+ m_headerItemShownHeight = 0;
208+ } else {
209+ m_headerItemShownHeight = qBound(0., m_headerItemShownHeight, m_headerItem->height());
210+ }
211+ if (m_headerItemShownHeight > 0) {
212+ m_headerItem->setY(contentY() - m_headerItem->height() + m_headerItemShownHeight);
213+ } else {
214+ m_headerItem->setY(-m_minYExtent);
215+ }
216+ }
217+ }
218+ // We will be changing the clip item, need to accomadate for it
219+ // otherwise we move the firstItem down/up twice
220+ diff += oldHeaderItemShownHeight - m_headerItemShownHeight;
221+ }
222+ if (!m_visibleItems.isEmpty()) {
223+ updateClipItem();
224+ QQuickItem *firstItem = m_visibleItems.first();
225+ firstItem->setY(firstItem->y() + diff);
226+ }
227+ m_previousContentY = contentY();
228+ polish();
229+}
230+
231+void ListViewWithPageHeader::setContentY(qreal /*pos*/)
232+{
233+ qWarning() << "ListViewWithPageHeader::setContentY unsupported feature";
234+}
235+
236+void ListViewWithPageHeader::createDelegateModel()
237+{
238+ m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
239+ connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
240+ if (isComponentComplete())
241+ m_delegateModel->componentComplete();
242+}
243+
244+void ListViewWithPageHeader::refill()
245+{
246+ if (!isComponentComplete()) {
247+ qWarning() << "Incomplete ListViewWithPageHeader::refill";
248+ return;
249+ }
250+
251+ const qreal buffer = height() / 2;
252+ const qreal from = contentY();
253+ const qreal to = from + height();
254+ const qreal bufferFrom = from - buffer;
255+ const qreal bufferTo = to + buffer;
256+
257+ bool added = addVisibleItems(from, to, false);
258+ bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
259+ added |= addVisibleItems(bufferFrom, bufferTo, true);
260+
261+ if (added || removed) {
262+ m_contentHeightDirty = true;
263+ }
264+}
265+
266+bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
267+{
268+ if (!delegate())
269+ return false;
270+
271+ if (m_delegateModel->count() == 0)
272+ return false;
273+
274+ QQuickItem *item;
275+// qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
276+
277+ int modelIndex = 0;
278+ qreal pos = 0;
279+ if (!m_visibleItems.isEmpty()) {
280+ modelIndex = m_firstVisibleIndex + m_visibleItems.count();
281+ item = m_visibleItems.last();
282+ pos = item->y() + item->height() + m_clipItem->y();
283+ }
284+ bool changed = false;
285+// qDebug() << (modelIndex < m_delegateModel->count()) << pos << fillTo;
286+ while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
287+// qDebug() << "refill: append item" << modelIndex << "pos" << pos << "asynchronous" << asynchronous;
288+ if (!(item = createItem(modelIndex, asynchronous)))
289+ break;
290+ pos += item->height();
291+ ++modelIndex;
292+ changed = true;
293+ }
294+
295+ modelIndex = 0;
296+ pos = 0;
297+ if (!m_visibleItems.isEmpty()) {
298+ modelIndex = m_firstVisibleIndex - 1;
299+ item = m_visibleItems.first();
300+ pos = item->y() + m_clipItem->y();
301+ }
302+ while (modelIndex >= 0 && pos > fillFrom) {
303+// qDebug() << "refill: prepend item" << modelIndex << "pos" << pos << "fillFrom" << fillFrom << "asynchronous" << asynchronous;
304+ if (!(item = createItem(modelIndex, asynchronous)))
305+ break;
306+ pos -= item->height();
307+ --modelIndex;
308+ changed = true;
309+ }
310+
311+ return changed;
312+}
313+
314+void ListViewWithPageHeader::releaseItem(QQuickItem *item)
315+{
316+ QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
317+ itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
318+ QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
319+ if (flags & QQuickVisualModel::Destroyed) {
320+ item->setParentItem(nullptr);
321+ }
322+}
323+
324+bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
325+{
326+// qDebug() << "ListViewWithPageHeader::removeNonVisibleItems" << bufferFrom << bufferTo;
327+ bool changed = false;
328+
329+ bool foundVisible = false;
330+ int i = 0;
331+ int removedItems = 0;
332+ while (i < m_visibleItems.count()) {
333+ QQuickItem *item = m_visibleItems[i];
334+ const qreal pos = item->y() + m_clipItem->y();
335+// qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
336+ if (pos + item->height() < bufferFrom || pos > bufferTo) {
337+// qDebug() << "Releasing" << i << (pos + item->height() < bufferFrom) << pos + item->height() << bufferFrom << (pos > bufferTo) << pos << bufferTo;
338+ releaseItem(item);
339+ m_visibleItems.removeAt(i);
340+ changed = true;
341+ ++removedItems;
342+ } else {
343+ if (!foundVisible) {
344+ foundVisible = true;
345+ const int itemIndex = m_firstVisibleIndex + removedItems + i;
346+ m_firstVisibleIndex = itemIndex;
347+ }
348+ ++i;
349+ }
350+ }
351+
352+ return changed;
353+}
354+
355+QQuickItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
356+{
357+// qDebug() << "CREATE ITEM" << modelIndex;
358+ if (asynchronous && m_asyncRequestedIndexes.contains(modelIndex))
359+ return nullptr;
360+
361+ m_asyncRequestedIndexes.remove(modelIndex);
362+ QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
363+ if (!item) {
364+ m_asyncRequestedIndexes << modelIndex;
365+ return 0;
366+ } else {
367+// qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
368+ QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
369+ QQuickItem *prevItem = itemAtIndex(modelIndex - 1);
370+ if (prevItem) {
371+ item->setY(prevItem->y() + prevItem->height());
372+ } else {
373+ QQuickItem *nextItem = itemAtIndex(modelIndex + 1);
374+ if (nextItem) {
375+ item->setY(nextItem->y() - item->height());
376+ } else if (modelIndex == 0 && m_headerItem) {
377+ item->setY(m_headerItem->height());
378+ }
379+ }
380+ QQuickItemPrivate::get(item)->setCulled(item->y() + item->height() + m_clipItem->y() < contentY() || item->y() + m_clipItem->y() >= contentY() + height());
381+ if (m_visibleItems.isEmpty()) {
382+ m_visibleItems << item;
383+ } else {
384+ m_visibleItems.insert(modelIndex - m_firstVisibleIndex, item);
385+ }
386+ if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
387+ m_firstVisibleIndex = modelIndex;
388+ adjustMinYExtent();
389+ }
390+ return item;
391+ }
392+}
393+
394+void ListViewWithPageHeader::itemCreated(int modelIndex, QQuickItem *item)
395+{
396+// qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
397+
398+ item->setParentItem(m_clipItem);
399+ if (m_asyncRequestedIndexes.remove(modelIndex)) {
400+ createItem(modelIndex, false);
401+ }
402+}
403+
404+void ListViewWithPageHeader::updateClipItem()
405+{
406+ m_clipItem->setHeight(height() - m_headerItemShownHeight);
407+ m_clipItem->setY(contentY() + m_headerItemShownHeight);
408+ m_clipItem->setClip(m_headerItemShownHeight > 0);
409+}
410+
411+void ListViewWithPageHeader::onContentHeightChanged()
412+{
413+ updateClipItem();
414+}
415+
416+void ListViewWithPageHeader::onContentWidthChanged()
417+{
418+ m_clipItem->setWidth(contentItem()->width());
419+}
420+
421+void ListViewWithPageHeader::onModelUpdated(const QQuickChangeSet &changeSet, bool /*reset*/)
422+{
423+ // TODO Do something with reset
424+// qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
425+ foreach(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
426+// qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove;
427+ if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
428+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
429+ // If all the items we are removing are either not created or culled
430+ // we have to grow down to avoid viewport changing
431+ bool growDown = true;
432+ for (int i = 0; growDown && i < remove.count; ++i) {
433+ const int modelIndex = remove.index + i;
434+ QQuickItem *item = itemAtIndex(modelIndex);
435+ if (item && !QQuickItemPrivate::get(item)->culled) {
436+ growDown = false;
437+ }
438+ }
439+ for (int i = remove.count - 1; i >= 0; --i) {
440+ const int visibleIndex = remove.index + i - m_firstVisibleIndex;
441+ if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
442+ QQuickItem *item = m_visibleItems[visibleIndex];
443+ releaseItem(item);
444+ m_visibleItems.removeAt(visibleIndex);
445+ }
446+ }
447+ if (growDown) {
448+ adjustMinYExtent();
449+ } else if (remove.index <= m_firstVisibleIndex && !m_visibleItems.isEmpty()) {
450+ // We removed the first item that is the one that positions the rest
451+ // position the new first item correctly
452+ m_visibleItems.first()->setY(oldFirstValidIndexPos);
453+ }
454+ } else if (remove.index + remove.count <= m_firstVisibleIndex) {
455+ m_firstVisibleIndex -= remove.count;
456+ }
457+ }
458+
459+ foreach(const QQuickChangeSet::Insert &insert, changeSet.inserts()) {
460+// qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert;
461+ const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
462+ const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
463+ if (insertingInValidIndexes || firstItemWithViewOnTop)
464+ {
465+ // If the items we are adding won't be really visible
466+ // we grow up instead of down to not change the viewport
467+ bool growUp = false;
468+ if (!firstItemWithViewOnTop) {
469+ for (int i = 0; i < m_visibleItems.count(); ++i) {
470+ if (!QQuickItemPrivate::get(m_visibleItems[i])->culled) {
471+ if (insert.index <= m_firstVisibleIndex + i) {
472+ growUp = true;
473+ }
474+ break;
475+ }
476+ }
477+ }
478+
479+ const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
480+ for (int i = insert.count - 1; i >= 0; --i) {
481+ const int modelIndex = insert.index + i;
482+ QQuickItem *item = createItem(modelIndex, false);
483+ if (growUp) {
484+ QQuickItem *firstItem = m_visibleItems.first();
485+ firstItem->setY(firstItem->y() - item->height());
486+ adjustMinYExtent();
487+ }
488+ }
489+ if (firstItemWithViewOnTop) {
490+ QQuickItem *firstItem = m_visibleItems.first();
491+ firstItem->setY(oldFirstValidIndexPos);
492+ }
493+ } else if (insert.index <= m_firstVisibleIndex) {
494+ m_firstVisibleIndex += insert.count;
495+ }
496+ }
497+
498+ polish();
499+ m_contentHeightDirty = true;
500+}
501+
502+void ListViewWithPageHeader::itemGeometryChanged(QQuickItem *item, const QRectF &newGeometry, const QRectF &oldGeometry)
503+{
504+ const qreal heightDiff = newGeometry.height() - oldGeometry.height();
505+ if (heightDiff != 0) {
506+ if (item == m_headerItem) {
507+ headerHeightChanged(newGeometry.height(), oldGeometry.height(), oldGeometry.y());
508+ } else {
509+ if (oldGeometry.y() + oldGeometry.height() + m_clipItem->y() < contentY() && !m_visibleItems.isEmpty()) {
510+ item->setY(item->y() - heightDiff);
511+ if (m_visibleItems.first() == item) {
512+ adjustMinYExtent();
513+ }
514+ }
515+ }
516+ polish();
517+ m_contentHeightDirty = true;
518+ }
519+}
520+
521+void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
522+{
523+ const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
524+ if (m_headerItemShownHeight > 0) {
525+ // If the header is shown because of the clip
526+ // Change its size
527+ m_headerItemShownHeight += heightDiff;
528+ m_headerItemShownHeight = qBound(0., m_headerItemShownHeight, newHeaderHeight);
529+ updateClipItem();
530+ adjustMinYExtent();
531+ } else {
532+ if (oldHeaderY + oldHeaderHeight > contentY()) {
533+ // If the header is shown because its position
534+ // Change its size
535+ QQuickItem *firstItem = m_visibleItems.first();
536+ firstItem->setY(firstItem->y() + heightDiff);
537+ } else {
538+ // If the header is not on screen, just change the start of the list
539+ // so the viewport is not changed
540+ adjustMinYExtent();
541+ }
542+ }
543+}
544+
545+
546+void ListViewWithPageHeader::adjustMinYExtent()
547+{
548+ if (m_visibleItems.isEmpty()) {
549+ m_minYExtent = 0;
550+ } else {
551+ m_minYExtent = -m_visibleItems.first()->y() - m_clipItem->y() + (m_headerItem ? m_headerItem->height() : 0);
552+ }
553+}
554+
555+QQuickItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
556+{
557+ const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
558+ if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
559+ return m_visibleItems[visibleIndexedModelIndex];
560+
561+ return nullptr;
562+}
563+
564+void ListViewWithPageHeader::updatePolish()
565+{
566+ if (!model())
567+ return;
568+
569+ if (!m_visibleItems.isEmpty()) {
570+ const qreal visibleFrom = contentY() - m_clipItem->y();
571+ const qreal visibleTo = contentY() + height() - m_clipItem->y();
572+
573+ qreal pos = m_visibleItems.first()->y();
574+
575+// qDebug() << "ListViewWithPageHeader::updatePolish Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
576+ foreach(QQuickItem *item, m_visibleItems) {
577+ QQuickItemPrivate::get(item)->setCulled(pos + item->height() < visibleFrom || pos >= visibleTo);
578+ item->setY(pos);
579+// qDebug() << "ListViewWithPageHeader::updatePolish" << /*index << */pos + m_clipItem->y() << /*QQuickItemPrivate::get(item)->culled <<*/ (pos + item->height() < visibleFrom) << (pos > visibleTo);
580+ pos += item->height();
581+ }
582+ }
583+
584+ refill();
585+
586+ if (m_contentHeightDirty) {
587+ qreal contentHeight;
588+ const int modelCount = model()->rowCount();
589+ const int visibleItems = m_visibleItems.count();
590+ const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
591+ if (lastValidIndex == modelCount - 1) {
592+ QQuickItem *item = m_visibleItems.last();
593+ contentHeight = item->y() + item->height() + m_clipItem->y();
594+ if (m_firstVisibleIndex != 0) {
595+ // Make sure that if we are shrinking we tell the view we still fit
596+ m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
597+ }
598+ } else {
599+ contentHeight = m_headerItem ? m_headerItem->height() : 0;
600+ if (!m_visibleItems.isEmpty()) {
601+ foreach(QQuickItem *item, m_visibleItems) {
602+ contentHeight += item->height();
603+ }
604+ const int unknownSizes = modelCount - visibleItems;
605+ contentHeight += unknownSizes * contentHeight / visibleItems;
606+ }
607+ }
608+
609+ m_contentHeightDirty = false;
610+ setContentHeight(contentHeight);
611+ }
612+
613+}