Unity 8
 All Classes Functions Properties
listviewwithpageheader.cpp
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 /*
18  * Some documentation on how this thing works:
19  *
20  * A flickable has two very important concepts that define the top and
21  * height of the flickable area.
22  * The top is returned in minYExtent()
23  * The height is set using setContentHeight()
24  * By changing those two values we can make the list grow up or down
25  * as needed. e.g. if we are in the middle of the list
26  * and something that is above the viewport grows, since we do not
27  * want to change the viewport because of that we just adjust the
28  * minYExtent so that the list grows up.
29  *
30  * The implementation on the list relies on the delegateModel doing
31  * most of the instantiation work. You call createItem() when you
32  * need to create an item asking for it async or not. If returns null
33  * it means the item will be created async and the model will call the
34  * itemCreated slot with the item.
35  *
36  * updatePolish is the central point of dispatch for the work of the
37  * class. It is called by the scene graph just before drawing the class.
38  * In it we:
39  * * Make sure all items are positioned correctly
40  * * Add/Remove items if needed
41  * * Update the content height if it was dirty
42  *
43  * m_visibleItems contains all the items we have created at the moment.
44  * Actually not all of them are visible since it includes the ones
45  * in the cache area we create asynchronously to help performance.
46  * The first item in m_visibleItems has the m_firstVisibleIndex in
47  * the model. If you actually want to know what is the first
48  * item in the viewport you have to find the first non culled element
49  * in m_visibleItems
50  *
51  * All the items (except the header) are childs of m_clipItem which
52  * is a child of the contentItem() of the flickable (The contentItem()
53  * is what actually 'moves' in a a flickable). This way
54  * we can implement the clipping needed so we can have the header
55  * shown in the middle of the list over the items without the items
56  * leaking under the header in case it is transparent.
57  *
58  * The first item of m_visibleItems is the one that defines the
59  * positions of all the rest of items (see updatePolish()) and
60  * this is why sometimes we move it even if it's not the item
61  * that has triggered the function (i.e. in itemGeometryChanged())
62  *
63  * m_visibleItems is a list of ListItem. Each ListItem
64  * will contain a item and potentially a sectionItem. The sectionItem
65  * is only there when the list is using sectionDelegate+sectionProperty
66  * and this is the first item of the section. Each ListItem is vertically
67  * layouted with the sectionItem first and then the item.
68  *
69  * For sectioning we also have a section item alone (m_topSectionItem)
70  * that is used for the cases we need to show the sticky section item at
71  * the top of the view.
72  *
73  * Each delegate item has a context property called heightToClip that is
74  * used to communicate to the delegate implementation in case it has to
75  * clip itself because of overlapping with the top sticky section item.
76  * This is an implementation decision since it has been agreed it
77  * is easier to implement the clipping in QML with this info than to
78  * do it at the C++ level.
79  *
80  * Note that minYExtent and height are not always totally accurate, since
81  * we don't have the items created we can't guess their heights
82  * so we can only guarantee the values are correct when the first/last
83  * items of the list are visible, otherwise we just live with good enough
84  * values that make the list scrollable
85  *
86  * There are a few things that are not really implemented or tested properly
87  * which we don't use at the moment like changing the model, changing
88  * the section delegate, having a section delegate that changes its size, etc.
89  * The known missing features are marked with TODOs along the code.
90  */
91 
92 #include "listviewwithpageheader.h"
93 
94 #include <QCoreApplication>
95 #include <QDebug>
96 #include <qqmlinfo.h>
97 #include <qqmlengine.h>
98 #pragma GCC diagnostic push
99 #pragma GCC diagnostic ignored "-pedantic"
100 #include <private/qqmldelegatemodel_p.h>
101 #include <private/qqmlglobal_p.h>
102 #include <private/qquickitem_p.h>
103 #include <private/qquickanimation_p.h>
104 #pragma GCC diagnostic pop
105 // #include <private/qquickrectangle_p.h>
106 
107 static const qreal bufferRatio = 0.5;
108 
109 qreal ListViewWithPageHeader::ListItem::height() const
110 {
111  return m_item->height() + (m_sectionItem ? m_sectionItem->height() : 0);
112 }
113 
114 qreal ListViewWithPageHeader::ListItem::y() const
115 {
116  return m_item->y() - (m_sectionItem ? m_sectionItem->height() : 0);
117 }
118 
119 void ListViewWithPageHeader::ListItem::setY(qreal newY)
120 {
121  if (m_sectionItem) {
122  m_sectionItem->setY(newY);
123  m_item->setY(newY + m_sectionItem->height());
124  } else {
125  m_item->setY(newY);
126  }
127 }
128 
129 bool ListViewWithPageHeader::ListItem::culled() const
130 {
131  return QQuickItemPrivate::get(m_item)->culled;
132 }
133 
134 void ListViewWithPageHeader::ListItem::setCulled(bool culled)
135 {
136  QQuickItemPrivate::get(m_item)->setCulled(culled);
137  if (m_sectionItem)
138  QQuickItemPrivate::get(m_sectionItem)->setCulled(culled);
139 }
140 
141 ListViewWithPageHeader::ListViewWithPageHeader()
142  : m_delegateModel(nullptr)
143  , m_asyncRequestedIndex(-1)
144  , m_delegateValidated(false)
145  , m_firstVisibleIndex(-1)
146  , m_minYExtent(0)
147  , m_contentHeightDirty(false)
148  , m_headerItem(nullptr)
149  , m_previousContentY(0)
150  , m_headerItemShownHeight(0)
151  , m_sectionDelegate(nullptr)
152  , m_topSectionItem(nullptr)
153  , m_forceNoClip(false)
154  , m_inLayout(false)
155  , m_inContentHeightKeepHeaderShown(false)
156 {
157  m_clipItem = new QQuickItem(contentItem());
158 // m_clipItem = new QQuickRectangle(contentItem());
159 // ((QQuickRectangle*)m_clipItem)->setColor(Qt::gray);
160 
161  m_contentYAnimation = new QQuickNumberAnimation(this);
162  m_contentYAnimation->setEasing(QEasingCurve::OutQuad);
163  m_contentYAnimation->setProperty("contentY");
164  m_contentYAnimation->setDuration(200);
165  m_contentYAnimation->setTargetObject(this);
166 
167  connect(this, SIGNAL(contentWidthChanged()), this, SLOT(onContentWidthChanged()));
168  connect(this, SIGNAL(contentHeightChanged()), this, SLOT(onContentHeightChanged()));
169  connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
170  connect(m_contentYAnimation, SIGNAL(stopped()), this, SLOT(onShowHeaderAnimationFinished()));
171 
172  setFlickableDirection(VerticalFlick);
173 }
174 
175 ListViewWithPageHeader::~ListViewWithPageHeader()
176 {
177 }
178 
179 QAbstractItemModel *ListViewWithPageHeader::model() const
180 {
181  return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
182 }
183 
184 void ListViewWithPageHeader::setModel(QAbstractItemModel *model)
185 {
186  if (model != this->model()) {
187  if (!m_delegateModel) {
188  createDelegateModel();
189  } else {
190  disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
191  }
192  m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
193  connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
194  Q_EMIT modelChanged();
195  polish();
196  // TODO?
197 // Q_EMIT contentHeightChanged();
198 // Q_EMIT contentYChanged();
199  }
200 }
201 
202 QQmlComponent *ListViewWithPageHeader::delegate() const
203 {
204  return m_delegateModel ? m_delegateModel->delegate() : nullptr;
205 }
206 
207 void ListViewWithPageHeader::setDelegate(QQmlComponent *delegate)
208 {
209  if (delegate != this->delegate()) {
210  if (!m_delegateModel) {
211  createDelegateModel();
212  }
213 
214  // Cleanup the existing items
215  Q_FOREACH(ListItem *item, m_visibleItems)
216  releaseItem(item);
217  m_visibleItems.clear();
218  m_firstVisibleIndex = -1;
219  adjustMinYExtent();
220  setContentY(0);
221  m_clipItem->setY(0);
222  if (m_topSectionItem) {
223  QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
224  }
225 
226  m_delegateModel->setDelegate(delegate);
227 
228  Q_EMIT delegateChanged();
229  m_delegateValidated = false;
230  m_contentHeightDirty = true;
231  polish();
232  }
233 }
234 
235 QQuickItem *ListViewWithPageHeader::header() const
236 {
237  return m_headerItem;
238 }
239 
240 void ListViewWithPageHeader::setHeader(QQuickItem *headerItem)
241 {
242  if (m_headerItem != headerItem) {
243  qreal oldHeaderHeight = 0;
244  qreal oldHeaderY = 0;
245  if (m_headerItem) {
246  oldHeaderHeight = m_headerItem->height();
247  oldHeaderY = m_headerItem->y();
248  m_headerItem->setParentItem(nullptr);
249  }
250  m_headerItem = headerItem;
251  if (m_headerItem) {
252  m_headerItem->setParentItem(contentItem());
253  m_headerItem->setZ(1);
254  }
255  qreal newHeaderHeight = m_headerItem ? m_headerItem->height() : 0;
256  if (!m_visibleItems.isEmpty() && newHeaderHeight != oldHeaderHeight) {
257  headerHeightChanged(newHeaderHeight, oldHeaderHeight, oldHeaderY);
258  polish();
259  m_contentHeightDirty = true;
260  }
261  Q_EMIT headerChanged();
262  }
263 }
264 
265 QQmlComponent *ListViewWithPageHeader::sectionDelegate() const
266 {
267  return m_sectionDelegate;
268 }
269 
270 void ListViewWithPageHeader::setSectionDelegate(QQmlComponent *delegate)
271 {
272  if (delegate != m_sectionDelegate) {
273  // TODO clean existing sections
274 
275  m_sectionDelegate = delegate;
276 
277  m_topSectionItem = getSectionItem(QString());
278  m_topSectionItem->setZ(3);
279  QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
280  connect(m_topSectionItem, SIGNAL(heightChanged()), SIGNAL(stickyHeaderHeightChanged()));
281 
282  // TODO create sections for existing items
283 
284  Q_EMIT sectionDelegateChanged();
285  Q_EMIT stickyHeaderHeightChanged();
286  }
287 }
288 
289 QString ListViewWithPageHeader::sectionProperty() const
290 {
291  return m_sectionProperty;
292 }
293 
294 void ListViewWithPageHeader::setSectionProperty(const QString &property)
295 {
296  if (property != m_sectionProperty) {
297  m_sectionProperty = property;
298 
299  updateWatchedRoles();
300 
301  // TODO recreate sections
302 
303  Q_EMIT sectionPropertyChanged();
304  }
305 }
306 
307 bool ListViewWithPageHeader::forceNoClip() const
308 {
309  return m_forceNoClip;
310 }
311 
312 void ListViewWithPageHeader::setForceNoClip(bool noClip)
313 {
314  if (noClip != m_forceNoClip) {
315  m_forceNoClip = noClip;
316  updateClipItem();
317  Q_EMIT forceNoClipChanged();
318  }
319 }
320 
321 int ListViewWithPageHeader::stickyHeaderHeight() const
322 {
323  return m_topSectionItem ? m_topSectionItem->height() : 0;
324 }
325 
326 void ListViewWithPageHeader::positionAtBeginning()
327 {
328  if (m_delegateModel->count() <= 0)
329  return;
330 
331  qreal headerHeight = (m_headerItem ? m_headerItem->height() : 0);
332  if (m_firstVisibleIndex != 0) {
333  // TODO This could be optimized by trying to reuse the interesection
334  // of items that may end up intersecting between the existing
335  // m_visibleItems and the items we are creating in the next loop
336  Q_FOREACH(ListItem *item, m_visibleItems)
337  releaseItem(item);
338  m_visibleItems.clear();
339  m_firstVisibleIndex = -1;
340 
341  // Create the item 0, it will be already correctly positioned at createItem()
342  m_clipItem->setY(0);
343  ListItem *item = createItem(0, false);
344  // Create the subsequent items
345  int modelIndex = 1;
346  qreal pos = item->y() + item->height();
347  const qreal buffer = height() * bufferRatio;
348  const qreal bufferTo = height() + buffer;
349  while (modelIndex < m_delegateModel->count() && pos <= bufferTo) {
350  if (!(item = createItem(modelIndex, false)))
351  break;
352  pos += item->height();
353  ++modelIndex;
354  }
355 
356  m_previousContentY = m_visibleItems.first()->y() - headerHeight;
357  }
358  setContentY(m_visibleItems.first()->y() + m_clipItem->y() - headerHeight);
359  if (m_headerItem) {
360  // TODO This should not be needed and the code that adjust the m_headerItem position
361  // in viewportMoved() should be enough but in some cases we have not found a way to reproduce
362  // yet the code of viewportMoved() fails so here we make sure that at least if we are calling
363  // positionAtBeginning the header item will be correctly positioned
364  m_headerItem->setY(-m_minYExtent);
365  }
366 }
367 
368 static inline bool uFuzzyCompare(qreal r1, qreal r2)
369 {
370  return qFuzzyCompare(r1, r2) || (qFuzzyIsNull(r1) && qFuzzyIsNull(r2));
371 }
372 
373 void ListViewWithPageHeader::showHeader()
374 {
375  if (!m_headerItem)
376  return;
377 
378  const auto to = qMax(-minYExtent(), contentY() - m_headerItem->height() + m_headerItemShownHeight);
379  if (!uFuzzyCompare(to, contentY())) {
380  const bool headerShownByItsOwn = contentY() < m_headerItem->y() + m_headerItem->height();
381  if (headerShownByItsOwn && m_headerItemShownHeight == 0) {
382  // We are not clipping since we are just at the top of the viewport
383  // but because of the showHeader animation we will need to, so
384  // enable the clipping without logically moving the items
385  m_headerItemShownHeight = m_headerItem->y() + m_headerItem->height() - contentY();
386  if (!m_visibleItems.isEmpty()) {
387  updateClipItem();
388  ListItem *firstItem = m_visibleItems.first();
389  firstItem->setY(firstItem->y() - m_headerItemShownHeight);
390  layout();
391  }
392  }
393  m_contentYAnimation->setTo(to);
394  contentYAnimationType = ContentYAnimationShowHeader;
395  m_contentYAnimation->start();
396  }
397 }
398 
399 QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
400 {
401  ListItem *item = itemAtIndex(modelIndex);
402  if (item)
403  return item->m_item;
404  else
405  return nullptr;
406 }
407 
408 bool ListViewWithPageHeader::maximizeVisibleArea(int modelIndex)
409 {
410  ListItem *listItem = itemAtIndex(modelIndex);
411  if (listItem) {
412  return maximizeVisibleArea(listItem, listItem->height());
413  }
414 
415  return false;
416 }
417 
418 bool ListViewWithPageHeader::maximizeVisibleArea(int modelIndex, int itemHeight)
419 {
420  if (itemHeight < 0)
421  return false;
422 
423  ListItem *listItem = itemAtIndex(modelIndex);
424  if (listItem) {
425  return maximizeVisibleArea(listItem, itemHeight + (listItem->m_sectionItem ? listItem->m_sectionItem->height() : 0));
426  }
427 
428  return false;
429 }
430 
431 bool ListViewWithPageHeader::maximizeVisibleArea(ListItem *listItem, int listItemHeight)
432 {
433  if (listItem) {
434  layout();
435  const auto listItemY = m_clipItem->y() + listItem->y();
436  if (listItemY > contentY() && listItemY + listItemHeight > contentY() + height()) {
437  // we can scroll the list up to show more stuff
438  const auto to = qMin(listItemY, listItemY + listItemHeight - height());
439  m_contentYAnimation->setTo(to);
440  contentYAnimationType = ContentYAnimationMaximizeVisibleArea;
441  m_contentYAnimation->start();
442  } else if ((listItemY < contentY() && listItemY + listItemHeight < contentY() + height()) ||
443  (m_topSectionItem && !listItem->m_sectionItem && listItemY - m_topSectionItem->height() < contentY() && listItemY + listItemHeight < contentY() + height()))
444  {
445  // we can scroll the list down to show more stuff
446  auto realVisibleListItemY = listItemY;
447  if (m_topSectionItem) {
448  // If we are showing the top section sticky item and this item doesn't have a section
449  // item we have to make sure to scroll it a bit more so that it is not underlapping
450  // the top section sticky item
451  bool topSectionShown = !QQuickItemPrivate::get(m_topSectionItem)->culled;
452  if (topSectionShown && !listItem->m_sectionItem) {
453  realVisibleListItemY -= m_topSectionItem->height();
454  }
455  }
456  const auto to = qMax(realVisibleListItemY, listItemY + listItemHeight - height());
457  m_contentYAnimation->setTo(to);
458  contentYAnimationType = ContentYAnimationMaximizeVisibleArea;
459  m_contentYAnimation->start();
460  }
461  return true;
462  }
463  return false;
464 }
465 
466 qreal ListViewWithPageHeader::minYExtent() const
467 {
468 // qDebug() << "ListViewWithPageHeader::minYExtent" << m_minYExtent;
469  return m_minYExtent;
470 }
471 
472 void ListViewWithPageHeader::componentComplete()
473 {
474  if (m_delegateModel)
475  m_delegateModel->componentComplete();
476 
477  QQuickFlickable::componentComplete();
478 
479  polish();
480 }
481 
482 void ListViewWithPageHeader::viewportMoved(Qt::Orientations orient)
483 {
484  // Check we are not being taken down and don't paint anything
485  // TODO Check if we still need this in 5.2
486  // For reproduction just inifnite loop testDash or testDashContent
487  if (!QQmlEngine::contextForObject(this)->parentContext())
488  return;
489 
490  QQuickFlickable::viewportMoved(orient);
491 // qDebug() << "ListViewWithPageHeader::viewportMoved" << contentY();
492  qreal diff = m_previousContentY - contentY();
493  const bool showHeaderAnimationRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationShowHeader;
494  if (m_headerItem) {
495  const auto oldHeaderItemShownHeight = m_headerItemShownHeight;
496  if (uFuzzyCompare(contentY(), -m_minYExtent) || contentY() > -m_minYExtent) {
497  m_headerItem->setHeight(m_headerItem->implicitHeight());
498  // We are going down (but it's not because of the rebound at the end)
499  // (but the header was not shown by it's own position)
500  // or the header is partially shown and we are not doing a maximizeVisibleArea either
501  const bool scrolledUp = m_previousContentY > contentY();
502  const bool notRebounding = contentY() + height() < contentHeight();
503  const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
504  const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
505 
506  if (!scrolledUp && (contentY() == -m_minYExtent || (m_headerItemShownHeight == 0 && m_previousContentY == m_headerItem->y()))) {
507  m_headerItemShownHeight = 0;
508  m_headerItem->setY(-m_minYExtent);
509  } else if ((scrolledUp && notRebounding && notShownByItsOwn && !maximizeVisibleAreaRunning) || (m_headerItemShownHeight > 0) || m_inContentHeightKeepHeaderShown) {
510  if (maximizeVisibleAreaRunning && diff > 0) {
511  // If we are maximizing and the header was shown, make sure we hide it
512  m_headerItemShownHeight -= diff;
513  } else {
514  m_headerItemShownHeight += diff;
515  }
516  if (uFuzzyCompare(contentY(), -m_minYExtent)) {
517  m_headerItemShownHeight = 0;
518  } else {
519  m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, m_headerItem->height());
520  }
521  if (m_headerItemShownHeight > 0) {
522  if (uFuzzyCompare(m_headerItem->height(), m_headerItemShownHeight)) {
523  m_headerItem->setY(contentY());
524  m_headerItemShownHeight = m_headerItem->height();
525  } else {
526  m_headerItem->setY(contentY() - m_headerItem->height() + m_headerItemShownHeight);
527  }
528  } else {
529  m_headerItem->setY(-m_minYExtent);
530  }
531  }
532  } else {
533  // Stick the header item to the top when dragging down
534  m_headerItem->setY(contentY());
535  m_headerItem->setHeight(m_headerItem->implicitHeight() + (-m_minYExtent - contentY()));
536  }
537  // We will be changing the clip item, need to accomadate for it
538  // otherwise we move the firstItem down/up twice (unless the
539  // show header animation is running, where we want to keep the viewport stable)
540  if (!showHeaderAnimationRunning) {
541  diff += oldHeaderItemShownHeight - m_headerItemShownHeight;
542  } else {
543  diff = -diff;
544  }
545  }
546  if (!m_visibleItems.isEmpty()) {
547  updateClipItem();
548  ListItem *firstItem = m_visibleItems.first();
549  firstItem->setY(firstItem->y() + diff);
550  if (showHeaderAnimationRunning) {
551  adjustMinYExtent();
552  }
553  }
554 
555  m_previousContentY = contentY();
556  layout();
557  polish();
558 }
559 
560 void ListViewWithPageHeader::createDelegateModel()
561 {
562  m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
563  connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
564  if (isComponentComplete())
565  m_delegateModel->componentComplete();
566  updateWatchedRoles();
567 }
568 
569 void ListViewWithPageHeader::refill()
570 {
571  if (m_inLayout) {
572  return;
573  }
574  if (!isComponentComplete()) {
575  return;
576  }
577 
578  const qreal buffer = height() * bufferRatio;
579  const qreal from = contentY();
580  const qreal to = from + height();
581  const qreal bufferFrom = from - buffer;
582  const qreal bufferTo = to + buffer;
583 
584  bool added = addVisibleItems(from, to, false);
585  bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
586  added |= addVisibleItems(bufferFrom, bufferTo, true);
587 
588  if (added || removed) {
589  m_contentHeightDirty = true;
590  }
591 }
592 
593 bool ListViewWithPageHeader::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
594 {
595  if (!delegate())
596  return false;
597 
598  if (m_delegateModel->count() == 0)
599  return false;
600 
601  ListItem *item;
602 // qDebug() << "ListViewWithPageHeader::addVisibleItems" << fillFrom << fillTo << asynchronous;
603 
604  int modelIndex = 0;
605  qreal pos = 0;
606  if (!m_visibleItems.isEmpty()) {
607  modelIndex = m_firstVisibleIndex + m_visibleItems.count();
608  item = m_visibleItems.last();
609  pos = item->y() + item->height() + m_clipItem->y();
610  }
611  bool changed = false;
612 // qDebug() << (modelIndex < m_delegateModel->count()) << pos << fillTo;
613  while (modelIndex < m_delegateModel->count() && pos <= fillTo) {
614 // qDebug() << "refill: append item" << modelIndex << "pos" << pos << "asynchronous" << asynchronous;
615  if (!(item = createItem(modelIndex, asynchronous)))
616  break;
617  pos += item->height();
618  ++modelIndex;
619  changed = true;
620  }
621 
622  modelIndex = 0;
623  pos = 0;
624  if (!m_visibleItems.isEmpty()) {
625  modelIndex = m_firstVisibleIndex - 1;
626  item = m_visibleItems.first();
627  pos = item->y() + m_clipItem->y();
628  }
629  while (modelIndex >= 0 && pos > fillFrom) {
630 // qDebug() << "refill: prepend item" << modelIndex << "pos" << pos << "fillFrom" << fillFrom << "asynchronous" << asynchronous;
631  if (!(item = createItem(modelIndex, asynchronous)))
632  break;
633  pos -= item->height();
634  --modelIndex;
635  changed = true;
636  }
637 
638  return changed;
639 }
640 
641 void ListViewWithPageHeader::reallyReleaseItem(ListItem *listItem)
642 {
643  QQuickItem *item = listItem->m_item;
644  QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
645  if (flags & QQmlDelegateModel::Destroyed) {
646  item->setParentItem(nullptr);
647  }
648  delete listItem->m_sectionItem;
649  delete listItem;
650 }
651 
652 void ListViewWithPageHeader::releaseItem(ListItem *listItem)
653 {
654  QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(listItem->m_item);
655  itemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
656  m_itemsToRelease << listItem;
657 }
658 
659 void ListViewWithPageHeader::updateWatchedRoles()
660 {
661  if (m_delegateModel) {
662  QList<QByteArray> roles;
663  if (!m_sectionProperty.isEmpty())
664  roles << m_sectionProperty.toUtf8();
665  m_delegateModel->setWatchedRoles(roles);
666  }
667 }
668 
669 QQuickItem *ListViewWithPageHeader::getSectionItem(int modelIndex, bool alreadyInserted)
670 {
671  if (!m_sectionDelegate)
672  return nullptr;
673 
674  const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
675  if (modelIndex > 0) {
676  const QString prevSection = m_delegateModel->stringValue(modelIndex - 1, m_sectionProperty);
677  if (section == prevSection)
678  return nullptr;
679  }
680  if (modelIndex + 1 < model()->rowCount() && !alreadyInserted) {
681  // Already inserted items can't steal next section header
682  const QString nextSection = m_delegateModel->stringValue(modelIndex + 1, m_sectionProperty);
683  if (section == nextSection) {
684  // Steal the section header
685  ListItem *nextItem = itemAtIndex(modelIndex); // Not +1 since not yet inserted into m_visibleItems
686  if (nextItem) {
687  QQuickItem *sectionItem = nextItem->m_sectionItem;
688  nextItem->m_sectionItem = nullptr;
689  return sectionItem;
690  }
691  }
692  }
693 
694  return getSectionItem(section);
695 }
696 
697 QQuickItem *ListViewWithPageHeader::getSectionItem(const QString &sectionText)
698 {
699  QQuickItem *sectionItem = nullptr;
700 
701  QQmlContext *creationContext = m_sectionDelegate->creationContext();
702  QQmlContext *context = new QQmlContext(creationContext ? creationContext : qmlContext(this));
703  context->setContextProperty(QLatin1String("section"), sectionText);
704  context->setContextProperty(QLatin1String("delegateIndex"), -1);
705  QObject *nobj = m_sectionDelegate->beginCreate(context);
706  if (nobj) {
707  QQml_setParent_noEvent(context, nobj);
708  sectionItem = qobject_cast<QQuickItem *>(nobj);
709  if (!sectionItem) {
710  delete nobj;
711  } else {
712  sectionItem->setZ(2);
713  QQml_setParent_noEvent(sectionItem, m_clipItem);
714  sectionItem->setParentItem(m_clipItem);
715  }
716  } else {
717  delete context;
718  }
719  m_sectionDelegate->completeCreate();
720 
721  // TODO attach to sectionItem so we can accomodate to it changing its height
722 
723  return sectionItem;
724 }
725 
726 bool ListViewWithPageHeader::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
727 {
728 // qDebug() << "ListViewWithPageHeader::removeNonVisibleItems" << bufferFrom << bufferTo;
729  // Do not remove items if we are overshooting up or down, since we'll come back
730  // to the "stable" position and delete/create items without any reason
731  if (contentY() < -m_minYExtent) {
732  return false;
733  } else if (contentY() + height() > contentHeight()) {
734  return false;
735  }
736  bool changed = false;
737 
738  bool foundVisible = false;
739  int i = 0;
740  int removedItems = 0;
741  const auto oldFirstVisibleIndex = m_firstVisibleIndex;
742  while (i < m_visibleItems.count()) {
743  ListItem *item = m_visibleItems[i];
744  const qreal pos = item->y() + m_clipItem->y();
745 // qDebug() << i << pos << (pos + item->height()) << bufferFrom << bufferTo;
746  if (pos + item->height() < bufferFrom || pos > bufferTo) {
747 // qDebug() << "Releasing" << i << (pos + item->height() < bufferFrom) << pos + item->height() << bufferFrom << (pos > bufferTo) << pos << bufferTo;
748  releaseItem(item);
749  m_visibleItems.removeAt(i);
750  changed = true;
751  ++removedItems;
752  } else {
753  if (!foundVisible) {
754  foundVisible = true;
755  const int itemIndex = m_firstVisibleIndex + removedItems + i;
756  m_firstVisibleIndex = itemIndex;
757  }
758  ++i;
759  }
760  }
761  if (!foundVisible) {
762  m_firstVisibleIndex = -1;
763  }
764  if (m_firstVisibleIndex != oldFirstVisibleIndex) {
765  adjustMinYExtent();
766  }
767 
768  return changed;
769 }
770 
771 ListViewWithPageHeader::ListItem *ListViewWithPageHeader::createItem(int modelIndex, bool asynchronous)
772 {
773 // qDebug() << "CREATE ITEM" << modelIndex;
774  if (asynchronous && m_asyncRequestedIndex != -1)
775  return nullptr;
776 
777  m_asyncRequestedIndex = -1;
778  QObject* object = m_delegateModel->object(modelIndex, asynchronous);
779  QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
780  if (!item) {
781  if (object) {
782  m_delegateModel->release(object);
783  if (!m_delegateValidated) {
784  m_delegateValidated = true;
785  QObject* delegateObj = delegate();
786  qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
787  }
788  } else {
789  m_asyncRequestedIndex = modelIndex;
790  }
791  return 0;
792  } else {
793 // qDebug() << "ListViewWithPageHeader::createItem::We have the item" << modelIndex << item;
794  ListItem *listItem = new ListItem;
795  listItem->m_item = item;
796  listItem->m_sectionItem = getSectionItem(modelIndex, false /*Not yet inserted into m_visibleItems*/);
797  QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
798  ListItem *prevItem = itemAtIndex(modelIndex - 1);
799  bool lostItem = false; // Is an item that we requested async but because of model changes
800  // it is no longer attached to any of the existing items (has no prev nor next item)
801  // nor is the first item
802  if (prevItem) {
803  listItem->setY(prevItem->y() + prevItem->height());
804  } else {
805  ListItem *currItem = itemAtIndex(modelIndex);
806  if (currItem) {
807  // There's something already in m_visibleItems at out index, meaning this is an insert, so attach to its top
808  listItem->setY(currItem->y() - listItem->height());
809  } else {
810  ListItem *nextItem = itemAtIndex(modelIndex + 1);
811  if (nextItem) {
812  listItem->setY(nextItem->y() - listItem->height());
813  } else if (modelIndex == 0 && m_headerItem) {
814  listItem->setY(m_headerItem->height());
815  } else if (!m_visibleItems.isEmpty()) {
816  lostItem = true;
817  }
818  }
819  }
820  if (lostItem) {
821  listItem->setCulled(true);
822  releaseItem(listItem);
823  listItem = nullptr;
824  } else {
825  listItem->setCulled(listItem->y() + listItem->height() + m_clipItem->y() <= contentY() || listItem->y() + m_clipItem->y() >= contentY() + height());
826  if (m_visibleItems.isEmpty()) {
827  m_visibleItems << listItem;
828  } else {
829  m_visibleItems.insert(modelIndex - m_firstVisibleIndex, listItem);
830  }
831  if (m_firstVisibleIndex < 0 || modelIndex < m_firstVisibleIndex) {
832  m_firstVisibleIndex = modelIndex;
833  polish();
834  }
835  if (listItem->m_sectionItem) {
836  QQmlContext *context = QQmlEngine::contextForObject(listItem->m_sectionItem)->parentContext();
837  context->setContextProperty(QLatin1String("delegateIndex"), modelIndex);
838  }
839  adjustMinYExtent();
840  m_contentHeightDirty = true;
841  }
842  return listItem;
843  }
844 }
845 
846 void ListViewWithPageHeader::itemCreated(int modelIndex, QObject *object)
847 {
848  QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
849  if (!item) {
850  qWarning() << "ListViewWithPageHeader::itemCreated got a non item for index" << modelIndex;
851  return;
852  }
853 // qDebug() << "ListViewWithPageHeader::itemCreated" << modelIndex << item;
854  // Check we are not being taken down and don't paint anything
855  // TODO Check if we still need this in 5.2
856  // For reproduction just inifnite loop testDash or testDashContent
857  if (!QQmlEngine::contextForObject(this)->parentContext())
858  return;
859 
860  item->setParentItem(m_clipItem);
861  QQmlContext *context = QQmlEngine::contextForObject(item)->parentContext();
862  context->setContextProperty(QLatin1String("ListViewWithPageHeader"), this);
863  context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
864  if (modelIndex == m_asyncRequestedIndex) {
865  createItem(modelIndex, false);
866  refill();
867  }
868 }
869 
870 void ListViewWithPageHeader::updateClipItem()
871 {
872  m_clipItem->setHeight(height() - m_headerItemShownHeight);
873  m_clipItem->setY(contentY() + m_headerItemShownHeight);
874  m_clipItem->setClip(!m_forceNoClip && m_headerItemShownHeight > 0);
875 }
876 
877 void ListViewWithPageHeader::onContentHeightChanged()
878 {
879  updateClipItem();
880 }
881 
882 void ListViewWithPageHeader::onContentWidthChanged()
883 {
884  m_clipItem->setWidth(contentItem()->width());
885 }
886 
887 void ListViewWithPageHeader::onHeightChanged()
888 {
889  polish();
890 }
891 
892 
893 void ListViewWithPageHeader::onModelUpdated(const QQmlChangeSet &changeSet, bool /*reset*/)
894 {
895  // TODO Do something with reset
896 // qDebug() << "ListViewWithPageHeader::onModelUpdated" << changeSet << reset;
897  const auto oldFirstVisibleIndex = m_firstVisibleIndex;
898 
899  Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
900 // qDebug() << "ListViewWithPageHeader::onModelUpdated Remove" << remove.index << remove.count;
901  if (remove.index + remove.count > m_firstVisibleIndex && remove.index < m_firstVisibleIndex + m_visibleItems.count()) {
902  const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
903  // If all the items we are removing are either not created or culled
904  // we have to grow down to avoid viewport changing
905  bool growDown = true;
906  for (int i = 0; growDown && i < remove.count; ++i) {
907  const int modelIndex = remove.index + i;
908  ListItem *item = itemAtIndex(modelIndex);
909  if (item && !item->culled()) {
910  growDown = false;
911  }
912  }
913  for (int i = remove.count - 1; i >= 0; --i) {
914  const int visibleIndex = remove.index + i - m_firstVisibleIndex;
915  if (visibleIndex >= 0 && visibleIndex < m_visibleItems.count()) {
916  ListItem *item = m_visibleItems[visibleIndex];
917  // Pass the section item down if needed
918  if (item->m_sectionItem && visibleIndex + 1 < m_visibleItems.count()) {
919  ListItem *nextItem = m_visibleItems[visibleIndex + 1];
920  if (!nextItem->m_sectionItem) {
921  nextItem->m_sectionItem = item->m_sectionItem;
922  item->m_sectionItem = nullptr;
923  }
924  }
925  releaseItem(item);
926  m_visibleItems.removeAt(visibleIndex);
927  }
928  }
929  if (growDown) {
930  adjustMinYExtent();
931  } else if (remove.index <= m_firstVisibleIndex) {
932  if (!m_visibleItems.isEmpty()) {
933  // We removed the first item that is the one that positions the rest
934  // position the new first item correctly
935  m_visibleItems.first()->setY(oldFirstValidIndexPos);
936  } else {
937  m_firstVisibleIndex = -1;
938  }
939  }
940  } else if (remove.index + remove.count <= m_firstVisibleIndex) {
941  m_firstVisibleIndex -= remove.count;
942  }
943  for (int i = remove.count - 1; i >= 0; --i) {
944  const int modelIndex = remove.index + i;
945  if (modelIndex == m_asyncRequestedIndex) {
946  m_asyncRequestedIndex = -1;
947  } else if (modelIndex < m_asyncRequestedIndex) {
948  m_asyncRequestedIndex--;
949  }
950  }
951  }
952 
953  Q_FOREACH(const QQmlChangeSet::Insert &insert, changeSet.inserts()) {
954 // qDebug() << "ListViewWithPageHeader::onModelUpdated Insert" << insert.index << insert.count;
955  const bool insertingInValidIndexes = insert.index > m_firstVisibleIndex && insert.index < m_firstVisibleIndex + m_visibleItems.count();
956  const bool firstItemWithViewOnTop = insert.index == 0 && m_firstVisibleIndex == 0 && m_visibleItems.first()->y() + m_clipItem->y() > contentY();
957  if (insertingInValidIndexes || firstItemWithViewOnTop)
958  {
959  // If the items we are adding won't be really visible
960  // we grow up instead of down to not change the viewport
961  bool growUp = false;
962  if (!firstItemWithViewOnTop) {
963  for (int i = 0; i < m_visibleItems.count(); ++i) {
964  if (!m_visibleItems[i]->culled()) {
965  if (insert.index <= m_firstVisibleIndex + i) {
966  growUp = true;
967  }
968  break;
969  }
970  }
971  }
972 
973  const qreal oldFirstValidIndexPos = m_visibleItems.first()->y();
974  for (int i = insert.count - 1; i >= 0; --i) {
975  const int modelIndex = insert.index + i;
976  ListItem *item = createItem(modelIndex, false);
977  if (growUp) {
978  ListItem *firstItem = m_visibleItems.first();
979  firstItem->setY(firstItem->y() - item->height());
980  }
981  // Adding an item may break a "same section" chain, so check
982  // if we need adding a new section item
983  if (m_sectionDelegate) {
984  ListItem *nextItem = itemAtIndex(modelIndex + 1);
985  if (nextItem && !nextItem->m_sectionItem) {
986  nextItem->m_sectionItem = getSectionItem(modelIndex + 1, true /* alredy inserted into m_visibleItems*/);
987  if (growUp && nextItem->m_sectionItem) {
988  ListItem *firstItem = m_visibleItems.first();
989  firstItem->setY(firstItem->y() - nextItem->m_sectionItem->height());
990  }
991  }
992  }
993  }
994  if (firstItemWithViewOnTop) {
995  ListItem *firstItem = m_visibleItems.first();
996  firstItem->setY(oldFirstValidIndexPos);
997  }
998  adjustMinYExtent();
999  } else if (insert.index <= m_firstVisibleIndex) {
1000  m_firstVisibleIndex += insert.count;
1001  }
1002 
1003  for (int i = insert.count - 1; i >= 0; --i) {
1004  const int modelIndex = insert.index + i;
1005  if (modelIndex <= m_asyncRequestedIndex) {
1006  m_asyncRequestedIndex++;
1007  }
1008  }
1009  }
1010 
1011  Q_FOREACH(const QQmlChangeSet::Change &change, changeSet.changes()) {
1012 // qDebug() << "ListViewWithPageHeader::onModelUpdated Change" << change.index << change.count;
1013  for (int i = change.index; i < change.count; ++i) {
1014  ListItem *item = itemAtIndex(i);
1015  if (item && item->m_sectionItem) {
1016  QQmlContext *context = QQmlEngine::contextForObject(item->m_sectionItem)->parentContext();
1017  const QString sectionText = m_delegateModel->stringValue(i, m_sectionProperty);
1018  context->setContextProperty(QLatin1String("section"), sectionText);
1019  }
1020  }
1021  }
1022 
1023  if (m_firstVisibleIndex != oldFirstVisibleIndex) {
1024  adjustMinYExtent();
1025  }
1026 
1027  for (int i = 0; i < m_visibleItems.count(); ++i) {
1028  ListItem *item = m_visibleItems[i];
1029  if (item->m_sectionItem) {
1030  QQmlContext *context = QQmlEngine::contextForObject(item->m_sectionItem)->parentContext();
1031  context->setContextProperty(QLatin1String("delegateIndex"), m_firstVisibleIndex + i);
1032  }
1033  }
1034 
1035  layout();
1036  polish();
1037  m_contentHeightDirty = true;
1038 }
1039 
1040 void ListViewWithPageHeader::onShowHeaderAnimationFinished()
1041 {
1042  m_contentHeightDirty = true;
1043  polish();
1044 }
1045 
1046 void ListViewWithPageHeader::itemGeometryChanged(QQuickItem * /*item*/, const QRectF &newGeometry, const QRectF &oldGeometry)
1047 {
1048  const qreal heightDiff = newGeometry.height() - oldGeometry.height();
1049  if (heightDiff != 0) {
1050  if (!m_inContentHeightKeepHeaderShown && oldGeometry.y() + oldGeometry.height() + m_clipItem->y() <= contentY() && !m_visibleItems.isEmpty()) {
1051  ListItem *firstItem = m_visibleItems.first();
1052  firstItem->setY(firstItem->y() - heightDiff);
1053  adjustMinYExtent();
1054  layout();
1055  }
1056  refill();
1057  adjustMinYExtent();
1058  polish();
1059  m_contentHeightDirty = true;
1060  }
1061 }
1062 
1063 void ListViewWithPageHeader::headerHeightChanged(qreal newHeaderHeight, qreal oldHeaderHeight, qreal oldHeaderY)
1064 {
1065  const qreal heightDiff = newHeaderHeight - oldHeaderHeight;
1066  if (m_headerItemShownHeight > 0) {
1067  // If the header is shown because of the clip
1068  // Change its size
1069  m_headerItemShownHeight += heightDiff;
1070  m_headerItemShownHeight = qBound(static_cast<qreal>(0.), m_headerItemShownHeight, newHeaderHeight);
1071  updateClipItem();
1072  adjustMinYExtent();
1073  } else {
1074  if (oldHeaderY + oldHeaderHeight > contentY()) {
1075  // If the header is shown because its position
1076  // Change its size
1077  ListItem *firstItem = m_visibleItems.first();
1078  firstItem->setY(firstItem->y() + heightDiff);
1079  layout();
1080  } else {
1081  // If the header is not on screen, just change the start of the list
1082  // so the viewport is not changed
1083  adjustMinYExtent();
1084  }
1085  }
1086 }
1087 
1088 
1089 void ListViewWithPageHeader::adjustMinYExtent()
1090 {
1091  if (m_visibleItems.isEmpty()) {
1092  m_minYExtent = 0;
1093  } else {
1094  qreal nonCreatedHeight = 0;
1095  if (m_firstVisibleIndex != 0) {
1096  // Calculate the average height of items to estimate the position of the list start
1097  const int visibleItems = m_visibleItems.count();
1098  qreal visibleItemsHeight = 0;
1099  Q_FOREACH(ListItem *item, m_visibleItems) {
1100  visibleItemsHeight += item->height();
1101  }
1102  nonCreatedHeight = m_firstVisibleIndex * visibleItemsHeight / visibleItems;
1103 // qDebug() << m_firstVisibleIndex << visibleItemsHeight << visibleItems << nonCreatedHeight;
1104  }
1105  const qreal headerHeight = (m_headerItem ? m_headerItem->implicitHeight() : 0);
1106  m_minYExtent = nonCreatedHeight - m_visibleItems.first()->y() - m_clipItem->y() + headerHeight;
1107  if (m_minYExtent != 0 && qFuzzyIsNull(m_minYExtent)) {
1108  m_minYExtent = 0;
1109  m_visibleItems.first()->setY(nonCreatedHeight - m_clipItem->y() + headerHeight);
1110  }
1111  }
1112 }
1113 
1114 ListViewWithPageHeader::ListItem *ListViewWithPageHeader::itemAtIndex(int modelIndex) const
1115 {
1116  const int visibleIndexedModelIndex = modelIndex - m_firstVisibleIndex;
1117  if (visibleIndexedModelIndex >= 0 && visibleIndexedModelIndex < m_visibleItems.count())
1118  return m_visibleItems[visibleIndexedModelIndex];
1119 
1120  return nullptr;
1121 }
1122 
1123 void ListViewWithPageHeader::layout()
1124 {
1125  if (m_inLayout)
1126  return;
1127 
1128  m_inLayout = true;
1129  if (!m_visibleItems.isEmpty()) {
1130  const qreal visibleFrom = contentY() - m_clipItem->y() + m_headerItemShownHeight;
1131  const qreal visibleTo = contentY() + height() - m_clipItem->y();
1132 
1133  qreal pos = m_visibleItems.first()->y();
1134 
1135 // qDebug() << "ListViewWithPageHeader::layout Updating positions and heights. contentY" << contentY() << "minYExtent" << minYExtent();
1136  int firstReallyVisibleItem = -1;
1137  int modelIndex = m_firstVisibleIndex;
1138  Q_FOREACH(ListItem *item, m_visibleItems) {
1139  const bool cull = pos + item->height() <= visibleFrom || pos >= visibleTo;
1140  item->setCulled(cull);
1141  item->setY(pos);
1142  if (!cull && firstReallyVisibleItem == -1) {
1143  firstReallyVisibleItem = modelIndex;
1144  if (m_topSectionItem) {
1145  // Positing the top section sticky item is a two step process
1146  // First we set it either we cull it (because it doesn't need to be sticked to the top)
1147  // or stick it to the top
1148  // Then after the loop we'll make sure that if there's another section just below it
1149  // pushed the sticky section up to make it disappear
1150  const qreal topSectionStickPos = m_headerItemShownHeight + contentY() - m_clipItem->y();
1151  bool showStickySectionItem;
1152  // We need to show the "top section sticky item" when the position at the "top" of the
1153  // viewport is bigger than the start of the position of the first visible item
1154  // i.e. the first visible item starts before the viewport, or when the first
1155  // visible item starts just at the viewport start and it does not have its own section item
1156  if (topSectionStickPos > pos) {
1157  showStickySectionItem = true;
1158  } else if (topSectionStickPos == pos) {
1159  showStickySectionItem = !item->m_sectionItem;
1160  } else {
1161  showStickySectionItem = false;
1162  }
1163  if (!showStickySectionItem) {
1164  QQuickItemPrivate::get(m_topSectionItem)->setCulled(true);
1165  if (item->m_sectionItem) {
1166  // This seems it should happen since why would we cull the top section
1167  // if the first visible item has no section header? This only happens briefly
1168  // when increasing the height of a list that is at the bottom, the m_topSectionItem
1169  // gets shown shortly in the next polish call
1170  QQuickItemPrivate::get(item->m_sectionItem)->setCulled(false);
1171  }
1172  } else {
1173  // Update the top sticky section header
1174  const QString section = m_delegateModel->stringValue(modelIndex, m_sectionProperty);
1175  QQmlContext *context = QQmlEngine::contextForObject(m_topSectionItem)->parentContext();
1176  context->setContextProperty(QLatin1String("section"), section);
1177 
1178  QQuickItemPrivate::get(m_topSectionItem)->setCulled(false);
1179  m_topSectionItem->setY(topSectionStickPos);
1180  int delegateIndex = modelIndex;
1181  // Look for the first index with this section text
1182  while (delegateIndex > 0) {
1183  const QString prevSection = m_delegateModel->stringValue(delegateIndex - 1, m_sectionProperty);
1184  if (prevSection != section)
1185  break;
1186  delegateIndex--;
1187  }
1188  context->setContextProperty(QLatin1String("delegateIndex"), delegateIndex);
1189  if (item->m_sectionItem) {
1190  QQuickItemPrivate::get(item->m_sectionItem)->setCulled(true);
1191  }
1192  }
1193  }
1194  }
1195  QQmlContext *context = QQmlEngine::contextForObject(item->m_item)->parentContext();
1196  const qreal clipFrom = visibleFrom + (!item->m_sectionItem && m_topSectionItem && !QQuickItemPrivate::get(m_topSectionItem)->culled ? m_topSectionItem->height() : 0);
1197  if (!cull && pos < clipFrom) {
1198  context->setContextProperty(QLatin1String("heightToClip"), clipFrom - pos);
1199  } else {
1200  context->setContextProperty(QLatin1String("heightToClip"), QVariant::fromValue<int>(0));
1201  }
1202 // qDebug() << "ListViewWithPageHeader::layout" << item->m_item;
1203  pos += item->height();
1204  ++modelIndex;
1205  }
1206 
1207  // Second step of section sticky item positioning
1208  // Look at the next section header, check if it's pushing up the sticky one
1209  if (m_topSectionItem) {
1210  if (firstReallyVisibleItem >= 0) {
1211  for (int i = firstReallyVisibleItem - m_firstVisibleIndex + 1; i < m_visibleItems.count(); ++i) {
1212  ListItem *item = m_visibleItems[i];
1213  if (item->m_sectionItem) {
1214  if (m_topSectionItem->y() + m_topSectionItem->height() > item->y()) {
1215  m_topSectionItem->setY(item->y() - m_topSectionItem->height());
1216  }
1217  break;
1218  }
1219  }
1220  }
1221  }
1222  }
1223  m_inLayout = false;
1224 }
1225 
1226 void ListViewWithPageHeader::updatePolish()
1227 {
1228  // Check we are not being taken down and don't paint anything
1229  // TODO Check if we still need this in 5.2
1230  // For reproduction just inifnite loop testDash or testDashContent
1231  if (!QQmlEngine::contextForObject(this)->parentContext())
1232  return;
1233 
1234  Q_FOREACH(ListItem *item, m_itemsToRelease)
1235  reallyReleaseItem(item);
1236  m_itemsToRelease.clear();
1237 
1238  if (!model())
1239  return;
1240 
1241  layout();
1242 
1243  refill();
1244 
1245  if (m_contentHeightDirty) {
1246  qreal contentHeight;
1247  if (m_visibleItems.isEmpty()) {
1248  contentHeight = m_headerItem ? m_headerItem->height() : 0;
1249  } else {
1250  const int modelCount = model()->rowCount();
1251  const int visibleItems = m_visibleItems.count();
1252  const int lastValidIndex = m_firstVisibleIndex + visibleItems - 1;
1253  qreal nonCreatedHeight = 0;
1254  if (lastValidIndex != modelCount - 1) {
1255  const int visibleItems = m_visibleItems.count();
1256  qreal visibleItemsHeight = 0;
1257  Q_FOREACH(ListItem *item, m_visibleItems) {
1258  visibleItemsHeight += item->height();
1259  }
1260  const int unknownSizes = modelCount - (m_firstVisibleIndex + visibleItems);
1261  nonCreatedHeight = unknownSizes * visibleItemsHeight / visibleItems;
1262  }
1263  ListItem *item = m_visibleItems.last();
1264  contentHeight = nonCreatedHeight + item->y() + item->height() + m_clipItem->y();
1265  if (m_firstVisibleIndex != 0) {
1266  // Make sure that if we are shrinking we tell the view we still fit
1267  m_minYExtent = qMax(m_minYExtent, -(contentHeight - height()));
1268  }
1269  }
1270 
1271  m_contentHeightDirty = false;
1272  adjustMinYExtent();
1273  m_inContentHeightKeepHeaderShown = m_headerItem && m_headerItem->y() == contentY();
1274  setContentHeight(contentHeight);
1275  m_inContentHeightKeepHeaderShown = false;
1276  }
1277 }
1278 
1279 #include "moc_listviewwithpageheader.cpp"