1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquicklistview_p.h"
5#include "qquickitemview_p_p.h"
6#include "qquickflickablebehavior_p.h"
7
8#include <private/qqmlobjectmodel_p.h>
9#include <QtQml/qqmlexpression.h>
10#include <QtQml/qqmlengine.h>
11#include <QtQml/qqmlinfo.h>
12#include <QtGui/qevent.h>
13#include <QtCore/qcoreapplication.h>
14#include <QtCore/qmath.h>
15
16#include <private/qquicksmoothedanimation_p_p.h>
17#include <private/qqmlcomponent_p.h>
18
19QT_BEGIN_NAMESPACE
20
21#ifndef QML_FLICK_SNAPONETHRESHOLD
22#define QML_FLICK_SNAPONETHRESHOLD 30
23#endif
24
25Q_STATIC_LOGGING_CATEGORY(lcEvents, "qt.quick.listview.events")
26
27class FxListItemSG;
28
29class QQuickListViewPrivate : public QQuickItemViewPrivate
30{
31public:
32 Q_DECLARE_PUBLIC(QQuickListView)
33 static QQuickListViewPrivate* get(QQuickListView *item) { return item->d_func(); }
34
35 Qt::Orientation layoutOrientation() const override;
36 bool isContentFlowReversed() const override;
37 bool isRightToLeft() const;
38 bool isBottomToTop() const;
39
40 qreal positionAt(int index) const override;
41 qreal endPositionAt(int index) const override;
42 qreal originPosition() const override;
43 qreal lastPosition() const override;
44
45 FxViewItem *itemBefore(int modelIndex) const;
46 QString sectionAt(int modelIndex);
47 qreal snapPosAt(qreal pos);
48 FxViewItem *snapItemAt(qreal pos);
49
50 void init() override;
51 void clear(bool onDestruction) override;
52
53 bool addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer) override;
54 bool removeNonVisibleItems(qreal bufferFrom, qreal bufferTo) override;
55 void visibleItemsChanged() override;
56
57 void removeItem(FxViewItem *item);
58
59 FxViewItem *newViewItem(int index, QQuickItem *item) override;
60 void initializeViewItem(FxViewItem *item) override;
61 bool releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag) override;
62 void repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer) override;
63 void repositionPackageItemAt(QQuickItem *item, int index) override;
64 void resetFirstItemPosition(qreal pos = 0.0) override;
65 void adjustFirstItem(qreal forwards, qreal backwards, int) override;
66 void updateSizeChangesBeforeVisiblePos(FxViewItem *item, ChangeResult *removeResult) override;
67
68 void createHighlight(bool onDestruction = false) override;
69 void updateHighlight() override;
70 void resetHighlightPosition() override;
71 bool movingFromHighlight() override;
72
73 void setPosition(qreal pos) override;
74 void layoutVisibleItems(int fromModelIndex = 0) override;
75
76 bool applyInsertionChange(const QQmlChangeSet::Change &insert, ChangeResult *changeResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView) override;
77#if QT_CONFIG(quick_viewtransitions)
78 void translateAndTransitionItemsAfter(int afterIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult) override;
79#endif
80
81 void updateSectionCriteria() override;
82 void updateSections() override;
83 QQuickItem *getSectionItem(const QString &section);
84 void releaseSectionItem(QQuickItem *item);
85 void releaseSectionItems();
86 void updateInlineSection(FxListItemSG *);
87 void updateCurrentSection();
88 void updateStickySections();
89
90 qreal headerSize() const override;
91 qreal footerSize() const override;
92 bool showHeaderForIndex(int index) const override;
93 bool showFooterForIndex(int index) const override;
94 void updateHeader() override;
95 void updateFooter() override;
96 bool hasStickyHeader() const override;
97 bool hasStickyFooter() const override;
98
99 void initializeComponentItem(QQuickItem *item) const override;
100
101 void changedVisibleIndex(int newIndex) override;
102 void initializeCurrentItem() override;
103
104 void updateAverage();
105
106 void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &oldGeometry) override;
107 void fixupPosition() override;
108 void fixup(AxisData &data, qreal minExtent, qreal maxExtent) override;
109 bool flick(QQuickItemViewPrivate::AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
110 QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity) override;
111
112 QQuickItemViewAttached *getAttachedObject(const QObject *object) const override;
113
114 void fixupHeader();
115 void fixupHeaderCompleted();
116
117 bool wantsPointerEvent(const QPointerEvent *event) override;
118
119 QQuickListView::Orientation orient;
120 qreal visiblePos;
121 qreal averageSize;
122 qreal spacing;
123 QQuickListView::SnapMode snapMode;
124
125 QQuickListView::HeaderPositioning headerPositioning;
126 QQuickListView::FooterPositioning footerPositioning;
127
128 std::unique_ptr<QSmoothedAnimation> highlightPosAnimator;
129 std::unique_ptr<QSmoothedAnimation> highlightWidthAnimator;
130 std::unique_ptr<QSmoothedAnimation> highlightHeightAnimator;
131 qreal highlightMoveVelocity;
132 qreal highlightResizeVelocity;
133 int highlightResizeDuration;
134
135 QQuickViewSection *sectionCriteria;
136 QString currentSection;
137 static const int sectionCacheSize = 5;
138 QQuickItem *sectionCache[sectionCacheSize];
139 QQuickItem *currentSectionItem;
140 QString currentStickySection;
141 QQuickItem *nextSectionItem;
142 QString nextStickySection;
143 QString lastVisibleSection;
144 QString nextSection;
145
146 qreal overshootDist;
147
148 qreal desiredViewportPosition;
149 qreal fixupHeaderPosition;
150 bool headerNeedsSeparateFixup : 1;
151 bool desiredHeaderVisible : 1;
152
153 bool correctFlick : 1;
154 bool inFlickCorrection : 1;
155 bool wantedMousePress : 1;
156
157 QQuickListViewPrivate()
158 : orient(QQuickListView::Vertical)
159 , visiblePos(0)
160 , averageSize(100.0), spacing(0.0)
161 , snapMode(QQuickListView::NoSnap)
162 , headerPositioning(QQuickListView::InlineHeader)
163 , footerPositioning(QQuickListView::InlineFooter)
164 , highlightPosAnimator(nullptr), highlightWidthAnimator(nullptr), highlightHeightAnimator(nullptr)
165 , highlightMoveVelocity(400), highlightResizeVelocity(400), highlightResizeDuration(-1)
166 , sectionCriteria(nullptr), currentSectionItem(nullptr), nextSectionItem(nullptr)
167 , overshootDist(0.0), desiredViewportPosition(0.0), fixupHeaderPosition(0.0)
168 , headerNeedsSeparateFixup(false), desiredHeaderVisible(false)
169 , correctFlick(false), inFlickCorrection(false), wantedMousePress(false)
170 {
171 highlightMoveDuration = -1; //override default value set in base class
172 }
173
174 friend class QQuickViewSection;
175
176 static void setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString &section);
177};
178
179//----------------------------------------------------------------------------
180
181QQuickViewSection::QQuickViewSection(QQuickListView *parent)
182 : QObject(parent), m_criteria(FullString), m_delegate(nullptr), m_labelPositioning(InlineLabels)
183 , m_view(parent ? QQuickListViewPrivate::get(item: parent) : nullptr)
184{
185}
186
187void QQuickViewSection::setProperty(const QString &property)
188{
189 if (property != m_property) {
190 m_property = property;
191 emit propertyChanged();
192 // notify view that the contents of the sections must be recalculated
193 m_view->updateSectionCriteria();
194 }
195}
196
197void QQuickViewSection::setCriteria(QQuickViewSection::SectionCriteria criteria)
198{
199 if (criteria != m_criteria) {
200 m_criteria = criteria;
201 emit criteriaChanged();
202 // notify view that the contents of the sections must be recalculated
203 m_view->updateSectionCriteria();
204 }
205}
206
207void QQuickViewSection::setDelegate(QQmlComponent *delegate)
208{
209 if (delegate != m_delegate) {
210 if (m_delegate)
211 m_view->releaseSectionItems();
212 m_delegate = delegate;
213 emit delegateChanged();
214 m_view->forceLayoutPolish();
215 }
216}
217
218QString QQuickViewSection::sectionString(const QString &value)
219{
220 if (m_criteria == FirstCharacter)
221 return value.isEmpty() ? QString() : value.at(i: 0);
222 else
223 return value;
224}
225
226void QQuickViewSection::setLabelPositioning(int l)
227{
228 if (m_labelPositioning != l) {
229 m_labelPositioning = l;
230 emit labelPositioningChanged();
231 m_view->forceLayoutPolish();
232 }
233}
234
235//----------------------------------------------------------------------------
236
237class FxListItemSG : public FxViewItem
238{
239public:
240 FxListItemSG(QQuickItem *i, QQuickListView *v, bool own) : FxViewItem(i, v, own, static_cast<QQuickItemViewAttached*>(qmlAttachedPropertiesObject<QQuickListView>(obj: i))), view(v)
241 {
242 }
243
244 inline QQuickItem *section() const {
245 return item && attached ? static_cast<QQuickListViewAttached*>(attached)->m_sectionItem : nullptr;
246 }
247 void setSection(QQuickItem *s) {
248 static_cast<QQuickListViewAttached*>(attached)->m_sectionItem = s;
249 }
250
251 qreal position() const override {
252 if (section()) {
253 if (view->orientation() == QQuickListView::Vertical)
254 return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -section()->height()-section()->y() : section()->y());
255 else
256 return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -section()->width()-section()->x() : section()->x());
257 } else {
258 return itemPosition();
259 }
260 }
261 qreal itemPosition() const {
262 if (view->orientation() == QQuickListView::Vertical)
263 return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop ? -itemHeight()-itemY() : itemY());
264 else
265 return (view->effectiveLayoutDirection() == Qt::RightToLeft ? -itemWidth()-itemX() : itemX());
266 }
267 qreal size() const override {
268 if (section())
269 return (view->orientation() == QQuickListView::Vertical ? itemHeight()+section()->height() : itemWidth()+section()->width());
270 else
271 return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth());
272 }
273 qreal itemSize() const {
274 return (view->orientation() == QQuickListView::Vertical ? itemHeight() : itemWidth());
275 }
276 qreal sectionSize() const override {
277 if (section())
278 return (view->orientation() == QQuickListView::Vertical ? section()->height() : section()->width());
279 return 0.0;
280 }
281 qreal endPosition() const override {
282 if (view->orientation() == QQuickListView::Vertical) {
283 return (view->verticalLayoutDirection() == QQuickItemView::BottomToTop
284 ? -itemY()
285 : itemY() + itemHeight());
286 } else {
287 return (view->effectiveLayoutDirection() == Qt::RightToLeft
288 ? -itemX()
289 : itemX() + itemWidth());
290 }
291 }
292
293 void setPosition(qreal pos, bool immediate = false, bool resetInactiveAxis = true) {
294 // position the section immediately even if there is a transition
295 if (section()) {
296 if (view->orientation() == QQuickListView::Vertical) {
297 if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop)
298 section()->setY(-section()->height()-pos);
299 else
300 section()->setY(pos);
301 } else {
302 if (view->effectiveLayoutDirection() == Qt::RightToLeft)
303 section()->setX(-section()->width()-pos);
304 else
305 section()->setX(pos);
306 }
307 }
308 moveTo(pos: pointForPosition(pos, resetInactiveAxis), immediate);
309 }
310
311 void setSize(qreal size) {
312 if (view->orientation() == QQuickListView::Vertical)
313 item->setHeight(size);
314 else
315 item->setWidth(size);
316 }
317 bool contains(qreal x, qreal y) const override {
318 return (x >= itemX() && x < itemX() + itemWidth() &&
319 y >= itemY() && y < itemY() + itemHeight());
320 }
321
322 QQuickListView *view;
323
324private:
325 QPointF pointForPosition(qreal pos, bool resetInactiveAxis) const {
326 if (view->orientation() == QQuickListView::Vertical) {
327 if (view->verticalLayoutDirection() == QQuickItemView::BottomToTop) {
328 if (section())
329 pos += section()->height();
330 return QPointF(resetInactiveAxis ? 0 : itemX(), -itemHeight() - pos);
331 } else {
332 if (section())
333 pos += section()->height();
334 return QPointF(resetInactiveAxis ? 0 : itemX(), pos);
335 }
336 } else {
337 if (view->effectiveLayoutDirection() == Qt::RightToLeft) {
338 if (section())
339 pos += section()->width();
340 return QPointF(-itemWidth() - pos, resetInactiveAxis ? 0 : itemY());
341 } else {
342 if (section())
343 pos += section()->width();
344 return QPointF(pos, resetInactiveAxis ? 0 : itemY());
345 }
346 }
347 }
348};
349
350/*! \internal
351 \brief A helper class for iterating over a model that might change
352
353 When populating the ListView from a model under normal
354 circumstances, we would iterate over the range of model indices
355 correspondning to the visual range, and basically call
356 createItem(index++) in order to create each item.
357
358 This will also emit Component.onCompleted() for each item, which
359 might do some weird things... For instance, it might remove itself
360 from the model, and this might change model count and the indices
361 of the other subsequent entries in the model.
362
363 This class takes such changes to the model into consideration while
364 iterating, and will adjust the iterator index and keep track of
365 whether the iterator has reached the end of the range.
366
367 It keeps track of changes to the model by connecting to
368 QQmlInstanceModel::modelUpdated() from its constructor.
369 When destroyed, it will automatically disconnect. You can
370 explicitly disconnect earlier by calling \fn disconnect().
371*/
372class MutableModelIterator {
373public:
374 MutableModelIterator(QQmlInstanceModel *model, int iBegin, int iEnd)
375 : removedAtIndex(false)
376 , backwards(iEnd < iBegin)
377 {
378 conn = QObject::connect(sender: model, signal: &QQmlInstanceModel::modelUpdated,
379 slot: [&] (const QQmlChangeSet &changeSet, bool /*reset*/)
380 {
381 for (const QQmlChangeSet::Change &rem : changeSet.removes()) {
382 idxEnd -= rem.count;
383 if (rem.start() <= index) {
384 index -= rem.count;
385 if (index < rem.start() + rem.count)
386 removedAtIndex = true; // model index was removed
387 }
388 }
389 for (const QQmlChangeSet::Change &ins : changeSet.inserts()) {
390 idxEnd += ins.count;
391 if (ins.start() <= index)
392 index += ins.count;
393 }
394 }
395 );
396 index = iBegin;
397 idxEnd = iEnd;
398 }
399
400 bool hasNext() const {
401 return backwards ? index > idxEnd : index < idxEnd;
402 }
403
404 void next() { index += (backwards ? -1 : +1); }
405
406 ~MutableModelIterator()
407 {
408 disconnect();
409 }
410
411 void disconnect()
412 {
413 if (conn) {
414 QObject::disconnect(conn);
415 conn = QMetaObject::Connection(); // set to nullptr
416 }
417 }
418 int index = 0;
419 int idxEnd;
420 unsigned removedAtIndex : 1;
421 unsigned backwards : 1;
422private:
423 QMetaObject::Connection conn;
424};
425
426
427//----------------------------------------------------------------------------
428
429bool QQuickListViewPrivate::isContentFlowReversed() const
430{
431 return isRightToLeft() || isBottomToTop();
432}
433
434Qt::Orientation QQuickListViewPrivate::layoutOrientation() const
435{
436 return static_cast<Qt::Orientation>(orient);
437}
438
439bool QQuickListViewPrivate::isRightToLeft() const
440{
441 Q_Q(const QQuickListView);
442 return orient == QQuickListView::Horizontal && q->effectiveLayoutDirection() == Qt::RightToLeft;
443}
444
445bool QQuickListViewPrivate::isBottomToTop() const
446{
447 return orient == QQuickListView::Vertical && verticalLayoutDirection == QQuickItemView::BottomToTop;
448}
449
450// Returns the item before modelIndex, if created.
451// May return an item marked for removal.
452FxViewItem *QQuickListViewPrivate::itemBefore(int modelIndex) const
453{
454 if (modelIndex < visibleIndex)
455 return nullptr;
456 int idx = 1;
457 int lastIndex = -1;
458 while (idx < visibleItems.size()) {
459 FxViewItem *item = visibleItems.at(i: idx);
460 if (item->index != -1)
461 lastIndex = item->index;
462 if (item->index == modelIndex)
463 return visibleItems.at(i: idx-1);
464 ++idx;
465 }
466 if (lastIndex == modelIndex-1)
467 return visibleItems.constLast();
468 return nullptr;
469}
470
471void QQuickListViewPrivate::setPosition(qreal pos)
472{
473 Q_Q(QQuickListView);
474 if (orient == QQuickListView::Vertical) {
475 if (isBottomToTop())
476 q->QQuickFlickable::setContentY(-pos-size());
477 else
478 q->QQuickFlickable::setContentY(pos);
479 } else {
480 if (isRightToLeft())
481 q->QQuickFlickable::setContentX(-pos-size());
482 else
483 q->QQuickFlickable::setContentX(pos);
484 }
485}
486
487qreal QQuickListViewPrivate::originPosition() const
488{
489 qreal pos = 0;
490 if (!visibleItems.isEmpty()) {
491 pos = (*visibleItems.constBegin())->position();
492 if (visibleIndex > 0)
493 pos -= visibleIndex * (averageSize + spacing);
494 }
495 return pos;
496}
497
498qreal QQuickListViewPrivate::lastPosition() const
499{
500 qreal pos = 0;
501 if (!visibleItems.isEmpty()) {
502 int invisibleCount = INT_MIN;
503 int delayRemovedCount = 0;
504 for (int i = visibleItems.size()-1; i >= 0; --i) {
505 FxViewItem *item = visibleItems.at(i);
506 if (item->index != -1) {
507 // Find the invisible count after the last visible item with known index
508 invisibleCount = model->count() - (item->index + 1 + delayRemovedCount);
509 break;
510 } else if (item->attached->delayRemove()) {
511 ++delayRemovedCount;
512 }
513 }
514 if (invisibleCount == INT_MIN) {
515 // All visible items are in delayRemove state
516 invisibleCount = model->count();
517 }
518 pos = (*(visibleItems.constEnd() - 1))->endPosition();
519 if (invisibleCount > 0)
520 pos += invisibleCount * (averageSize + spacing);
521 } else if (model && model->count()) {
522 pos = (model->count() * averageSize + (model->count()-1) * spacing);
523 }
524 return pos;
525}
526
527qreal QQuickListViewPrivate::positionAt(int modelIndex) const
528{
529 if (FxViewItem *item = visibleItem(modelIndex)) {
530 return item->position();
531 }
532 if (!visibleItems.isEmpty()) {
533 if (modelIndex < visibleIndex) {
534 int count = visibleIndex - modelIndex;
535 qreal cs = 0;
536 if (modelIndex == currentIndex && currentItem) {
537 cs = currentItem->size() + spacing;
538 --count;
539 }
540 return (*visibleItems.constBegin())->position() - count * (averageSize + spacing) - cs;
541 } else {
542 int count = modelIndex - findLastVisibleIndex(defaultValue: visibleIndex) - 1;
543 return (*(visibleItems.constEnd() - 1))->endPosition() + spacing + count * (averageSize + spacing);
544 }
545 }
546 return 0;
547}
548
549qreal QQuickListViewPrivate::endPositionAt(int modelIndex) const
550{
551 if (FxViewItem *item = visibleItem(modelIndex))
552 return item->endPosition();
553 if (!visibleItems.isEmpty()) {
554 if (modelIndex < visibleIndex) {
555 int count = visibleIndex - modelIndex;
556 return (*visibleItems.constBegin())->position() - (count - 1) * (averageSize + spacing) - spacing;
557 } else {
558 int count = modelIndex - findLastVisibleIndex(defaultValue: visibleIndex) - 1;
559 return (*(visibleItems.constEnd() - 1))->endPosition() + count * (averageSize + spacing);
560 }
561 }
562 return 0;
563}
564
565QString QQuickListViewPrivate::sectionAt(int modelIndex)
566{
567 if (FxViewItem *item = visibleItem(modelIndex))
568 return item->attached->section();
569
570 QString section;
571 if (sectionCriteria && modelIndex >= 0 && modelIndex < itemCount) {
572 QString propValue = model->stringValue(index: modelIndex, role: sectionCriteria->property());
573 section = sectionCriteria->sectionString(value: propValue);
574 }
575
576 return section;
577}
578
579qreal QQuickListViewPrivate::snapPosAt(qreal pos)
580{
581 if (FxListItemSG *snapItem = static_cast<FxListItemSG*>(snapItemAt(pos)))
582 return snapItem->itemPosition();
583 if (visibleItems.size()) {
584 qreal firstPos = (*visibleItems.constBegin())->position();
585 qreal endPos = (*(visibleItems.constEnd() - 1))->position();
586 if (pos < firstPos) {
587 return firstPos - qRound(d: (firstPos - pos) / averageSize) * averageSize;
588 } else if (pos > endPos)
589 return endPos + qRound(d: (pos - endPos) / averageSize) * averageSize;
590 }
591 return qRound(d: (pos - originPosition()) / averageSize) * averageSize + originPosition();
592}
593
594FxViewItem *QQuickListViewPrivate::snapItemAt(qreal pos)
595{
596 const qreal velocity = orient == QQuickListView::Vertical ? vData.velocity : hData.velocity;
597 FxViewItem *snapItem = nullptr;
598 FxViewItem *prevItem = nullptr;
599 qreal prevItemSize = 0;
600 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
601 if (item->index == -1)
602 continue;
603
604 const FxListItemSG *listItem = static_cast<FxListItemSG *>(item);
605 qreal itemTop = listItem->position();
606 qreal itemSize = listItem->size();
607 if (highlight && itemTop >= pos && listItem->endPosition() <= pos + highlight->size())
608 return item;
609
610 if (listItem->section() && velocity > 0) {
611 if (itemTop + listItem->sectionSize() / 2 >= pos && itemTop - prevItemSize / 2 < pos)
612 snapItem = prevItem;
613 itemTop = listItem->itemPosition();
614 itemSize = listItem->itemSize();
615 }
616
617 // Middle of item and spacing (i.e. the middle of the distance between this item and the next
618 qreal halfwayToNextItem = itemTop + (itemSize+spacing) / 2;
619 qreal halfwayToPrevItem = itemTop - (prevItemSize+spacing) / 2;
620 if (halfwayToNextItem >= pos && halfwayToPrevItem < pos)
621 snapItem = item;
622
623 prevItemSize = listItem->itemSize();
624 prevItem = item;
625 }
626 return snapItem;
627}
628
629void QQuickListViewPrivate::changedVisibleIndex(int newIndex)
630{
631 visiblePos = positionAt(modelIndex: newIndex);
632 visibleIndex = newIndex;
633}
634
635void QQuickListViewPrivate::init()
636{
637 QQuickItemViewPrivate::init();
638 ::memset(s: sectionCache, c: 0, n: sizeof(QQuickItem*) * sectionCacheSize);
639}
640
641void QQuickListViewPrivate::clear(bool onDestruction)
642{
643 for (int i = 0; i < sectionCacheSize; ++i) {
644 delete sectionCache[i];
645 sectionCache[i] = nullptr;
646 }
647 visiblePos = 0;
648 releaseSectionItem(item: currentSectionItem);
649 currentSectionItem = nullptr;
650 releaseSectionItem(item: nextSectionItem);
651 nextSectionItem = nullptr;
652 lastVisibleSection = QString();
653 QQuickItemViewPrivate::clear(onDestruction);
654}
655
656FxViewItem *QQuickListViewPrivate::newViewItem(int modelIndex, QQuickItem *item)
657{
658 Q_Q(QQuickListView);
659
660 FxListItemSG *listItem = new FxListItemSG(item, q, false);
661 listItem->index = modelIndex;
662
663 // initialise attached properties
664 if (sectionCriteria) {
665 QString propValue = model->stringValue(index: modelIndex, role: sectionCriteria->property());
666 QString section = sectionCriteria->sectionString(value: propValue);
667 QString prevSection;
668 QString nextSection;
669 if (modelIndex > 0) {
670 if (FxViewItem *item = itemBefore(modelIndex))
671 prevSection = item->attached->section();
672 else
673 prevSection = sectionAt(modelIndex: modelIndex-1);
674 }
675 if (modelIndex < model->count()-1) {
676 nextSection = sectionAt(modelIndex: modelIndex+1);
677 }
678 listItem->attached->setSections(prev: prevSection, sect: section, next: nextSection);
679 }
680
681 return listItem;
682}
683
684void QQuickListViewPrivate::initializeViewItem(FxViewItem *item)
685{
686 QQuickItemViewPrivate::initializeViewItem(item);
687
688 // need to track current items that are animating
689 item->trackGeometry(track: true);
690
691 if (sectionCriteria && sectionCriteria->delegate()) {
692 if (QString::compare(s1: item->attached->m_prevSection, s2: item->attached->m_section, cs: Qt::CaseInsensitive))
693 updateInlineSection(static_cast<FxListItemSG*>(item));
694 }
695}
696
697bool QQuickListViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag)
698{
699 if (!item || !model)
700 return QQuickItemViewPrivate::releaseItem(item, reusableFlag);
701
702 QPointer<QQuickItem> it = item->item;
703 QQuickListViewAttached *att = static_cast<QQuickListViewAttached*>(item->attached);
704
705 bool released = QQuickItemViewPrivate::releaseItem(item, reusableFlag);
706 if (released && it && att && att->m_sectionItem) {
707 QQuickItemPrivate::get(item: att->m_sectionItem)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
708
709 // We hold no more references to this item
710 int i = 0;
711 do {
712 if (!sectionCache[i]) {
713 sectionCache[i] = att->m_sectionItem;
714 sectionCache[i]->setVisible(false);
715 att->m_sectionItem = nullptr;
716 break;
717 }
718 ++i;
719 } while (i < sectionCacheSize);
720 delete att->m_sectionItem;
721 att->m_sectionItem = nullptr;
722 }
723
724 return released;
725}
726
727bool QQuickListViewPrivate::addVisibleItems(qreal fillFrom, qreal fillTo, qreal bufferFrom, qreal bufferTo, bool doBuffer)
728{
729 qreal itemEnd = visiblePos;
730 if (visibleItems.size()) {
731 visiblePos = (*visibleItems.constBegin())->position();
732 itemEnd = (*(visibleItems.constEnd() - 1))->endPosition() + spacing;
733 }
734
735 int modelIndex = findLastVisibleIndex();
736 bool haveValidItems = modelIndex >= 0;
737 modelIndex = modelIndex < 0 ? visibleIndex : modelIndex + 1;
738
739 if (haveValidItems && (bufferFrom > itemEnd+averageSize+spacing
740 || bufferTo < visiblePos - averageSize - spacing)) {
741 // We've jumped more than a page. Estimate which items are now
742 // visible and fill from there.
743 int count = (fillFrom - itemEnd) / (averageSize + spacing);
744 int newModelIdx = qBound(min: 0, val: modelIndex + count, max: model->count());
745 count = newModelIdx - modelIndex;
746 if (count) {
747 releaseVisibleItems(reusableFlag);
748 modelIndex = newModelIdx;
749 visibleIndex = modelIndex;
750 visiblePos = itemEnd + count * (averageSize + spacing);
751 itemEnd = visiblePos;
752 }
753 }
754
755 QQmlIncubator::IncubationMode incubationMode = doBuffer ? QQmlIncubator::Asynchronous : QQmlIncubator::AsynchronousIfNested;
756
757 bool changed = false;
758 FxListItemSG *item = nullptr;
759 qreal pos = itemEnd;
760 while (modelIndex < model->count() && pos <= fillTo) {
761 if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex, incubationMode))))
762 break;
763 qCDebug(lcItemViewDelegateLifecycle) << "refill: append item" << modelIndex << "pos" << pos << "buffer" << doBuffer << "item" << (QObject *)(item->item);
764#if QT_CONFIG(quick_viewtransitions)
765 if (!transitioner || !transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true)) // pos will be set by layoutVisibleItems()
766#endif
767 item->setPosition(pos, immediate: true);
768 if (item->item)
769 QQuickItemPrivate::get(item: item->item)->setCulled(doBuffer);
770 pos += item->size() + spacing;
771 visibleItems.append(t: item);
772 ++modelIndex;
773 changed = true;
774 }
775
776 if (doBuffer && requestedIndex != -1) // already waiting for an item
777 return changed;
778
779 while (visibleIndex > 0 && visibleIndex <= model->count() && visiblePos > fillFrom) {
780 if (!(item = static_cast<FxListItemSG*>(createItem(modelIndex: visibleIndex-1, incubationMode))))
781 break;
782 qCDebug(lcItemViewDelegateLifecycle) << "refill: prepend item" << visibleIndex-1 << "current top pos" << visiblePos << "buffer" << doBuffer << "item" << (QObject *)(item->item);
783 --visibleIndex;
784 visiblePos -= item->size() + spacing;
785#if QT_CONFIG(quick_viewtransitions)
786 if (!transitioner || !transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true)) // pos will be set by layoutVisibleItems()
787#endif
788 item->setPosition(pos: visiblePos, immediate: true);
789 if (item->item)
790 QQuickItemPrivate::get(item: item->item)->setCulled(doBuffer);
791 visibleItems.prepend(t: item);
792 changed = true;
793 }
794
795 return changed;
796}
797
798void QQuickListViewPrivate::removeItem(FxViewItem *item)
799{
800#if QT_CONFIG(quick_viewtransitions)
801 if (item->transitionScheduledOrRunning()) {
802 qCDebug(lcItemViewDelegateLifecycle) << "\tnot releasing animating item" << item->index << (QObject *)(item->item);
803 item->releaseAfterTransition = true;
804 releasePendingTransition.append(t: item);
805 } else
806#endif
807 {
808 qCDebug(lcItemViewDelegateLifecycle) << "\treleasing stationary item" << item->index << (QObject *)(item->item);
809 releaseItem(item, reusableFlag);
810 }
811}
812
813bool QQuickListViewPrivate::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
814{
815 FxViewItem *item = nullptr;
816 bool changed = false;
817
818 // Remove items from the start of the view.
819 // Zero-sized items shouldn't be removed unless a non-zero-sized item is also being
820 // removed, otherwise a zero-sized item is infinitely added and removed over and
821 // over by refill().
822 int index = 0;
823 while (visibleItems.size() > 1 && index < visibleItems.size()
824 && (item = visibleItems.at(i: index)) && item->endPosition() < bufferFrom) {
825 if (item->attached->delayRemove())
826 break;
827
828 if (item->size() > 0) {
829 qCDebug(lcItemViewDelegateLifecycle) << "refill: remove first" << visibleIndex << "top end pos" << item->endPosition();
830 // remove this item and all zero-sized items before it
831 while (item) {
832 if (item->index != -1)
833 visibleIndex++;
834 visibleItems.removeAt(i: index);
835 removeItem(item);
836 if (index == 0)
837 break;
838 item = visibleItems.at(i: --index);
839 }
840 changed = true;
841 } else {
842 index++;
843 }
844 }
845
846 while (visibleItems.size() > 1 && (item = visibleItems.constLast()) && item->position() > bufferTo) {
847 if (item->attached->delayRemove())
848 break;
849 qCDebug(lcItemViewDelegateLifecycle) << "refill: remove last" << visibleIndex+visibleItems.size()-1 << item->position() << (QObject *)(item->item);
850 visibleItems.removeLast();
851 removeItem(item);
852 changed = true;
853 }
854
855 return changed;
856}
857
858void QQuickListViewPrivate::visibleItemsChanged()
859{
860 if (visibleItems.size())
861 visiblePos = (*visibleItems.constBegin())->position();
862 updateAverage();
863 if (currentIndex >= 0 && currentItem && !visibleItem(modelIndex: currentIndex)) {
864 static_cast<FxListItemSG*>(currentItem)->setPosition(pos: positionAt(modelIndex: currentIndex));
865 updateHighlight();
866 }
867 if (sectionCriteria)
868 updateCurrentSection();
869 updateUnrequestedPositions();
870}
871
872void QQuickListViewPrivate::layoutVisibleItems(int fromModelIndex)
873{
874 if (!visibleItems.isEmpty()) {
875 const qreal from = isContentFlowReversed() ? -position()-displayMarginBeginning-size() : position()-displayMarginBeginning;
876 const qreal to = isContentFlowReversed() ? -position()+displayMarginEnd : position()+size()+displayMarginEnd;
877
878 FxListItemSG *firstItem = static_cast<FxListItemSG *>(visibleItems.constFirst());
879 bool fixedCurrent = currentItem && firstItem->item == currentItem->item;
880
881#if QT_CONFIG(quick_viewtransitions)
882 /* Set position of first item in list view when populate transition is configured, as it doesn't set
883 while adding visible item (addVisibleItem()) to the view */
884 if (transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true))
885 resetFirstItemPosition(pos: isContentFlowReversed() ? -firstItem->position()-firstItem->size() : firstItem->position());
886#endif
887
888 firstVisibleItemPosition = firstItem->position();
889 qreal sum = firstItem->size();
890 qreal pos = firstItem->position() + firstItem->size() + spacing;
891 firstItem->setVisible(firstItem->endPosition() >= from && firstItem->position() <= to);
892
893 // setPosition will affect the position of the item, and its section, if it has one.
894 // This will prevent them from potentially overlapping.
895 if (firstItem->section())
896 firstItem->setPosition(pos: firstItem->position());
897
898 for (int i=1; i < visibleItems.size(); ++i) {
899 FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.at(i));
900 if (item->index >= fromModelIndex) {
901 item->setPosition(pos);
902 item->setVisible(item->endPosition() >= from && item->position() <= to);
903 }
904 pos += item->size() + spacing;
905 sum += item->size();
906 fixedCurrent = fixedCurrent || (currentItem && item->item == currentItem->item);
907 }
908 averageSize = qRound(d: sum / visibleItems.size());
909
910 // move current item if it is not a visible item.
911 if (currentIndex >= 0 && currentItem && !fixedCurrent)
912 static_cast<FxListItemSG*>(currentItem)->setPosition(pos: positionAt(modelIndex: currentIndex));
913
914 updateCurrentSection();
915 updateStickySections();
916 }
917}
918
919void QQuickListViewPrivate::repositionItemAt(FxViewItem *item, int index, qreal sizeBuffer)
920{
921 static_cast<FxListItemSG *>(item)->setPosition(pos: positionAt(modelIndex: index) + sizeBuffer);
922}
923
924void QQuickListViewPrivate::repositionPackageItemAt(QQuickItem *item, int index)
925{
926 Q_Q(QQuickListView);
927 qreal pos = position();
928 if (orient == QQuickListView::Vertical) {
929 if (item->y() + item->height() > pos && item->y() < pos + q->height()) {
930 if (isBottomToTop())
931 item->setY(-positionAt(modelIndex: index)-item->height());
932 else
933 item->setY(positionAt(modelIndex: index));
934 }
935 } else {
936 if (item->x() + item->width() > pos && item->x() < pos + q->width()) {
937 if (isRightToLeft())
938 item->setX(-positionAt(modelIndex: index)-item->width());
939 else
940 item->setX(positionAt(modelIndex: index));
941 }
942 }
943}
944
945void QQuickListViewPrivate::resetFirstItemPosition(qreal pos)
946{
947 FxListItemSG *item = static_cast<FxListItemSG*>(visibleItems.constFirst());
948 item->setPosition(pos);
949}
950
951void QQuickListViewPrivate::adjustFirstItem(qreal forwards, qreal backwards, int)
952{
953 if (!visibleItems.size())
954 return;
955 qreal diff = forwards - backwards;
956 static_cast<FxListItemSG*>(visibleItems.constFirst())->setPosition(pos: visibleItems.constFirst()->position() + diff);
957}
958
959void QQuickListViewPrivate::updateSizeChangesBeforeVisiblePos(FxViewItem *item, ChangeResult *removeResult)
960{
961 if (item != visibleItems.constFirst())
962 QQuickItemViewPrivate::updateSizeChangesBeforeVisiblePos(item, removeResult);
963}
964
965void QQuickListViewPrivate::createHighlight(bool onDestruction)
966{
967 bool changed = false;
968 if (highlight) {
969 if (trackedItem == highlight.get())
970 trackedItem = nullptr;
971 highlight.reset();
972
973 highlightPosAnimator.reset();
974 highlightWidthAnimator.reset();
975 highlightHeightAnimator.reset();
976 highlightPosAnimator = nullptr;
977 highlightWidthAnimator = nullptr;
978 highlightHeightAnimator = nullptr;
979
980 changed = true;
981 }
982
983 if (onDestruction)
984 return;
985
986 Q_Q(QQuickListView);
987 if (currentItem) {
988 QQuickItem *item = createHighlightItem();
989 if (item) {
990 std::unique_ptr<FxListItemSG> newHighlight
991 = std::make_unique<FxListItemSG>(args&: item, args: q, args: true);
992 newHighlight->trackGeometry(track: true);
993
994 if (autoHighlight) {
995 newHighlight->setSize(static_cast<FxListItemSG*>(currentItem)->itemSize());
996 newHighlight->setPosition(pos: static_cast<FxListItemSG*>(currentItem)->itemPosition());
997 }
998 const QLatin1String posProp(orient == QQuickListView::Vertical ? "y" : "x");
999 highlightPosAnimator = std::make_unique<QSmoothedAnimation>();
1000 highlightPosAnimator->target = QQmlProperty(item, posProp);
1001 highlightPosAnimator->velocity = highlightMoveVelocity;
1002 highlightPosAnimator->userDuration = highlightMoveDuration;
1003
1004 highlightWidthAnimator = std::make_unique<QSmoothedAnimation>();
1005 highlightWidthAnimator->velocity = highlightResizeVelocity;
1006 highlightWidthAnimator->userDuration = highlightResizeDuration;
1007 highlightWidthAnimator->target = QQmlProperty(item, QStringLiteral("width"));
1008
1009 highlightHeightAnimator = std::make_unique<QSmoothedAnimation>();
1010 highlightHeightAnimator->velocity = highlightResizeVelocity;
1011 highlightHeightAnimator->userDuration = highlightResizeDuration;
1012 highlightHeightAnimator->target = QQmlProperty(item, QStringLiteral("height"));
1013
1014 highlight = std::move(newHighlight);
1015 changed = true;
1016 }
1017 }
1018 if (changed)
1019 emit q->highlightItemChanged();
1020}
1021
1022void QQuickListViewPrivate::updateHighlight()
1023{
1024 applyPendingChanges();
1025
1026 if ((!currentItem && highlight) || (currentItem && !highlight))
1027 createHighlight();
1028 bool strictHighlight = haveHighlightRange && highlightRange == QQuickListView::StrictlyEnforceRange;
1029 if (currentItem && autoHighlight && highlight && (!strictHighlight || !pressed)) {
1030 // auto-update highlight
1031 FxListItemSG *listItem = static_cast<FxListItemSG*>(currentItem);
1032 highlightPosAnimator->to = isContentFlowReversed()
1033 ? -listItem->itemPosition()-listItem->itemSize()
1034 : listItem->itemPosition();
1035 highlightWidthAnimator->to = listItem->item->width();
1036 highlightHeightAnimator->to = listItem->item->height();
1037 if (orient == QQuickListView::Vertical) {
1038 if (highlight->item->width() == 0)
1039 highlight->item->setWidth(currentItem->item->width());
1040 } else {
1041 if (highlight->item->height() == 0)
1042 highlight->item->setHeight(currentItem->item->height());
1043 }
1044
1045 highlightPosAnimator->restart();
1046 highlightWidthAnimator->restart();
1047 highlightHeightAnimator->restart();
1048 }
1049 updateTrackedItem();
1050}
1051
1052void QQuickListViewPrivate::resetHighlightPosition()
1053{
1054 if (highlight && currentItem) {
1055 static_cast<FxListItemSG*>(highlight.get())->setPosition(
1056 pos: static_cast<FxListItemSG*>(currentItem)->itemPosition());
1057 }
1058}
1059
1060bool QQuickListViewPrivate::movingFromHighlight()
1061{
1062 if (!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange)
1063 return false;
1064
1065 return (highlightPosAnimator && highlightPosAnimator->isRunning()) ||
1066 (highlightHeightAnimator && highlightHeightAnimator->isRunning()) ||
1067 (highlightWidthAnimator && highlightWidthAnimator->isRunning());
1068}
1069
1070
1071QQuickItem * QQuickListViewPrivate::getSectionItem(const QString &section)
1072{
1073 Q_Q(QQuickListView);
1074 QQuickItem *sectionItem = nullptr;
1075 int i = sectionCacheSize-1;
1076 while (i >= 0 && !sectionCache[i])
1077 --i;
1078 if (i >= 0) {
1079 sectionItem = sectionCache[i];
1080 sectionCache[i] = nullptr;
1081 sectionItem->setVisible(true);
1082 QQmlContext *context = QQmlEngine::contextForObject(sectionItem)->parentContext();
1083 setSectionHelper(context, sectionItem, section);
1084 } else {
1085 QQmlComponent* delegate = sectionCriteria->delegate();
1086 const bool reuseExistingContext = delegate->isBound();
1087 auto delegatePriv = QQmlComponentPrivate::get(c: delegate);
1088 QQmlPropertyCache::ConstPtr rootPropertyCache;
1089
1090 QQmlContext *creationContext = sectionCriteria->delegate()->creationContext();
1091 auto baseContext = creationContext ? creationContext : qmlContext(q);
1092 // if we need to insert a context property, we need a separate context
1093 QQmlContext *context = reuseExistingContext ? baseContext : new QQmlContext(baseContext);
1094 QObject *nobj = delegate->beginCreate(context);
1095 if (nobj) {
1096 if (delegatePriv->hadTopLevelRequiredProperties()) {
1097 delegate->setInitialProperties(component: nobj, properties: {{QLatin1String("section"), section}});
1098 } else if (!reuseExistingContext) {
1099 context->setContextProperty(QLatin1String("section"), section);
1100 }
1101 if (!reuseExistingContext)
1102 QQml_setParent_noEvent(object: context, parent: nobj);
1103 sectionItem = qobject_cast<QQuickItem *>(o: nobj);
1104 if (!sectionItem) {
1105 delete nobj;
1106 } else {
1107 if (qFuzzyIsNull(d: sectionItem->z()))
1108 sectionItem->setZ(2);
1109 QQml_setParent_noEvent(object: sectionItem, parent: contentItem);
1110 sectionItem->setParentItem(contentItem);
1111 }
1112 // sections are not controlled by FxListItemSG, so apply attached properties here
1113 auto *attached = static_cast<QQuickListViewAttached*>(qmlAttachedPropertiesObject<QQuickListView>(obj: sectionItem));
1114 attached->setView(q);
1115 } else if (!reuseExistingContext) {
1116 delete context;
1117 }
1118 sectionCriteria->delegate()->completeCreate();
1119 }
1120
1121 if (sectionItem)
1122 QQuickItemPrivate::get(item: sectionItem)->addItemChangeListener(listener: this, types: QQuickItemPrivate::Geometry);
1123
1124 return sectionItem;
1125}
1126
1127void QQuickListViewPrivate::releaseSectionItem(QQuickItem *item)
1128{
1129 if (!item)
1130 return;
1131 int i = 0;
1132
1133 QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: QQuickItemPrivate::Geometry);
1134
1135 do {
1136 if (!sectionCache[i]) {
1137 sectionCache[i] = item;
1138 sectionCache[i]->setVisible(false);
1139 return;
1140 }
1141 ++i;
1142 } while (i < sectionCacheSize);
1143 delete item;
1144}
1145
1146
1147void QQuickListViewPrivate::releaseSectionItems()
1148{
1149 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
1150 FxListItemSG *listItem = static_cast<FxListItemSG *>(item);
1151 if (listItem->section()) {
1152 qreal pos = listItem->position();
1153 releaseSectionItem(item: listItem->section());
1154 listItem->setSection(nullptr);
1155 listItem->setPosition(pos);
1156 }
1157 }
1158 for (int i = 0; i < sectionCacheSize; ++i) {
1159 delete sectionCache[i];
1160 sectionCache[i] = nullptr;
1161 }
1162}
1163
1164void QQuickListViewPrivate::updateInlineSection(FxListItemSG *listItem)
1165{
1166 if (!sectionCriteria || !sectionCriteria->delegate())
1167 return;
1168 if (QString::compare(s1: listItem->attached->m_prevSection, s2: listItem->attached->m_section, cs: Qt::CaseInsensitive)
1169 && (sectionCriteria->labelPositioning() & QQuickViewSection::InlineLabels
1170 || (listItem->index == 0 && sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart))) {
1171 if (!listItem->section()) {
1172 qreal pos = listItem->position();
1173 listItem->setSection(getSectionItem(section: listItem->attached->m_section));
1174 listItem->setPosition(pos);
1175 } else {
1176 QQmlContext *context = QQmlEngine::contextForObject(listItem->section())->parentContext();
1177 setSectionHelper(context, sectionItem: listItem->section(), section: listItem->attached->m_section);
1178 }
1179 } else if (listItem->section()) {
1180 qreal pos = listItem->position();
1181 releaseSectionItem(item: listItem->section());
1182 listItem->setSection(nullptr);
1183 listItem->setPosition(pos);
1184 }
1185}
1186
1187void QQuickListViewPrivate::updateStickySections()
1188{
1189 if (!sectionCriteria || !sectionCriteria->delegate()
1190 || (!sectionCriteria->labelPositioning() && !currentSectionItem && !nextSectionItem))
1191 return;
1192
1193 bool isFlowReversed = isContentFlowReversed();
1194 qreal viewPos = isFlowReversed ? -position()-size() : position();
1195 qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos;
1196 qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size();
1197
1198 QQuickItem *sectionItem = nullptr;
1199 QQuickItem *lastSectionItem = nullptr;
1200 int index = 0;
1201 while (index < visibleItems.size()) {
1202 if (QQuickItem *section = static_cast<FxListItemSG *>(visibleItems.at(i: index))->section()) {
1203 // Find the current section header and last visible section header
1204 // and hide them if they will overlap a static section header.
1205 qreal sectionPos = orient == QQuickListView::Vertical ? section->y() : section->x();
1206 qreal sectionSize = orient == QQuickListView::Vertical ? section->height() : section->width();
1207 bool visTop = true;
1208 if (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart)
1209 visTop = isFlowReversed ? -sectionPos-sectionSize >= startPos : sectionPos >= startPos;
1210 bool visBot = true;
1211 if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd)
1212 visBot = isFlowReversed ? -sectionPos <= endPos : sectionPos + sectionSize < endPos;
1213 section->setVisible(visBot && visTop);
1214 if (visTop && !sectionItem)
1215 sectionItem = section;
1216 if (isFlowReversed) {
1217 if (-sectionPos <= endPos)
1218 lastSectionItem = section;
1219 } else {
1220 if (sectionPos + sectionSize < endPos)
1221 lastSectionItem = section;
1222 }
1223 }
1224 ++index;
1225 }
1226
1227 // Current section header
1228 if (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart && isValid() && visibleItems.size()) {
1229 if (!currentSectionItem) {
1230 currentSectionItem = getSectionItem(section: currentSection);
1231 } else if (QString::compare(s1: currentStickySection, s2: currentSection, cs: Qt::CaseInsensitive)) {
1232 QQmlContext *context = QQmlEngine::contextForObject(currentSectionItem)->parentContext();
1233 setSectionHelper(context, sectionItem: currentSectionItem, section: currentSection);
1234 }
1235 currentStickySection = currentSection;
1236 if (!currentSectionItem)
1237 return;
1238
1239 qreal sectionSize = orient == QQuickListView::Vertical ? currentSectionItem->height() : currentSectionItem->width();
1240 bool atBeginning = orient == QQuickListView::Vertical ? (isBottomToTop() ? vData.atEnd : vData.atBeginning) : (isRightToLeft() ? hData.atEnd : hData.atBeginning);
1241
1242 currentSectionItem->setVisible(!atBeginning && (!header || hasStickyHeader() || header->endPosition() < viewPos));
1243 qreal pos = isFlowReversed ? position() + size() - sectionSize : startPos;
1244 if (header)
1245 pos = isFlowReversed ? qMin(a: -header->endPosition() - sectionSize, b: pos) : qMax(a: header->endPosition(), b: pos);
1246 if (sectionItem) {
1247 qreal sectionPos = orient == QQuickListView::Vertical ? sectionItem->y() : sectionItem->x();
1248 pos = isFlowReversed ? qMax(a: pos, b: sectionPos + sectionSize) : qMin(a: pos, b: sectionPos - sectionSize);
1249 }
1250 if (footer)
1251 pos = isFlowReversed ? qMax(a: -footer->position(), b: pos) : qMin(a: footer->position() - sectionSize, b: pos);
1252 if (orient == QQuickListView::Vertical)
1253 currentSectionItem->setY(pos);
1254 else
1255 currentSectionItem->setX(pos);
1256 } else if (currentSectionItem) {
1257 releaseSectionItem(item: currentSectionItem);
1258 currentSectionItem = nullptr;
1259 }
1260
1261 // Next section footer
1262 if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd && isValid() && visibleItems.size()) {
1263 if (!nextSectionItem) {
1264 nextSectionItem = getSectionItem(section: nextSection);
1265 } else if (QString::compare(s1: nextStickySection, s2: nextSection, cs: Qt::CaseInsensitive)) {
1266 QQmlContext *context = QQmlEngine::contextForObject(nextSectionItem)->parentContext();
1267 setSectionHelper(context, sectionItem: nextSectionItem, section: nextSection);
1268 }
1269 nextStickySection = nextSection;
1270 if (!nextSectionItem)
1271 return;
1272
1273 qreal sectionSize = orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
1274 nextSectionItem->setVisible(!nextSection.isEmpty());
1275 qreal pos = isFlowReversed ? position() : endPos - sectionSize;
1276 if (footer)
1277 pos = isFlowReversed ? qMax(a: -footer->position(), b: pos) : qMin(a: footer->position() - sectionSize, b: pos);
1278 if (lastSectionItem) {
1279 qreal sectionPos = orient == QQuickListView::Vertical ? lastSectionItem->y() : lastSectionItem->x();
1280 pos = isFlowReversed ? qMin(a: pos, b: sectionPos - sectionSize) : qMax(a: pos, b: sectionPos + sectionSize);
1281 }
1282 if (header)
1283 pos = isFlowReversed ? qMin(a: -header->endPosition() - sectionSize, b: pos) : qMax(a: header->endPosition(), b: pos);
1284 if (orient == QQuickListView::Vertical)
1285 nextSectionItem->setY(pos);
1286 else
1287 nextSectionItem->setX(pos);
1288 } else if (nextSectionItem) {
1289 releaseSectionItem(item: nextSectionItem);
1290 nextSectionItem = nullptr;
1291 }
1292}
1293
1294void QQuickListViewPrivate::updateSections()
1295{
1296 Q_Q(QQuickListView);
1297 if (!q->isComponentComplete())
1298 return;
1299
1300 QQuickItemViewPrivate::updateSections();
1301
1302 if (sectionCriteria && !visibleItems.isEmpty() && isValid()) {
1303 QString prevSection;
1304 if (visibleIndex > 0)
1305 prevSection = sectionAt(modelIndex: visibleIndex-1);
1306 QQuickListViewAttached *prevAtt = nullptr;
1307 int prevIdx = -1;
1308 int idx = -1;
1309 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
1310 QQuickListViewAttached *attached = static_cast<QQuickListViewAttached*>(item->attached);
1311 attached->setPrevSection(prevSection);
1312 if (item->index != -1) {
1313 QString propValue = model->stringValue(index: item->index, role: sectionCriteria->property());
1314 attached->setSection(sectionCriteria->sectionString(value: propValue));
1315 idx = item->index;
1316 }
1317 updateInlineSection(listItem: static_cast<FxListItemSG*>(item));
1318 if (prevAtt)
1319 prevAtt->setNextSection(sectionAt(modelIndex: prevIdx+1));
1320 prevSection = attached->section();
1321 prevAtt = attached;
1322 prevIdx = item->index;
1323 }
1324 if (prevAtt) {
1325 if (idx > 0 && idx < model->count()-1)
1326 prevAtt->setNextSection(sectionAt(modelIndex: idx+1));
1327 else
1328 prevAtt->setNextSection(QString());
1329 }
1330 }
1331
1332 lastVisibleSection = QString();
1333}
1334
1335void QQuickListViewPrivate::updateCurrentSection()
1336{
1337 Q_Q(QQuickListView);
1338 if (!sectionCriteria || visibleItems.isEmpty()) {
1339 if (!currentSection.isEmpty()) {
1340 currentSection.clear();
1341 emit q->currentSectionChanged();
1342 }
1343 return;
1344 }
1345 bool inlineSections = sectionCriteria->labelPositioning() & QQuickViewSection::InlineLabels;
1346 qreal viewPos = isContentFlowReversed() ? -position()-size() : position();
1347 qreal startPos = hasStickyHeader() ? header->endPosition() : viewPos;
1348 int index = 0;
1349 int modelIndex = visibleIndex;
1350 while (index < visibleItems.size()) {
1351 FxViewItem *item = visibleItems.at(i: index);
1352 if (item->endPosition() > startPos)
1353 break;
1354 if (item->index != -1)
1355 modelIndex = item->index;
1356 ++index;
1357 }
1358
1359 QString newSection = currentSection;
1360 if (index < visibleItems.size())
1361 newSection = visibleItems.at(i: index)->attached->section();
1362 else
1363 newSection = (*visibleItems.constBegin())->attached->section();
1364 if (newSection != currentSection) {
1365 currentSection = newSection;
1366 updateStickySections();
1367 emit q->currentSectionChanged();
1368 }
1369
1370 if (sectionCriteria->labelPositioning() & QQuickViewSection::NextLabelAtEnd) {
1371 // Don't want to scan for next section on every movement, so remember
1372 // the last section in the visible area and only scan for the next
1373 // section when that changes. Clearing lastVisibleSection will also
1374 // force searching.
1375 QString lastSection = currentSection;
1376 qreal endPos = hasStickyFooter() ? footer->position() : viewPos + size();
1377 if (nextSectionItem && !inlineSections)
1378 endPos -= orient == QQuickListView::Vertical ? nextSectionItem->height() : nextSectionItem->width();
1379 while (index < visibleItems.size()) {
1380 FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems.at(i: index));
1381 if (listItem->itemPosition() >= endPos)
1382 break;
1383 if (listItem->index != -1)
1384 modelIndex = listItem->index;
1385 lastSection = listItem->attached->section();
1386 ++index;
1387 }
1388
1389 if (lastVisibleSection != lastSection) {
1390 nextSection = QString();
1391 lastVisibleSection = lastSection;
1392 for (int i = modelIndex; i < itemCount; ++i) {
1393 QString section = sectionAt(modelIndex: i);
1394 if (section != lastSection) {
1395 nextSection = section;
1396 updateStickySections();
1397 break;
1398 }
1399 }
1400 }
1401 }
1402}
1403
1404void QQuickListViewPrivate::initializeCurrentItem()
1405{
1406 QQuickItemViewPrivate::initializeCurrentItem();
1407
1408 if (currentItem) {
1409 FxListItemSG *listItem = static_cast<FxListItemSG *>(currentItem);
1410
1411 // don't reposition the item if it is already in the visibleItems list
1412 FxViewItem *actualItem = visibleItem(modelIndex: currentIndex);
1413 if (!actualItem) {
1414 if (currentIndex == visibleIndex - 1 && visibleItems.size()) {
1415 // We can calculate exact postion in this case
1416 listItem->setPosition(pos: visibleItems.constFirst()->position() - currentItem->size() - spacing);
1417 } else {
1418 // Create current item now and position as best we can.
1419 // Its position will be corrected when it becomes visible.
1420 listItem->setPosition(pos: positionAt(modelIndex: currentIndex));
1421 }
1422 }
1423
1424 if (visibleItems.isEmpty())
1425 averageSize = listItem->size();
1426 }
1427}
1428
1429void QQuickListViewPrivate::updateAverage()
1430{
1431 if (!visibleItems.size())
1432 return;
1433 qreal sum = 0.0;
1434 for (FxViewItem *item : std::as_const(t&: visibleItems))
1435 sum += item->size();
1436 averageSize = qRound(d: sum / visibleItems.size());
1437}
1438
1439qreal QQuickListViewPrivate::headerSize() const
1440{
1441 return header ? header->size() : 0.0;
1442}
1443
1444qreal QQuickListViewPrivate::footerSize() const
1445{
1446 return footer ? footer->size() : 0.0;
1447}
1448
1449bool QQuickListViewPrivate::showHeaderForIndex(int index) const
1450{
1451 return index == 0;
1452}
1453
1454bool QQuickListViewPrivate::showFooterForIndex(int index) const
1455{
1456 return model && index == model->count()-1;
1457}
1458
1459void QQuickListViewPrivate::updateFooter()
1460{
1461 Q_Q(QQuickListView);
1462 bool created = false;
1463 if (!footer) {
1464 QQuickItem *item = createComponentItem(component: footerComponent, zValue: 1.0);
1465 if (!item)
1466 return;
1467 footer = new FxListItemSG(item, q, true);
1468 footer->trackGeometry(track: true);
1469 created = true;
1470 }
1471
1472 FxListItemSG *listItem = static_cast<FxListItemSG*>(footer);
1473 if (footerPositioning == QQuickListView::OverlayFooter) {
1474 listItem->setPosition(pos: isContentFlowReversed() ? -position() - footerSize() : position() + size() - footerSize(), immediate: false, resetInactiveAxis: false);
1475 } else if (visibleItems.size()) {
1476 if (footerPositioning == QQuickListView::PullBackFooter) {
1477 qreal viewPos = isContentFlowReversed() ? -position() : position() + size();
1478 // using qBound() would throw an assert here, because max < min is a valid case
1479 // here, if the list's delegates do not fill the whole view
1480 qreal clampedPos = qMax(a: originPosition() - footerSize() + size(), b: qMin(a: listItem->position(), b: lastPosition()));
1481 listItem->setPosition(pos: qBound(min: viewPos - footerSize(), val: clampedPos, max: viewPos), immediate: false, resetInactiveAxis: false);
1482 } else {
1483 qreal endPos = lastPosition();
1484 if (findLastVisibleIndex() == model->count()-1) {
1485 listItem->setPosition(pos: endPos, immediate: false, resetInactiveAxis: false);
1486 } else {
1487 qreal visiblePos = position() + q->height();
1488 if (endPos <= visiblePos || listItem->position() < endPos)
1489 listItem->setPosition(pos: endPos, immediate: false, resetInactiveAxis: false);
1490 }
1491 }
1492 } else {
1493 listItem->setPosition(pos: visiblePos, immediate: false, resetInactiveAxis: false);
1494 }
1495
1496 if (created)
1497 emit q->footerItemChanged();
1498}
1499
1500void QQuickListViewPrivate::fixupHeaderCompleted()
1501{
1502 headerNeedsSeparateFixup = false;
1503 QObjectPrivate::disconnect(sender: &timeline, signal: &QQuickTimeLine::updated, receiverPrivate: this, slot: &QQuickListViewPrivate::fixupHeader);
1504}
1505
1506void QQuickListViewPrivate::fixupHeader()
1507{
1508 FxListItemSG *listItem = static_cast<FxListItemSG*>(header);
1509 const bool fixingUp = (orient == QQuickListView::Vertical ? vData : hData).fixingUp;
1510 if (fixingUp && headerPositioning == QQuickListView::PullBackHeader && visibleItems.size()) {
1511 int fixupDura = timeline.duration();
1512 if (fixupDura < 0)
1513 fixupDura = fixupDuration/2;
1514 const int t = timeline.time();
1515
1516 const qreal progress = qreal(t)/fixupDura;
1517 const qreal ultimateHeaderPosition = desiredHeaderVisible ? desiredViewportPosition : desiredViewportPosition - headerSize();
1518 const qreal headerPosition = fixupHeaderPosition * (1 - progress) + ultimateHeaderPosition * progress;
1519 const qreal viewPos = isContentFlowReversed() ? -position() - size() : position();
1520 const qreal clampedPos = qBound(min: originPosition() - headerSize(), val: headerPosition, max: lastPosition() - size());
1521 listItem->setPosition(pos: qBound(min: viewPos - headerSize(), val: clampedPos, max: viewPos));
1522 }
1523}
1524
1525void QQuickListViewPrivate::updateHeader()
1526{
1527 Q_Q(QQuickListView);
1528 bool created = false;
1529 if (!header) {
1530 QQuickItem *item = createComponentItem(component: headerComponent, zValue: 1.0);
1531 if (!item)
1532 return;
1533 header = new FxListItemSG(item, q, true);
1534 header->trackGeometry(track: true);
1535 created = true;
1536 }
1537
1538 FxListItemSG *listItem = static_cast<FxListItemSG*>(header);
1539 if (headerPositioning == QQuickListView::OverlayHeader) {
1540 listItem->setPosition(pos: isContentFlowReversed() ? -position() - size() : position(), immediate: false, resetInactiveAxis: false);
1541 } else if (visibleItems.size()) {
1542 const bool fixingUp = (orient == QQuickListView::Vertical ? vData : hData).fixingUp;
1543 if (headerPositioning == QQuickListView::PullBackHeader) {
1544 qreal headerPosition = listItem->position();
1545 const qreal viewPos = isContentFlowReversed() ? -position() - size() : position();
1546 // Make sure the header is not shown if we absolutely do not have any plans to show it
1547 if (fixingUp && !headerNeedsSeparateFixup)
1548 headerPosition = viewPos - headerSize();
1549 // using qBound() would throw an assert here, because max < min is a valid case
1550 // here, if the list's delegates do not fill the whole view
1551 qreal clampedPos = qMax(a: originPosition() - headerSize(), b: qMin(a: headerPosition, b: lastPosition() - size()));
1552 listItem->setPosition(pos: qBound(min: viewPos - headerSize(), val: clampedPos, max: viewPos), immediate: false, resetInactiveAxis: false);
1553 } else {
1554 qreal startPos = originPosition();
1555 if (visibleIndex == 0) {
1556 listItem->setPosition(pos: startPos - headerSize(), immediate: false, resetInactiveAxis: false);
1557 } else {
1558 if (position() <= startPos || listItem->position() > startPos - headerSize())
1559 listItem->setPosition(pos: startPos - headerSize(), immediate: false, resetInactiveAxis: false);
1560 }
1561 }
1562 } else {
1563 listItem->setPosition(pos: -headerSize(), immediate: false, resetInactiveAxis: false);
1564 }
1565
1566 if (created)
1567 emit q->headerItemChanged();
1568}
1569
1570bool QQuickListViewPrivate::hasStickyHeader() const
1571{
1572 return header && headerPositioning != QQuickListView::InlineHeader;
1573}
1574
1575bool QQuickListViewPrivate::hasStickyFooter() const
1576{
1577 return footer && footerPositioning != QQuickListView::InlineFooter;
1578}
1579
1580void QQuickListViewPrivate::initializeComponentItem(QQuickItem *item) const
1581{
1582 QQuickListViewAttached *attached = static_cast<QQuickListViewAttached *>(
1583 qmlAttachedPropertiesObject<QQuickListView>(obj: item));
1584 if (attached) // can be null for default components (see createComponentItem)
1585 attached->setView(const_cast<QQuickListView*>(q_func()));
1586}
1587
1588void QQuickListViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change,
1589 const QRectF &oldGeometry)
1590{
1591 Q_Q(QQuickListView);
1592
1593 QQuickItemViewPrivate::itemGeometryChanged(item, change, oldGeometry);
1594 if (!q->isComponentComplete())
1595 return;
1596
1597 if (currentItem && currentItem->item == item) {
1598 const bool contentFlowReversed = isContentFlowReversed();
1599 const qreal pos = position();
1600 const qreal sz = size();
1601 const qreal from = contentFlowReversed ? -pos - displayMarginBeginning - sz : pos - displayMarginBeginning;
1602 const qreal to = contentFlowReversed ? -pos + displayMarginEnd : pos + sz + displayMarginEnd;
1603 QQuickItemPrivate::get(item: currentItem->item)->setCulled(currentItem->endPosition() < from || currentItem->position() > to);
1604 }
1605
1606 if (item != contentItem && (!highlight || item != highlight->item)) {
1607 if ((orient == QQuickListView::Vertical && change.heightChange())
1608 || (orient == QQuickListView::Horizontal && change.widthChange())) {
1609
1610 // if visibleItems.first() has resized, adjust its pos since it is used to
1611 // position all subsequent items
1612 if (visibleItems.size() && item == visibleItems.constFirst()->item) {
1613 FxListItemSG *listItem = static_cast<FxListItemSG*>(visibleItems.constFirst());
1614#if QT_CONFIG(quick_viewtransitions)
1615 if (listItem->transitionScheduledOrRunning())
1616 return;
1617#endif
1618 if (orient == QQuickListView::Vertical) {
1619 const qreal oldItemEndPosition = verticalLayoutDirection == QQuickItemView::BottomToTop ? -oldGeometry.y() : oldGeometry.y() + oldGeometry.height();
1620 const qreal heightDiff = item->height() - oldGeometry.height();
1621 if (verticalLayoutDirection == QQuickListView::TopToBottom && oldItemEndPosition < q->contentY())
1622 listItem->setPosition(pos: listItem->position() - heightDiff, immediate: true);
1623 else if (verticalLayoutDirection == QQuickListView::BottomToTop && oldItemEndPosition > q->contentY())
1624 listItem->setPosition(pos: listItem->position() + heightDiff, immediate: true);
1625 } else {
1626 const qreal oldItemEndPosition = q->effectiveLayoutDirection() == Qt::RightToLeft ? -oldGeometry.x() : oldGeometry.x() + oldGeometry.width();
1627 const qreal widthDiff = item->width() - oldGeometry.width();
1628 if (q->effectiveLayoutDirection() == Qt::LeftToRight && oldItemEndPosition < q->contentX())
1629 listItem->setPosition(pos: listItem->position() - widthDiff, immediate: true);
1630 else if (q->effectiveLayoutDirection() == Qt::RightToLeft && oldItemEndPosition > q->contentX())
1631 listItem->setPosition(pos: listItem->position() + widthDiff, immediate: true);
1632 }
1633 }
1634 forceLayoutPolish();
1635 }
1636 }
1637}
1638
1639void QQuickListViewPrivate::fixupPosition()
1640{
1641 if (orient == QQuickListView::Vertical)
1642 fixupY();
1643 else
1644 fixupX();
1645}
1646
1647void QQuickListViewPrivate::fixup(AxisData &data, qreal minExtent, qreal maxExtent)
1648{
1649 if (orient == QQuickListView::Horizontal && &data == &vData) {
1650 if (flickableDirection != QQuickFlickable::HorizontalFlick)
1651 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1652 return;
1653 } else if (orient == QQuickListView::Vertical && &data == &hData) {
1654 if (flickableDirection != QQuickFlickable::VerticalFlick)
1655 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1656 return;
1657 }
1658
1659 // update footer if all visible items have been removed
1660 if (visibleItems.size() == 0)
1661 updateFooter();
1662
1663 correctFlick = false;
1664 fixupMode = moveReason == Mouse ? fixupMode : Immediate;
1665 bool strictHighlightRange = haveHighlightRange && highlightRange == QQuickListView::StrictlyEnforceRange;
1666
1667 qreal viewPos = isContentFlowReversed() ? -position()-size() : position();
1668
1669 if (snapMode != QQuickListView::NoSnap && moveReason != QQuickListViewPrivate::SetIndex) {
1670 /*
1671 There are many ways items can "snap" (align) when a flick by mouse/touch is about to end.
1672 The following table describes how things are snapped for a TopToBottom ListView (the
1673 behavior of the other orientations can be derived from TopToBottom):
1674
1675 | header\\range | No highlight range | Has a highlight range |
1676 |------------------ | ----------------------------- | --------------------- |
1677 | No header | Snaps to ListView top | Snaps to preferredHighlightBegin position [1] |
1678 | InlineHeader | Snaps to ListView top | Snaps to preferredHighlightBegin position [1] |
1679 | OverlayHeader | Snaps to header | Snaps to neither [!] |
1680 | PullbackHeader | Snaps to header/ListView top | Snaps to preferredHighlightBegin when header is pulled back. Snaps to neither when header is pulled in. |
1681
1682 Notes:
1683 [1]: If there is no item below preferredHighlightBegin, it will snap to preferredHighlightEnd
1684 [!]: This is likely not intended behavior
1685 */
1686 qreal tempPosition = isContentFlowReversed() ? -position()-size() : position();
1687 if (snapMode == QQuickListView::SnapOneItem && moveReason == Mouse) {
1688 // if we've been dragged < averageSize/2 then bias towards the next item
1689 qreal dist = data.move.value() - data.pressPos;
1690 qreal bias = 0;
1691 if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < averageSize/2)
1692 bias = averageSize/2;
1693 else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -averageSize/2)
1694 bias = -averageSize/2;
1695 if (isContentFlowReversed())
1696 bias = -bias;
1697 tempPosition -= bias;
1698 }
1699
1700 qreal snapOffset = 0;
1701 qreal overlayHeaderOffset = 0;
1702 bool isHeaderWithinBounds = false;
1703 if (header) {
1704 qreal visiblePartOfHeader = header->position() + header->size() - tempPosition;
1705 isHeaderWithinBounds = visiblePartOfHeader > 0;
1706 switch (headerPositioning) {
1707 case QQuickListView::OverlayHeader:
1708 snapOffset = header->size();
1709 overlayHeaderOffset = header->size();
1710 break;
1711 case QQuickListView::InlineHeader:
1712 if (isHeaderWithinBounds && tempPosition < originPosition())
1713 // For the inline header, we want to snap to the first item
1714 // if we're more than halfway down the inline header.
1715 // So if we look for an item halfway down of the header
1716 snapOffset = header->size() / 2;
1717 break;
1718 case QQuickListView::PullBackHeader:
1719 desiredHeaderVisible = visiblePartOfHeader > header->size()/2;
1720 if (qFuzzyCompare(p1: header->position(), p2: tempPosition)) {
1721 // header was pulled down; make sure it remains visible and snap items to bottom of header
1722 snapOffset = header->size();
1723 } else if (desiredHeaderVisible) {
1724 // More than 50% of the header is shown. Show it fully.
1725 // Scroll the view so the next item snaps to the header.
1726 snapOffset = header->size();
1727 overlayHeaderOffset = header->size();
1728 }
1729 break;
1730 }
1731 }
1732
1733 // If there are pending changes, the item returned from snapItemAt might get deleted as
1734 // soon as applyPendingChanges() is called (from e.g. updateHighlight()).
1735 // Therefore, apply the pending changes before we call snapItemAt()
1736 if (strictHighlightRange)
1737 updateHighlight();
1738
1739 FxViewItem *topItem = snapItemAt(pos: tempPosition + snapOffset + highlightRangeStart);
1740 FxViewItem *bottomItem = snapItemAt(pos: tempPosition + snapOffset + highlightRangeEnd);
1741 if (strictHighlightRange && currentItem) {
1742 // StrictlyEnforceRange always keeps an item in range
1743 if (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))
1744 topItem = currentItem;
1745 if (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))
1746 bottomItem = currentItem;
1747 }
1748
1749 qreal pos = 0;
1750 bool isInBounds = -position() > maxExtent && -position() <= minExtent;
1751
1752 if (header && !topItem && isInBounds) {
1753 // We are trying to pull back further than needed
1754 switch (headerPositioning) {
1755 case QQuickListView::OverlayHeader:
1756 pos = startPosition() - overlayHeaderOffset;
1757 break;
1758 case QQuickListView::InlineHeader:
1759 pos = isContentFlowReversed() ? header->size() - size() : header->position();
1760 break;
1761 case QQuickListView::PullBackHeader:
1762 pos = isContentFlowReversed() ? -size() : startPosition();
1763 break;
1764 }
1765 } else if (topItem && (isInBounds || strictHighlightRange)) {
1766 if (topItem->index == 0 && header && !hasStickyHeader() && tempPosition+highlightRangeStart < header->position()+header->size()/2 && !strictHighlightRange) {
1767 pos = isContentFlowReversed() ? -header->position() + highlightRangeStart - size() : (header->position() - highlightRangeStart + header->size());
1768 } else {
1769 if (header && headerPositioning == QQuickListView::PullBackHeader) {
1770 // We pulled down the header. If it isn't pulled all way down, we need to snap
1771 // the header.
1772 if (qFuzzyCompare(p1: tempPosition, p2: header->position())) {
1773 // It is pulled all way down. Scroll-snap the content, but not the header.
1774 if (isContentFlowReversed())
1775 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + snapOffset;
1776 else
1777 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - snapOffset;
1778 } else {
1779 // Header is not pulled all way down, make it completely visible or hide it.
1780 // Depends on how much of the header is visible.
1781 if (desiredHeaderVisible) {
1782 // More than half of the header is visible - show it.
1783 // Scroll so that the topItem is aligned to a fully visible header
1784 if (isContentFlowReversed())
1785 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + headerSize();
1786 else
1787 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - headerSize();
1788 } else {
1789 // Less than half is visible - hide the header. Scroll so
1790 // that the topItem is aligned to the top of the view
1791 if (isContentFlowReversed())
1792 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size();
1793 else
1794 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart;
1795 }
1796 }
1797
1798 headerNeedsSeparateFixup = isHeaderWithinBounds || desiredHeaderVisible;
1799 if (headerNeedsSeparateFixup) {
1800 // We need to animate the header independently if it starts visible or should end as visible,
1801 // since the header should not necessarily follow the content.
1802 // Store the desired viewport position.
1803 // Also store the header position so we know where to animate the header from (fixupHeaderPosition).
1804 // We deduce the desired header position from the desiredViewportPosition variable.
1805 pos = qBound(min: -minExtent, val: pos, max: -maxExtent);
1806 desiredViewportPosition = isContentFlowReversed() ? -pos - size() : pos;
1807
1808 FxListItemSG *headerItem = static_cast<FxListItemSG*>(header);
1809 fixupHeaderPosition = headerItem->position();
1810
1811 // follow the same fixup timeline
1812 QObjectPrivate::connect(sender: &timeline, signal: &QQuickTimeLine::updated, receiverPrivate: this, slot: &QQuickListViewPrivate::fixupHeader);
1813 QObjectPrivate::connect(sender: &timeline, signal: &QQuickTimeLine::completed, receiverPrivate: this, slot: &QQuickListViewPrivate::fixupHeaderCompleted);
1814 }
1815 } else if (isContentFlowReversed()) {
1816 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + overlayHeaderOffset;
1817 } else {
1818 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - overlayHeaderOffset;
1819 }
1820 }
1821 } else if (bottomItem && isInBounds) {
1822 if (isContentFlowReversed())
1823 pos = -static_cast<FxListItemSG*>(bottomItem)->itemPosition() + highlightRangeEnd - size() + overlayHeaderOffset;
1824 else
1825 pos = static_cast<FxListItemSG*>(bottomItem)->itemPosition() - highlightRangeEnd - overlayHeaderOffset;
1826 } else {
1827 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1828 return;
1829 }
1830 // If we have the CurrentLabelAtStart flag set, then we need to consider
1831 // the section size while calculating the position
1832 if (sectionCriteria
1833 && (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart)
1834 && currentSectionItem) {
1835 auto sectionSize = (orient == QQuickListView::Vertical) ? currentSectionItem->height()
1836 : currentSectionItem->width();
1837 if (isContentFlowReversed())
1838 pos += sectionSize;
1839 else
1840 pos -= sectionSize;
1841 }
1842
1843 pos = qBound(min: -minExtent, val: pos, max: -maxExtent);
1844
1845 qreal dist = qAbs(t: data.move + pos);
1846 if (dist >= 0) {
1847 // Even if dist == 0 we still start the timeline, because we use the same timeline for
1848 // moving the header. And we might need to move the header while the content does not
1849 // need moving
1850 timeline.reset(data.move);
1851 if (fixupMode != Immediate) {
1852 timeline.move(data.move, destination: -pos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
1853 data.fixingUp = true;
1854 } else {
1855 timeline.set(data.move, -pos);
1856 }
1857 vTime = timeline.time();
1858 }
1859 } else if (currentItem && strictHighlightRange && moveReason != QQuickListViewPrivate::SetIndex) {
1860 updateHighlight();
1861 qreal pos = static_cast<FxListItemSG*>(currentItem)->itemPosition();
1862 if (viewPos < pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd)
1863 viewPos = pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd;
1864 if (viewPos > pos - highlightRangeStart)
1865 viewPos = pos - highlightRangeStart;
1866 if (isContentFlowReversed())
1867 viewPos = -viewPos-size();
1868
1869 timeline.reset(data.move);
1870 if (viewPos != position()) {
1871 if (fixupMode != Immediate) {
1872 if (fixupMode == ExtentChanged && data.fixingUp)
1873 timeline.move(data.move, destination: -viewPos, QEasingCurve(QEasingCurve::OutQuad), time: fixupDuration/2);
1874 else
1875 timeline.move(data.move, destination: -viewPos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
1876 data.fixingUp = true;
1877 } else {
1878 timeline.set(data.move, -viewPos);
1879 }
1880 }
1881 vTime = timeline.time();
1882 } else {
1883 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1884 }
1885 data.inOvershoot = false;
1886 fixupMode = Normal;
1887}
1888
1889bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
1890 QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity)
1891{
1892 data.fixingUp = false;
1893 moveReason = Mouse;
1894 if ((!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange) && snapMode == QQuickListView::NoSnap) {
1895 correctFlick = true;
1896 return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, eventType, velocity);
1897 }
1898 qreal maxDistance = 0;
1899 const qreal dataValue =
1900 isContentFlowReversed() ? -data.move.value() + size() : data.move.value();
1901
1902 // -ve velocity means list is moving up/left
1903 if (velocity > 0) {
1904 if (data.move.value() < minExtent) {
1905 if (snapMode == QQuickListView::SnapOneItem && !hData.flicking && !vData.flicking) {
1906 // averageSize/2 + 1 - next item
1907 qreal bias = averageSize / 2 + 1 - (pressed ? data.pressPos : 0);
1908 if (isContentFlowReversed())
1909 bias = -bias;
1910 data.flickTarget = -snapPosAt(pos: -(dataValue - highlightRangeStart) - bias) + highlightRangeStart;
1911 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1912 velocity = maxVelocity;
1913 } else {
1914 maxDistance = qAbs(t: minExtent - data.move.value());
1915 }
1916 }
1917 if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange)
1918 data.flickTarget = minExtent;
1919 } else {
1920 if (data.move.value() > maxExtent) {
1921 if (snapMode == QQuickListView::SnapOneItem && !hData.flicking && !vData.flicking) {
1922 // averageSize/2 + 1 - next item
1923 qreal bias = averageSize / 2 + 1 - (pressed ? data.pressPos : 0);
1924 if (isContentFlowReversed())
1925 bias = -bias;
1926 data.flickTarget =
1927 -snapPosAt(pos: -(dataValue - highlightRangeStart) + bias) + highlightRangeStart;
1928 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1929 velocity = -maxVelocity;
1930 } else {
1931 maxDistance = qAbs(t: maxExtent - data.move.value());
1932 }
1933 }
1934 if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange)
1935 data.flickTarget = maxExtent;
1936 }
1937 bool overShoot = boundsBehavior & QQuickFlickable::OvershootBounds;
1938 if (maxDistance > 0 || overShoot) {
1939 // These modes require the list to stop exactly on an item boundary.
1940 // The initial flick will estimate the boundary to stop on.
1941 // Since list items can have variable sizes, the boundary will be
1942 // reevaluated and adjusted as we approach the boundary.
1943 qreal v = velocity;
1944 if (maxVelocity != -1 && maxVelocity < qAbs(t: v)) {
1945 if (v < 0)
1946 v = -maxVelocity;
1947 else
1948 v = maxVelocity;
1949 }
1950 if (!hData.flicking && !vData.flicking) {
1951 // the initial flick - estimate boundary
1952 qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
1953 qreal v2 = v * v;
1954 overshootDist = 0.0;
1955 // + averageSize/4 to encourage moving at least one item in the flick direction
1956 qreal dist = v2 / (accel * 2.0) + averageSize/4;
1957 if (maxDistance > 0)
1958 dist = qMin(a: dist, b: maxDistance);
1959 if (v > 0)
1960 dist = -dist;
1961 if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickListView::SnapOneItem) {
1962 if (snapMode != QQuickListView::SnapOneItem) {
1963 qreal distTemp = isContentFlowReversed() ? -dist : dist;
1964 data.flickTarget = -snapPosAt(pos: -(dataValue - highlightRangeStart) + distTemp) + highlightRangeStart;
1965 }
1966 data.flickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget;
1967 if (overShoot) {
1968 if (data.flickTarget > minExtent) {
1969 overshootDist = overShootDistance(velocity: vSize);
1970 data.flickTarget += overshootDist;
1971 } else if (data.flickTarget < maxExtent) {
1972 overshootDist = overShootDistance(velocity: vSize);
1973 data.flickTarget -= overshootDist;
1974 }
1975 }
1976 qreal adjDist = -data.flickTarget + data.move.value();
1977 if (qAbs(t: adjDist) > qAbs(t: dist)) {
1978 // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration
1979 qreal adjv2 = accel * 2.0f * qAbs(t: adjDist);
1980 if (adjv2 > v2) {
1981 v2 = adjv2;
1982 v = qSqrt(v: v2);
1983 if (dist > 0)
1984 v = -v;
1985 }
1986 }
1987 dist = adjDist;
1988 accel = v2 / (2.0f * qAbs(t: dist));
1989 } else if (overShoot) {
1990 data.flickTarget = data.move.value() - dist;
1991 if (data.flickTarget > minExtent) {
1992 overshootDist = overShootDistance(velocity: vSize);
1993 data.flickTarget += overshootDist;
1994 } else if (data.flickTarget < maxExtent) {
1995 overshootDist = overShootDistance(velocity: vSize);
1996 data.flickTarget -= overshootDist;
1997 }
1998 }
1999 timeline.reset(data.move);
2000 timeline.accel(data.move, velocity: v, accel, maxDistance: maxDistance + overshootDist);
2001 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2002 correctFlick = true;
2003 return true;
2004 } else {
2005 // reevaluate the target boundary.
2006 qreal newtarget = data.flickTarget;
2007 if (snapMode != QQuickListView::NoSnap || highlightRange == QQuickListView::StrictlyEnforceRange) {
2008 qreal tempFlickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget;
2009 newtarget = -snapPosAt(pos: -(tempFlickTarget - highlightRangeStart)) + highlightRangeStart;
2010 newtarget = isContentFlowReversed() ? -newtarget+size() : newtarget;
2011 }
2012 if (velocity < 0 && newtarget <= maxExtent)
2013 newtarget = maxExtent - overshootDist;
2014 else if (velocity > 0 && newtarget >= minExtent)
2015 newtarget = minExtent + overshootDist;
2016 if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do
2017 if (qAbs(t: velocity) < _q_MinimumFlickVelocity)
2018 correctFlick = false;
2019 return false;
2020 }
2021 data.flickTarget = newtarget;
2022 qreal dist = -newtarget + data.move.value();
2023 if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) {
2024 correctFlick = false;
2025 timeline.reset(data.move);
2026 fixup(data, minExtent, maxExtent);
2027 return false;
2028 }
2029 timeline.reset(data.move);
2030 timeline.accelDistance(data.move, velocity: v, distance: -dist);
2031 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2032 return false;
2033 }
2034 } else {
2035 correctFlick = false;
2036 timeline.reset(data.move);
2037 fixup(data, minExtent, maxExtent);
2038 return false;
2039 }
2040}
2041
2042void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString &section)
2043{
2044 if (!QQmlContextData::get(context)->isInternal() && context->contextProperty(QLatin1String("section")).isValid())
2045 context->setContextProperty(QLatin1String("section"), section);
2046 else
2047 sectionItem->setProperty(name: "section", value: section);
2048}
2049
2050QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *object) const
2051{
2052 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickListView>(obj: object);
2053 return static_cast<QQuickItemViewAttached *>(attachedObject);
2054}
2055
2056//----------------------------------------------------------------------------
2057
2058/*!
2059 \qmltype ListView
2060 \nativetype QQuickListView
2061 \inqmlmodule QtQuick
2062 \ingroup qtquick-views
2063 \inherits Flickable
2064 \brief Provides a list view of items provided by a model.
2065
2066 A ListView displays data from models created from built-in QML types like ListModel
2067 and XmlListModel, or custom model classes defined in C++ that inherit from
2068 QAbstractItemModel or QAbstractListModel.
2069
2070 A ListView has a \l model, which defines the data to be displayed, and
2071 a \l delegate, which defines how the data should be displayed. Items in a
2072 ListView are laid out horizontally or vertically. List views are inherently
2073 flickable because ListView inherits from \l Flickable.
2074
2075 \note ListView will only load as many delegate items as needed to fill up the view.
2076 Items outside of the view will not be loaded unless a sufficient \l cacheBuffer has
2077 been set. Hence, a ListView with zero width or height might not load any delegate
2078 items at all.
2079
2080 \section1 Example Usage
2081
2082 The following example shows the definition of a simple list model defined
2083 in a file called \c ContactModel.qml:
2084
2085 \snippet qml/listview/ContactModel.qml 0
2086
2087 Another component can display this model data in a ListView, like this:
2088
2089 \snippet qml/listview/listview.qml import
2090 \codeline
2091 \snippet qml/listview/listview.qml classdocs simple
2092
2093 \image listview-simple.png
2094
2095 Here, the ListView creates a \c ContactModel component for its model, and a \l Text item
2096 for its delegate. The view will create a new \l Text component for each item in the model. Notice
2097 the delegate is able to access the model's \c name and \c number data directly.
2098
2099 An improved list view is shown below. The delegate is visually improved and is moved
2100 into a separate \c contactDelegate component.
2101
2102 \snippet qml/listview/listview.qml classdocs advanced
2103 \image listview-highlight.png
2104
2105 The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property,
2106 and \c focus is set to \c true to enable keyboard navigation for the list view.
2107 The list view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
2108
2109 Delegates are instantiated as needed and may be destroyed at any time.
2110 As such, \l {Avoid Storing State in Delegates}{state should \e never be stored in a delegate}.
2111 Delegates are usually parented to ListView's \l {Flickable::contentItem}{contentItem}, but
2112 typically depending on whether it's visible in the view or not, the \e parent
2113 can change, and sometimes be \c null. Because of that, binding to
2114 the parent's properties from within the delegate is \e not recommended. If you
2115 want the delegate to fill out the width of the ListView, consider
2116 using one of the following approaches instead:
2117
2118 \code
2119 ListView {
2120 id: listView
2121 // ...
2122
2123 delegate: Item {
2124 // Incorrect.
2125 width: parent.width
2126
2127 // Correct.
2128 width: listView.width
2129 width: ListView.view.width
2130 // ...
2131 }
2132 }
2133 \endcode
2134
2135 ListView attaches a number of properties to the root item of the delegate, for example
2136 \c ListView.isCurrentItem. In the following example, the root delegate item can access
2137 this attached property directly as \c ListView.isCurrentItem, while the child
2138 \c contactInfo object must refer to this property as \c wrapper.ListView.isCurrentItem.
2139
2140 \snippet qml/listview/listview.qml isCurrentItem
2141
2142 \note Views do not enable \e clip automatically. If the view
2143 is not clipped by another item or the screen, it will be necessary
2144 to set \e {clip: true} in order to have the out of view items clipped
2145 nicely.
2146
2147
2148 \section1 ListView Layouts
2149
2150 The layout of the items in a ListView can be controlled by these properties:
2151
2152 \list
2153 \li \l orientation - controls whether items flow horizontally or vertically.
2154 This value can be either Qt.Horizontal or Qt.Vertical.
2155 \li \l layoutDirection - controls the horizontal layout direction for a
2156 horizontally-oriented view: that is, whether items are laid out from the left side of
2157 the view to the right, or vice-versa. This value can be either Qt.LeftToRight or Qt.RightToLeft.
2158 \li \l verticalLayoutDirection - controls the vertical layout direction for a vertically-oriented
2159 view: that is, whether items are laid out from the top of the view down towards the bottom of
2160 the view, or vice-versa. This value can be either ListView.TopToBottom or ListView.BottomToTop.
2161 \endlist
2162
2163 By default, a ListView has a vertical orientation, and items are laid out from top to bottom. The
2164 table below shows the different layouts that a ListView can have, depending on the values of
2165 the properties listed above.
2166
2167 \table
2168 \header
2169 \li {2, 1}
2170 \b ListViews with Qt.Vertical orientation
2171 \row
2172 \li Top to bottom
2173 \image listview-layout-toptobottom.png
2174 \li Bottom to top
2175 \image listview-layout-bottomtotop.png
2176 \header
2177 \li {2, 1}
2178 \b ListViews with Qt.Horizontal orientation
2179 \row
2180 \li Left to right
2181 \image listview-layout-lefttoright.png
2182 \li Right to left
2183 \image listview-layout-righttoleft.png
2184 \endtable
2185
2186 \section1 Flickable Direction
2187
2188 By default, a vertical ListView sets \l {Flickable::}{flickableDirection} to \e Flickable.Vertical,
2189 and a horizontal ListView sets it to \e Flickable.Horizontal. Furthermore, a vertical ListView only
2190 calculates (estimates) the \l {Flickable::}{contentHeight}, and a horizontal ListView only calculates
2191 the \l {Flickable::}{contentWidth}. The other dimension is set to \e -1.
2192
2193 Since Qt 5.9 (Qt Quick 2.9), it is possible to make a ListView that can be flicked to both directions.
2194 In order to do this, the \l {Flickable::}{flickableDirection} can be set to \e Flickable.AutoFlickDirection
2195 or \e Flickable.AutoFlickIfNeeded, and the desired \e contentWidth or \e contentHeight must be provided.
2196
2197 \snippet qml/listview/listview.qml flickBothDirections
2198
2199 \section1 Stacking Order in ListView
2200
2201 The \l {QQuickItem::z}{Z value} of items determines whether they are
2202 rendered above or below other items. ListView uses several different
2203 default Z values, depending on what type of item is being created:
2204
2205 \table
2206 \header
2207 \li Property
2208 \li Default Z value
2209 \row
2210 \li \l delegate
2211 \li 1
2212 \row
2213 \li \l footer
2214 \li 1
2215 \row
2216 \li \l header
2217 \li 1
2218 \row
2219 \li \l highlight
2220 \li 0
2221 \row
2222 \li \l section.delegate
2223 \li 2
2224 \endtable
2225
2226 These default values are set if the Z value of the item is \c 0, so setting
2227 the Z value of these items to \c 0 has no effect. Note that the Z value is
2228 of type \l [QML] {real}, so it is possible to set fractional
2229 values like \c 0.1.
2230
2231 \section1 Reusing Items
2232
2233 Since 5.15, ListView can be configured to recycle items instead of instantiating
2234 from the \l delegate whenever new rows are flicked into view. This approach improves
2235 performance, depending on the complexity of the delegate. Reusing
2236 items is off by default (for backwards compatibility reasons), but can be switched
2237 on by setting the \l reuseItems property to \c true.
2238
2239 When an item is flicked out, it moves to the \e{reuse pool}, which is an
2240 internal cache of unused items. When this happens, the \l ListView::pooled
2241 signal is emitted to inform the item about it. Likewise, when the item is
2242 moved back from the pool, the \l ListView::reused signal is emitted.
2243
2244 Any item properties that come from the model are updated when the
2245 item is reused. This includes \c index and \c row, but also
2246 any model roles.
2247
2248 \note \l {Avoid Storing State in Delegates}{Avoid storing any state inside
2249 a delegate}. If you do, reset it manually on receiving the
2250 \l ListView::reused signal.
2251
2252 If an item has timers or animations, consider pausing them on receiving
2253 the \l ListView::pooled signal. That way you avoid using the CPU resources
2254 for items that are not visible. Likewise, if an item has resources that
2255 cannot be reused, they could be freed up.
2256
2257 \note While an item is in the pool, it might still be alive and respond
2258 to connected signals and bindings.
2259
2260 \note For an item to be pooled, it needs to be completely flicked out of the bounds
2261 of the view, \e including the extra margins set with \l {ListView::}{cacheBuffer}.
2262 Some items will also never be pooled or reused, such as \l currentItem.
2263
2264 The following example shows a delegate that animates a spinning rectangle. When
2265 it is pooled, the animation is temporarily paused:
2266
2267 \snippet qml/listview/ReusableDelegate.qml 0
2268
2269 \sa {QML Data Models}, GridView, PathView, {Qt Quick Examples - Views}
2270
2271 \section1 Variable Delegate Size and Section Labels
2272
2273 Variable delegate sizes might lead to resizing and skipping of any attached
2274 \l {ScrollBar}. This is because ListView estimates its content size from
2275 allocated items (usually only the visible items, the rest are assumed to be of
2276 similar size), and variable delegate sizes prevent an accurate estimation. To
2277 reduce this effect, \l {ListView::}{cacheBuffer} can be set to higher values,
2278 effectively creating more items and improving the size estimate of unallocated
2279 items, at the expense of additional memory usage. \l{ListView::section}{Sections}
2280 have the same effect because they attach and elongate the section label to the
2281 first item within the section.
2282
2283 \section1 Avoid Storing State in Delegates
2284
2285 ListView's delegates are instantiated as needed and may be destroyed when
2286 out of view. For an illustration of this, run the following example:
2287
2288 \snippet qml/listview/stateInDelegate.qml ListView
2289
2290 When an item is clicked, \c channelActivated is set to \c true. However,
2291 because delegates can be \l {Reusing Items}{reused} and destroyed, all
2292 state is lost when the view is moved far enough. When the delegate becomes
2293 visible again, it will have its default, unmodified state (or, in the case
2294 of an item that was reused, old state from a previous item).
2295
2296 To avoid this, state should be stored in the model:
2297
2298 \snippet qml/listview/stateInModel.qml ListView
2299
2300 \section1 Hiding Delegates
2301
2302 Setting a delegate's \l {Item::}{visible} property to \c false will hide
2303 that item, but the space it occupied in the view will remain. It is
2304 possible to set the item's \l {Item::}{height} to \c 0 (for a \l
2305 {ListView::orientation}{vertical} ListView):
2306
2307 \snippet qml/listview/hideDelegate.qml ListView
2308
2309 Note that the hidden state is stored in the model, following the advice of
2310 the \l {Avoid Storing State in Delegates} section.
2311
2312 However, if \l spacing is non-zero, there will be uneven gaps between
2313 delegates.
2314
2315 A better option is to filter your model so that items that should not be
2316 visible are not loaded by the view at all. This can be achieved with
2317 \l QSortFilterProxyModel.
2318
2319 Another option is to \l {Item::enabled}{disable} the delegate instead of
2320 hiding it.
2321*/
2322QQuickListView::QQuickListView(QQuickItem *parent)
2323 : QQuickItemView(*(new QQuickListViewPrivate), parent)
2324{
2325}
2326
2327QQuickListView::~QQuickListView()
2328{
2329}
2330
2331/*!
2332 \qmlattachedproperty bool QtQuick::ListView::isCurrentItem
2333 \readonly
2334
2335 This attached property is true if this delegate is the current item; otherwise false.
2336
2337 It is attached to each instance of the delegate.
2338
2339 This property may be used to adjust the appearance of the current item, for example:
2340
2341 \snippet qml/listview/listview.qml isCurrentItem
2342*/
2343
2344/*!
2345 \qmlattachedproperty ListView QtQuick::ListView::view
2346 \readonly
2347
2348 This attached property holds the view that manages this delegate instance.
2349
2350 It is attached to each instance of the delegate and also to the header, the footer,
2351 the section and the highlight delegates.
2352*/
2353
2354/*!
2355 \qmlattachedproperty string QtQuick::ListView::previousSection
2356 \readonly
2357
2358 This attached property holds the section of the previous element.
2359
2360 It is attached to each instance of the delegate.
2361
2362 The section is evaluated using the \l {ListView::section.property}{section} properties.
2363*/
2364
2365/*!
2366 \qmlattachedproperty string QtQuick::ListView::nextSection
2367 \readonly
2368
2369 This attached property holds the section of the next element.
2370
2371 It is attached to each instance of the delegate.
2372
2373 The section is evaluated using the \l {ListView::section.property}{section} properties.
2374*/
2375
2376/*!
2377 \qmlattachedproperty string QtQuick::ListView::section
2378 \readonly
2379
2380 This attached property holds the section of this element.
2381
2382 It is attached to each instance of the delegate.
2383
2384 The section is evaluated using the \l {ListView::section.property}{section} properties.
2385*/
2386
2387/*!
2388 \qmlattachedproperty bool QtQuick::ListView::delayRemove
2389
2390 This attached property holds whether the delegate may be destroyed. It
2391 is attached to each instance of the delegate. The default value is false.
2392
2393 It is sometimes necessary to delay the destruction of an item
2394 until an animation completes. The example delegate below ensures that the
2395 animation completes before the item is removed from the list.
2396
2397 \snippet qml/listview/listview.qml delayRemove
2398
2399 If a \l remove transition has been specified, it will not be applied until
2400 delayRemove is returned to \c false.
2401*/
2402
2403/*!
2404 \qmlattachedsignal QtQuick::ListView::add()
2405 This attached signal is emitted immediately after an item is added to the view.
2406
2407 If an \l add transition is specified, it is applied immediately after
2408 this signal is handled.
2409*/
2410
2411/*!
2412 \qmlattachedsignal QtQuick::ListView::remove()
2413 This attached signal is emitted immediately before an item is removed from the view.
2414
2415 If a \l remove transition has been specified, it is applied after
2416 this signal is handled, providing that \l delayRemove is false.
2417*/
2418
2419/*!
2420 \qmlproperty model QtQuick::ListView::model
2421 This property holds the model providing data for the list.
2422
2423 The model provides the set of data that is used to create the items
2424 in the view. Models can be created directly in QML using \l ListModel,
2425 \l ObjectModel, or provided by C++ model classes. If a C++ model class is
2426 used, it must be a subclass of \l QAbstractItemModel or a simple list.
2427
2428 \sa {qml-data-models}{Data Models}
2429*/
2430
2431/*!
2432 \qmlproperty Component QtQuick::ListView::delegate
2433
2434 The delegate provides a template defining each item instantiated by the view.
2435 The index is exposed as an accessible \c index property. Properties of the
2436 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
2437
2438 The number of objects and bindings in the delegate has a direct effect on the
2439 flicking performance of the view. If at all possible, place functionality
2440 that is not needed for the normal display of the delegate in a \l Loader which
2441 can load additional components when needed.
2442
2443 The ListView will lay out the items based on the size of the root item
2444 in the delegate.
2445
2446 It is recommended that the delegate's size be a whole number to avoid sub-pixel
2447 alignment of items.
2448
2449 The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1.
2450
2451 \note Delegates are instantiated as needed and may be destroyed at any time.
2452 They are parented to ListView's \l {Flickable::contentItem}{contentItem}, not to the view itself.
2453 State should \e never be stored in a delegate.
2454
2455 \sa {Stacking Order in ListView}
2456*/
2457
2458/*!
2459 \qmlproperty enumeration QtQuick::ListView::delegateModelAccess
2460
2461 \include delegatemodelaccess.qdocinc
2462*/
2463
2464/*!
2465 \qmlproperty int QtQuick::ListView::currentIndex
2466 \qmlproperty Item QtQuick::ListView::currentItem
2467
2468 The \c currentIndex property holds the index of the current item, and
2469 \c currentItem holds the current item. Setting the currentIndex to -1
2470 will clear the highlight and set currentItem to null.
2471
2472 If highlightFollowsCurrentItem is \c true, setting either of these
2473 properties will smoothly scroll the ListView so that the current
2474 item becomes visible.
2475
2476 Note that the position of the current item
2477 may only be approximate until it becomes visible in the view.
2478*/
2479
2480/*!
2481 \qmlproperty Item QtQuick::ListView::highlightItem
2482
2483 This holds the highlight item created from the \l highlight component.
2484
2485 The \c highlightItem is managed by the view unless
2486 \l highlightFollowsCurrentItem is set to false.
2487 The default \l {QQuickItem::z}{stacking order}
2488 of the highlight item is \c 0.
2489
2490 \sa highlight, highlightFollowsCurrentItem, {Stacking Order in ListView}
2491*/
2492
2493/*!
2494 \qmlproperty int QtQuick::ListView::count
2495 The property reflects the number of items in the \l ListView's model,
2496 regardless of whether they are visible or instantiated as \c Item of a delegate component.
2497*/
2498
2499/*!
2500 \qmlproperty bool QtQuick::ListView::reuseItems
2501
2502 This property enables you to reuse items that are instantiated
2503 from the \l delegate. If set to \c false, any currently
2504 pooled items are destroyed.
2505
2506 This property is \c false by default.
2507
2508 \since 5.15
2509
2510 \sa {Reusing items}, pooled(), reused()
2511*/
2512
2513/*!
2514 \qmlattachedsignal QtQuick::ListView::pooled()
2515
2516 This signal is emitted after an item has been added to the reuse
2517 pool. You can use it to pause ongoing timers or animations inside
2518 the item, or free up resources that cannot be reused.
2519
2520 This signal is emitted only if the \l reuseItems property is \c true.
2521
2522 \sa {Reusing items}, reuseItems, reused()
2523*/
2524
2525/*!
2526 \qmlattachedsignal QtQuick::ListView::reused()
2527
2528 This signal is emitted after an item has been reused. At this point, the
2529 item has been taken out of the pool and placed inside the content view,
2530 and the model properties such as \c index and \c row have been updated.
2531
2532 Other properties that are not provided by the model does not change when an
2533 item is reused. You should avoid storing any state inside a delegate, but if
2534 you do, manually reset that state on receiving this signal.
2535
2536 This signal is emitted when the item is reused, and not the first time the
2537 item is created.
2538
2539 This signal is emitted only if the \l reuseItems property is \c true.
2540
2541 \sa {Reusing items}, reuseItems, pooled()
2542*/
2543
2544/*!
2545 \qmlproperty Component QtQuick::ListView::highlight
2546 This property holds the component to use as the highlight.
2547
2548 An instance of the highlight component is created for each list.
2549 The geometry of the resulting component instance is managed by the list
2550 so as to stay with the current item, unless the highlightFollowsCurrentItem
2551 property is false. The default \l {QQuickItem::z}{stacking order} of the
2552 highlight item is \c 0.
2553
2554 \sa highlightItem, highlightFollowsCurrentItem,
2555 {Qt Quick Examples - Views#Using Highlight}{ListView Highlight Example},
2556 {Stacking Order in ListView}
2557*/
2558
2559/*!
2560 \qmlproperty bool QtQuick::ListView::highlightFollowsCurrentItem
2561 This property holds whether the highlight is managed by the view.
2562
2563 If this property is true (the default value), the highlight is moved smoothly
2564 to follow the current item. Otherwise, the
2565 highlight is not moved by the view, and any movement must be implemented
2566 by the highlight.
2567
2568 Here is a highlight with its motion defined by a \l {SpringAnimation} item:
2569
2570 \snippet qml/listview/listview.qml highlightFollowsCurrentItem
2571
2572 Note that the highlight animation also affects the way that the view
2573 is scrolled. This is because the view moves to maintain the
2574 highlight within the preferred highlight range (or visible viewport).
2575
2576 \sa highlight, highlightMoveVelocity
2577*/
2578//###Possibly rename these properties, since they are very useful even without a highlight?
2579/*!
2580 \qmlproperty real QtQuick::ListView::preferredHighlightBegin
2581 \qmlproperty real QtQuick::ListView::preferredHighlightEnd
2582 \qmlproperty enumeration QtQuick::ListView::highlightRangeMode
2583
2584 These properties define the preferred range of the highlight (for the current item)
2585 within the view. The \c preferredHighlightBegin value must be less than the
2586 \c preferredHighlightEnd value.
2587
2588 These properties affect the position of the current item when the list is scrolled.
2589 For example, if the currently selected item should stay in the middle of the
2590 list when the view is scrolled, set the \c preferredHighlightBegin and
2591 \c preferredHighlightEnd values to the top and bottom coordinates of where the middle
2592 item would be. If the \c currentItem is changed programmatically, the list will
2593 automatically scroll so that the current item is in the middle of the view.
2594 Furthermore, the behavior of the current item index will occur whether or not a
2595 highlight exists.
2596
2597 Valid values for \c highlightRangeMode are:
2598
2599 \value ListView.ApplyRange the view attempts to maintain the highlight within the range.
2600 However, the highlight can move outside of the range at the
2601 ends of the list or due to mouse interaction.
2602 \value ListView.StrictlyEnforceRange the highlight never moves outside of the range.
2603 The current item changes if a keyboard or mouse action would
2604 cause the highlight to move outside of the range.
2605 \value ListView.NoHighlightRange this is the default value.
2606*/
2607void QQuickListView::setHighlightFollowsCurrentItem(bool autoHighlight)
2608{
2609 Q_D(QQuickListView);
2610 if (d->autoHighlight != autoHighlight) {
2611 if (!autoHighlight) {
2612 if (d->highlightPosAnimator)
2613 d->highlightPosAnimator->stop();
2614 if (d->highlightWidthAnimator)
2615 d->highlightWidthAnimator->stop();
2616 if (d->highlightHeightAnimator)
2617 d->highlightHeightAnimator->stop();
2618 }
2619 QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight);
2620 }
2621}
2622
2623/*!
2624 \qmlproperty real QtQuick::ListView::spacing
2625
2626 This property holds the spacing between items.
2627
2628 The default value is 0.
2629*/
2630qreal QQuickListView::spacing() const
2631{
2632 Q_D(const QQuickListView);
2633 return d->spacing;
2634}
2635
2636void QQuickListView::setSpacing(qreal spacing)
2637{
2638 Q_D(QQuickListView);
2639 if (spacing != d->spacing) {
2640 d->spacing = spacing;
2641 d->forceLayoutPolish();
2642 emit spacingChanged();
2643 }
2644}
2645
2646/*!
2647 \qmlproperty enumeration QtQuick::ListView::orientation
2648 This property holds the orientation of the list.
2649
2650 Possible values:
2651
2652 \value ListView.Horizontal Items are laid out horizontally
2653 \br
2654 \inlineimage ListViewHorizontal.png
2655 \value ListView.Vertical (default) Items are laid out vertically
2656 \br
2657 \inlineimage listview-highlight.png
2658
2659 \sa {Flickable Direction}
2660*/
2661QQuickListView::Orientation QQuickListView::orientation() const
2662{
2663 Q_D(const QQuickListView);
2664 return d->orient;
2665}
2666
2667void QQuickListView::setOrientation(QQuickListView::Orientation orientation)
2668{
2669 Q_D(QQuickListView);
2670 if (d->orient != orientation) {
2671 d->orient = orientation;
2672 if (d->orient == Vertical) {
2673 if (d->flickableDirection == HorizontalFlick) {
2674 setFlickableDirection(VerticalFlick);
2675 if (isComponentComplete())
2676 setContentWidth(-1);
2677 }
2678 setContentX(0);
2679 } else {
2680 if (d->flickableDirection == VerticalFlick) {
2681 setFlickableDirection(HorizontalFlick);
2682 if (isComponentComplete())
2683 setContentHeight(-1);
2684 }
2685 setContentY(0);
2686 }
2687 d->regenerate(orientationChanged: true);
2688 emit orientationChanged();
2689 }
2690}
2691
2692/*!
2693 \qmlproperty enumeration QtQuick::ListView::layoutDirection
2694 This property holds the layout direction of a horizontally-oriented list.
2695
2696 Possible values:
2697
2698 \value Qt.LeftToRight (default) Items will be laid out from left to right.
2699 \value Qt.RightToLeft Items will be laid out from right to left.
2700
2701 Setting this property has no effect if the \l orientation is Qt.Vertical.
2702
2703 \sa ListView::effectiveLayoutDirection, ListView::verticalLayoutDirection
2704*/
2705
2706
2707/*!
2708 \qmlproperty enumeration QtQuick::ListView::effectiveLayoutDirection
2709 This property holds the effective layout direction of a horizontally-oriented list.
2710
2711 When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts,
2712 the visual layout direction of the horizontal list will be mirrored. However, the
2713 property \l {ListView::layoutDirection}{layoutDirection} will remain unchanged.
2714
2715 \sa ListView::layoutDirection, {LayoutMirroring}{LayoutMirroring}
2716*/
2717
2718
2719/*!
2720 \qmlproperty enumeration QtQuick::ListView::verticalLayoutDirection
2721 This property holds the layout direction of a vertically-oriented list.
2722
2723 Possible values:
2724
2725 \value ListView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
2726 \value ListView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
2727
2728 Setting this property has no effect if the \l orientation is Qt.Horizontal.
2729
2730 \sa ListView::layoutDirection
2731*/
2732
2733
2734/*!
2735 \qmlproperty bool QtQuick::ListView::keyNavigationWraps
2736 This property holds whether the list wraps key navigation.
2737
2738 If this is true, key navigation that would move the current item selection
2739 past the end of the list instead wraps around and moves the selection to
2740 the start of the list, and vice-versa.
2741
2742 By default, key navigation is not wrapped.
2743*/
2744
2745/*!
2746 \qmlproperty bool QtQuick::ListView::keyNavigationEnabled
2747 \since 5.7
2748
2749 This property holds whether the key navigation of the list is enabled.
2750
2751 If this is \c true, the user can navigate the view with a keyboard.
2752 It is useful for applications that need to selectively enable or
2753 disable mouse and keyboard interaction.
2754
2755 By default, the value of this property is bound to
2756 \l {Flickable::}{interactive} to ensure behavior compatibility for
2757 existing applications. When explicitly set, it will cease to be bound to
2758 the interactive property.
2759
2760 \sa {Flickable::}{interactive}
2761*/
2762
2763
2764/*!
2765 \qmlproperty int QtQuick::ListView::cacheBuffer
2766 This property determines whether delegates are retained outside the
2767 visible area of the view.
2768
2769 If this value is greater than zero, the view may keep as many delegates
2770 instantiated as it can fit within the buffer specified. For example,
2771 if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is
2772 set to 40, then up to 2 delegates above and 2 delegates below the visible
2773 area may be created/retained. The buffered delegates are created asynchronously,
2774 allowing creation to occur across multiple frames and reducing the
2775 likelihood of skipping frames. In order to improve painting performance
2776 delegates outside the visible area are not painted.
2777
2778 The default value of this property is platform dependent, but will usually
2779 be a value greater than zero. Negative values are ignored.
2780
2781 Note that cacheBuffer is not a pixel buffer - it only maintains additional
2782 instantiated delegates.
2783
2784 \note Setting this property is not a replacement for creating efficient delegates.
2785 It can improve the smoothness of scrolling behavior at the expense of additional
2786 memory usage. The fewer objects and bindings in a delegate, the faster a
2787 view can be scrolled. It is important to realize that setting a cacheBuffer
2788 will only postpone issues caused by slow-loading delegates, it is not a
2789 solution for this scenario.
2790
2791 The cacheBuffer operates outside of any display margins specified by
2792 displayMarginBeginning or displayMarginEnd.
2793*/
2794
2795/*!
2796 \qmlproperty int QtQuick::ListView::displayMarginBeginning
2797 \qmlproperty int QtQuick::ListView::displayMarginEnd
2798 \since QtQuick 2.3
2799
2800 This property allows delegates to be displayed outside of the view geometry.
2801
2802 If this value is non-zero, the view will create extra delegates before the
2803 start of the view, or after the end. The view will create as many delegates
2804 as it can fit into the pixel size specified.
2805
2806 For example, if in a vertical view the delegate is 20 pixels high and
2807 \c displayMarginBeginning and \c displayMarginEnd are both set to 40,
2808 then 2 delegates above and 2 delegates below will be created and shown.
2809
2810 The default value is 0.
2811
2812 This property is meant for allowing certain UI configurations,
2813 and not as a performance optimization. If you wish to create delegates
2814 outside of the view geometry for performance reasons, you probably
2815 want to use the cacheBuffer property instead.
2816*/
2817
2818/*!
2819 \qmlpropertygroup QtQuick::ListView::section
2820 \qmlproperty string QtQuick::ListView::section.property
2821 \qmlproperty enumeration QtQuick::ListView::section.criteria
2822 \qmlproperty Component QtQuick::ListView::section.delegate
2823 \qmlproperty enumeration QtQuick::ListView::section.labelPositioning
2824
2825 These properties determine the expression to be evaluated and appearance
2826 of the section labels.
2827
2828 \c section.property holds the name of the property that is the basis
2829 of each section.
2830
2831 \c section.criteria holds the criteria for forming each section based on
2832 \c section.property. This value can be one of:
2833
2834 \value ViewSection.FullString (default) sections are created based on the
2835 \c section.property value.
2836 \value ViewSection.FirstCharacter sections are created based on the first character of
2837 the \c section.property value (for example,
2838 'A', 'B', 'C' ... sections for an address book.)
2839
2840 A case insensitive comparison is used when determining section
2841 boundaries.
2842
2843 \c section.delegate holds the delegate component for each section. The
2844 default \l {QQuickItem::z}{stacking order} of section delegate instances
2845 is \c 2. If you declare a \c required property named "section" in it,
2846 that property will contain the section's title.
2847
2848 \c section.labelPositioning determines whether the current and/or
2849 next section labels stick to the start/end of the view, and whether
2850 the labels are shown inline. This value can be a combination of:
2851
2852 \value ViewSection.InlineLabels
2853 (default) section labels are shown inline between the item delegates
2854 separating sections.
2855 \value ViewSection.CurrentLabelAtStart
2856 the current section label sticks to the start of the view as it is moved.
2857 \value ViewSection.NextLabelAtEnd
2858 the next section label (beyond all visible sections) sticks to the end
2859 of the view as it is moved.
2860 \note Enabling \c ViewSection.NextLabelAtEnd requires the view to scan
2861 ahead for the next section, which has performance implications,
2862 especially for slower models.
2863
2864 Each item in the list has attached properties named \c ListView.section,
2865 \c ListView.previousSection and \c ListView.nextSection.
2866
2867 For example, here is a ListView that displays a list of animals, separated
2868 into sections. Each item in the ListView is placed in a different section
2869 depending on the "size" property of the model item. The \c sectionHeading
2870 delegate component provides the light blue bar that marks the beginning of
2871 each section.
2872
2873
2874 \snippet views/listview/sections.qml 0
2875
2876 \image qml-listview-sections-example.png
2877
2878 \note Adding sections to a ListView does not automatically re-order the
2879 list items by the section criteria.
2880 If the model is not ordered by section, then it is possible that
2881 the sections created will not be unique; each boundary between
2882 differing sections will result in a section header being created
2883 even if that section exists elsewhere.
2884
2885 \sa {Qt Quick Examples - Views}{ListView examples},
2886 {Stacking Order in ListView}
2887*/
2888QQuickViewSection *QQuickListView::sectionCriteria()
2889{
2890 Q_D(QQuickListView);
2891 if (!d->sectionCriteria)
2892 d->sectionCriteria = new QQuickViewSection(this);
2893 return d->sectionCriteria;
2894}
2895
2896/*!
2897 \qmlproperty string QtQuick::ListView::currentSection
2898 This property holds the section that is currently at the beginning of the view.
2899*/
2900QString QQuickListView::currentSection() const
2901{
2902 Q_D(const QQuickListView);
2903 return d->currentSection;
2904}
2905
2906/*!
2907 \qmlproperty real QtQuick::ListView::highlightMoveVelocity
2908 \qmlproperty int QtQuick::ListView::highlightMoveDuration
2909 \qmlproperty real QtQuick::ListView::highlightResizeVelocity
2910 \qmlproperty int QtQuick::ListView::highlightResizeDuration
2911
2912 These properties control the speed of the move and resize animations for the
2913 highlight delegate.
2914
2915 \l highlightFollowsCurrentItem must be true for these properties
2916 to have effect.
2917
2918 The default value for the velocity properties is 400 pixels/second.
2919 The default value for the duration properties is -1, i.e. the
2920 highlight will take as much time as necessary to move at the set speed.
2921
2922 These properties have the same characteristics as a SmoothedAnimation:
2923 if both the velocity and duration are set, the animation will use
2924 whichever gives the shorter duration.
2925
2926 The move velocity and duration properties are used to control movement due
2927 to index changes; for example, when incrementCurrentIndex() is called. When
2928 the user flicks a ListView, the velocity from the flick is used to control
2929 the movement instead.
2930
2931 To set only one property, the other can be set to \c -1. For example,
2932 if you only want to animate the duration and not velocity, use the
2933 following code:
2934
2935 \code
2936 highlightMoveDuration: 1000
2937 highlightMoveVelocity: -1
2938 \endcode
2939
2940 \sa highlightFollowsCurrentItem
2941*/
2942qreal QQuickListView::highlightMoveVelocity() const
2943{
2944 Q_D(const QQuickListView);
2945 return d->highlightMoveVelocity;
2946}
2947
2948void QQuickListView::setHighlightMoveVelocity(qreal speed)
2949{
2950 Q_D(QQuickListView);
2951 if (d->highlightMoveVelocity != speed) {
2952 d->highlightMoveVelocity = speed;
2953 if (d->highlightPosAnimator)
2954 d->highlightPosAnimator->velocity = d->highlightMoveVelocity;
2955 emit highlightMoveVelocityChanged();
2956 }
2957}
2958
2959void QQuickListView::setHighlightMoveDuration(int duration)
2960{
2961 Q_D(QQuickListView);
2962 if (d->highlightMoveDuration != duration) {
2963 if (d->highlightPosAnimator)
2964 d->highlightPosAnimator->userDuration = duration;
2965 QQuickItemView::setHighlightMoveDuration(duration);
2966 }
2967}
2968
2969qreal QQuickListView::highlightResizeVelocity() const
2970{
2971 Q_D(const QQuickListView);
2972 return d->highlightResizeVelocity;
2973}
2974
2975void QQuickListView::setHighlightResizeVelocity(qreal speed)
2976{
2977 Q_D(QQuickListView);
2978 if (d->highlightResizeVelocity != speed) {
2979 d->highlightResizeVelocity = speed;
2980 if (d->highlightWidthAnimator)
2981 d->highlightWidthAnimator->velocity = d->highlightResizeVelocity;
2982 if (d->highlightHeightAnimator)
2983 d->highlightHeightAnimator->velocity = d->highlightResizeVelocity;
2984 emit highlightResizeVelocityChanged();
2985 }
2986}
2987
2988int QQuickListView::highlightResizeDuration() const
2989{
2990 Q_D(const QQuickListView);
2991 return d->highlightResizeDuration;
2992}
2993
2994void QQuickListView::setHighlightResizeDuration(int duration)
2995{
2996 Q_D(QQuickListView);
2997 if (d->highlightResizeDuration != duration) {
2998 d->highlightResizeDuration = duration;
2999 if (d->highlightWidthAnimator)
3000 d->highlightWidthAnimator->userDuration = d->highlightResizeDuration;
3001 if (d->highlightHeightAnimator)
3002 d->highlightHeightAnimator->userDuration = d->highlightResizeDuration;
3003 emit highlightResizeDurationChanged();
3004 }
3005}
3006
3007/*!
3008 \qmlproperty enumeration QtQuick::ListView::snapMode
3009
3010 This property determines how the view scrolling will settle following a drag or flick.
3011 The possible values are:
3012
3013 \value ListView.NoSnap (default) the view stops anywhere within the visible area.
3014 \value ListView.SnapToItem the view settles with an item aligned with the start of the view.
3015 \value ListView.SnapOneItem the view settles no more than one item away from the first
3016 visible item at the time the mouse button is released. This mode is particularly
3017 useful for moving one page at a time. When SnapOneItem is enabled, the ListView will
3018 show a stronger affinity to neighboring items when movement occurs. For example, a
3019 short drag that snaps back to the current item with SnapToItem might snap to a
3020 neighboring item with SnapOneItem.
3021
3022 \c snapMode does not affect the \l currentIndex. To update the
3023 \l currentIndex as the list is moved, set \l highlightRangeMode
3024 to \c ListView.StrictlyEnforceRange.
3025
3026 \sa highlightRangeMode
3027*/
3028QQuickListView::SnapMode QQuickListView::snapMode() const
3029{
3030 Q_D(const QQuickListView);
3031 return d->snapMode;
3032}
3033
3034void QQuickListView::setSnapMode(SnapMode mode)
3035{
3036 Q_D(QQuickListView);
3037 if (d->snapMode != mode) {
3038 d->snapMode = mode;
3039 emit snapModeChanged();
3040 d->fixupPosition();
3041 }
3042}
3043
3044
3045/*!
3046 \qmlproperty Component QtQuick::ListView::footer
3047 This property holds the component to use as the footer.
3048
3049 An instance of the footer component is created for each view. The
3050 footer is positioned at the end of the view, after any items. The
3051 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
3052
3053 \sa header, footerItem, {Stacking Order in ListView}
3054*/
3055
3056
3057/*!
3058 \qmlproperty Component QtQuick::ListView::header
3059 This property holds the component to use as the header.
3060
3061 An instance of the header component is created for each view. The
3062 header is positioned at the beginning of the view, before any items.
3063 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
3064
3065 \sa footer, headerItem, {Stacking Order in ListView}
3066*/
3067
3068/*!
3069 \qmlproperty Item QtQuick::ListView::headerItem
3070 This holds the header item created from the \l header component.
3071
3072 An instance of the header component is created for each view. The
3073 header is positioned at the beginning of the view, before any items.
3074 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
3075
3076 \sa header, footerItem, {Stacking Order in ListView}
3077*/
3078
3079/*!
3080 \qmlproperty Item QtQuick::ListView::footerItem
3081 This holds the footer item created from the \l footer component.
3082
3083 An instance of the footer component is created for each view. The
3084 footer is positioned at the end of the view, after any items. The
3085 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
3086
3087 \sa footer, headerItem, {Stacking Order in ListView}
3088*/
3089
3090/*!
3091 \qmlproperty enumeration QtQuick::ListView::headerPositioning
3092 \since Qt 5.4
3093
3094 This property determines the positioning of the \l{headerItem}{header item}.
3095
3096 \value ListView.InlineHeader (default) The header is positioned at the beginning
3097 of the content and moves together with the content like an ordinary item.
3098
3099 \value ListView.OverlayHeader The header is positioned at the beginning of the view.
3100
3101 \value ListView.PullBackHeader The header is positioned at the beginning of the view.
3102 The header can be pushed away by moving the content forwards, and pulled back by
3103 moving the content backwards.
3104
3105 \note This property has no effect on the \l {QQuickItem::z}{stacking order}
3106 of the header. For example, if the header should be shown above the
3107 \l delegate items when using \c ListView.OverlayHeader, its Z value
3108 should be set to a value higher than that of the delegates. For more
3109 information, see \l {Stacking Order in ListView}.
3110
3111 \note If \c headerPositioning is not set to \c ListView.InlineHeader, the
3112 user cannot press and flick the list from the header. In any case, the
3113 \l{headerItem}{header item} may contain items or event handlers that
3114 provide custom handling of mouse or touch input.
3115*/
3116QQuickListView::HeaderPositioning QQuickListView::headerPositioning() const
3117{
3118 Q_D(const QQuickListView);
3119 return d->headerPositioning;
3120}
3121
3122void QQuickListView::setHeaderPositioning(QQuickListView::HeaderPositioning positioning)
3123{
3124 Q_D(QQuickListView);
3125 if (d->headerPositioning != positioning) {
3126 d->applyPendingChanges();
3127 d->headerPositioning = positioning;
3128 if (isComponentComplete()) {
3129 d->updateHeader();
3130 d->updateViewport();
3131 d->fixupPosition();
3132 }
3133 emit headerPositioningChanged();
3134 }
3135}
3136
3137/*!
3138 \qmlproperty enumeration QtQuick::ListView::footerPositioning
3139 \since Qt 5.4
3140
3141 This property determines the positioning of the \l{footerItem}{footer item}.
3142
3143 \value ListView.InlineFooter (default) The footer is positioned at the end
3144 of the content and moves together with the content like an ordinary item.
3145
3146 \value ListView.OverlayFooter The footer is positioned at the end of the view.
3147
3148 \value ListView.PullBackFooter The footer is positioned at the end of the view.
3149 The footer can be pushed away by moving the content backwards, and pulled back by
3150 moving the content forwards.
3151
3152 \note This property has no effect on the \l {QQuickItem::z}{stacking order}
3153 of the footer. For example, if the footer should be shown above the
3154 \l delegate items when using \c ListView.OverlayFooter, its Z value
3155 should be set to a value higher than that of the delegates. For more
3156 information, see \l {Stacking Order in ListView}.
3157
3158 \note If \c footerPositioning is not set to \c ListView.InlineFooter, the
3159 user cannot press and flick the list from the footer. In any case, the
3160 \l{footerItem}{footer item} may contain items or event handlers that
3161 provide custom handling of mouse or touch input.
3162*/
3163QQuickListView::FooterPositioning QQuickListView::footerPositioning() const
3164{
3165 Q_D(const QQuickListView);
3166 return d->footerPositioning;
3167}
3168
3169void QQuickListView::setFooterPositioning(QQuickListView::FooterPositioning positioning)
3170{
3171 Q_D(QQuickListView);
3172 if (d->footerPositioning != positioning) {
3173 d->applyPendingChanges();
3174 d->footerPositioning = positioning;
3175 if (isComponentComplete()) {
3176 d->updateFooter();
3177 d->updateViewport();
3178 d->fixupPosition();
3179 }
3180 emit footerPositioningChanged();
3181 }
3182}
3183
3184/*!
3185 \qmlproperty Transition QtQuick::ListView::populate
3186
3187 This property holds the transition to apply to the items that are initially created
3188 for a view.
3189
3190 It is applied to all items that are created when:
3191
3192 \list
3193 \li The view is first created
3194 \li The view's \l model changes in such a way that the visible delegates are completely replaced
3195 \li The view's \l model is \l {QAbstractItemModel::beginResetModel()}{reset}, if the model is a
3196 QAbstractItemModel subclass
3197 \endlist
3198
3199 For example, here is a view that specifies such a transition:
3200
3201 \code
3202 ListView {
3203 ...
3204 populate: Transition {
3205 NumberAnimation { properties: "x,y"; duration: 1000 }
3206 }
3207 }
3208 \endcode
3209
3210 When the view is initialized, the view will create all the necessary items for the view,
3211 then animate them to their correct positions within the view over one second.
3212
3213 However when scrolling the view later, the populate transition does not
3214 run, even though delegates are being instantiated as they become visible.
3215 When the model changes in a way that new delegates become visible, the
3216 \l add transition is the one that runs. So you should not depend on the
3217 \c populate transition to initialize properties in the delegate, because it
3218 does not apply to every delegate. If your animation sets the \c to value of
3219 a property, the property should initially have the \c to value, and the
3220 animation should set the \c from value in case it is animated:
3221
3222 \code
3223 ListView {
3224 ...
3225 delegate: Rectangle {
3226 opacity: 1 // not necessary because it's the default
3227 }
3228 populate: Transition {
3229 NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
3230 }
3231 }
3232 \endcode
3233
3234 For more details and examples on how to use view transitions, see the ViewTransition
3235 documentation.
3236
3237 \sa add, ViewTransition
3238*/
3239
3240/*!
3241 \qmlproperty Transition QtQuick::ListView::add
3242
3243 This property holds the transition to apply to items that are added to the view.
3244
3245 For example, here is a view that specifies such a transition:
3246
3247 \code
3248 ListView {
3249 ...
3250 add: Transition {
3251 NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
3252 }
3253 }
3254 \endcode
3255
3256 Whenever an item is added to the above view, the item will be animated from the position (100,100)
3257 to its final x,y position within the view, over one second. The transition only applies to
3258 the new items that are added to the view; it does not apply to the items below that are
3259 displaced by the addition of the new items. To animate the displaced items, set the \l displaced
3260 or \l addDisplaced properties.
3261
3262 For more details and examples on how to use view transitions, see the ViewTransition
3263 documentation.
3264
3265 \note This transition is not applied to the items that are created when the view is initially
3266 populated, or when the view's \l model changes. (In those cases, the \l populate transition is
3267 applied instead.) Additionally, this transition should \e not animate the height of the new item;
3268 doing so will cause any items beneath the new item to be laid out at the wrong position. Instead,
3269 the height can be animated within the \l {add}{onAdd} handler in the delegate.
3270
3271 \sa addDisplaced, populate, ViewTransition
3272*/
3273
3274/*!
3275 \qmlproperty Transition QtQuick::ListView::addDisplaced
3276
3277 This property holds the transition to apply to items within the view that are displaced by
3278 the addition of other items to the view.
3279
3280 For example, here is a view that specifies such a transition:
3281
3282 \code
3283 ListView {
3284 ...
3285 addDisplaced: Transition {
3286 NumberAnimation { properties: "x,y"; duration: 1000 }
3287 }
3288 }
3289 \endcode
3290
3291 Whenever an item is added to the above view, all items beneath the new item are displaced, causing
3292 them to move down (or sideways, if horizontally orientated) within the view. As this
3293 displacement occurs, the items' movement to their new x,y positions within the view will be
3294 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3295 the new item that has been added to the view; to animate the added items, set the \l add
3296 property.
3297
3298 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3299 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3300 if it is not necessary to specify different transitions depending on whether an item is displaced
3301 by an add, move or remove operation, consider setting the \l displaced property instead.
3302
3303 For more details and examples on how to use view transitions, see the ViewTransition
3304 documentation.
3305
3306 \note This transition is not applied to the items that are created when the view is initially
3307 populated, or when the view's \l model changes. In those cases, the \l populate transition is
3308 applied instead.
3309
3310 \sa displaced, add, populate, ViewTransition
3311*/
3312
3313/*!
3314 \qmlproperty Transition QtQuick::ListView::move
3315
3316 This property holds the transition to apply to items in the view that are being moved due
3317 to a move operation in the view's \l model.
3318
3319 For example, here is a view that specifies such a transition:
3320
3321 \code
3322 ListView {
3323 ...
3324 move: Transition {
3325 NumberAnimation { properties: "x,y"; duration: 1000 }
3326 }
3327 }
3328 \endcode
3329
3330 Whenever the \l model performs a move operation to move a particular set of indexes, the
3331 respective items in the view will be animated to their new positions in the view over one
3332 second. The transition only applies to the items that are the subject of the move operation
3333 in the model; it does not apply to items below them that are displaced by the move operation.
3334 To animate the displaced items, set the \l displaced or \l moveDisplaced properties.
3335
3336 For more details and examples on how to use view transitions, see the ViewTransition
3337 documentation.
3338
3339 \sa moveDisplaced, ViewTransition
3340*/
3341
3342/*!
3343 \qmlproperty Transition QtQuick::ListView::moveDisplaced
3344
3345 This property holds the transition to apply to items that are displaced by a move operation in
3346 the view's \l model.
3347
3348 For example, here is a view that specifies such a transition:
3349
3350 \code
3351 ListView {
3352 ...
3353 moveDisplaced: Transition {
3354 NumberAnimation { properties: "x,y"; duration: 1000 }
3355 }
3356 }
3357 \endcode
3358
3359 Whenever the \l model performs a move operation to move a particular set of indexes, the items
3360 between the source and destination indexes of the move operation are displaced, causing them
3361 to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this
3362 displacement occurs, the items' movement to their new x,y positions within the view will be
3363 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3364 the items that are the actual subjects of the move operation; to animate the moved items, set
3365 the \l move property.
3366
3367 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3368 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3369 if it is not necessary to specify different transitions depending on whether an item is displaced
3370 by an add, move or remove operation, consider setting the \l displaced property instead.
3371
3372 For more details and examples on how to use view transitions, see the ViewTransition
3373 documentation.
3374
3375 \sa displaced, move, ViewTransition
3376*/
3377
3378/*!
3379 \qmlproperty Transition QtQuick::ListView::remove
3380
3381 This property holds the transition to apply to items that are removed from the view.
3382
3383 For example, here is a view that specifies such a transition:
3384
3385 \code
3386 ListView {
3387 ...
3388 remove: Transition {
3389 ParallelAnimation {
3390 NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
3391 NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
3392 }
3393 }
3394 }
3395 \endcode
3396
3397 Whenever an item is removed from the above view, the item will be animated to the position (100,100)
3398 over one second, and in parallel will also change its opacity to 0. The transition
3399 only applies to the items that are removed from the view; it does not apply to the items below
3400 them that are displaced by the removal of the items. To animate the displaced items, set the
3401 \l displaced or \l removeDisplaced properties.
3402
3403 Note that by the time the transition is applied, the item has already been removed from the
3404 model; any references to the model data for the removed index will not be valid.
3405
3406 Additionally, if the \l delayRemove attached property has been set for a delegate item, the
3407 remove transition will not be applied until \l delayRemove becomes false again.
3408
3409 For more details and examples on how to use view transitions, see the ViewTransition
3410 documentation.
3411
3412 \sa removeDisplaced, ViewTransition
3413*/
3414
3415/*!
3416 \qmlproperty Transition QtQuick::ListView::removeDisplaced
3417
3418 This property holds the transition to apply to items in the view that are displaced by the
3419 removal of other items in the view.
3420
3421 For example, here is a view that specifies such a transition:
3422
3423 \code
3424 ListView {
3425 ...
3426 removeDisplaced: Transition {
3427 NumberAnimation { properties: "x,y"; duration: 1000 }
3428 }
3429 }
3430 \endcode
3431
3432 Whenever an item is removed from the above view, all items beneath it are displaced, causing
3433 them to move upwards (or sideways, if horizontally orientated) within the view. As this
3434 displacement occurs, the items' movement to their new x,y positions within the view will be
3435 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3436 the item that has actually been removed from the view; to animate the removed items, set the
3437 \l remove property.
3438
3439 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3440 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3441 if it is not necessary to specify different transitions depending on whether an item is displaced
3442 by an add, move or remove operation, consider setting the \l displaced property instead.
3443
3444 For more details and examples on how to use view transitions, see the ViewTransition
3445 documentation.
3446
3447 \sa displaced, remove, ViewTransition
3448*/
3449
3450/*!
3451 \qmlproperty Transition QtQuick::ListView::displaced
3452 This property holds the generic transition to apply to items that have been displaced by
3453 any model operation that affects the view.
3454
3455 This is a convenience for specifying the generic transition to be applied to any items
3456 that are displaced by an add, move or remove operation, without having to specify the
3457 individual addDisplaced, moveDisplaced and removeDisplaced properties. For example, here
3458 is a view that specifies a displaced transition:
3459
3460 \code
3461 ListView {
3462 ...
3463 displaced: Transition {
3464 NumberAnimation { properties: "x,y"; duration: 1000 }
3465 }
3466 }
3467 \endcode
3468
3469 When any item is added, moved or removed within the above view, the items below it are
3470 displaced, causing them to move down (or sideways, if horizontally orientated) within the
3471 view. As this displacement occurs, the items' movement to their new x,y positions within
3472 the view will be animated by a NumberAnimation over one second, as specified.
3473
3474 If a view specifies this generic displaced transition as well as a specific addDisplaced,
3475 moveDisplaced or removeDisplaced transition, the more specific transition will be used
3476 instead of the generic displaced transition when the relevant operation occurs, providing that
3477 the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled}
3478 to false). If it has indeed been disabled, the generic displaced transition is applied instead.
3479
3480 For more details and examples on how to use view transitions, see the ViewTransition
3481 documentation.
3482
3483 \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition
3484*/
3485
3486void QQuickListView::viewportMoved(Qt::Orientations orient)
3487{
3488 Q_D(QQuickListView);
3489 QQuickItemView::viewportMoved(orient);
3490
3491 if (!d->itemCount) {
3492 if (d->hasStickyHeader())
3493 d->updateHeader();
3494 if (d->hasStickyFooter())
3495 d->updateFooter();
3496 return;
3497 }
3498
3499 // Recursion can occur due to refill changing the content size.
3500 if (d->inViewportMoved)
3501 return;
3502 d->inViewportMoved = true;
3503
3504 if (yflick()) {
3505 if (d->isBottomToTop())
3506 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore;
3507 else
3508 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter;
3509 } else {
3510 if (d->isRightToLeft())
3511 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore;
3512 else
3513 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter;
3514 }
3515
3516 d->refillOrLayout();
3517
3518 // Set visibility of items to eliminate cost of items outside the visible area.
3519 qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning;
3520 qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd;
3521 for (FxViewItem *item : std::as_const(t&: d->visibleItems)) {
3522 if (item->item)
3523 QQuickItemPrivate::get(item: item->item)->setCulled(item->endPosition() < from || item->position() > to);
3524 }
3525 if (d->currentItem)
3526 QQuickItemPrivate::get(item: d->currentItem->item)->setCulled(d->currentItem->endPosition() < from || d->currentItem->position() > to);
3527
3528 if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving)
3529 d->moveReason = QQuickListViewPrivate::Mouse;
3530 if (d->moveReason != QQuickListViewPrivate::SetIndex) {
3531 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) {
3532 // reposition highlight
3533 qreal pos = d->highlight->position();
3534 qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
3535 if (pos > viewPos + d->highlightRangeEnd - d->highlight->size())
3536 pos = viewPos + d->highlightRangeEnd - d->highlight->size();
3537 if (pos < viewPos + d->highlightRangeStart)
3538 pos = viewPos + d->highlightRangeStart;
3539 if (pos != d->highlight->position()) {
3540 d->highlightPosAnimator->stop();
3541 static_cast<FxListItemSG*>(d->highlight.get())->setPosition(pos);
3542 } else {
3543 d->updateHighlight();
3544 }
3545
3546 // update current index
3547 if (FxViewItem *snapItem = d->snapItemAt(pos: d->highlight->position())) {
3548 if (snapItem->index >= 0 && snapItem->index != d->currentIndex)
3549 d->updateCurrent(modelIndex: snapItem->index);
3550 }
3551 }
3552 }
3553
3554 if ((d->hData.flicking || d->vData.flicking) && d->correctFlick && !d->inFlickCorrection) {
3555 d->inFlickCorrection = true;
3556 // Near an end and it seems that the extent has changed?
3557 // Recalculate the flick so that we don't end up in an odd position.
3558 if (yflick() && !d->vData.inOvershoot) {
3559 if (d->vData.velocity > 0) {
3560 const qreal minY = minYExtent();
3561 if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2)
3562 && minY != d->vData.flickTarget)
3563 d->flickY(eventType: QEvent::TouchUpdate, velocity: -d->vData.smoothVelocity.value());
3564 } else if (d->vData.velocity < 0) {
3565 const qreal maxY = maxYExtent();
3566 if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2)
3567 && maxY != d->vData.flickTarget)
3568 d->flickY(eventType: QEvent::TouchUpdate, velocity: -d->vData.smoothVelocity.value());
3569 }
3570 }
3571
3572 if (xflick() && !d->hData.inOvershoot) {
3573 if (d->hData.velocity > 0) {
3574 const qreal minX = minXExtent();
3575 if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2)
3576 && minX != d->hData.flickTarget)
3577 d->flickX(eventType: QEvent::TouchUpdate, velocity: -d->hData.smoothVelocity.value());
3578 } else if (d->hData.velocity < 0) {
3579 const qreal maxX = maxXExtent();
3580 if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2)
3581 && maxX != d->hData.flickTarget)
3582 d->flickX(eventType: QEvent::TouchUpdate, velocity: -d->hData.smoothVelocity.value());
3583 }
3584 }
3585 d->inFlickCorrection = false;
3586 }
3587 if (d->hasStickyHeader())
3588 d->updateHeader();
3589 if (d->hasStickyFooter())
3590 d->updateFooter();
3591 if (d->sectionCriteria) {
3592 d->updateCurrentSection();
3593 d->updateStickySections();
3594 }
3595 d->inViewportMoved = false;
3596}
3597
3598void QQuickListView::keyPressEvent(QKeyEvent *event)
3599{
3600 Q_D(QQuickListView);
3601 if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled)
3602 || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) {
3603 if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Left)
3604 || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Right)
3605 || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Up)
3606 || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Down)) {
3607 if (currentIndex() > 0 || (d->wrap && !event->isAutoRepeat())) {
3608 decrementCurrentIndex();
3609 event->accept();
3610 return;
3611 } else if (d->wrap) {
3612 event->accept();
3613 return;
3614 }
3615 } else if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Right)
3616 || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Left)
3617 || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Down)
3618 || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Up)) {
3619 if (currentIndex() < d->model->count() - 1 || (d->wrap && !event->isAutoRepeat())) {
3620 incrementCurrentIndex();
3621 event->accept();
3622 return;
3623 } else if (d->wrap) {
3624 event->accept();
3625 return;
3626 }
3627 }
3628 }
3629 event->ignore();
3630 QQuickItemView::keyPressEvent(event);
3631}
3632
3633void QQuickListView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
3634{
3635 Q_D(QQuickListView);
3636
3637 if (d->model) {
3638 // When the view changes size, we force the pool to
3639 // shrink by releasing all pooled items.
3640 d->model->drainReusableItemsPool(maxPoolTime: 0);
3641 }
3642
3643 if (d->isRightToLeft()) {
3644 // maintain position relative to the right edge
3645 qreal dx = newGeometry.width() - oldGeometry.width();
3646 setContentX(contentX() - dx);
3647 } else if (d->isBottomToTop()) {
3648 // maintain position relative to the bottom edge
3649 qreal dy = newGeometry.height() - oldGeometry.height();
3650 setContentY(contentY() - dy);
3651 }
3652 QQuickItemView::geometryChange(newGeometry, oldGeometry);
3653}
3654
3655void QQuickListView::initItem(int index, QObject *object)
3656{
3657 QQuickItemView::initItem(index, item: object);
3658
3659 // setting the view from the FxViewItem wrapper is too late if the delegate
3660 // needs access to the view in Component.onCompleted
3661 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
3662 if (item) {
3663 QQuickListViewAttached *attached = static_cast<QQuickListViewAttached *>(
3664 qmlAttachedPropertiesObject<QQuickListView>(obj: item));
3665 if (attached)
3666 attached->setView(this);
3667 }
3668}
3669
3670qreal QQuickListView::maxYExtent() const
3671{
3672 Q_D(const QQuickListView);
3673 if (d->layoutOrientation() == Qt::Horizontal && d->flickableDirection != HorizontalFlick)
3674 return QQuickFlickable::maxYExtent();
3675 return QQuickItemView::maxYExtent();
3676}
3677
3678qreal QQuickListView::maxXExtent() const
3679{
3680 Q_D(const QQuickListView);
3681 if (d->layoutOrientation() == Qt::Vertical && d->flickableDirection != VerticalFlick)
3682 return QQuickFlickable::maxXExtent();
3683 return QQuickItemView::maxXExtent();
3684}
3685
3686/*!
3687 \qmlmethod QtQuick::ListView::incrementCurrentIndex()
3688
3689 Increments the current index. The current index will wrap
3690 if keyNavigationWraps is true and it is currently at the end.
3691 This method has no effect if the \l count is zero.
3692
3693 \b Note: methods should only be called after the Component has completed.
3694*/
3695void QQuickListView::incrementCurrentIndex()
3696{
3697 Q_D(QQuickListView);
3698 int count = d->model ? d->model->count() : 0;
3699 if (count && (currentIndex() < count - 1 || d->wrap)) {
3700 d->moveReason = QQuickListViewPrivate::SetIndex;
3701 int index = currentIndex()+1;
3702 setCurrentIndex((index >= 0 && index < count) ? index : 0);
3703 }
3704}
3705
3706/*!
3707 \qmlmethod QtQuick::ListView::decrementCurrentIndex()
3708
3709 Decrements the current index. The current index will wrap
3710 if keyNavigationWraps is true and it is currently at the beginning.
3711 This method has no effect if the \l count is zero.
3712
3713 \b Note: methods should only be called after the Component has completed.
3714*/
3715void QQuickListView::decrementCurrentIndex()
3716{
3717 Q_D(QQuickListView);
3718 int count = d->model ? d->model->count() : 0;
3719 if (count && (currentIndex() > 0 || d->wrap)) {
3720 d->moveReason = QQuickListViewPrivate::SetIndex;
3721 int index = currentIndex()-1;
3722 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
3723 }
3724}
3725
3726void QQuickListViewPrivate::updateSectionCriteria()
3727{
3728 Q_Q(QQuickListView);
3729 if (q->isComponentComplete() && model) {
3730 QList<QByteArray> roles;
3731 if (sectionCriteria && !sectionCriteria->property().isEmpty())
3732 roles << sectionCriteria->property().toUtf8();
3733 model->setWatchedRoles(roles);
3734 updateSections();
3735 if (itemCount)
3736 forceLayoutPolish();
3737 }
3738}
3739
3740bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
3741{
3742 Q_Q(QQuickListView);
3743#if QT_CONFIG(quick_viewtransitions)
3744 Q_UNUSED(movingIntoView)
3745#endif
3746 int modelIndex = change.index;
3747 int count = change.count;
3748
3749 if (q->size().isNull() && visibleItems.isEmpty())
3750 return false;
3751
3752 qreal tempPos = isContentFlowReversed() ? -position()-size() : position();
3753 int index = visibleItems.size() ? mapFromModel(modelIndex) : 0;
3754 qreal lastVisiblePos = buffer + displayMarginEnd + tempPos + size();
3755
3756 if (index < 0) {
3757 int i = visibleItems.size() - 1;
3758 while (i > 0 && visibleItems.at(i)->index == -1)
3759 --i;
3760 if (i == 0 && visibleItems.constFirst()->index == -1) {
3761 // there are no visible items except items marked for removal
3762 index = visibleItems.size();
3763 } else if (visibleItems.at(i)->index + 1 == modelIndex
3764 && visibleItems.at(i)->endPosition() <= lastVisiblePos) {
3765 // Special case of appending an item to the model.
3766 index = visibleItems.size();
3767 } else {
3768 if (modelIndex < visibleIndex) {
3769 // Insert before visible items
3770 visibleIndex += count;
3771 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
3772 if (item->index != -1 && item->index >= modelIndex)
3773 item->index += count;
3774 }
3775 }
3776 return true;
3777 }
3778 }
3779
3780 // index can be the next item past the end of the visible items list (i.e. appended)
3781 qreal pos = 0;
3782 if (visibleItems.size()) {
3783 pos = index < visibleItems.size() ? visibleItems.at(i: index)->position()
3784 : visibleItems.constLast()->endPosition() + spacing;
3785 }
3786
3787 // Update the indexes of the following visible items.
3788 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
3789 if (item->index != -1 && item->index >= modelIndex) {
3790 item->index += count;
3791#if QT_CONFIG(quick_viewtransitions)
3792 if (change.isMove())
3793 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: false);
3794 else
3795 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: false);
3796#endif
3797 }
3798 }
3799
3800 bool visibleAffected = false;
3801 if (insertResult->visiblePos.isValid() && pos < insertResult->visiblePos) {
3802 // Insert items before the visible item.
3803 int insertionIdx = index;
3804 qreal from = tempPos - displayMarginBeginning - buffer;
3805
3806 if (insertionIdx < visibleIndex) {
3807 if (pos >= from) {
3808 // items won't be visible, just note the size for repositioning
3809 insertResult->sizeChangesBeforeVisiblePos += count * (averageSize + spacing);
3810 }
3811 } else {
3812 MutableModelIterator it(model, modelIndex + count - 1, modelIndex -1);
3813 for (; it.hasNext() && pos >= from; it.next()) {
3814 // item is before first visible e.g. in cache buffer
3815 FxViewItem *item = nullptr;
3816 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: it.index))))
3817 item->index = it.index;
3818 if (!item)
3819 item = createItem(modelIndex: it.index, incubationMode: QQmlIncubator::Synchronous);
3820 if (!item)
3821 return false;
3822 if (it.removedAtIndex)
3823 continue;
3824
3825 visibleAffected = true;
3826 visibleItems.insert(i: insertionIdx, t: item);
3827 if (insertionIdx == 0)
3828 insertResult->changedFirstItem = true;
3829 if (!change.isMove()) {
3830 addedItems->append(t: item);
3831#if QT_CONFIG(quick_viewtransitions)
3832 if (transitioner)
3833 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
3834 else
3835#endif
3836 static_cast<FxListItemSG *>(item)->setPosition(pos, immediate: true);
3837 }
3838 insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing;
3839 pos -= item->size() + spacing;
3840 index++;
3841 }
3842 }
3843
3844 int firstOkIdx = -1;
3845 for (int i = 0; i <= insertionIdx && i < visibleItems.size() - 1; i++) {
3846 if (visibleItems.at(i)->index + 1 != visibleItems.at(i: i + 1)->index) {
3847 firstOkIdx = i + 1;
3848 break;
3849 }
3850 }
3851 for (int i = 0; i < firstOkIdx; i++) {
3852 FxViewItem *nvItem = visibleItems.takeFirst();
3853 addedItems->removeOne(t: nvItem);
3854 removeItem(item: nvItem);
3855 }
3856
3857 } else {
3858 MutableModelIterator it(model, modelIndex, modelIndex + count);
3859 for (; it.hasNext() && pos <= lastVisiblePos; it.next()) {
3860 visibleAffected = true;
3861 FxViewItem *item = nullptr;
3862 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: it.index))))
3863 item->index = it.index;
3864#if QT_CONFIG(quick_viewtransitions)
3865 bool newItem = !item;
3866#endif
3867 it.removedAtIndex = false;
3868 if (!item)
3869 item = createItem(modelIndex: it.index, incubationMode: QQmlIncubator::Synchronous);
3870 if (!item)
3871 return false;
3872 if (it.removedAtIndex) {
3873 releaseItem(item, reusableFlag);
3874 continue;
3875 }
3876
3877 if (index < visibleItems.size())
3878 visibleItems.insert(i: index, t: item);
3879 else // special case of appending an item to the model - as above
3880 visibleItems.append(t: item);
3881 if (index == 0)
3882 insertResult->changedFirstItem = true;
3883 if (change.isMove()) {
3884 // we know this is a move target, since move displaced items that are
3885 // shuffled into view due to a move would be added in refill()
3886#if QT_CONFIG(quick_viewtransitions)
3887 if (newItem && transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: true))
3888 movingIntoView->append(t: MovedItem(item, change.moveKey(index: item->index)));
3889#endif
3890 } else {
3891 addedItems->append(t: item);
3892#if QT_CONFIG(quick_viewtransitions)
3893 if (transitioner)
3894 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
3895 else
3896#endif
3897 static_cast<FxListItemSG *>(item)->setPosition(pos, immediate: true);
3898 }
3899 insertResult->sizeChangesAfterVisiblePos += item->size() + spacing;
3900 pos += item->size() + spacing;
3901 ++index;
3902 }
3903 it.disconnect();
3904
3905 if (0 < index && index < visibleItems.size()) {
3906 FxViewItem *prevItem = visibleItems.at(i: index - 1);
3907 FxViewItem *item = visibleItems.at(i: index);
3908 if (prevItem->index != item->index - 1) {
3909 int i = index;
3910#if QT_CONFIG(quick_viewtransitions)
3911 qreal prevPos = prevItem->position();
3912#endif
3913 while (i < visibleItems.size()) {
3914 FxListItemSG *nvItem = static_cast<FxListItemSG *>(visibleItems.takeLast());
3915 insertResult->sizeChangesAfterVisiblePos -= nvItem->size() + spacing;
3916 addedItems->removeOne(t: nvItem);
3917#if QT_CONFIG(quick_viewtransitions)
3918 if (nvItem->transitionScheduledOrRunning())
3919 nvItem->setPosition(pos: prevPos + (nvItem->index - prevItem->index) * averageSize);
3920#endif
3921 removeItem(item: nvItem);
3922 }
3923 }
3924 }
3925 }
3926
3927 updateVisibleIndex();
3928
3929 return visibleAffected;
3930}
3931
3932#if QT_CONFIG(quick_viewtransitions)
3933void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
3934{
3935 Q_UNUSED(insertionResult);
3936
3937 if (!transitioner)
3938 return;
3939
3940 int markerItemIndex = -1;
3941 for (int i=0; i<visibleItems.size(); i++) {
3942 if (visibleItems.at(i)->index == afterModelIndex) {
3943 markerItemIndex = i;
3944 break;
3945 }
3946 }
3947 if (markerItemIndex < 0)
3948 return;
3949
3950 const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
3951 qreal sizeRemoved = -removalResult.sizeChangesAfterVisiblePos
3952 - (removalResult.countChangeAfterVisibleItems * (averageSize + spacing));
3953
3954 for (int i=markerItemIndex+1; i<visibleItems.size(); i++) {
3955 FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems.at(i));
3956 if (listItem->position() >= viewEndPos)
3957 break;
3958 if (!listItem->transitionScheduledOrRunning()) {
3959 qreal pos = listItem->position();
3960 listItem->setPosition(pos: pos - sizeRemoved);
3961 listItem->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
3962 listItem->setPosition(pos);
3963 }
3964 }
3965}
3966#endif
3967
3968/*!
3969 \qmlmethod QtQuick::ListView::positionViewAtIndex(int index, PositionMode mode)
3970
3971 Positions the view such that the \a index is at the position specified by \a mode:
3972
3973 \value ListView.Beginning position item at the top (or left for horizontal orientation) of the view.
3974 \value ListView.Center position item in the center of the view.
3975 \value ListView.End position item at bottom (or right for horizontal orientation) of the view.
3976 \value ListView.Visible if any part of the item is visible then take no action, otherwise
3977 bring the item into view.
3978 \value ListView.Contain ensure the entire item is visible. If the item is larger than the view,
3979 the item is positioned at the top (or left for horizontal orientation) of the view.
3980 \value ListView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid
3981 if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled via \l snapMode.
3982
3983 If positioning the view at \a index would cause empty space to be displayed at
3984 the beginning or end of the view, the view will be positioned at the boundary.
3985
3986 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
3987 at a particular index. This is unreliable since removing items from the start
3988 of the list does not cause all other items to be repositioned, and because
3989 the actual start of the view can vary based on the size of the delegates.
3990 The correct way to bring an item into view is with \c positionViewAtIndex.
3991
3992 \b Note: methods should only be called after the Component has completed. To position
3993 the view at startup, this method should be called by Component.onCompleted. For
3994 example, to position the view at the end:
3995
3996 \code
3997 Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning)
3998 \endcode
3999*/
4000
4001/*!
4002 \qmlmethod QtQuick::ListView::positionViewAtBeginning()
4003 \qmlmethod QtQuick::ListView::positionViewAtEnd()
4004
4005 Positions the view at the beginning or end, taking into account any header or footer.
4006
4007 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
4008 at a particular index. This is unreliable since removing items from the start
4009 of the list does not cause all other items to be repositioned, and because
4010 the actual start of the view can vary based on the size of the delegates.
4011
4012 \b Note: methods should only be called after the Component has completed. To position
4013 the view at startup, this method should be called by Component.onCompleted. For
4014 example, to position the view at the end on startup:
4015
4016 \code
4017 Component.onCompleted: positionViewAtEnd()
4018 \endcode
4019*/
4020
4021/*!
4022 \qmlmethod int QtQuick::ListView::indexAt(real x, real y)
4023
4024 Returns the index of the visible item containing the point \a x, \a y in content
4025 coordinates. If there is no item at the point specified, or the item is
4026 not visible -1 is returned.
4027
4028 If the item is outside the visible area, -1 is returned, regardless of
4029 whether an item will exist at that point when scrolled into view.
4030
4031 \b Note: methods should only be called after the Component has completed.
4032*/
4033
4034/*!
4035 \qmlmethod Item QtQuick::ListView::itemAt(real x, real y)
4036
4037 Returns the visible item containing the point \a x, \a y in content
4038 coordinates. If there is no item at the point specified, or the item is
4039 not visible null is returned.
4040
4041 If the item is outside the visible area, null is returned, regardless of
4042 whether an item will exist at that point when scrolled into view.
4043
4044 \b Note: methods should only be called after the Component has completed.
4045*/
4046
4047/*!
4048 \qmlmethod Item QtQuick::ListView::itemAtIndex(int index)
4049
4050 Returns the item for \a index. If there is no item for that index, for example
4051 because it has not been created yet, or because it has been panned out of
4052 the visible area and removed from the cache, null is returned.
4053
4054 \b Note: this method should only be called after the Component has completed.
4055 The returned value should also not be stored since it can turn to null
4056 as soon as control goes out of the calling scope, if the view releases that item.
4057
4058 \since 5.13
4059*/
4060
4061/*!
4062 \qmlmethod QtQuick::ListView::forceLayout()
4063
4064 Responding to changes in the model is usually batched to happen only once
4065 per frame. This means that inside script blocks it is possible for the
4066 underlying model to have changed, but the ListView has not caught up yet.
4067
4068 This method forces the ListView to immediately respond to any outstanding
4069 changes in the model.
4070
4071 \since 5.1
4072
4073 \b Note: methods should only be called after the Component has completed.
4074*/
4075
4076QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj)
4077{
4078 return new QQuickListViewAttached(obj);
4079}
4080
4081/*! \internal
4082 Prevents clicking or dragging through floating headers (QTBUG-74046).
4083*/
4084bool QQuickListViewPrivate::wantsPointerEvent(const QPointerEvent *event)
4085{
4086 Q_Q(const QQuickListView);
4087 bool ret = true;
4088
4089 QPointF pos = event->points().first().position();
4090 if (!pos.isNull()) {
4091 if (auto header = q->headerItem()) {
4092 if (q->headerPositioning() != QQuickListView::InlineHeader &&
4093 header->contains(point: q->mapToItem(item: header, point: pos)))
4094 ret = false;
4095 }
4096 if (auto footer = q->footerItem()) {
4097 if (q->footerPositioning() != QQuickListView::InlineFooter &&
4098 footer->contains(point: q->mapToItem(item: footer, point: pos)))
4099 ret = false;
4100 }
4101 }
4102
4103 switch (event->type()) {
4104 case QEvent::MouseButtonPress:
4105 wantedMousePress = ret;
4106 break;
4107 case QEvent::MouseMove:
4108 ret = wantedMousePress;
4109 break;
4110 default:
4111 break;
4112 }
4113
4114 qCDebug(lcEvents) << q << (ret ? "WANTS" : "DOESN'T want") << event;
4115 return ret;
4116}
4117
4118QT_END_NAMESPACE
4119
4120#include "moc_qquicklistview_p.cpp"
4121

source code of qtdeclarative/src/quick/items/qquicklistview.cpp