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