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