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_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 qreal tempPosition = isContentFlowReversed() ? -position()-size() : position();
1671 if (snapMode == QQuickListView::SnapOneItem && moveReason == Mouse) {
1672 // if we've been dragged < averageSize/2 then bias towards the next item
1673 qreal dist = data.move.value() - data.pressPos;
1674 qreal bias = 0;
1675 if (data.velocity > 0 && dist > QML_FLICK_SNAPONETHRESHOLD && dist < averageSize/2)
1676 bias = averageSize/2;
1677 else if (data.velocity < 0 && dist < -QML_FLICK_SNAPONETHRESHOLD && dist > -averageSize/2)
1678 bias = -averageSize/2;
1679 if (isContentFlowReversed())
1680 bias = -bias;
1681 tempPosition -= bias;
1682 }
1683
1684 qreal snapOffset = 0;
1685 qreal overlayHeaderOffset = 0;
1686 bool isHeaderWithinBounds = false;
1687 if (header) {
1688 qreal visiblePartOfHeader = header->position() + header->size() - tempPosition;
1689 isHeaderWithinBounds = visiblePartOfHeader > 0;
1690 switch (headerPositioning) {
1691 case QQuickListView::OverlayHeader:
1692 snapOffset = header->size();
1693 overlayHeaderOffset = header->size();
1694 break;
1695 case QQuickListView::InlineHeader:
1696 if (isHeaderWithinBounds && tempPosition < originPosition())
1697 // For the inline header, we want to snap to the first item
1698 // if we're more than halfway down the inline header.
1699 // So if we look for an item halfway down of the header
1700 snapOffset = header->size() / 2;
1701 break;
1702 case QQuickListView::PullBackHeader:
1703 desiredHeaderVisible = visiblePartOfHeader > header->size()/2;
1704 if (qFuzzyCompare(p1: header->position(), p2: tempPosition)) {
1705 // header was pulled down; make sure it remains visible and snap items to bottom of header
1706 snapOffset = header->size();
1707 } else if (desiredHeaderVisible) {
1708 // More than 50% of the header is shown. Show it fully.
1709 // Scroll the view so the next item snaps to the header.
1710 snapOffset = header->size();
1711 overlayHeaderOffset = header->size();
1712 }
1713 break;
1714 }
1715 }
1716
1717 // If there are pending changes, the item returned from snapItemAt might get deleted as
1718 // soon as applyPendingChanges() is called (from e.g. updateHighlight()).
1719 // Therefore, apply the pending changes before we call snapItemAt()
1720 if (strictHighlightRange)
1721 updateHighlight();
1722
1723 FxViewItem *topItem = snapItemAt(pos: tempPosition + snapOffset + highlightRangeStart);
1724 FxViewItem *bottomItem = snapItemAt(pos: tempPosition + snapOffset + highlightRangeEnd);
1725 if (strictHighlightRange && currentItem) {
1726 // StrictlyEnforceRange always keeps an item in range
1727 if (!topItem || (topItem->index != currentIndex && fixupMode == Immediate))
1728 topItem = currentItem;
1729 if (!bottomItem || (bottomItem->index != currentIndex && fixupMode == Immediate))
1730 bottomItem = currentItem;
1731 }
1732
1733 qreal pos = 0;
1734 bool isInBounds = -position() > maxExtent && -position() <= minExtent;
1735
1736 if (header && !topItem && isInBounds) {
1737 // We are trying to pull back further than needed
1738 switch (headerPositioning) {
1739 case QQuickListView::OverlayHeader:
1740 pos = startPosition() - overlayHeaderOffset;
1741 break;
1742 case QQuickListView::InlineHeader:
1743 pos = isContentFlowReversed() ? header->size() - size() : header->position();
1744 break;
1745 case QQuickListView::PullBackHeader:
1746 pos = isContentFlowReversed() ? -size() : startPosition();
1747 break;
1748 }
1749 } else if (topItem && (isInBounds || strictHighlightRange)) {
1750 if (topItem->index == 0 && header && !hasStickyHeader() && tempPosition+highlightRangeStart < header->position()+header->size()/2 && !strictHighlightRange) {
1751 pos = isContentFlowReversed() ? -header->position() + highlightRangeStart - size() : (header->position() - highlightRangeStart + header->size());
1752 } else {
1753 if (header && headerPositioning == QQuickListView::PullBackHeader) {
1754 // We pulled down the header. If it isn't pulled all way down, we need to snap
1755 // the header.
1756 if (qFuzzyCompare(p1: tempPosition, p2: header->position())) {
1757 // It is pulled all way down. Scroll-snap the content, but not the header.
1758 if (isContentFlowReversed())
1759 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + snapOffset;
1760 else
1761 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - snapOffset;
1762 } else {
1763 // Header is not pulled all way down, make it completely visible or hide it.
1764 // Depends on how much of the header is visible.
1765 if (desiredHeaderVisible) {
1766 // More than half of the header is visible - show it.
1767 // Scroll so that the topItem is aligned to a fully visible header
1768 if (isContentFlowReversed())
1769 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + headerSize();
1770 else
1771 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - headerSize();
1772 } else {
1773 // Less than half is visible - hide the header. Scroll so
1774 // that the topItem is aligned to the top of the view
1775 if (isContentFlowReversed())
1776 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size();
1777 else
1778 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart;
1779 }
1780 }
1781
1782 headerNeedsSeparateFixup = isHeaderWithinBounds || desiredHeaderVisible;
1783 if (headerNeedsSeparateFixup) {
1784 // We need to animate the header independently if it starts visible or should end as visible,
1785 // since the header should not necessarily follow the content.
1786 // Store the desired viewport position.
1787 // Also store the header position so we know where to animate the header from (fixupHeaderPosition).
1788 // We deduce the desired header position from the desiredViewportPosition variable.
1789 pos = qBound(min: -minExtent, val: pos, max: -maxExtent);
1790 desiredViewportPosition = isContentFlowReversed() ? -pos - size() : pos;
1791
1792 FxListItemSG *headerItem = static_cast<FxListItemSG*>(header);
1793 fixupHeaderPosition = headerItem->position();
1794
1795 // follow the same fixup timeline
1796 QObjectPrivate::connect(sender: &timeline, signal: &QQuickTimeLine::updated, receiverPrivate: this, slot: &QQuickListViewPrivate::fixupHeader);
1797 QObjectPrivate::connect(sender: &timeline, signal: &QQuickTimeLine::completed, receiverPrivate: this, slot: &QQuickListViewPrivate::fixupHeaderCompleted);
1798 }
1799 } else if (isContentFlowReversed()) {
1800 pos = -static_cast<FxListItemSG*>(topItem)->itemPosition() + highlightRangeStart - size() + overlayHeaderOffset;
1801 } else {
1802 pos = static_cast<FxListItemSG*>(topItem)->itemPosition() - highlightRangeStart - overlayHeaderOffset;
1803 }
1804 }
1805 } else if (bottomItem && isInBounds) {
1806 if (isContentFlowReversed())
1807 pos = -static_cast<FxListItemSG*>(bottomItem)->itemPosition() + highlightRangeEnd - size() + overlayHeaderOffset;
1808 else
1809 pos = static_cast<FxListItemSG*>(bottomItem)->itemPosition() - highlightRangeEnd - overlayHeaderOffset;
1810 } else {
1811 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1812 return;
1813 }
1814 // If we have the CurrentLabelAtStart flag set, then we need to consider
1815 // the section size while calculating the position
1816 if (sectionCriteria
1817 && (sectionCriteria->labelPositioning() & QQuickViewSection::CurrentLabelAtStart)
1818 && currentSectionItem) {
1819 auto sectionSize = (orient == QQuickListView::Vertical) ? currentSectionItem->height()
1820 : currentSectionItem->width();
1821 if (isContentFlowReversed())
1822 pos += sectionSize;
1823 else
1824 pos -= sectionSize;
1825 }
1826
1827 pos = qBound(min: -minExtent, val: pos, max: -maxExtent);
1828
1829 qreal dist = qAbs(t: data.move + pos);
1830 if (dist >= 0) {
1831 // Even if dist == 0 we still start the timeline, because we use the same timeline for
1832 // moving the header. And we might need to move the header while the content does not
1833 // need moving
1834 timeline.reset(data.move);
1835 if (fixupMode != Immediate) {
1836 timeline.move(data.move, destination: -pos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
1837 data.fixingUp = true;
1838 } else {
1839 timeline.set(data.move, -pos);
1840 }
1841 vTime = timeline.time();
1842 }
1843 } else if (currentItem && strictHighlightRange && moveReason != QQuickListViewPrivate::SetIndex) {
1844 updateHighlight();
1845 qreal pos = static_cast<FxListItemSG*>(currentItem)->itemPosition();
1846 if (viewPos < pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd)
1847 viewPos = pos + static_cast<FxListItemSG*>(currentItem)->itemSize() - highlightRangeEnd;
1848 if (viewPos > pos - highlightRangeStart)
1849 viewPos = pos - highlightRangeStart;
1850 if (isContentFlowReversed())
1851 viewPos = -viewPos-size();
1852
1853 timeline.reset(data.move);
1854 if (viewPos != position()) {
1855 if (fixupMode != Immediate) {
1856 if (fixupMode == ExtentChanged && data.fixingUp)
1857 timeline.move(data.move, destination: -viewPos, QEasingCurve(QEasingCurve::OutQuad), time: fixupDuration/2);
1858 else
1859 timeline.move(data.move, destination: -viewPos, QEasingCurve(QEasingCurve::InOutQuad), time: fixupDuration/2);
1860 data.fixingUp = true;
1861 } else {
1862 timeline.set(data.move, -viewPos);
1863 }
1864 }
1865 vTime = timeline.time();
1866 } else {
1867 QQuickItemViewPrivate::fixup(data, minExtent, maxExtent);
1868 }
1869 data.inOvershoot = false;
1870 fixupMode = Normal;
1871}
1872
1873bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize,
1874 QQuickTimeLineCallback::Callback fixupCallback, QEvent::Type eventType, qreal velocity)
1875{
1876 data.fixingUp = false;
1877 moveReason = Mouse;
1878 if ((!haveHighlightRange || highlightRange != QQuickListView::StrictlyEnforceRange) && snapMode == QQuickListView::NoSnap) {
1879 correctFlick = true;
1880 return QQuickItemViewPrivate::flick(data, minExtent, maxExtent, vSize, fixupCallback, eventType, velocity);
1881 }
1882 qreal maxDistance = 0;
1883 const qreal dataValue =
1884 isContentFlowReversed() ? -data.move.value() + size() : data.move.value();
1885
1886 // -ve velocity means list is moving up/left
1887 if (velocity > 0) {
1888 if (data.move.value() < minExtent) {
1889 if (snapMode == QQuickListView::SnapOneItem && !hData.flicking && !vData.flicking) {
1890 // averageSize/2 + 1 - next item
1891 qreal bias = averageSize / 2 + 1 - (pressed ? data.pressPos : 0);
1892 if (isContentFlowReversed())
1893 bias = -bias;
1894 data.flickTarget = -snapPosAt(pos: -(dataValue - highlightRangeStart) - bias) + highlightRangeStart;
1895 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1896 velocity = maxVelocity;
1897 } else {
1898 maxDistance = qAbs(t: minExtent - data.move.value());
1899 }
1900 }
1901 if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange)
1902 data.flickTarget = minExtent;
1903 } else {
1904 if (data.move.value() > maxExtent) {
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 =
1911 -snapPosAt(pos: -(dataValue - highlightRangeStart) + bias) + highlightRangeStart;
1912 maxDistance = qAbs(t: data.flickTarget - data.move.value());
1913 velocity = -maxVelocity;
1914 } else {
1915 maxDistance = qAbs(t: maxExtent - data.move.value());
1916 }
1917 }
1918 if (snapMode == QQuickListView::NoSnap && highlightRange != QQuickListView::StrictlyEnforceRange)
1919 data.flickTarget = maxExtent;
1920 }
1921 bool overShoot = boundsBehavior & QQuickFlickable::OvershootBounds;
1922 if (maxDistance > 0 || overShoot) {
1923 // These modes require the list to stop exactly on an item boundary.
1924 // The initial flick will estimate the boundary to stop on.
1925 // Since list items can have variable sizes, the boundary will be
1926 // reevaluated and adjusted as we approach the boundary.
1927 qreal v = velocity;
1928 if (maxVelocity != -1 && maxVelocity < qAbs(t: v)) {
1929 if (v < 0)
1930 v = -maxVelocity;
1931 else
1932 v = maxVelocity;
1933 }
1934 if (!hData.flicking && !vData.flicking) {
1935 // the initial flick - estimate boundary
1936 qreal accel = eventType == QEvent::Wheel ? wheelDeceleration : deceleration;
1937 qreal v2 = v * v;
1938 overshootDist = 0.0;
1939 // + averageSize/4 to encourage moving at least one item in the flick direction
1940 qreal dist = v2 / (accel * 2.0) + averageSize/4;
1941 if (maxDistance > 0)
1942 dist = qMin(a: dist, b: maxDistance);
1943 if (v > 0)
1944 dist = -dist;
1945 if ((maxDistance > 0.0 && v2 / (2.0f * maxDistance) < accel) || snapMode == QQuickListView::SnapOneItem) {
1946 if (snapMode != QQuickListView::SnapOneItem) {
1947 qreal distTemp = isContentFlowReversed() ? -dist : dist;
1948 data.flickTarget = -snapPosAt(pos: -(dataValue - highlightRangeStart) + distTemp) + highlightRangeStart;
1949 }
1950 data.flickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget;
1951 if (overShoot) {
1952 if (data.flickTarget > minExtent) {
1953 overshootDist = overShootDistance(velocity: vSize);
1954 data.flickTarget += overshootDist;
1955 } else if (data.flickTarget < maxExtent) {
1956 overshootDist = overShootDistance(velocity: vSize);
1957 data.flickTarget -= overshootDist;
1958 }
1959 }
1960 qreal adjDist = -data.flickTarget + data.move.value();
1961 if (qAbs(t: adjDist) > qAbs(t: dist)) {
1962 // Prevent painfully slow flicking - adjust velocity to suit flickDeceleration
1963 qreal adjv2 = accel * 2.0f * qAbs(t: adjDist);
1964 if (adjv2 > v2) {
1965 v2 = adjv2;
1966 v = qSqrt(v: v2);
1967 if (dist > 0)
1968 v = -v;
1969 }
1970 }
1971 dist = adjDist;
1972 accel = v2 / (2.0f * qAbs(t: dist));
1973 } else if (overShoot) {
1974 data.flickTarget = data.move.value() - dist;
1975 if (data.flickTarget > minExtent) {
1976 overshootDist = overShootDistance(velocity: vSize);
1977 data.flickTarget += overshootDist;
1978 } else if (data.flickTarget < maxExtent) {
1979 overshootDist = overShootDistance(velocity: vSize);
1980 data.flickTarget -= overshootDist;
1981 }
1982 }
1983 timeline.reset(data.move);
1984 timeline.accel(data.move, velocity: v, accel, maxDistance: maxDistance + overshootDist);
1985 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
1986 correctFlick = true;
1987 return true;
1988 } else {
1989 // reevaluate the target boundary.
1990 qreal newtarget = data.flickTarget;
1991 if (snapMode != QQuickListView::NoSnap || highlightRange == QQuickListView::StrictlyEnforceRange) {
1992 qreal tempFlickTarget = isContentFlowReversed() ? -data.flickTarget+size() : data.flickTarget;
1993 newtarget = -snapPosAt(pos: -(tempFlickTarget - highlightRangeStart)) + highlightRangeStart;
1994 newtarget = isContentFlowReversed() ? -newtarget+size() : newtarget;
1995 }
1996 if (velocity < 0 && newtarget <= maxExtent)
1997 newtarget = maxExtent - overshootDist;
1998 else if (velocity > 0 && newtarget >= minExtent)
1999 newtarget = minExtent + overshootDist;
2000 if (newtarget == data.flickTarget) { // boundary unchanged - nothing to do
2001 if (qAbs(t: velocity) < _q_MinimumFlickVelocity)
2002 correctFlick = false;
2003 return false;
2004 }
2005 data.flickTarget = newtarget;
2006 qreal dist = -newtarget + data.move.value();
2007 if ((v < 0 && dist < 0) || (v > 0 && dist > 0)) {
2008 correctFlick = false;
2009 timeline.reset(data.move);
2010 fixup(data, minExtent, maxExtent);
2011 return false;
2012 }
2013 timeline.reset(data.move);
2014 timeline.accelDistance(data.move, velocity: v, distance: -dist);
2015 timeline.callback(QQuickTimeLineCallback(&data.move, fixupCallback, this));
2016 return false;
2017 }
2018 } else {
2019 correctFlick = false;
2020 timeline.reset(data.move);
2021 fixup(data, minExtent, maxExtent);
2022 return false;
2023 }
2024}
2025
2026void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString &section)
2027{
2028 if (!QQmlContextData::get(context)->isInternal() && context->contextProperty(QLatin1String("section")).isValid())
2029 context->setContextProperty(QLatin1String("section"), section);
2030 else
2031 sectionItem->setProperty(name: "section", value: section);
2032}
2033
2034QQuickItemViewAttached *QQuickListViewPrivate::getAttachedObject(const QObject *object) const
2035{
2036 QObject *attachedObject = qmlAttachedPropertiesObject<QQuickListView>(obj: object);
2037 return static_cast<QQuickItemViewAttached *>(attachedObject);
2038}
2039
2040//----------------------------------------------------------------------------
2041
2042/*!
2043 \qmltype ListView
2044 \nativetype QQuickListView
2045 \inqmlmodule QtQuick
2046 \ingroup qtquick-views
2047 \inherits Flickable
2048 \brief Provides a list view of items provided by a model.
2049
2050 A ListView displays data from models created from built-in QML types like ListModel
2051 and XmlListModel, or custom model classes defined in C++ that inherit from
2052 QAbstractItemModel or QAbstractListModel.
2053
2054 A ListView has a \l model, which defines the data to be displayed, and
2055 a \l delegate, which defines how the data should be displayed. Items in a
2056 ListView are laid out horizontally or vertically. List views are inherently
2057 flickable because ListView inherits from \l Flickable.
2058
2059 \note ListView will only load as many delegate items as needed to fill up the view.
2060 Items outside of the view will not be loaded unless a sufficient \l cacheBuffer has
2061 been set. Hence, a ListView with zero width or height might not load any delegate
2062 items at all.
2063
2064 \section1 Example Usage
2065
2066 The following example shows the definition of a simple list model defined
2067 in a file called \c ContactModel.qml:
2068
2069 \snippet qml/listview/ContactModel.qml 0
2070
2071 Another component can display this model data in a ListView, like this:
2072
2073 \snippet qml/listview/listview.qml import
2074 \codeline
2075 \snippet qml/listview/listview.qml classdocs simple
2076
2077 \image listview-simple.png
2078
2079 Here, the ListView creates a \c ContactModel component for its model, and a \l Text item
2080 for its delegate. The view will create a new \l Text component for each item in the model. Notice
2081 the delegate is able to access the model's \c name and \c number data directly.
2082
2083 An improved list view is shown below. The delegate is visually improved and is moved
2084 into a separate \c contactDelegate component.
2085
2086 \snippet qml/listview/listview.qml classdocs advanced
2087 \image listview-highlight.png
2088
2089 The currently selected item is highlighted with a blue \l Rectangle using the \l highlight property,
2090 and \c focus is set to \c true to enable keyboard navigation for the list view.
2091 The list view itself is a focus scope (see \l{Keyboard Focus in Qt Quick} for more details).
2092
2093 Delegates are instantiated as needed and may be destroyed at any time.
2094 As such, \l {Avoid Storing State in Delegates}{state should \e never be stored in a delegate}.
2095 Delegates are usually parented to ListView's \l {Flickable::contentItem}{contentItem}, but
2096 typically depending on whether it's visible in the view or not, the \e parent
2097 can change, and sometimes be \c null. Because of that, binding to
2098 the parent's properties from within the delegate is \e not recommended. If you
2099 want the delegate to fill out the width of the ListView, consider
2100 using one of the following approaches instead:
2101
2102 \code
2103 ListView {
2104 id: listView
2105 // ...
2106
2107 delegate: Item {
2108 // Incorrect.
2109 width: parent.width
2110
2111 // Correct.
2112 width: listView.width
2113 width: ListView.view.width
2114 // ...
2115 }
2116 }
2117 \endcode
2118
2119 ListView attaches a number of properties to the root item of the delegate, for example
2120 \c ListView.isCurrentItem. In the following example, the root delegate item can access
2121 this attached property directly as \c ListView.isCurrentItem, while the child
2122 \c contactInfo object must refer to this property as \c wrapper.ListView.isCurrentItem.
2123
2124 \snippet qml/listview/listview.qml isCurrentItem
2125
2126 \note Views do not enable \e clip automatically. If the view
2127 is not clipped by another item or the screen, it will be necessary
2128 to set \e {clip: true} in order to have the out of view items clipped
2129 nicely.
2130
2131
2132 \section1 ListView Layouts
2133
2134 The layout of the items in a ListView can be controlled by these properties:
2135
2136 \list
2137 \li \l orientation - controls whether items flow horizontally or vertically.
2138 This value can be either Qt.Horizontal or Qt.Vertical.
2139 \li \l layoutDirection - controls the horizontal layout direction for a
2140 horizontally-oriented view: that is, whether items are laid out from the left side of
2141 the view to the right, or vice-versa. This value can be either Qt.LeftToRight or Qt.RightToLeft.
2142 \li \l verticalLayoutDirection - controls the vertical layout direction for a vertically-oriented
2143 view: that is, whether items are laid out from the top of the view down towards the bottom of
2144 the view, or vice-versa. This value can be either ListView.TopToBottom or ListView.BottomToTop.
2145 \endlist
2146
2147 By default, a ListView has a vertical orientation, and items are laid out from top to bottom. The
2148 table below shows the different layouts that a ListView can have, depending on the values of
2149 the properties listed above.
2150
2151 \table
2152 \header
2153 \li {2, 1}
2154 \b ListViews with Qt.Vertical orientation
2155 \row
2156 \li Top to bottom
2157 \image listview-layout-toptobottom.png
2158 \li Bottom to top
2159 \image listview-layout-bottomtotop.png
2160 \header
2161 \li {2, 1}
2162 \b ListViews with Qt.Horizontal orientation
2163 \row
2164 \li Left to right
2165 \image listview-layout-lefttoright.png
2166 \li Right to left
2167 \image listview-layout-righttoleft.png
2168 \endtable
2169
2170 \section1 Flickable Direction
2171
2172 By default, a vertical ListView sets \l {Flickable::}{flickableDirection} to \e Flickable.Vertical,
2173 and a horizontal ListView sets it to \e Flickable.Horizontal. Furthermore, a vertical ListView only
2174 calculates (estimates) the \l {Flickable::}{contentHeight}, and a horizontal ListView only calculates
2175 the \l {Flickable::}{contentWidth}. The other dimension is set to \e -1.
2176
2177 Since Qt 5.9 (Qt Quick 2.9), it is possible to make a ListView that can be flicked to both directions.
2178 In order to do this, the \l {Flickable::}{flickableDirection} can be set to \e Flickable.AutoFlickDirection
2179 or \e Flickable.AutoFlickIfNeeded, and the desired \e contentWidth or \e contentHeight must be provided.
2180
2181 \snippet qml/listview/listview.qml flickBothDirections
2182
2183 \section1 Stacking Order in ListView
2184
2185 The \l {QQuickItem::z}{Z value} of items determines whether they are
2186 rendered above or below other items. ListView uses several different
2187 default Z values, depending on what type of item is being created:
2188
2189 \table
2190 \header
2191 \li Property
2192 \li Default Z value
2193 \row
2194 \li \l delegate
2195 \li 1
2196 \row
2197 \li \l footer
2198 \li 1
2199 \row
2200 \li \l header
2201 \li 1
2202 \row
2203 \li \l highlight
2204 \li 0
2205 \row
2206 \li \l section.delegate
2207 \li 2
2208 \endtable
2209
2210 These default values are set if the Z value of the item is \c 0, so setting
2211 the Z value of these items to \c 0 has no effect. Note that the Z value is
2212 of type \l [QML] {real}, so it is possible to set fractional
2213 values like \c 0.1.
2214
2215 \section1 Reusing Items
2216
2217 Since 5.15, ListView can be configured to recycle items instead of instantiating
2218 from the \l delegate whenever new rows are flicked into view. This approach improves
2219 performance, depending on the complexity of the delegate. Reusing
2220 items is off by default (for backwards compatibility reasons), but can be switched
2221 on by setting the \l reuseItems property to \c true.
2222
2223 When an item is flicked out, it moves to the \e{reuse pool}, which is an
2224 internal cache of unused items. When this happens, the \l ListView::pooled
2225 signal is emitted to inform the item about it. Likewise, when the item is
2226 moved back from the pool, the \l ListView::reused signal is emitted.
2227
2228 Any item properties that come from the model are updated when the
2229 item is reused. This includes \c index and \c row, but also
2230 any model roles.
2231
2232 \note \l {Avoid Storing State in Delegates}{Avoid storing any state inside
2233 a delegate}. If you do, reset it manually on receiving the
2234 \l ListView::reused signal.
2235
2236 If an item has timers or animations, consider pausing them on receiving
2237 the \l ListView::pooled signal. That way you avoid using the CPU resources
2238 for items that are not visible. Likewise, if an item has resources that
2239 cannot be reused, they could be freed up.
2240
2241 \note While an item is in the pool, it might still be alive and respond
2242 to connected signals and bindings.
2243
2244 \note For an item to be pooled, it needs to be completely flicked out of the bounds
2245 of the view, \e including the extra margins set with \l {ListView::}{cacheBuffer}.
2246 Some items will also never be pooled or reused, such as \l currentItem.
2247
2248 The following example shows a delegate that animates a spinning rectangle. When
2249 it is pooled, the animation is temporarily paused:
2250
2251 \snippet qml/listview/ReusableDelegate.qml 0
2252
2253 \sa {QML Data Models}, GridView, PathView, {Qt Quick Examples - Views}
2254
2255 \section1 Variable Delegate Size and Section Labels
2256
2257 Variable delegate sizes might lead to resizing and skipping of any attached
2258 \l {ScrollBar}. This is because ListView estimates its content size from
2259 allocated items (usually only the visible items, the rest are assumed to be of
2260 similar size), and variable delegate sizes prevent an accurate estimation. To
2261 reduce this effect, \l {ListView::}{cacheBuffer} can be set to higher values,
2262 effectively creating more items and improving the size estimate of unallocated
2263 items, at the expense of additional memory usage. \l{ListView::section}{Sections}
2264 have the same effect because they attach and elongate the section label to the
2265 first item within the section.
2266
2267 \section1 Avoid Storing State in Delegates
2268
2269 ListView's delegates are instantiated as needed and may be destroyed when
2270 out of view. For an illustration of this, run the following example:
2271
2272 \snippet qml/listview/stateInDelegate.qml ListView
2273
2274 When an item is clicked, \c channelActivated is set to \c true. However,
2275 because delegates can be \l {Reusing Items}{reused} and destroyed, all
2276 state is lost when the view is moved far enough. When the delegate becomes
2277 visible again, it will have its default, unmodified state (or, in the case
2278 of an item that was reused, old state from a previous item).
2279
2280 To avoid this, state should be stored in the model:
2281
2282 \snippet qml/listview/stateInModel.qml ListView
2283
2284 \section1 Hiding Delegates
2285
2286 Setting a delegate's \l {Item::}{visible} property to \c false will hide
2287 that item, but the space it occupied in the view will remain. It is
2288 possible to set the item's \l {Item::}{height} to \c 0 (for a \l
2289 {ListView::orientation}{vertical} ListView):
2290
2291 \snippet qml/listview/hideDelegate.qml ListView
2292
2293 Note that the hidden state is stored in the model, following the advice of
2294 the \l {Avoid Storing State in Delegates} section.
2295
2296 However, if \l spacing is non-zero, there will be uneven gaps between
2297 delegates.
2298
2299 A better option is to filter your model so that items that should not be
2300 visible are not loaded by the view at all. This can be achieved with
2301 \l QSortFilterProxyModel.
2302
2303 Another option is to \l {Item::enabled}{disable} the delegate instead of
2304 hiding it.
2305*/
2306QQuickListView::QQuickListView(QQuickItem *parent)
2307 : QQuickItemView(*(new QQuickListViewPrivate), parent)
2308{
2309}
2310
2311QQuickListView::~QQuickListView()
2312{
2313}
2314
2315/*!
2316 \qmlattachedproperty bool QtQuick::ListView::isCurrentItem
2317 \readonly
2318
2319 This attached property is true if this delegate is the current item; otherwise false.
2320
2321 It is attached to each instance of the delegate.
2322
2323 This property may be used to adjust the appearance of the current item, for example:
2324
2325 \snippet qml/listview/listview.qml isCurrentItem
2326*/
2327
2328/*!
2329 \qmlattachedproperty ListView QtQuick::ListView::view
2330 \readonly
2331
2332 This attached property holds the view that manages this delegate instance.
2333
2334 It is attached to each instance of the delegate and also to the header, the footer,
2335 the section and the highlight delegates.
2336*/
2337
2338/*!
2339 \qmlattachedproperty string QtQuick::ListView::previousSection
2340 \readonly
2341
2342 This attached property holds the section of the previous element.
2343
2344 It is attached to each instance of the delegate.
2345
2346 The section is evaluated using the \l {ListView::section.property}{section} properties.
2347*/
2348
2349/*!
2350 \qmlattachedproperty string QtQuick::ListView::nextSection
2351 \readonly
2352
2353 This attached property holds the section of the next element.
2354
2355 It is attached to each instance of the delegate.
2356
2357 The section is evaluated using the \l {ListView::section.property}{section} properties.
2358*/
2359
2360/*!
2361 \qmlattachedproperty string QtQuick::ListView::section
2362 \readonly
2363
2364 This attached property holds the section of this element.
2365
2366 It is attached to each instance of the delegate.
2367
2368 The section is evaluated using the \l {ListView::section.property}{section} properties.
2369*/
2370
2371/*!
2372 \qmlattachedproperty bool QtQuick::ListView::delayRemove
2373
2374 This attached property holds whether the delegate may be destroyed. It
2375 is attached to each instance of the delegate. The default value is false.
2376
2377 It is sometimes necessary to delay the destruction of an item
2378 until an animation completes. The example delegate below ensures that the
2379 animation completes before the item is removed from the list.
2380
2381 \snippet qml/listview/listview.qml delayRemove
2382
2383 If a \l remove transition has been specified, it will not be applied until
2384 delayRemove is returned to \c false.
2385*/
2386
2387/*!
2388 \qmlattachedsignal QtQuick::ListView::add()
2389 This attached signal is emitted immediately after an item is added to the view.
2390
2391 If an \l add transition is specified, it is applied immediately after
2392 this signal is handled.
2393*/
2394
2395/*!
2396 \qmlattachedsignal QtQuick::ListView::remove()
2397 This attached signal is emitted immediately before an item is removed from the view.
2398
2399 If a \l remove transition has been specified, it is applied after
2400 this signal is handled, providing that \l delayRemove is false.
2401*/
2402
2403/*!
2404 \qmlproperty model QtQuick::ListView::model
2405 This property holds the model providing data for the list.
2406
2407 The model provides the set of data that is used to create the items
2408 in the view. Models can be created directly in QML using \l ListModel,
2409 \l ObjectModel, or provided by C++ model classes. If a C++ model class is
2410 used, it must be a subclass of \l QAbstractItemModel or a simple list.
2411
2412 \sa {qml-data-models}{Data Models}
2413*/
2414
2415/*!
2416 \qmlproperty Component QtQuick::ListView::delegate
2417
2418 The delegate provides a template defining each item instantiated by the view.
2419 The index is exposed as an accessible \c index property. Properties of the
2420 model are also available depending upon the type of \l {qml-data-models}{Data Model}.
2421
2422 The number of objects and bindings in the delegate has a direct effect on the
2423 flicking performance of the view. If at all possible, place functionality
2424 that is not needed for the normal display of the delegate in a \l Loader which
2425 can load additional components when needed.
2426
2427 The ListView will lay out the items based on the size of the root item
2428 in the delegate.
2429
2430 It is recommended that the delegate's size be a whole number to avoid sub-pixel
2431 alignment of items.
2432
2433 The default \l {QQuickItem::z}{stacking order} of delegate instances is \c 1.
2434
2435 \note Delegates are instantiated as needed and may be destroyed at any time.
2436 They are parented to ListView's \l {Flickable::contentItem}{contentItem}, not to the view itself.
2437 State should \e never be stored in a delegate.
2438
2439 \sa {Stacking Order in ListView}
2440*/
2441/*!
2442 \qmlproperty int QtQuick::ListView::currentIndex
2443 \qmlproperty Item QtQuick::ListView::currentItem
2444
2445 The \c currentIndex property holds the index of the current item, and
2446 \c currentItem holds the current item. Setting the currentIndex to -1
2447 will clear the highlight and set currentItem to null.
2448
2449 If highlightFollowsCurrentItem is \c true, setting either of these
2450 properties will smoothly scroll the ListView so that the current
2451 item becomes visible.
2452
2453 Note that the position of the current item
2454 may only be approximate until it becomes visible in the view.
2455*/
2456
2457/*!
2458 \qmlproperty Item QtQuick::ListView::highlightItem
2459
2460 This holds the highlight item created from the \l highlight component.
2461
2462 The \c highlightItem is managed by the view unless
2463 \l highlightFollowsCurrentItem is set to false.
2464 The default \l {QQuickItem::z}{stacking order}
2465 of the highlight item is \c 0.
2466
2467 \sa highlight, highlightFollowsCurrentItem, {Stacking Order in ListView}
2468*/
2469
2470/*!
2471 \qmlproperty int QtQuick::ListView::count
2472 The property reflects the number of items in the \l ListView's model,
2473 regardless of whether they are visible or instantiated as \c Item of a delegate component.
2474*/
2475
2476/*!
2477 \qmlproperty bool QtQuick::ListView::reuseItems
2478
2479 This property enables you to reuse items that are instantiated
2480 from the \l delegate. If set to \c false, any currently
2481 pooled items are destroyed.
2482
2483 This property is \c false by default.
2484
2485 \since 5.15
2486
2487 \sa {Reusing items}, pooled(), reused()
2488*/
2489
2490/*!
2491 \qmlattachedsignal QtQuick::ListView::pooled()
2492
2493 This signal is emitted after an item has been added to the reuse
2494 pool. You can use it to pause ongoing timers or animations inside
2495 the item, or free up resources that cannot be reused.
2496
2497 This signal is emitted only if the \l reuseItems property is \c true.
2498
2499 \sa {Reusing items}, reuseItems, reused()
2500*/
2501
2502/*!
2503 \qmlattachedsignal QtQuick::ListView::reused()
2504
2505 This signal is emitted after an item has been reused. At this point, the
2506 item has been taken out of the pool and placed inside the content view,
2507 and the model properties such as \c index and \c row have been updated.
2508
2509 Other properties that are not provided by the model does not change when an
2510 item is reused. You should avoid storing any state inside a delegate, but if
2511 you do, manually reset that state on receiving this signal.
2512
2513 This signal is emitted when the item is reused, and not the first time the
2514 item is created.
2515
2516 This signal is emitted only if the \l reuseItems property is \c true.
2517
2518 \sa {Reusing items}, reuseItems, pooled()
2519*/
2520
2521/*!
2522 \qmlproperty Component QtQuick::ListView::highlight
2523 This property holds the component to use as the highlight.
2524
2525 An instance of the highlight component is created for each list.
2526 The geometry of the resulting component instance is managed by the list
2527 so as to stay with the current item, unless the highlightFollowsCurrentItem
2528 property is false. The default \l {QQuickItem::z}{stacking order} of the
2529 highlight item is \c 0.
2530
2531 \sa highlightItem, highlightFollowsCurrentItem,
2532 {Qt Quick Examples - Views#Using Highlight}{ListView Highlight Example},
2533 {Stacking Order in ListView}
2534*/
2535
2536/*!
2537 \qmlproperty bool QtQuick::ListView::highlightFollowsCurrentItem
2538 This property holds whether the highlight is managed by the view.
2539
2540 If this property is true (the default value), the highlight is moved smoothly
2541 to follow the current item. Otherwise, the
2542 highlight is not moved by the view, and any movement must be implemented
2543 by the highlight.
2544
2545 Here is a highlight with its motion defined by a \l {SpringAnimation} item:
2546
2547 \snippet qml/listview/listview.qml highlightFollowsCurrentItem
2548
2549 Note that the highlight animation also affects the way that the view
2550 is scrolled. This is because the view moves to maintain the
2551 highlight within the preferred highlight range (or visible viewport).
2552
2553 \sa highlight, highlightMoveVelocity
2554*/
2555//###Possibly rename these properties, since they are very useful even without a highlight?
2556/*!
2557 \qmlproperty real QtQuick::ListView::preferredHighlightBegin
2558 \qmlproperty real QtQuick::ListView::preferredHighlightEnd
2559 \qmlproperty enumeration QtQuick::ListView::highlightRangeMode
2560
2561 These properties define the preferred range of the highlight (for the current item)
2562 within the view. The \c preferredHighlightBegin value must be less than the
2563 \c preferredHighlightEnd value.
2564
2565 These properties affect the position of the current item when the list is scrolled.
2566 For example, if the currently selected item should stay in the middle of the
2567 list when the view is scrolled, set the \c preferredHighlightBegin and
2568 \c preferredHighlightEnd values to the top and bottom coordinates of where the middle
2569 item would be. If the \c currentItem is changed programmatically, the list will
2570 automatically scroll so that the current item is in the middle of the view.
2571 Furthermore, the behavior of the current item index will occur whether or not a
2572 highlight exists.
2573
2574 Valid values for \c highlightRangeMode are:
2575
2576 \value ListView.ApplyRange the view attempts to maintain the highlight within the range.
2577 However, the highlight can move outside of the range at the
2578 ends of the list or due to mouse interaction.
2579 \value ListView.StrictlyEnforceRange the highlight never moves outside of the range.
2580 The current item changes if a keyboard or mouse action would
2581 cause the highlight to move outside of the range.
2582 \value ListView.NoHighlightRange this is the default value.
2583*/
2584void QQuickListView::setHighlightFollowsCurrentItem(bool autoHighlight)
2585{
2586 Q_D(QQuickListView);
2587 if (d->autoHighlight != autoHighlight) {
2588 if (!autoHighlight) {
2589 if (d->highlightPosAnimator)
2590 d->highlightPosAnimator->stop();
2591 if (d->highlightWidthAnimator)
2592 d->highlightWidthAnimator->stop();
2593 if (d->highlightHeightAnimator)
2594 d->highlightHeightAnimator->stop();
2595 }
2596 QQuickItemView::setHighlightFollowsCurrentItem(autoHighlight);
2597 }
2598}
2599
2600/*!
2601 \qmlproperty real QtQuick::ListView::spacing
2602
2603 This property holds the spacing between items.
2604
2605 The default value is 0.
2606*/
2607qreal QQuickListView::spacing() const
2608{
2609 Q_D(const QQuickListView);
2610 return d->spacing;
2611}
2612
2613void QQuickListView::setSpacing(qreal spacing)
2614{
2615 Q_D(QQuickListView);
2616 if (spacing != d->spacing) {
2617 d->spacing = spacing;
2618 d->forceLayoutPolish();
2619 emit spacingChanged();
2620 }
2621}
2622
2623/*!
2624 \qmlproperty enumeration QtQuick::ListView::orientation
2625 This property holds the orientation of the list.
2626
2627 Possible values:
2628
2629 \value ListView.Horizontal Items are laid out horizontally
2630 \br
2631 \inlineimage ListViewHorizontal.png
2632 \value ListView.Vertical (default) Items are laid out vertically
2633 \br
2634 \inlineimage listview-highlight.png
2635
2636 \sa {Flickable Direction}
2637*/
2638QQuickListView::Orientation QQuickListView::orientation() const
2639{
2640 Q_D(const QQuickListView);
2641 return d->orient;
2642}
2643
2644void QQuickListView::setOrientation(QQuickListView::Orientation orientation)
2645{
2646 Q_D(QQuickListView);
2647 if (d->orient != orientation) {
2648 d->orient = orientation;
2649 if (d->orient == Vertical) {
2650 if (d->flickableDirection == HorizontalFlick) {
2651 setFlickableDirection(VerticalFlick);
2652 if (isComponentComplete())
2653 setContentWidth(-1);
2654 }
2655 setContentX(0);
2656 } else {
2657 if (d->flickableDirection == VerticalFlick) {
2658 setFlickableDirection(HorizontalFlick);
2659 if (isComponentComplete())
2660 setContentHeight(-1);
2661 }
2662 setContentY(0);
2663 }
2664 d->regenerate(orientationChanged: true);
2665 emit orientationChanged();
2666 }
2667}
2668
2669/*!
2670 \qmlproperty enumeration QtQuick::ListView::layoutDirection
2671 This property holds the layout direction of a horizontally-oriented list.
2672
2673 Possible values:
2674
2675 \value Qt.LeftToRight (default) Items will be laid out from left to right.
2676 \value Qt.RightToLeft Items will be laid out from right to left.
2677
2678 Setting this property has no effect if the \l orientation is Qt.Vertical.
2679
2680 \sa ListView::effectiveLayoutDirection, ListView::verticalLayoutDirection
2681*/
2682
2683
2684/*!
2685 \qmlproperty enumeration QtQuick::ListView::effectiveLayoutDirection
2686 This property holds the effective layout direction of a horizontally-oriented list.
2687
2688 When using the attached property \l {LayoutMirroring::enabled}{LayoutMirroring::enabled} for locale layouts,
2689 the visual layout direction of the horizontal list will be mirrored. However, the
2690 property \l {ListView::layoutDirection}{layoutDirection} will remain unchanged.
2691
2692 \sa ListView::layoutDirection, {LayoutMirroring}{LayoutMirroring}
2693*/
2694
2695
2696/*!
2697 \qmlproperty enumeration QtQuick::ListView::verticalLayoutDirection
2698 This property holds the layout direction of a vertically-oriented list.
2699
2700 Possible values:
2701
2702 \value ListView.TopToBottom (default) Items are laid out from the top of the view down to the bottom of the view.
2703 \value ListView.BottomToTop Items are laid out from the bottom of the view up to the top of the view.
2704
2705 Setting this property has no effect if the \l orientation is Qt.Horizontal.
2706
2707 \sa ListView::layoutDirection
2708*/
2709
2710
2711/*!
2712 \qmlproperty bool QtQuick::ListView::keyNavigationWraps
2713 This property holds whether the list wraps key navigation.
2714
2715 If this is true, key navigation that would move the current item selection
2716 past the end of the list instead wraps around and moves the selection to
2717 the start of the list, and vice-versa.
2718
2719 By default, key navigation is not wrapped.
2720*/
2721
2722/*!
2723 \qmlproperty bool QtQuick::ListView::keyNavigationEnabled
2724 \since 5.7
2725
2726 This property holds whether the key navigation of the list is enabled.
2727
2728 If this is \c true, the user can navigate the view with a keyboard.
2729 It is useful for applications that need to selectively enable or
2730 disable mouse and keyboard interaction.
2731
2732 By default, the value of this property is bound to
2733 \l {Flickable::}{interactive} to ensure behavior compatibility for
2734 existing applications. When explicitly set, it will cease to be bound to
2735 the interactive property.
2736
2737 \sa {Flickable::}{interactive}
2738*/
2739
2740
2741/*!
2742 \qmlproperty int QtQuick::ListView::cacheBuffer
2743 This property determines whether delegates are retained outside the
2744 visible area of the view.
2745
2746 If this value is greater than zero, the view may keep as many delegates
2747 instantiated as it can fit within the buffer specified. For example,
2748 if in a vertical view the delegate is 20 pixels high and \c cacheBuffer is
2749 set to 40, then up to 2 delegates above and 2 delegates below the visible
2750 area may be created/retained. The buffered delegates are created asynchronously,
2751 allowing creation to occur across multiple frames and reducing the
2752 likelihood of skipping frames. In order to improve painting performance
2753 delegates outside the visible area are not painted.
2754
2755 The default value of this property is platform dependent, but will usually
2756 be a value greater than zero. Negative values are ignored.
2757
2758 Note that cacheBuffer is not a pixel buffer - it only maintains additional
2759 instantiated delegates.
2760
2761 \note Setting this property is not a replacement for creating efficient delegates.
2762 It can improve the smoothness of scrolling behavior at the expense of additional
2763 memory usage. The fewer objects and bindings in a delegate, the faster a
2764 view can be scrolled. It is important to realize that setting a cacheBuffer
2765 will only postpone issues caused by slow-loading delegates, it is not a
2766 solution for this scenario.
2767
2768 The cacheBuffer operates outside of any display margins specified by
2769 displayMarginBeginning or displayMarginEnd.
2770*/
2771
2772/*!
2773 \qmlproperty int QtQuick::ListView::displayMarginBeginning
2774 \qmlproperty int QtQuick::ListView::displayMarginEnd
2775 \since QtQuick 2.3
2776
2777 This property allows delegates to be displayed outside of the view geometry.
2778
2779 If this value is non-zero, the view will create extra delegates before the
2780 start of the view, or after the end. The view will create as many delegates
2781 as it can fit into the pixel size specified.
2782
2783 For example, if in a vertical view the delegate is 20 pixels high and
2784 \c displayMarginBeginning and \c displayMarginEnd are both set to 40,
2785 then 2 delegates above and 2 delegates below will be created and shown.
2786
2787 The default value is 0.
2788
2789 This property is meant for allowing certain UI configurations,
2790 and not as a performance optimization. If you wish to create delegates
2791 outside of the view geometry for performance reasons, you probably
2792 want to use the cacheBuffer property instead.
2793*/
2794
2795/*!
2796 \qmlpropertygroup QtQuick::ListView::section
2797 \qmlproperty string QtQuick::ListView::section.property
2798 \qmlproperty enumeration QtQuick::ListView::section.criteria
2799 \qmlproperty Component QtQuick::ListView::section.delegate
2800 \qmlproperty enumeration QtQuick::ListView::section.labelPositioning
2801
2802 These properties determine the expression to be evaluated and appearance
2803 of the section labels.
2804
2805 \c section.property holds the name of the property that is the basis
2806 of each section.
2807
2808 \c section.criteria holds the criteria for forming each section based on
2809 \c section.property. This value can be one of:
2810
2811 \value ViewSection.FullString (default) sections are created based on the
2812 \c section.property value.
2813 \value ViewSection.FirstCharacter sections are created based on the first character of
2814 the \c section.property value (for example,
2815 'A', 'B', 'C' ... sections for an address book.)
2816
2817 A case insensitive comparison is used when determining section
2818 boundaries.
2819
2820 \c section.delegate holds the delegate component for each section. The
2821 default \l {QQuickItem::z}{stacking order} of section delegate instances
2822 is \c 2. If you declare a \c required property named "section" in it,
2823 that property will contain the section's title.
2824
2825 \c section.labelPositioning determines whether the current and/or
2826 next section labels stick to the start/end of the view, and whether
2827 the labels are shown inline. This value can be a combination of:
2828
2829 \value ViewSection.InlineLabels
2830 (default) section labels are shown inline between the item delegates
2831 separating sections.
2832 \value ViewSection.CurrentLabelAtStart
2833 the current section label sticks to the start of the view as it is moved.
2834 \value ViewSection.NextLabelAtEnd
2835 the next section label (beyond all visible sections) sticks to the end
2836 of the view as it is moved.
2837 \note Enabling \c ViewSection.NextLabelAtEnd requires the view to scan
2838 ahead for the next section, which has performance implications,
2839 especially for slower models.
2840
2841 Each item in the list has attached properties named \c ListView.section,
2842 \c ListView.previousSection and \c ListView.nextSection.
2843
2844 For example, here is a ListView that displays a list of animals, separated
2845 into sections. Each item in the ListView is placed in a different section
2846 depending on the "size" property of the model item. The \c sectionHeading
2847 delegate component provides the light blue bar that marks the beginning of
2848 each section.
2849
2850
2851 \snippet views/listview/sections.qml 0
2852
2853 \image qml-listview-sections-example.png
2854
2855 \note Adding sections to a ListView does not automatically re-order the
2856 list items by the section criteria.
2857 If the model is not ordered by section, then it is possible that
2858 the sections created will not be unique; each boundary between
2859 differing sections will result in a section header being created
2860 even if that section exists elsewhere.
2861
2862 \sa {Qt Quick Examples - Views}{ListView examples},
2863 {Stacking Order in ListView}
2864*/
2865QQuickViewSection *QQuickListView::sectionCriteria()
2866{
2867 Q_D(QQuickListView);
2868 if (!d->sectionCriteria)
2869 d->sectionCriteria = new QQuickViewSection(this);
2870 return d->sectionCriteria;
2871}
2872
2873/*!
2874 \qmlproperty string QtQuick::ListView::currentSection
2875 This property holds the section that is currently at the beginning of the view.
2876*/
2877QString QQuickListView::currentSection() const
2878{
2879 Q_D(const QQuickListView);
2880 return d->currentSection;
2881}
2882
2883/*!
2884 \qmlproperty real QtQuick::ListView::highlightMoveVelocity
2885 \qmlproperty int QtQuick::ListView::highlightMoveDuration
2886 \qmlproperty real QtQuick::ListView::highlightResizeVelocity
2887 \qmlproperty int QtQuick::ListView::highlightResizeDuration
2888
2889 These properties control the speed of the move and resize animations for the
2890 highlight delegate.
2891
2892 \l highlightFollowsCurrentItem must be true for these properties
2893 to have effect.
2894
2895 The default value for the velocity properties is 400 pixels/second.
2896 The default value for the duration properties is -1, i.e. the
2897 highlight will take as much time as necessary to move at the set speed.
2898
2899 These properties have the same characteristics as a SmoothedAnimation:
2900 if both the velocity and duration are set, the animation will use
2901 whichever gives the shorter duration.
2902
2903 The move velocity and duration properties are used to control movement due
2904 to index changes; for example, when incrementCurrentIndex() is called. When
2905 the user flicks a ListView, the velocity from the flick is used to control
2906 the movement instead.
2907
2908 To set only one property, the other can be set to \c -1. For example,
2909 if you only want to animate the duration and not velocity, use the
2910 following code:
2911
2912 \code
2913 highlightMoveDuration: 1000
2914 highlightMoveVelocity: -1
2915 \endcode
2916
2917 \sa highlightFollowsCurrentItem
2918*/
2919qreal QQuickListView::highlightMoveVelocity() const
2920{
2921 Q_D(const QQuickListView);
2922 return d->highlightMoveVelocity;
2923}
2924
2925void QQuickListView::setHighlightMoveVelocity(qreal speed)
2926{
2927 Q_D(QQuickListView);
2928 if (d->highlightMoveVelocity != speed) {
2929 d->highlightMoveVelocity = speed;
2930 if (d->highlightPosAnimator)
2931 d->highlightPosAnimator->velocity = d->highlightMoveVelocity;
2932 emit highlightMoveVelocityChanged();
2933 }
2934}
2935
2936void QQuickListView::setHighlightMoveDuration(int duration)
2937{
2938 Q_D(QQuickListView);
2939 if (d->highlightMoveDuration != duration) {
2940 if (d->highlightPosAnimator)
2941 d->highlightPosAnimator->userDuration = duration;
2942 QQuickItemView::setHighlightMoveDuration(duration);
2943 }
2944}
2945
2946qreal QQuickListView::highlightResizeVelocity() const
2947{
2948 Q_D(const QQuickListView);
2949 return d->highlightResizeVelocity;
2950}
2951
2952void QQuickListView::setHighlightResizeVelocity(qreal speed)
2953{
2954 Q_D(QQuickListView);
2955 if (d->highlightResizeVelocity != speed) {
2956 d->highlightResizeVelocity = speed;
2957 if (d->highlightWidthAnimator)
2958 d->highlightWidthAnimator->velocity = d->highlightResizeVelocity;
2959 if (d->highlightHeightAnimator)
2960 d->highlightHeightAnimator->velocity = d->highlightResizeVelocity;
2961 emit highlightResizeVelocityChanged();
2962 }
2963}
2964
2965int QQuickListView::highlightResizeDuration() const
2966{
2967 Q_D(const QQuickListView);
2968 return d->highlightResizeDuration;
2969}
2970
2971void QQuickListView::setHighlightResizeDuration(int duration)
2972{
2973 Q_D(QQuickListView);
2974 if (d->highlightResizeDuration != duration) {
2975 d->highlightResizeDuration = duration;
2976 if (d->highlightWidthAnimator)
2977 d->highlightWidthAnimator->userDuration = d->highlightResizeDuration;
2978 if (d->highlightHeightAnimator)
2979 d->highlightHeightAnimator->userDuration = d->highlightResizeDuration;
2980 emit highlightResizeDurationChanged();
2981 }
2982}
2983
2984/*!
2985 \qmlproperty enumeration QtQuick::ListView::snapMode
2986
2987 This property determines how the view scrolling will settle following a drag or flick.
2988 The possible values are:
2989
2990 \value ListView.NoSnap (default) the view stops anywhere within the visible area.
2991 \value ListView.SnapToItem the view settles with an item aligned with the start of the view.
2992 \value ListView.SnapOneItem the view settles no more than one item away from the first
2993 visible item at the time the mouse button is released. This mode is particularly
2994 useful for moving one page at a time. When SnapOneItem is enabled, the ListView will
2995 show a stronger affinity to neighboring items when movement occurs. For example, a
2996 short drag that snaps back to the current item with SnapToItem might snap to a
2997 neighboring item with SnapOneItem.
2998
2999 \c snapMode does not affect the \l currentIndex. To update the
3000 \l currentIndex as the list is moved, set \l highlightRangeMode
3001 to \c ListView.StrictlyEnforceRange.
3002
3003 \sa highlightRangeMode
3004*/
3005QQuickListView::SnapMode QQuickListView::snapMode() const
3006{
3007 Q_D(const QQuickListView);
3008 return d->snapMode;
3009}
3010
3011void QQuickListView::setSnapMode(SnapMode mode)
3012{
3013 Q_D(QQuickListView);
3014 if (d->snapMode != mode) {
3015 d->snapMode = mode;
3016 emit snapModeChanged();
3017 d->fixupPosition();
3018 }
3019}
3020
3021
3022/*!
3023 \qmlproperty Component QtQuick::ListView::footer
3024 This property holds the component to use as the footer.
3025
3026 An instance of the footer component is created for each view. The
3027 footer is positioned at the end of the view, after any items. The
3028 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
3029
3030 \sa header, footerItem, {Stacking Order in ListView}
3031*/
3032
3033
3034/*!
3035 \qmlproperty Component QtQuick::ListView::header
3036 This property holds the component to use as the header.
3037
3038 An instance of the header component is created for each view. The
3039 header is positioned at the beginning of the view, before any items.
3040 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
3041
3042 \sa footer, headerItem, {Stacking Order in ListView}
3043*/
3044
3045/*!
3046 \qmlproperty Item QtQuick::ListView::headerItem
3047 This holds the header item created from the \l header component.
3048
3049 An instance of the header component is created for each view. The
3050 header is positioned at the beginning of the view, before any items.
3051 The default \l {QQuickItem::z}{stacking order} of the header is \c 1.
3052
3053 \sa header, footerItem, {Stacking Order in ListView}
3054*/
3055
3056/*!
3057 \qmlproperty Item QtQuick::ListView::footerItem
3058 This holds the footer item created from the \l footer component.
3059
3060 An instance of the footer component is created for each view. The
3061 footer is positioned at the end of the view, after any items. The
3062 default \l {QQuickItem::z}{stacking order} of the footer is \c 1.
3063
3064 \sa footer, headerItem, {Stacking Order in ListView}
3065*/
3066
3067/*!
3068 \qmlproperty enumeration QtQuick::ListView::headerPositioning
3069 \since Qt 5.4
3070
3071 This property determines the positioning of the \l{headerItem}{header item}.
3072
3073 \value ListView.InlineHeader (default) The header is positioned at the beginning
3074 of the content and moves together with the content like an ordinary item.
3075
3076 \value ListView.OverlayHeader The header is positioned at the beginning of the view.
3077
3078 \value ListView.PullBackHeader The header is positioned at the beginning of the view.
3079 The header can be pushed away by moving the content forwards, and pulled back by
3080 moving the content backwards.
3081
3082 \note This property has no effect on the \l {QQuickItem::z}{stacking order}
3083 of the header. For example, if the header should be shown above the
3084 \l delegate items when using \c ListView.OverlayHeader, its Z value
3085 should be set to a value higher than that of the delegates. For more
3086 information, see \l {Stacking Order in ListView}.
3087
3088 \note If \c headerPositioning is not set to \c ListView.InlineHeader, the
3089 user cannot press and flick the list from the header. In any case, the
3090 \l{headerItem}{header item} may contain items or event handlers that
3091 provide custom handling of mouse or touch input.
3092*/
3093QQuickListView::HeaderPositioning QQuickListView::headerPositioning() const
3094{
3095 Q_D(const QQuickListView);
3096 return d->headerPositioning;
3097}
3098
3099void QQuickListView::setHeaderPositioning(QQuickListView::HeaderPositioning positioning)
3100{
3101 Q_D(QQuickListView);
3102 if (d->headerPositioning != positioning) {
3103 d->applyPendingChanges();
3104 d->headerPositioning = positioning;
3105 if (isComponentComplete()) {
3106 d->updateHeader();
3107 d->updateViewport();
3108 d->fixupPosition();
3109 }
3110 emit headerPositioningChanged();
3111 }
3112}
3113
3114/*!
3115 \qmlproperty enumeration QtQuick::ListView::footerPositioning
3116 \since Qt 5.4
3117
3118 This property determines the positioning of the \l{footerItem}{footer item}.
3119
3120 \value ListView.InlineFooter (default) The footer is positioned at the end
3121 of the content and moves together with the content like an ordinary item.
3122
3123 \value ListView.OverlayFooter The footer is positioned at the end of the view.
3124
3125 \value ListView.PullBackFooter The footer is positioned at the end of the view.
3126 The footer can be pushed away by moving the content backwards, and pulled back by
3127 moving the content forwards.
3128
3129 \note This property has no effect on the \l {QQuickItem::z}{stacking order}
3130 of the footer. For example, if the footer should be shown above the
3131 \l delegate items when using \c ListView.OverlayFooter, its Z value
3132 should be set to a value higher than that of the delegates. For more
3133 information, see \l {Stacking Order in ListView}.
3134
3135 \note If \c footerPositioning is not set to \c ListView.InlineFooter, the
3136 user cannot press and flick the list from the footer. In any case, the
3137 \l{footerItem}{footer item} may contain items or event handlers that
3138 provide custom handling of mouse or touch input.
3139*/
3140QQuickListView::FooterPositioning QQuickListView::footerPositioning() const
3141{
3142 Q_D(const QQuickListView);
3143 return d->footerPositioning;
3144}
3145
3146void QQuickListView::setFooterPositioning(QQuickListView::FooterPositioning positioning)
3147{
3148 Q_D(QQuickListView);
3149 if (d->footerPositioning != positioning) {
3150 d->applyPendingChanges();
3151 d->footerPositioning = positioning;
3152 if (isComponentComplete()) {
3153 d->updateFooter();
3154 d->updateViewport();
3155 d->fixupPosition();
3156 }
3157 emit footerPositioningChanged();
3158 }
3159}
3160
3161/*!
3162 \qmlproperty Transition QtQuick::ListView::populate
3163
3164 This property holds the transition to apply to the items that are initially created
3165 for a view.
3166
3167 It is applied to all items that are created when:
3168
3169 \list
3170 \li The view is first created
3171 \li The view's \l model changes in such a way that the visible delegates are completely replaced
3172 \li The view's \l model is \l {QAbstractItemModel::beginResetModel()}{reset}, if the model is a
3173 QAbstractItemModel subclass
3174 \endlist
3175
3176 For example, here is a view that specifies such a transition:
3177
3178 \code
3179 ListView {
3180 ...
3181 populate: Transition {
3182 NumberAnimation { properties: "x,y"; duration: 1000 }
3183 }
3184 }
3185 \endcode
3186
3187 When the view is initialized, the view will create all the necessary items for the view,
3188 then animate them to their correct positions within the view over one second.
3189
3190 However when scrolling the view later, the populate transition does not
3191 run, even though delegates are being instantiated as they become visible.
3192 When the model changes in a way that new delegates become visible, the
3193 \l add transition is the one that runs. So you should not depend on the
3194 \c populate transition to initialize properties in the delegate, because it
3195 does not apply to every delegate. If your animation sets the \c to value of
3196 a property, the property should initially have the \c to value, and the
3197 animation should set the \c from value in case it is animated:
3198
3199 \code
3200 ListView {
3201 ...
3202 delegate: Rectangle {
3203 opacity: 1 // not necessary because it's the default
3204 }
3205 populate: Transition {
3206 NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 1000 }
3207 }
3208 }
3209 \endcode
3210
3211 For more details and examples on how to use view transitions, see the ViewTransition
3212 documentation.
3213
3214 \sa add, ViewTransition
3215*/
3216
3217/*!
3218 \qmlproperty Transition QtQuick::ListView::add
3219
3220 This property holds the transition to apply to items that are added to the view.
3221
3222 For example, here is a view that specifies such a transition:
3223
3224 \code
3225 ListView {
3226 ...
3227 add: Transition {
3228 NumberAnimation { properties: "x,y"; from: 100; duration: 1000 }
3229 }
3230 }
3231 \endcode
3232
3233 Whenever an item is added to the above view, the item will be animated from the position (100,100)
3234 to its final x,y position within the view, over one second. The transition only applies to
3235 the new items that are added to the view; it does not apply to the items below that are
3236 displaced by the addition of the new items. To animate the displaced items, set the \l displaced
3237 or \l addDisplaced properties.
3238
3239 For more details and examples on how to use view transitions, see the ViewTransition
3240 documentation.
3241
3242 \note This transition is not applied to the items that are created when the view is initially
3243 populated, or when the view's \l model changes. (In those cases, the \l populate transition is
3244 applied instead.) Additionally, this transition should \e not animate the height of the new item;
3245 doing so will cause any items beneath the new item to be laid out at the wrong position. Instead,
3246 the height can be animated within the \l {add}{onAdd} handler in the delegate.
3247
3248 \sa addDisplaced, populate, ViewTransition
3249*/
3250
3251/*!
3252 \qmlproperty Transition QtQuick::ListView::addDisplaced
3253
3254 This property holds the transition to apply to items within the view that are displaced by
3255 the addition of other items to the view.
3256
3257 For example, here is a view that specifies such a transition:
3258
3259 \code
3260 ListView {
3261 ...
3262 addDisplaced: Transition {
3263 NumberAnimation { properties: "x,y"; duration: 1000 }
3264 }
3265 }
3266 \endcode
3267
3268 Whenever an item is added to the above view, all items beneath the new item are displaced, causing
3269 them to move down (or sideways, if horizontally orientated) within the view. As this
3270 displacement occurs, the items' movement to their new x,y positions within the view will be
3271 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3272 the new item that has been added to the view; to animate the added items, set the \l add
3273 property.
3274
3275 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3276 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3277 if it is not necessary to specify different transitions depending on whether an item is displaced
3278 by an add, move or remove operation, consider setting the \l displaced property instead.
3279
3280 For more details and examples on how to use view transitions, see the ViewTransition
3281 documentation.
3282
3283 \note This transition is not applied to the items that are created when the view is initially
3284 populated, or when the view's \l model changes. In those cases, the \l populate transition is
3285 applied instead.
3286
3287 \sa displaced, add, populate, ViewTransition
3288*/
3289
3290/*!
3291 \qmlproperty Transition QtQuick::ListView::move
3292
3293 This property holds the transition to apply to items in the view that are being moved due
3294 to a move operation in the view's \l model.
3295
3296 For example, here is a view that specifies such a transition:
3297
3298 \code
3299 ListView {
3300 ...
3301 move: Transition {
3302 NumberAnimation { properties: "x,y"; duration: 1000 }
3303 }
3304 }
3305 \endcode
3306
3307 Whenever the \l model performs a move operation to move a particular set of indexes, the
3308 respective items in the view will be animated to their new positions in the view over one
3309 second. The transition only applies to the items that are the subject of the move operation
3310 in the model; it does not apply to items below them that are displaced by the move operation.
3311 To animate the displaced items, set the \l displaced or \l moveDisplaced properties.
3312
3313 For more details and examples on how to use view transitions, see the ViewTransition
3314 documentation.
3315
3316 \sa moveDisplaced, ViewTransition
3317*/
3318
3319/*!
3320 \qmlproperty Transition QtQuick::ListView::moveDisplaced
3321
3322 This property holds the transition to apply to items that are displaced by a move operation in
3323 the view's \l model.
3324
3325 For example, here is a view that specifies such a transition:
3326
3327 \code
3328 ListView {
3329 ...
3330 moveDisplaced: Transition {
3331 NumberAnimation { properties: "x,y"; duration: 1000 }
3332 }
3333 }
3334 \endcode
3335
3336 Whenever the \l model performs a move operation to move a particular set of indexes, the items
3337 between the source and destination indexes of the move operation are displaced, causing them
3338 to move upwards or downwards (or sideways, if horizontally orientated) within the view. As this
3339 displacement occurs, the items' movement to their new x,y positions within the view will be
3340 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3341 the items that are the actual subjects of the move operation; to animate the moved items, set
3342 the \l move property.
3343
3344 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3345 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3346 if it is not necessary to specify different transitions depending on whether an item is displaced
3347 by an add, move or remove operation, consider setting the \l displaced property instead.
3348
3349 For more details and examples on how to use view transitions, see the ViewTransition
3350 documentation.
3351
3352 \sa displaced, move, ViewTransition
3353*/
3354
3355/*!
3356 \qmlproperty Transition QtQuick::ListView::remove
3357
3358 This property holds the transition to apply to items that are removed from the view.
3359
3360 For example, here is a view that specifies such a transition:
3361
3362 \code
3363 ListView {
3364 ...
3365 remove: Transition {
3366 ParallelAnimation {
3367 NumberAnimation { property: "opacity"; to: 0; duration: 1000 }
3368 NumberAnimation { properties: "x,y"; to: 100; duration: 1000 }
3369 }
3370 }
3371 }
3372 \endcode
3373
3374 Whenever an item is removed from the above view, the item will be animated to the position (100,100)
3375 over one second, and in parallel will also change its opacity to 0. The transition
3376 only applies to the items that are removed from the view; it does not apply to the items below
3377 them that are displaced by the removal of the items. To animate the displaced items, set the
3378 \l displaced or \l removeDisplaced properties.
3379
3380 Note that by the time the transition is applied, the item has already been removed from the
3381 model; any references to the model data for the removed index will not be valid.
3382
3383 Additionally, if the \l delayRemove attached property has been set for a delegate item, the
3384 remove transition will not be applied until \l delayRemove becomes false again.
3385
3386 For more details and examples on how to use view transitions, see the ViewTransition
3387 documentation.
3388
3389 \sa removeDisplaced, ViewTransition
3390*/
3391
3392/*!
3393 \qmlproperty Transition QtQuick::ListView::removeDisplaced
3394
3395 This property holds the transition to apply to items in the view that are displaced by the
3396 removal of other items in the view.
3397
3398 For example, here is a view that specifies such a transition:
3399
3400 \code
3401 ListView {
3402 ...
3403 removeDisplaced: Transition {
3404 NumberAnimation { properties: "x,y"; duration: 1000 }
3405 }
3406 }
3407 \endcode
3408
3409 Whenever an item is removed from the above view, all items beneath it are displaced, causing
3410 them to move upwards (or sideways, if horizontally orientated) within the view. As this
3411 displacement occurs, the items' movement to their new x,y positions within the view will be
3412 animated by a NumberAnimation over one second, as specified. This transition is not applied to
3413 the item that has actually been removed from the view; to animate the removed items, set the
3414 \l remove property.
3415
3416 If an item is displaced by multiple types of operations at the same time, it is not defined as to
3417 whether the addDisplaced, moveDisplaced or removeDisplaced transition will be applied. Additionally,
3418 if it is not necessary to specify different transitions depending on whether an item is displaced
3419 by an add, move or remove operation, consider setting the \l displaced property instead.
3420
3421 For more details and examples on how to use view transitions, see the ViewTransition
3422 documentation.
3423
3424 \sa displaced, remove, ViewTransition
3425*/
3426
3427/*!
3428 \qmlproperty Transition QtQuick::ListView::displaced
3429 This property holds the generic transition to apply to items that have been displaced by
3430 any model operation that affects the view.
3431
3432 This is a convenience for specifying the generic transition to be applied to any items
3433 that are displaced by an add, move or remove operation, without having to specify the
3434 individual addDisplaced, moveDisplaced and removeDisplaced properties. For example, here
3435 is a view that specifies a displaced transition:
3436
3437 \code
3438 ListView {
3439 ...
3440 displaced: Transition {
3441 NumberAnimation { properties: "x,y"; duration: 1000 }
3442 }
3443 }
3444 \endcode
3445
3446 When any item is added, moved or removed within the above view, the items below it are
3447 displaced, causing them to move down (or sideways, if horizontally orientated) within the
3448 view. As this displacement occurs, the items' movement to their new x,y positions within
3449 the view will be animated by a NumberAnimation over one second, as specified.
3450
3451 If a view specifies this generic displaced transition as well as a specific addDisplaced,
3452 moveDisplaced or removeDisplaced transition, the more specific transition will be used
3453 instead of the generic displaced transition when the relevant operation occurs, providing that
3454 the more specific transition has not been disabled (by setting \l {Transition::enabled}{enabled}
3455 to false). If it has indeed been disabled, the generic displaced transition is applied instead.
3456
3457 For more details and examples on how to use view transitions, see the ViewTransition
3458 documentation.
3459
3460 \sa addDisplaced, moveDisplaced, removeDisplaced, ViewTransition
3461*/
3462
3463void QQuickListView::viewportMoved(Qt::Orientations orient)
3464{
3465 Q_D(QQuickListView);
3466 QQuickItemView::viewportMoved(orient);
3467
3468 if (!d->itemCount) {
3469 if (d->hasStickyHeader())
3470 d->updateHeader();
3471 if (d->hasStickyFooter())
3472 d->updateFooter();
3473 return;
3474 }
3475
3476 // Recursion can occur due to refill changing the content size.
3477 if (d->inViewportMoved)
3478 return;
3479 d->inViewportMoved = true;
3480
3481 if (yflick()) {
3482 if (d->isBottomToTop())
3483 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore;
3484 else
3485 d->bufferMode = d->vData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter;
3486 } else {
3487 if (d->isRightToLeft())
3488 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferAfter : QQuickListViewPrivate::BufferBefore;
3489 else
3490 d->bufferMode = d->hData.smoothVelocity < 0 ? QQuickListViewPrivate::BufferBefore : QQuickListViewPrivate::BufferAfter;
3491 }
3492
3493 d->refillOrLayout();
3494
3495 // Set visibility of items to eliminate cost of items outside the visible area.
3496 qreal from = d->isContentFlowReversed() ? -d->position()-d->displayMarginBeginning-d->size() : d->position()-d->displayMarginBeginning;
3497 qreal to = d->isContentFlowReversed() ? -d->position()+d->displayMarginEnd : d->position()+d->size()+d->displayMarginEnd;
3498 for (FxViewItem *item : std::as_const(t&: d->visibleItems)) {
3499 if (item->item)
3500 QQuickItemPrivate::get(item: item->item)->setCulled(item->endPosition() < from || item->position() > to);
3501 }
3502 if (d->currentItem)
3503 QQuickItemPrivate::get(item: d->currentItem->item)->setCulled(d->currentItem->endPosition() < from || d->currentItem->position() > to);
3504
3505 if (d->hData.flicking || d->vData.flicking || d->hData.moving || d->vData.moving)
3506 d->moveReason = QQuickListViewPrivate::Mouse;
3507 if (d->moveReason != QQuickListViewPrivate::SetIndex) {
3508 if (d->haveHighlightRange && d->highlightRange == StrictlyEnforceRange && d->highlight) {
3509 // reposition highlight
3510 qreal pos = d->highlight->position();
3511 qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
3512 if (pos > viewPos + d->highlightRangeEnd - d->highlight->size())
3513 pos = viewPos + d->highlightRangeEnd - d->highlight->size();
3514 if (pos < viewPos + d->highlightRangeStart)
3515 pos = viewPos + d->highlightRangeStart;
3516 if (pos != d->highlight->position()) {
3517 d->highlightPosAnimator->stop();
3518 static_cast<FxListItemSG*>(d->highlight.get())->setPosition(pos);
3519 } else {
3520 d->updateHighlight();
3521 }
3522
3523 // update current index
3524 if (FxViewItem *snapItem = d->snapItemAt(pos: d->highlight->position())) {
3525 if (snapItem->index >= 0 && snapItem->index != d->currentIndex)
3526 d->updateCurrent(modelIndex: snapItem->index);
3527 }
3528 }
3529 }
3530
3531 if ((d->hData.flicking || d->vData.flicking) && d->correctFlick && !d->inFlickCorrection) {
3532 d->inFlickCorrection = true;
3533 // Near an end and it seems that the extent has changed?
3534 // Recalculate the flick so that we don't end up in an odd position.
3535 if (yflick() && !d->vData.inOvershoot) {
3536 if (d->vData.velocity > 0) {
3537 const qreal minY = minYExtent();
3538 if ((minY - d->vData.move.value() < height()/2 || d->vData.flickTarget - d->vData.move.value() < height()/2)
3539 && minY != d->vData.flickTarget)
3540 d->flickY(eventType: QEvent::TouchUpdate, velocity: -d->vData.smoothVelocity.value());
3541 } else if (d->vData.velocity < 0) {
3542 const qreal maxY = maxYExtent();
3543 if ((d->vData.move.value() - maxY < height()/2 || d->vData.move.value() - d->vData.flickTarget < height()/2)
3544 && maxY != d->vData.flickTarget)
3545 d->flickY(eventType: QEvent::TouchUpdate, velocity: -d->vData.smoothVelocity.value());
3546 }
3547 }
3548
3549 if (xflick() && !d->hData.inOvershoot) {
3550 if (d->hData.velocity > 0) {
3551 const qreal minX = minXExtent();
3552 if ((minX - d->hData.move.value() < width()/2 || d->hData.flickTarget - d->hData.move.value() < width()/2)
3553 && minX != d->hData.flickTarget)
3554 d->flickX(eventType: QEvent::TouchUpdate, velocity: -d->hData.smoothVelocity.value());
3555 } else if (d->hData.velocity < 0) {
3556 const qreal maxX = maxXExtent();
3557 if ((d->hData.move.value() - maxX < width()/2 || d->hData.move.value() - d->hData.flickTarget < width()/2)
3558 && maxX != d->hData.flickTarget)
3559 d->flickX(eventType: QEvent::TouchUpdate, velocity: -d->hData.smoothVelocity.value());
3560 }
3561 }
3562 d->inFlickCorrection = false;
3563 }
3564 if (d->hasStickyHeader())
3565 d->updateHeader();
3566 if (d->hasStickyFooter())
3567 d->updateFooter();
3568 if (d->sectionCriteria) {
3569 d->updateCurrentSection();
3570 d->updateStickySections();
3571 }
3572 d->inViewportMoved = false;
3573}
3574
3575void QQuickListView::keyPressEvent(QKeyEvent *event)
3576{
3577 Q_D(QQuickListView);
3578 if (d->model && d->model->count() && ((d->interactive && !d->explicitKeyNavigationEnabled)
3579 || (d->explicitKeyNavigationEnabled && d->keyNavigationEnabled))) {
3580 if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Left)
3581 || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Right)
3582 || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Up)
3583 || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Down)) {
3584 if (currentIndex() > 0 || (d->wrap && !event->isAutoRepeat())) {
3585 decrementCurrentIndex();
3586 event->accept();
3587 return;
3588 } else if (d->wrap) {
3589 event->accept();
3590 return;
3591 }
3592 } else if ((d->orient == QQuickListView::Horizontal && !d->isRightToLeft() && event->key() == Qt::Key_Right)
3593 || (d->orient == QQuickListView::Horizontal && d->isRightToLeft() && event->key() == Qt::Key_Left)
3594 || (d->orient == QQuickListView::Vertical && !d->isBottomToTop() && event->key() == Qt::Key_Down)
3595 || (d->orient == QQuickListView::Vertical && d->isBottomToTop() && event->key() == Qt::Key_Up)) {
3596 if (currentIndex() < d->model->count() - 1 || (d->wrap && !event->isAutoRepeat())) {
3597 incrementCurrentIndex();
3598 event->accept();
3599 return;
3600 } else if (d->wrap) {
3601 event->accept();
3602 return;
3603 }
3604 }
3605 }
3606 event->ignore();
3607 QQuickItemView::keyPressEvent(event);
3608}
3609
3610void QQuickListView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
3611{
3612 Q_D(QQuickListView);
3613
3614 if (d->model) {
3615 // When the view changes size, we force the pool to
3616 // shrink by releasing all pooled items.
3617 d->model->drainReusableItemsPool(maxPoolTime: 0);
3618 }
3619
3620 if (d->isRightToLeft()) {
3621 // maintain position relative to the right edge
3622 qreal dx = newGeometry.width() - oldGeometry.width();
3623 setContentX(contentX() - dx);
3624 } else if (d->isBottomToTop()) {
3625 // maintain position relative to the bottom edge
3626 qreal dy = newGeometry.height() - oldGeometry.height();
3627 setContentY(contentY() - dy);
3628 }
3629 QQuickItemView::geometryChange(newGeometry, oldGeometry);
3630}
3631
3632void QQuickListView::initItem(int index, QObject *object)
3633{
3634 QQuickItemView::initItem(index, item: object);
3635
3636 // setting the view from the FxViewItem wrapper is too late if the delegate
3637 // needs access to the view in Component.onCompleted
3638 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
3639 if (item) {
3640 QQuickListViewAttached *attached = static_cast<QQuickListViewAttached *>(
3641 qmlAttachedPropertiesObject<QQuickListView>(obj: item));
3642 if (attached)
3643 attached->setView(this);
3644 }
3645}
3646
3647qreal QQuickListView::maxYExtent() const
3648{
3649 Q_D(const QQuickListView);
3650 if (d->layoutOrientation() == Qt::Horizontal && d->flickableDirection != HorizontalFlick)
3651 return QQuickFlickable::maxYExtent();
3652 return QQuickItemView::maxYExtent();
3653}
3654
3655qreal QQuickListView::maxXExtent() const
3656{
3657 Q_D(const QQuickListView);
3658 if (d->layoutOrientation() == Qt::Vertical && d->flickableDirection != VerticalFlick)
3659 return QQuickFlickable::maxXExtent();
3660 return QQuickItemView::maxXExtent();
3661}
3662
3663/*!
3664 \qmlmethod QtQuick::ListView::incrementCurrentIndex()
3665
3666 Increments the current index. The current index will wrap
3667 if keyNavigationWraps is true and it is currently at the end.
3668 This method has no effect if the \l count is zero.
3669
3670 \b Note: methods should only be called after the Component has completed.
3671*/
3672void QQuickListView::incrementCurrentIndex()
3673{
3674 Q_D(QQuickListView);
3675 int count = d->model ? d->model->count() : 0;
3676 if (count && (currentIndex() < count - 1 || d->wrap)) {
3677 d->moveReason = QQuickListViewPrivate::SetIndex;
3678 int index = currentIndex()+1;
3679 setCurrentIndex((index >= 0 && index < count) ? index : 0);
3680 }
3681}
3682
3683/*!
3684 \qmlmethod QtQuick::ListView::decrementCurrentIndex()
3685
3686 Decrements the current index. The current index will wrap
3687 if keyNavigationWraps is true and it is currently at the beginning.
3688 This method has no effect if the \l count is zero.
3689
3690 \b Note: methods should only be called after the Component has completed.
3691*/
3692void QQuickListView::decrementCurrentIndex()
3693{
3694 Q_D(QQuickListView);
3695 int count = d->model ? d->model->count() : 0;
3696 if (count && (currentIndex() > 0 || d->wrap)) {
3697 d->moveReason = QQuickListViewPrivate::SetIndex;
3698 int index = currentIndex()-1;
3699 setCurrentIndex((index >= 0 && index < count) ? index : count-1);
3700 }
3701}
3702
3703void QQuickListViewPrivate::updateSectionCriteria()
3704{
3705 Q_Q(QQuickListView);
3706 if (q->isComponentComplete() && model) {
3707 QList<QByteArray> roles;
3708 if (sectionCriteria && !sectionCriteria->property().isEmpty())
3709 roles << sectionCriteria->property().toUtf8();
3710 model->setWatchedRoles(roles);
3711 updateSections();
3712 if (itemCount)
3713 forceLayoutPolish();
3714 }
3715}
3716
3717bool QQuickListViewPrivate::applyInsertionChange(const QQmlChangeSet::Change &change, ChangeResult *insertResult, QList<FxViewItem *> *addedItems, QList<MovedItem> *movingIntoView)
3718{
3719 Q_Q(QQuickListView);
3720#if QT_CONFIG(quick_viewtransitions)
3721 Q_UNUSED(movingIntoView)
3722#endif
3723 int modelIndex = change.index;
3724 int count = change.count;
3725
3726 if (q->size().isNull() && visibleItems.isEmpty())
3727 return false;
3728
3729 qreal tempPos = isContentFlowReversed() ? -position()-size() : position();
3730 int index = visibleItems.size() ? mapFromModel(modelIndex) : 0;
3731 qreal lastVisiblePos = buffer + displayMarginEnd + tempPos + size();
3732
3733 if (index < 0) {
3734 int i = visibleItems.size() - 1;
3735 while (i > 0 && visibleItems.at(i)->index == -1)
3736 --i;
3737 if (i == 0 && visibleItems.constFirst()->index == -1) {
3738 // there are no visible items except items marked for removal
3739 index = visibleItems.size();
3740 } else if (visibleItems.at(i)->index + 1 == modelIndex
3741 && visibleItems.at(i)->endPosition() <= lastVisiblePos) {
3742 // Special case of appending an item to the model.
3743 index = visibleItems.size();
3744 } else {
3745 if (modelIndex < visibleIndex) {
3746 // Insert before visible items
3747 visibleIndex += count;
3748 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
3749 if (item->index != -1 && item->index >= modelIndex)
3750 item->index += count;
3751 }
3752 }
3753 return true;
3754 }
3755 }
3756
3757 // index can be the next item past the end of the visible items list (i.e. appended)
3758 qreal pos = 0;
3759 if (visibleItems.size()) {
3760 pos = index < visibleItems.size() ? visibleItems.at(i: index)->position()
3761 : visibleItems.constLast()->endPosition() + spacing;
3762 }
3763
3764 // Update the indexes of the following visible items.
3765 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
3766 if (item->index != -1 && item->index >= modelIndex) {
3767 item->index += count;
3768#if QT_CONFIG(quick_viewtransitions)
3769 if (change.isMove())
3770 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: false);
3771 else
3772 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: false);
3773#endif
3774 }
3775 }
3776
3777 bool visibleAffected = false;
3778 if (insertResult->visiblePos.isValid() && pos < insertResult->visiblePos) {
3779 // Insert items before the visible item.
3780 int insertionIdx = index;
3781 qreal from = tempPos - displayMarginBeginning - buffer;
3782
3783 if (insertionIdx < visibleIndex) {
3784 if (pos >= from) {
3785 // items won't be visible, just note the size for repositioning
3786 insertResult->sizeChangesBeforeVisiblePos += count * (averageSize + spacing);
3787 }
3788 } else {
3789 MutableModelIterator it(model, modelIndex + count - 1, modelIndex -1);
3790 for (; it.hasNext() && pos >= from; it.next()) {
3791 // item is before first visible e.g. in cache buffer
3792 FxViewItem *item = nullptr;
3793 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: it.index))))
3794 item->index = it.index;
3795 if (!item)
3796 item = createItem(modelIndex: it.index, incubationMode: QQmlIncubator::Synchronous);
3797 if (!item)
3798 return false;
3799 if (it.removedAtIndex)
3800 continue;
3801
3802 visibleAffected = true;
3803 visibleItems.insert(i: insertionIdx, t: item);
3804 if (insertionIdx == 0)
3805 insertResult->changedFirstItem = true;
3806 if (!change.isMove()) {
3807 addedItems->append(t: item);
3808#if QT_CONFIG(quick_viewtransitions)
3809 if (transitioner)
3810 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
3811 else
3812#endif
3813 static_cast<FxListItemSG *>(item)->setPosition(pos, immediate: true);
3814 }
3815 insertResult->sizeChangesBeforeVisiblePos += item->size() + spacing;
3816 pos -= item->size() + spacing;
3817 index++;
3818 }
3819 }
3820
3821 int firstOkIdx = -1;
3822 for (int i = 0; i <= insertionIdx && i < visibleItems.size() - 1; i++) {
3823 if (visibleItems.at(i)->index + 1 != visibleItems.at(i: i + 1)->index) {
3824 firstOkIdx = i + 1;
3825 break;
3826 }
3827 }
3828 for (int i = 0; i < firstOkIdx; i++) {
3829 FxViewItem *nvItem = visibleItems.takeFirst();
3830 addedItems->removeOne(t: nvItem);
3831 removeItem(item: nvItem);
3832 }
3833
3834 } else {
3835 MutableModelIterator it(model, modelIndex, modelIndex + count);
3836 for (; it.hasNext() && pos <= lastVisiblePos; it.next()) {
3837 visibleAffected = true;
3838 FxViewItem *item = nullptr;
3839 if (change.isMove() && (item = currentChanges.removedItems.take(key: change.moveKey(index: it.index))))
3840 item->index = it.index;
3841#if QT_CONFIG(quick_viewtransitions)
3842 bool newItem = !item;
3843#endif
3844 it.removedAtIndex = false;
3845 if (!item)
3846 item = createItem(modelIndex: it.index, incubationMode: QQmlIncubator::Synchronous);
3847 if (!item)
3848 return false;
3849 if (it.removedAtIndex) {
3850 releaseItem(item, reusableFlag);
3851 continue;
3852 }
3853
3854 if (index < visibleItems.size())
3855 visibleItems.insert(i: index, t: item);
3856 else // special case of appending an item to the model - as above
3857 visibleItems.append(t: item);
3858 if (index == 0)
3859 insertResult->changedFirstItem = true;
3860 if (change.isMove()) {
3861 // we know this is a move target, since move displaced items that are
3862 // shuffled into view due to a move would be added in refill()
3863#if QT_CONFIG(quick_viewtransitions)
3864 if (newItem && transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: true))
3865 movingIntoView->append(t: MovedItem(item, change.moveKey(index: item->index)));
3866#endif
3867 } else {
3868 addedItems->append(t: item);
3869#if QT_CONFIG(quick_viewtransitions)
3870 if (transitioner)
3871 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::AddTransition, asTarget: true);
3872 else
3873#endif
3874 static_cast<FxListItemSG *>(item)->setPosition(pos, immediate: true);
3875 }
3876 insertResult->sizeChangesAfterVisiblePos += item->size() + spacing;
3877 pos += item->size() + spacing;
3878 ++index;
3879 }
3880 it.disconnect();
3881
3882 if (0 < index && index < visibleItems.size()) {
3883 FxViewItem *prevItem = visibleItems.at(i: index - 1);
3884 FxViewItem *item = visibleItems.at(i: index);
3885 if (prevItem->index != item->index - 1) {
3886 int i = index;
3887#if QT_CONFIG(quick_viewtransitions)
3888 qreal prevPos = prevItem->position();
3889#endif
3890 while (i < visibleItems.size()) {
3891 FxListItemSG *nvItem = static_cast<FxListItemSG *>(visibleItems.takeLast());
3892 insertResult->sizeChangesAfterVisiblePos -= nvItem->size() + spacing;
3893 addedItems->removeOne(t: nvItem);
3894#if QT_CONFIG(quick_viewtransitions)
3895 if (nvItem->transitionScheduledOrRunning())
3896 nvItem->setPosition(pos: prevPos + (nvItem->index - prevItem->index) * averageSize);
3897#endif
3898 removeItem(item: nvItem);
3899 }
3900 }
3901 }
3902 }
3903
3904 updateVisibleIndex();
3905
3906 return visibleAffected;
3907}
3908
3909#if QT_CONFIG(quick_viewtransitions)
3910void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex, const ChangeResult &insertionResult, const ChangeResult &removalResult)
3911{
3912 Q_UNUSED(insertionResult);
3913
3914 if (!transitioner)
3915 return;
3916
3917 int markerItemIndex = -1;
3918 for (int i=0; i<visibleItems.size(); i++) {
3919 if (visibleItems.at(i)->index == afterModelIndex) {
3920 markerItemIndex = i;
3921 break;
3922 }
3923 }
3924 if (markerItemIndex < 0)
3925 return;
3926
3927 const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
3928 qreal sizeRemoved = -removalResult.sizeChangesAfterVisiblePos
3929 - (removalResult.countChangeAfterVisibleItems * (averageSize + spacing));
3930
3931 for (int i=markerItemIndex+1; i<visibleItems.size(); i++) {
3932 FxListItemSG *listItem = static_cast<FxListItemSG *>(visibleItems.at(i));
3933 if (listItem->position() >= viewEndPos)
3934 break;
3935 if (!listItem->transitionScheduledOrRunning()) {
3936 qreal pos = listItem->position();
3937 listItem->setPosition(pos: pos - sizeRemoved);
3938 listItem->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
3939 listItem->setPosition(pos);
3940 }
3941 }
3942}
3943#endif
3944
3945/*!
3946 \qmlmethod QtQuick::ListView::positionViewAtIndex(int index, PositionMode mode)
3947
3948 Positions the view such that the \a index is at the position specified by \a mode:
3949
3950 \value ListView.Beginning position item at the top (or left for horizontal orientation) of the view.
3951 \value ListView.Center position item in the center of the view.
3952 \value ListView.End position item at bottom (or right for horizontal orientation) of the view.
3953 \value ListView.Visible if any part of the item is visible then take no action, otherwise
3954 bring the item into view.
3955 \value ListView.Contain ensure the entire item is visible. If the item is larger than the view,
3956 the item is positioned at the top (or left for horizontal orientation) of the view.
3957 \value ListView.SnapPosition position the item at \l preferredHighlightBegin. This mode is only valid
3958 if \l highlightRangeMode is StrictlyEnforceRange or snapping is enabled via \l snapMode.
3959
3960 If positioning the view at \a index would cause empty space to be displayed at
3961 the beginning or end of the view, the view will be positioned at the boundary.
3962
3963 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
3964 at a particular index. This is unreliable since removing items from the start
3965 of the list does not cause all other items to be repositioned, and because
3966 the actual start of the view can vary based on the size of the delegates.
3967 The correct way to bring an item into view is with \c positionViewAtIndex.
3968
3969 \b Note: methods should only be called after the Component has completed. To position
3970 the view at startup, this method should be called by Component.onCompleted. For
3971 example, to position the view at the end:
3972
3973 \code
3974 Component.onCompleted: positionViewAtIndex(count - 1, ListView.Beginning)
3975 \endcode
3976*/
3977
3978/*!
3979 \qmlmethod QtQuick::ListView::positionViewAtBeginning()
3980 \qmlmethod QtQuick::ListView::positionViewAtEnd()
3981
3982 Positions the view at the beginning or end, taking into account any header or footer.
3983
3984 It is not recommended to use \l {Flickable::}{contentX} or \l {Flickable::}{contentY} to position the view
3985 at a particular index. This is unreliable since removing items from the start
3986 of the list does not cause all other items to be repositioned, and because
3987 the actual start of the view can vary based on the size of the delegates.
3988
3989 \b Note: methods should only be called after the Component has completed. To position
3990 the view at startup, this method should be called by Component.onCompleted. For
3991 example, to position the view at the end on startup:
3992
3993 \code
3994 Component.onCompleted: positionViewAtEnd()
3995 \endcode
3996*/
3997
3998/*!
3999 \qmlmethod int QtQuick::ListView::indexAt(real x, real y)
4000
4001 Returns the index of the visible item containing the point \a x, \a y in content
4002 coordinates. If there is no item at the point specified, or the item is
4003 not visible -1 is returned.
4004
4005 If the item is outside the visible area, -1 is returned, regardless of
4006 whether an item will exist at that point when scrolled into view.
4007
4008 \b Note: methods should only be called after the Component has completed.
4009*/
4010
4011/*!
4012 \qmlmethod Item QtQuick::ListView::itemAt(real x, real y)
4013
4014 Returns the visible item containing the point \a x, \a y in content
4015 coordinates. If there is no item at the point specified, or the item is
4016 not visible null is returned.
4017
4018 If the item is outside the visible area, null is returned, regardless of
4019 whether an item will exist at that point when scrolled into view.
4020
4021 \b Note: methods should only be called after the Component has completed.
4022*/
4023
4024/*!
4025 \qmlmethod Item QtQuick::ListView::itemAtIndex(int index)
4026
4027 Returns the item for \a index. If there is no item for that index, for example
4028 because it has not been created yet, or because it has been panned out of
4029 the visible area and removed from the cache, null is returned.
4030
4031 \b Note: this method should only be called after the Component has completed.
4032 The returned value should also not be stored since it can turn to null
4033 as soon as control goes out of the calling scope, if the view releases that item.
4034
4035 \since 5.13
4036*/
4037
4038/*!
4039 \qmlmethod QtQuick::ListView::forceLayout()
4040
4041 Responding to changes in the model is usually batched to happen only once
4042 per frame. This means that inside script blocks it is possible for the
4043 underlying model to have changed, but the ListView has not caught up yet.
4044
4045 This method forces the ListView to immediately respond to any outstanding
4046 changes in the model.
4047
4048 \since 5.1
4049
4050 \b Note: methods should only be called after the Component has completed.
4051*/
4052
4053QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj)
4054{
4055 return new QQuickListViewAttached(obj);
4056}
4057
4058/*! \internal
4059 Prevents clicking or dragging through floating headers (QTBUG-74046).
4060*/
4061bool QQuickListViewPrivate::wantsPointerEvent(const QPointerEvent *event)
4062{
4063 Q_Q(const QQuickListView);
4064 bool ret = true;
4065
4066 QPointF pos = event->points().first().position();
4067 if (!pos.isNull()) {
4068 if (auto header = q->headerItem()) {
4069 if (q->headerPositioning() != QQuickListView::InlineHeader &&
4070 header->contains(point: q->mapToItem(item: header, point: pos)))
4071 ret = false;
4072 }
4073 if (auto footer = q->footerItem()) {
4074 if (q->footerPositioning() != QQuickListView::InlineFooter &&
4075 footer->contains(point: q->mapToItem(item: footer, point: pos)))
4076 ret = false;
4077 }
4078 }
4079
4080 switch (event->type()) {
4081 case QEvent::MouseButtonPress:
4082 wantedMousePress = ret;
4083 break;
4084 case QEvent::MouseMove:
4085 ret = wantedMousePress;
4086 break;
4087 default:
4088 break;
4089 }
4090
4091 qCDebug(lcEvents) << q << (ret ? "WANTS" : "DOESN'T want") << event;
4092 return ret;
4093}
4094
4095QT_END_NAMESPACE
4096
4097#include "moc_qquicklistview_p.cpp"
4098

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