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