1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickitemview_p_p.h"
5#include "qquickitemviewfxitem_p_p.h"
6#include <QtQuick/private/qquicktransition_p.h>
7#include <QtQml/QQmlInfo>
8#include "qplatformdefs.h"
9
10QT_BEGIN_NAMESPACE
11
12Q_LOGGING_CATEGORY(lcItemViewDelegateLifecycle, "qt.quick.itemview.lifecycle")
13
14// Default cacheBuffer for all views.
15#ifndef QML_VIEW_DEFAULTCACHEBUFFER
16#define QML_VIEW_DEFAULTCACHEBUFFER 320
17#endif
18
19FxViewItem::FxViewItem(QQuickItem *i, QQuickItemView *v, bool own, QQuickItemViewAttached *attached)
20 : QQuickItemViewFxItem(i, own, QQuickItemViewPrivate::get(o: v))
21 , view(v)
22 , attached(attached)
23{
24}
25
26QQuickItemViewChangeSet::QQuickItemViewChangeSet()
27 : active(false)
28{
29 reset();
30}
31
32bool QQuickItemViewChangeSet::hasPendingChanges() const
33{
34 return !pendingChanges.isEmpty();
35}
36
37void QQuickItemViewChangeSet::applyChanges(const QQmlChangeSet &changeSet)
38{
39 pendingChanges.apply(changeSet);
40
41 int moveId = -1;
42 int moveOffset = 0;
43
44 for (const QQmlChangeSet::Change &r : changeSet.removes()) {
45 itemCount -= r.count;
46 if (moveId == -1 && newCurrentIndex >= r.index + r.count) {
47 newCurrentIndex -= r.count;
48 currentChanged = true;
49 } else if (moveId == -1 && newCurrentIndex >= r.index && newCurrentIndex < r.index + r.count) {
50 // current item has been removed.
51 if (r.isMove()) {
52 moveId = r.moveId;
53 moveOffset = newCurrentIndex - r.index;
54 } else {
55 currentRemoved = true;
56 newCurrentIndex = -1;
57 if (itemCount)
58 newCurrentIndex = qMin(a: r.index, b: itemCount - 1);
59 }
60 currentChanged = true;
61 }
62 }
63 for (const QQmlChangeSet::Change &i : changeSet.inserts()) {
64 if (moveId == -1) {
65 if (itemCount && newCurrentIndex >= i.index) {
66 newCurrentIndex += i.count;
67 currentChanged = true;
68 } else if (newCurrentIndex < 0) {
69 newCurrentIndex = 0;
70 currentChanged = true;
71 } else if (newCurrentIndex == 0 && !itemCount) {
72 // this is the first item, set the initial current index
73 currentChanged = true;
74 }
75 } else if (moveId == i.moveId) {
76 newCurrentIndex = i.index + moveOffset;
77 }
78 itemCount += i.count;
79 }
80}
81
82void QQuickItemViewChangeSet::applyBufferedChanges(const QQuickItemViewChangeSet &other)
83{
84 if (!other.hasPendingChanges())
85 return;
86
87 pendingChanges.apply(changeSet: other.pendingChanges);
88 itemCount = other.itemCount;
89 newCurrentIndex = other.newCurrentIndex;
90 currentChanged = other.currentChanged;
91 currentRemoved = other.currentRemoved;
92}
93
94void QQuickItemViewChangeSet::prepare(int currentIndex, int count)
95{
96 if (active)
97 return;
98 reset();
99 active = true;
100 itemCount = count;
101 newCurrentIndex = currentIndex;
102}
103
104void QQuickItemViewChangeSet::reset()
105{
106 itemCount = 0;
107 newCurrentIndex = -1;
108 pendingChanges.clear();
109 removedItems.clear();
110 active = false;
111 currentChanged = false;
112 currentRemoved = false;
113}
114
115//-----------------------------------
116
117QQuickItemView::QQuickItemView(QQuickFlickablePrivate &dd, QQuickItem *parent)
118 : QQuickFlickable(dd, parent)
119{
120 Q_D(QQuickItemView);
121 d->init();
122}
123
124QQuickItemView::~QQuickItemView()
125{
126 Q_D(QQuickItemView);
127 d->clear(onDestruction: true);
128 if (d->ownModel)
129 delete d->model;
130 delete d->header;
131 delete d->footer;
132}
133
134
135QQuickItem *QQuickItemView::currentItem() const
136{
137 Q_D(const QQuickItemView);
138 return d->currentItem ? d->currentItem->item : nullptr;
139}
140
141QVariant QQuickItemView::model() const
142{
143 Q_D(const QQuickItemView);
144 return d->modelVariant;
145}
146
147void QQuickItemView::setModel(const QVariant &m)
148{
149 Q_D(QQuickItemView);
150 QVariant model = m;
151 if (model.userType() == qMetaTypeId<QJSValue>())
152 model = model.value<QJSValue>().toVariant();
153
154 if (d->modelVariant == model)
155 return;
156 if (d->model) {
157 disconnect(sender: d->model, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
158 receiver: this, SLOT(modelUpdated(QQmlChangeSet,bool)));
159 disconnect(sender: d->model, SIGNAL(initItem(int,QObject*)), receiver: this, SLOT(initItem(int,QObject*)));
160 disconnect(sender: d->model, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(createdItem(int,QObject*)));
161 disconnect(sender: d->model, SIGNAL(destroyingItem(QObject*)), receiver: this, SLOT(destroyingItem(QObject*)));
162 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(object: d->model)) {
163 disconnect(sender: delegateModel, SIGNAL(itemPooled(int,QObject*)), receiver: this, SLOT(onItemPooled(int,QObject*)));
164 disconnect(sender: delegateModel, SIGNAL(itemReused(int,QObject*)), receiver: this, SLOT(onItemReused(int,QObject*)));
165 }
166 }
167
168 QQmlInstanceModel *oldModel = d->model;
169
170 d->clear();
171 d->model = nullptr;
172 d->setPosition(d->contentStartOffset());
173 d->modelVariant = model;
174
175 QObject *object = qvariant_cast<QObject*>(v: model);
176 QQmlInstanceModel *vim = nullptr;
177 if (object && (vim = qobject_cast<QQmlInstanceModel *>(object))) {
178 if (d->ownModel) {
179 delete oldModel;
180 d->ownModel = false;
181 }
182 d->model = vim;
183 } else {
184 if (!d->ownModel) {
185 d->model = new QQmlDelegateModel(qmlContext(this), this);
186 d->ownModel = true;
187 if (isComponentComplete())
188 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
189 } else {
190 d->model = oldModel;
191 }
192 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
193 dataModel->setModel(model);
194 }
195
196 if (d->model) {
197 d->bufferMode = QQuickItemViewPrivate::BufferBefore | QQuickItemViewPrivate::BufferAfter;
198 connect(sender: d->model, SIGNAL(createdItem(int,QObject*)), receiver: this, SLOT(createdItem(int,QObject*)));
199 connect(sender: d->model, SIGNAL(initItem(int,QObject*)), receiver: this, SLOT(initItem(int,QObject*)));
200 connect(sender: d->model, SIGNAL(destroyingItem(QObject*)), receiver: this, SLOT(destroyingItem(QObject*)));
201 if (QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(object: d->model)) {
202 connect(sender: delegateModel, SIGNAL(itemPooled(int,QObject*)), receiver: this, SLOT(onItemPooled(int,QObject*)));
203 connect(sender: delegateModel, SIGNAL(itemReused(int,QObject*)), receiver: this, SLOT(onItemReused(int,QObject*)));
204 }
205 if (isComponentComplete()) {
206 d->updateSectionCriteria();
207 d->refill();
208 /* Setting currentIndex to -2 ensures that we always enter the "currentIndex changed"
209 code path in setCurrentIndex, updating bindings depending on currentIndex.*/
210 d->currentIndex = -2;
211 setCurrentIndex(d->model->count() > 0 ? 0 : -1);
212 d->updateViewport();
213
214#if QT_CONFIG(quick_viewtransitions)
215 if (d->transitioner && d->transitioner->populateTransition) {
216 d->transitioner->setPopulateTransitionEnabled(true);
217 d->forceLayoutPolish();
218 }
219#endif
220 }
221
222 connect(sender: d->model, SIGNAL(modelUpdated(QQmlChangeSet,bool)),
223 receiver: this, SLOT(modelUpdated(QQmlChangeSet,bool)));
224 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
225 QObjectPrivate::connect(sender: dataModel, signal: &QQmlDelegateModel::delegateChanged, receiverPrivate: d, slot: &QQuickItemViewPrivate::applyDelegateChange);
226 emit countChanged();
227 }
228 emit modelChanged();
229 d->moveReason = QQuickItemViewPrivate::Other;
230}
231
232QQmlComponent *QQuickItemView::delegate() const
233{
234 Q_D(const QQuickItemView);
235 if (d->model) {
236 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model))
237 return dataModel->delegate();
238 }
239
240 return nullptr;
241}
242
243void QQuickItemView::setDelegate(QQmlComponent *delegate)
244{
245 Q_D(QQuickItemView);
246 if (delegate == this->delegate())
247 return;
248 if (!d->ownModel) {
249 d->model = new QQmlDelegateModel(qmlContext(this));
250 d->ownModel = true;
251 if (isComponentComplete())
252 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
253 }
254 if (QQmlDelegateModel *dataModel = qobject_cast<QQmlDelegateModel*>(object: d->model)) {
255 int oldCount = dataModel->count();
256 dataModel->setDelegate(delegate);
257 if (oldCount != dataModel->count())
258 emit countChanged();
259 }
260 emit delegateChanged();
261 d->delegateValidated = false;
262}
263
264
265int QQuickItemView::count() const
266{
267 Q_D(const QQuickItemView);
268 if (!d->model)
269 return 0;
270 return d->model->count();
271}
272
273int QQuickItemView::currentIndex() const
274{
275 Q_D(const QQuickItemView);
276 return d->currentIndex;
277}
278
279void QQuickItemView::setCurrentIndex(int index)
280{
281 Q_D(QQuickItemView);
282 if (d->inRequest) // currently creating item
283 return;
284 d->currentIndexCleared = (index == -1);
285
286 d->applyPendingChanges();
287 if (index == d->currentIndex)
288 return;
289 if (isComponentComplete() && d->isValid()) {
290 d->moveReason = QQuickItemViewPrivate::SetIndex;
291 d->updateCurrent(modelIndex: index);
292 } else if (d->currentIndex != index) {
293 d->currentIndex = index;
294 emit currentIndexChanged();
295 }
296}
297
298
299bool QQuickItemView::isWrapEnabled() const
300{
301 Q_D(const QQuickItemView);
302 return d->wrap;
303}
304
305void QQuickItemView::setWrapEnabled(bool wrap)
306{
307 Q_D(QQuickItemView);
308 if (d->wrap == wrap)
309 return;
310 d->wrap = wrap;
311 emit keyNavigationWrapsChanged();
312}
313
314bool QQuickItemView::isKeyNavigationEnabled() const
315{
316 Q_D(const QQuickItemView);
317 return d->explicitKeyNavigationEnabled ? d->keyNavigationEnabled : d->interactive;
318}
319
320void QQuickItemView::setKeyNavigationEnabled(bool keyNavigationEnabled)
321{
322 // TODO: default binding to "interactive" can be removed in Qt 6; it only exists for compatibility reasons.
323 Q_D(QQuickItemView);
324 const bool wasImplicit = !d->explicitKeyNavigationEnabled;
325 if (wasImplicit)
326 QObject::disconnect(sender: this, signal: &QQuickFlickable::interactiveChanged, receiver: this, slot: &QQuickItemView::keyNavigationEnabledChanged);
327
328 d->explicitKeyNavigationEnabled = true;
329
330 // Ensure that we emit the change signal in case there is no different in value.
331 if (d->keyNavigationEnabled != keyNavigationEnabled || wasImplicit) {
332 d->keyNavigationEnabled = keyNavigationEnabled;
333 emit keyNavigationEnabledChanged();
334 }
335}
336
337int QQuickItemView::cacheBuffer() const
338{
339 Q_D(const QQuickItemView);
340 return d->buffer;
341}
342
343void QQuickItemView::setCacheBuffer(int b)
344{
345 Q_D(QQuickItemView);
346 if (b < 0) {
347 qmlWarning(me: this) << "Cannot set a negative cache buffer";
348 return;
349 }
350
351 if (d->buffer != b) {
352 d->buffer = b;
353 if (isComponentComplete()) {
354 d->bufferMode = QQuickItemViewPrivate::BufferBefore | QQuickItemViewPrivate::BufferAfter;
355 d->refillOrLayout();
356 }
357 emit cacheBufferChanged();
358 }
359}
360
361int QQuickItemView::displayMarginBeginning() const
362{
363 Q_D(const QQuickItemView);
364 return d->displayMarginBeginning;
365}
366
367void QQuickItemView::setDisplayMarginBeginning(int margin)
368{
369 Q_D(QQuickItemView);
370 if (d->displayMarginBeginning != margin) {
371 d->displayMarginBeginning = margin;
372 if (isComponentComplete()) {
373 d->forceLayoutPolish();
374 }
375 emit displayMarginBeginningChanged();
376 }
377}
378
379int QQuickItemView::displayMarginEnd() const
380{
381 Q_D(const QQuickItemView);
382 return d->displayMarginEnd;
383}
384
385void QQuickItemView::setDisplayMarginEnd(int margin)
386{
387 Q_D(QQuickItemView);
388 if (d->displayMarginEnd != margin) {
389 d->displayMarginEnd = margin;
390 if (isComponentComplete()) {
391 d->forceLayoutPolish();
392 }
393 emit displayMarginEndChanged();
394 }
395}
396
397Qt::LayoutDirection QQuickItemView::layoutDirection() const
398{
399 Q_D(const QQuickItemView);
400 return d->layoutDirection;
401}
402
403void QQuickItemView::setLayoutDirection(Qt::LayoutDirection layoutDirection)
404{
405 Q_D(QQuickItemView);
406 if (d->layoutDirection != layoutDirection) {
407 d->layoutDirection = layoutDirection;
408 d->regenerate();
409 emit layoutDirectionChanged();
410 emit effectiveLayoutDirectionChanged();
411 }
412}
413
414Qt::LayoutDirection QQuickItemView::effectiveLayoutDirection() const
415{
416 Q_D(const QQuickItemView);
417 if (d->effectiveLayoutMirror)
418 return d->layoutDirection == Qt::RightToLeft ? Qt::LeftToRight : Qt::RightToLeft;
419 else
420 return d->layoutDirection;
421}
422
423QQuickItemView::VerticalLayoutDirection QQuickItemView::verticalLayoutDirection() const
424{
425 Q_D(const QQuickItemView);
426 return d->verticalLayoutDirection;
427}
428
429void QQuickItemView::setVerticalLayoutDirection(VerticalLayoutDirection layoutDirection)
430{
431 Q_D(QQuickItemView);
432 if (d->verticalLayoutDirection != layoutDirection) {
433 d->verticalLayoutDirection = layoutDirection;
434 d->regenerate();
435 emit verticalLayoutDirectionChanged();
436 }
437}
438
439QQmlComponent *QQuickItemView::header() const
440{
441 Q_D(const QQuickItemView);
442 return d->headerComponent;
443}
444
445QQuickItem *QQuickItemView::headerItem() const
446{
447 Q_D(const QQuickItemView);
448 return d->header ? d->header->item : nullptr;
449}
450
451void QQuickItemView::setHeader(QQmlComponent *headerComponent)
452{
453 Q_D(QQuickItemView);
454 if (d->headerComponent != headerComponent) {
455 d->applyPendingChanges();
456 delete d->header;
457 d->header = nullptr;
458 d->headerComponent = headerComponent;
459
460 d->markExtentsDirty();
461
462 if (isComponentComplete()) {
463 d->updateHeader();
464 d->updateFooter();
465 d->updateViewport();
466 d->fixupPosition();
467 } else {
468 emit headerItemChanged();
469 }
470 emit headerChanged();
471 }
472}
473
474QQmlComponent *QQuickItemView::footer() const
475{
476 Q_D(const QQuickItemView);
477 return d->footerComponent;
478}
479
480QQuickItem *QQuickItemView::footerItem() const
481{
482 Q_D(const QQuickItemView);
483 return d->footer ? d->footer->item : nullptr;
484}
485
486void QQuickItemView::setFooter(QQmlComponent *footerComponent)
487{
488 Q_D(QQuickItemView);
489 if (d->footerComponent != footerComponent) {
490 d->applyPendingChanges();
491 delete d->footer;
492 d->footer = nullptr;
493 d->footerComponent = footerComponent;
494
495 if (isComponentComplete()) {
496 d->updateFooter();
497 d->updateViewport();
498 d->fixupPosition();
499 } else {
500 emit footerItemChanged();
501 }
502 emit footerChanged();
503 }
504}
505
506QQmlComponent *QQuickItemView::highlight() const
507{
508 Q_D(const QQuickItemView);
509 return d->highlightComponent;
510}
511
512void QQuickItemView::setHighlight(QQmlComponent *highlightComponent)
513{
514 Q_D(QQuickItemView);
515 if (highlightComponent != d->highlightComponent) {
516 d->applyPendingChanges();
517 d->highlightComponent = highlightComponent;
518 d->createHighlight();
519 if (d->currentItem)
520 d->updateHighlight();
521 emit highlightChanged();
522 }
523}
524
525QQuickItem *QQuickItemView::highlightItem() const
526{
527 Q_D(const QQuickItemView);
528 return d->highlight ? d->highlight->item : nullptr;
529}
530
531bool QQuickItemView::highlightFollowsCurrentItem() const
532{
533 Q_D(const QQuickItemView);
534 return d->autoHighlight;
535}
536
537void QQuickItemView::setHighlightFollowsCurrentItem(bool autoHighlight)
538{
539 Q_D(QQuickItemView);
540 if (d->autoHighlight != autoHighlight) {
541 d->autoHighlight = autoHighlight;
542 if (autoHighlight)
543 d->updateHighlight();
544 emit highlightFollowsCurrentItemChanged();
545 }
546}
547
548QQuickItemView::HighlightRangeMode QQuickItemView::highlightRangeMode() const
549{
550 Q_D(const QQuickItemView);
551 return static_cast<QQuickItemView::HighlightRangeMode>(d->highlightRange);
552}
553
554void QQuickItemView::setHighlightRangeMode(HighlightRangeMode mode)
555{
556 Q_D(QQuickItemView);
557 if (d->highlightRange == mode)
558 return;
559 d->highlightRange = mode;
560 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
561 if (isComponentComplete()) {
562 d->updateViewport();
563 d->moveReason = QQuickItemViewPrivate::Other;
564 d->fixupPosition();
565 }
566 emit highlightRangeModeChanged();
567}
568
569//###Possibly rename these properties, since they are very useful even without a highlight?
570qreal QQuickItemView::preferredHighlightBegin() const
571{
572 Q_D(const QQuickItemView);
573 return d->highlightRangeStart;
574}
575
576void QQuickItemView::setPreferredHighlightBegin(qreal start)
577{
578 Q_D(QQuickItemView);
579 d->highlightRangeStartValid = true;
580 if (d->highlightRangeStart == start)
581 return;
582 d->highlightRangeStart = start;
583 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
584 if (isComponentComplete()) {
585 d->updateViewport();
586 if (!isMoving() && !isFlicking()) {
587 d->moveReason = QQuickItemViewPrivate::Other;
588 d->fixupPosition();
589 }
590 }
591 emit preferredHighlightBeginChanged();
592}
593
594void QQuickItemView::resetPreferredHighlightBegin()
595{
596 Q_D(QQuickItemView);
597 d->highlightRangeStartValid = false;
598 if (d->highlightRangeStart == 0)
599 return;
600 d->highlightRangeStart = 0;
601 if (isComponentComplete()) {
602 d->updateViewport();
603 if (!isMoving() && !isFlicking()) {
604 d->moveReason = QQuickItemViewPrivate::Other;
605 d->fixupPosition();
606 }
607 }
608 emit preferredHighlightBeginChanged();
609}
610
611qreal QQuickItemView::preferredHighlightEnd() const
612{
613 Q_D(const QQuickItemView);
614 return d->highlightRangeEnd;
615}
616
617void QQuickItemView::setPreferredHighlightEnd(qreal end)
618{
619 Q_D(QQuickItemView);
620 d->highlightRangeEndValid = true;
621 if (d->highlightRangeEnd == end)
622 return;
623 d->highlightRangeEnd = end;
624 d->haveHighlightRange = d->highlightRange != NoHighlightRange && d->highlightRangeStart <= d->highlightRangeEnd;
625 if (isComponentComplete()) {
626 d->updateViewport();
627 if (!isMoving() && !isFlicking()) {
628 d->moveReason = QQuickItemViewPrivate::Other;
629 d->fixupPosition();
630 }
631 }
632 emit preferredHighlightEndChanged();
633}
634
635void QQuickItemView::resetPreferredHighlightEnd()
636{
637 Q_D(QQuickItemView);
638 d->highlightRangeEndValid = false;
639 if (d->highlightRangeEnd == 0)
640 return;
641 d->highlightRangeEnd = 0;
642 if (isComponentComplete()) {
643 d->updateViewport();
644 if (!isMoving() && !isFlicking()) {
645 d->moveReason = QQuickItemViewPrivate::Other;
646 d->fixupPosition();
647 }
648 }
649 emit preferredHighlightEndChanged();
650}
651
652int QQuickItemView::highlightMoveDuration() const
653{
654 Q_D(const QQuickItemView);
655 return d->highlightMoveDuration;
656}
657
658void QQuickItemView::setHighlightMoveDuration(int duration)
659{
660 Q_D(QQuickItemView);
661 if (d->highlightMoveDuration != duration) {
662 d->highlightMoveDuration = duration;
663 emit highlightMoveDurationChanged();
664 }
665}
666
667bool QQuickItemView::reuseItems() const
668{
669 return bool(d_func()->reusableFlag == QQmlDelegateModel::Reusable);
670}
671
672void QQuickItemView::setReuseItems(bool reuse)
673{
674 Q_D(QQuickItemView);
675 if (reuseItems() == reuse)
676 return;
677
678 d->reusableFlag = reuse ? QQmlDelegateModel::Reusable : QQmlDelegateModel::NotReusable;
679
680 if (!reuse && d->model) {
681 // When we're told to not reuse items, we
682 // immediately, as documented, drain the pool.
683 d->model->drainReusableItemsPool(maxPoolTime: 0);
684 }
685
686 emit reuseItemsChanged();
687}
688
689#if QT_CONFIG(quick_viewtransitions)
690QQuickTransition *QQuickItemView::populateTransition() const
691{
692 Q_D(const QQuickItemView);
693 return d->transitioner ? d->transitioner->populateTransition : nullptr;
694}
695
696void QQuickItemView::setPopulateTransition(QQuickTransition *transition)
697{
698 Q_D(QQuickItemView);
699 d->createTransitioner();
700 if (d->transitioner->populateTransition != transition) {
701 d->transitioner->populateTransition = transition;
702 emit populateTransitionChanged();
703 }
704}
705
706QQuickTransition *QQuickItemView::addTransition() const
707{
708 Q_D(const QQuickItemView);
709 return d->transitioner ? d->transitioner->addTransition : nullptr;
710}
711
712void QQuickItemView::setAddTransition(QQuickTransition *transition)
713{
714 Q_D(QQuickItemView);
715 d->createTransitioner();
716 if (d->transitioner->addTransition != transition) {
717 d->transitioner->addTransition = transition;
718 emit addTransitionChanged();
719 }
720}
721
722QQuickTransition *QQuickItemView::addDisplacedTransition() const
723{
724 Q_D(const QQuickItemView);
725 return d->transitioner ? d->transitioner->addDisplacedTransition : nullptr;
726}
727
728void QQuickItemView::setAddDisplacedTransition(QQuickTransition *transition)
729{
730 Q_D(QQuickItemView);
731 d->createTransitioner();
732 if (d->transitioner->addDisplacedTransition != transition) {
733 d->transitioner->addDisplacedTransition = transition;
734 emit addDisplacedTransitionChanged();
735 }
736}
737
738QQuickTransition *QQuickItemView::moveTransition() const
739{
740 Q_D(const QQuickItemView);
741 return d->transitioner ? d->transitioner->moveTransition : nullptr;
742}
743
744void QQuickItemView::setMoveTransition(QQuickTransition *transition)
745{
746 Q_D(QQuickItemView);
747 d->createTransitioner();
748 if (d->transitioner->moveTransition != transition) {
749 d->transitioner->moveTransition = transition;
750 emit moveTransitionChanged();
751 }
752}
753
754QQuickTransition *QQuickItemView::moveDisplacedTransition() const
755{
756 Q_D(const QQuickItemView);
757 return d->transitioner ? d->transitioner->moveDisplacedTransition : nullptr;
758}
759
760void QQuickItemView::setMoveDisplacedTransition(QQuickTransition *transition)
761{
762 Q_D(QQuickItemView);
763 d->createTransitioner();
764 if (d->transitioner->moveDisplacedTransition != transition) {
765 d->transitioner->moveDisplacedTransition = transition;
766 emit moveDisplacedTransitionChanged();
767 }
768}
769
770QQuickTransition *QQuickItemView::removeTransition() const
771{
772 Q_D(const QQuickItemView);
773 return d->transitioner ? d->transitioner->removeTransition : nullptr;
774}
775
776void QQuickItemView::setRemoveTransition(QQuickTransition *transition)
777{
778 Q_D(QQuickItemView);
779 d->createTransitioner();
780 if (d->transitioner->removeTransition != transition) {
781 d->transitioner->removeTransition = transition;
782 emit removeTransitionChanged();
783 }
784}
785
786QQuickTransition *QQuickItemView::removeDisplacedTransition() const
787{
788 Q_D(const QQuickItemView);
789 return d->transitioner ? d->transitioner->removeDisplacedTransition : nullptr;
790}
791
792void QQuickItemView::setRemoveDisplacedTransition(QQuickTransition *transition)
793{
794 Q_D(QQuickItemView);
795 d->createTransitioner();
796 if (d->transitioner->removeDisplacedTransition != transition) {
797 d->transitioner->removeDisplacedTransition = transition;
798 emit removeDisplacedTransitionChanged();
799 }
800}
801
802QQuickTransition *QQuickItemView::displacedTransition() const
803{
804 Q_D(const QQuickItemView);
805 return d->transitioner ? d->transitioner->displacedTransition : nullptr;
806}
807
808void QQuickItemView::setDisplacedTransition(QQuickTransition *transition)
809{
810 Q_D(QQuickItemView);
811 d->createTransitioner();
812 if (d->transitioner->displacedTransition != transition) {
813 d->transitioner->displacedTransition = transition;
814 emit displacedTransitionChanged();
815 }
816}
817#endif
818
819void QQuickItemViewPrivate::positionViewAtIndex(int index, int mode)
820{
821 if (!isValid())
822 return;
823 if (mode < QQuickItemView::Beginning || mode > QQuickItemView::SnapPosition)
824 return;
825
826 Q_Q(QQuickItemView);
827 q->cancelFlick();
828 applyPendingChanges();
829 const int modelCount = model->count();
830 int idx = qMax(a: qMin(a: index, b: modelCount - 1), b: 0);
831
832 const auto viewSize = size();
833 qreal pos = isContentFlowReversed() ? -position() - viewSize : position();
834 FxViewItem *item = visibleItem(modelIndex: idx);
835 qreal maxExtent = calculatedMaxExtent();
836 if (!item) {
837 qreal itemPos = positionAt(index: idx);
838 changedVisibleIndex(newIndex: idx);
839 // save the currently visible items in case any of them end up visible again
840 const QList<FxViewItem *> oldVisible = visibleItems;
841 visibleItems.clear();
842 setPosition(qMin(a: itemPos, b: maxExtent));
843 // now release the reference to all the old visible items.
844 for (FxViewItem *item : oldVisible)
845 releaseItem(item, reusableFlag);
846 item = visibleItem(modelIndex: idx);
847 }
848 if (item) {
849 const bool stickyHeader = hasStickyHeader();
850 const bool stickyFooter = hasStickyFooter();
851 const qreal stickyHeaderSize = stickyHeader ? headerSize() : 0;
852 const qreal stickyFooterSize = stickyFooter ? footerSize() : 0;
853
854 const qreal itemPos = item->position();
855 switch (mode) {
856 case QQuickItemView::Beginning:
857 pos = itemPos;
858 if (header && (index < 0 || stickyHeader))
859 pos -= headerSize();
860 break;
861 case QQuickItemView::Center:
862 pos = itemPos - (viewSize - item->size())/2;
863 break;
864 case QQuickItemView::End:
865 pos = itemPos - viewSize + item->size();
866 if (footer && (index >= modelCount || stickyFooter))
867 pos += footerSize();
868 break;
869 case QQuickItemView::Visible:
870 if (itemPos > pos + viewSize - stickyFooterSize)
871 pos = item->endPosition() - viewSize + stickyFooterSize;
872 else if (item->endPosition() <= pos - stickyHeaderSize)
873 pos = itemPos - stickyHeaderSize;
874 break;
875 case QQuickItemView::Contain:
876 if (item->endPosition() >= pos + viewSize + stickyFooterSize)
877 pos = itemPos - viewSize + item->size() + stickyFooterSize;
878 if (itemPos - stickyHeaderSize < pos)
879 pos = itemPos - stickyHeaderSize;
880 break;
881 case QQuickItemView::SnapPosition:
882 pos = itemPos - highlightRangeStart - stickyHeaderSize;
883 break;
884 }
885 pos = qMin(a: pos, b: maxExtent);
886 qreal minExtent = calculatedMinExtent();
887 pos = qMax(a: pos, b: minExtent);
888 moveReason = QQuickItemViewPrivate::Other;
889 setPosition(pos);
890
891 if (highlight) {
892 if (autoHighlight)
893 resetHighlightPosition();
894 updateHighlight();
895 }
896 }
897 fixupPosition();
898}
899
900void QQuickItemView::positionViewAtIndex(int index, int mode)
901{
902 Q_D(QQuickItemView);
903 if (!d->isValid() || index < 0 || index >= d->model->count())
904 return;
905 d->positionViewAtIndex(index, mode);
906}
907
908
909void QQuickItemView::positionViewAtBeginning()
910{
911 Q_D(QQuickItemView);
912 if (!d->isValid())
913 return;
914 d->positionViewAtIndex(index: -1, mode: Beginning);
915}
916
917void QQuickItemView::positionViewAtEnd()
918{
919 Q_D(QQuickItemView);
920 if (!d->isValid())
921 return;
922 d->positionViewAtIndex(index: d->model->count(), mode: End);
923}
924
925static FxViewItem * fxViewItemAtPosition(const QList<FxViewItem *> &items, qreal x, qreal y)
926{
927 for (FxViewItem *item : items) {
928 if (item->contains(x, y))
929 return item;
930 }
931 return nullptr;
932}
933
934int QQuickItemView::indexAt(qreal x, qreal y) const
935{
936 Q_D(const QQuickItemView);
937 const FxViewItem *item = fxViewItemAtPosition(items: d->visibleItems, x, y);
938 return item ? item->index : -1;
939}
940
941QQuickItem *QQuickItemView::itemAt(qreal x, qreal y) const
942{
943 Q_D(const QQuickItemView);
944 const FxViewItem *item = fxViewItemAtPosition(items: d->visibleItems, x, y);
945 return item ? item->item : nullptr;
946}
947
948QQuickItem *QQuickItemView::itemAtIndex(int index) const
949{
950 Q_D(const QQuickItemView);
951 const FxViewItem *item = d->visibleItem(modelIndex: index);
952 return item ? item->item : nullptr;
953}
954
955void QQuickItemView::forceLayout()
956{
957 Q_D(QQuickItemView);
958 if (isComponentComplete() && (d->currentChanges.hasPendingChanges() || d->forceLayout))
959 d->layout();
960}
961
962void QQuickItemViewPrivate::applyPendingChanges()
963{
964 Q_Q(QQuickItemView);
965 if (q->isComponentComplete() && currentChanges.hasPendingChanges())
966 layout();
967}
968
969int QQuickItemViewPrivate::findMoveKeyIndex(QQmlChangeSet::MoveKey key, const QVector<QQmlChangeSet::Change> &changes) const
970{
971 for (int i=0; i<changes.size(); i++) {
972 for (int j=changes[i].index; j<changes[i].index + changes[i].count; j++) {
973 if (changes[i].moveKey(index: j) == key)
974 return j;
975 }
976 }
977 return -1;
978}
979
980qreal QQuickItemViewPrivate::minExtentForAxis(const AxisData &axisData, bool forXAxis) const
981{
982 Q_Q(const QQuickItemView);
983
984 qreal highlightStart;
985 qreal highlightEnd;
986 qreal endPositionFirstItem = 0;
987 qreal extent = -startPosition() + axisData.startMargin;
988 if (isContentFlowReversed()) {
989 if (model && model->count())
990 endPositionFirstItem = positionAt(index: model->count()-1);
991 else
992 extent += headerSize();
993 highlightStart = highlightRangeEndValid ? size() - highlightRangeEnd : size();
994 highlightEnd = highlightRangeStartValid ? size() - highlightRangeStart : size();
995 extent += footerSize();
996 qreal maxExtentAlongAxis = forXAxis ? q->maxXExtent() : q->maxYExtent();
997 if (extent < maxExtentAlongAxis)
998 extent = maxExtentAlongAxis;
999 } else {
1000 endPositionFirstItem = endPositionAt(index: 0);
1001 highlightStart = highlightRangeStart;
1002 highlightEnd = highlightRangeEnd;
1003 extent += headerSize();
1004 }
1005 if (haveHighlightRange && highlightRange == QQuickItemView::StrictlyEnforceRange) {
1006 extent += highlightStart;
1007 FxViewItem *firstItem = visibleItem(modelIndex: 0);
1008 if (firstItem)
1009 extent -= firstItem->sectionSize();
1010 extent = isContentFlowReversed()
1011 ? qMin(a: extent, b: endPositionFirstItem + highlightEnd)
1012 : qMax(a: extent, b: -(endPositionFirstItem - highlightEnd));
1013 }
1014 return extent;
1015}
1016
1017qreal QQuickItemViewPrivate::maxExtentForAxis(const AxisData &axisData, bool forXAxis) const
1018{
1019 Q_Q(const QQuickItemView);
1020
1021 qreal highlightStart;
1022 qreal highlightEnd;
1023 qreal lastItemPosition = 0;
1024 qreal extent = 0;
1025 if (isContentFlowReversed()) {
1026 highlightStart = highlightRangeEndValid ? size() - highlightRangeEnd : size();
1027 highlightEnd = highlightRangeStartValid ? size() - highlightRangeStart : size();
1028 lastItemPosition = endPosition();
1029 } else {
1030 highlightStart = highlightRangeStart;
1031 highlightEnd = highlightRangeEnd;
1032 if (model && model->count())
1033 lastItemPosition = positionAt(index: model->count()-1);
1034 }
1035 if (!model || !model->count()) {
1036 if (!isContentFlowReversed())
1037 maxExtent = header ? -headerSize() : 0;
1038 extent += forXAxis ? q->width() : q->height();
1039 } else if (haveHighlightRange && highlightRange == QQuickItemView::StrictlyEnforceRange) {
1040 extent = -(lastItemPosition - highlightStart);
1041 if (highlightEnd != highlightStart) {
1042 extent = isContentFlowReversed()
1043 ? qMax(a: extent, b: -(endPosition() - highlightEnd))
1044 : qMin(a: extent, b: -(endPosition() - highlightEnd));
1045 }
1046 } else {
1047 extent = -(endPosition() - (forXAxis ? q->width() : q->height()));
1048 }
1049 if (isContentFlowReversed()) {
1050 extent -= headerSize();
1051 extent -= axisData.endMargin;
1052 } else {
1053 extent -= footerSize();
1054 extent -= axisData.endMargin;
1055 qreal minExtentAlongAxis = forXAxis ? q->minXExtent() : q->minYExtent();
1056 if (extent > minExtentAlongAxis)
1057 extent = minExtentAlongAxis;
1058 }
1059
1060 return extent;
1061}
1062
1063qreal QQuickItemViewPrivate::calculatedMinExtent() const
1064{
1065 Q_Q(const QQuickItemView);
1066 qreal minExtent;
1067 if (layoutOrientation() == Qt::Vertical)
1068 minExtent = isContentFlowReversed() ? q->maxYExtent() - size(): -q->minYExtent();
1069 else
1070 minExtent = isContentFlowReversed() ? q->maxXExtent() - size(): -q->minXExtent();
1071 return minExtent;
1072
1073}
1074
1075qreal QQuickItemViewPrivate::calculatedMaxExtent() const
1076{
1077 Q_Q(const QQuickItemView);
1078 qreal maxExtent;
1079 if (layoutOrientation() == Qt::Vertical)
1080 maxExtent = isContentFlowReversed() ? q->minYExtent() - size(): -q->maxYExtent();
1081 else
1082 maxExtent = isContentFlowReversed() ? q->minXExtent() - size(): -q->maxXExtent();
1083 return maxExtent;
1084}
1085
1086void QQuickItemViewPrivate::applyDelegateChange()
1087{
1088 releaseVisibleItems(reusableFlag: QQmlDelegateModel::NotReusable);
1089 releaseItem(item: currentItem, reusableFlag: QQmlDelegateModel::NotReusable);
1090 currentItem = nullptr;
1091 updateSectionCriteria();
1092 refill();
1093 moveReason = QQuickItemViewPrivate::SetIndex;
1094 updateCurrent(modelIndex: currentIndex);
1095 if (highlight && currentItem) {
1096 if (autoHighlight)
1097 resetHighlightPosition();
1098 updateTrackedItem();
1099 }
1100 moveReason = QQuickItemViewPrivate::Other;
1101 updateViewport();
1102}
1103
1104// for debugging only
1105void QQuickItemViewPrivate::checkVisible() const
1106{
1107 int skip = 0;
1108 for (int i = 0; i < visibleItems.size(); ++i) {
1109 FxViewItem *item = visibleItems.at(i);
1110 if (item->index == -1) {
1111 ++skip;
1112 } else if (item->index != visibleIndex + i - skip) {
1113 qFatal(msg: "index %d %d %d", visibleIndex, i, item->index);
1114 }
1115 }
1116}
1117
1118// for debugging only
1119void QQuickItemViewPrivate::showVisibleItems() const
1120{
1121 qDebug() << "Visible items:";
1122 for (FxViewItem *item : visibleItems) {
1123 qDebug() << "\t" << item->index
1124 << item->item->objectName()
1125 << item->position();
1126 }
1127}
1128
1129void QQuickItemViewPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change,
1130 const QRectF &oldGeometry)
1131{
1132 Q_Q(QQuickItemView);
1133 QQuickFlickablePrivate::itemGeometryChanged(item, change, oldGeometry);
1134 if (!q->isComponentComplete())
1135 return;
1136
1137 if (header && header->item == item) {
1138 updateHeader();
1139 markExtentsDirty();
1140 updateViewport();
1141 if (!q->isMoving() && !q->isFlicking())
1142 fixupPosition();
1143 } else if (footer && footer->item == item) {
1144 updateFooter();
1145 markExtentsDirty();
1146 updateViewport();
1147 if (!q->isMoving() && !q->isFlicking())
1148 fixupPosition();
1149 }
1150
1151 if (currentItem && currentItem->item == item) {
1152 // don't allow item movement transitions to trigger a re-layout and
1153 // start new transitions
1154 bool prevInLayout = inLayout;
1155#if QT_CONFIG(quick_viewtransitions)
1156 if (!inLayout) {
1157 FxViewItem *actualItem = transitioner ? visibleItem(modelIndex: currentIndex) : nullptr;
1158 if (actualItem && actualItem->transitionRunning())
1159 inLayout = true;
1160 }
1161#endif
1162 updateHighlight();
1163 inLayout = prevInLayout;
1164 }
1165
1166 if (trackedItem && trackedItem->item == item)
1167 q->trackedPositionChanged();
1168}
1169
1170void QQuickItemView::destroyRemoved()
1171{
1172 Q_D(QQuickItemView);
1173
1174#if QT_CONFIG(quick_viewtransitions)
1175 bool hasRemoveTransition = false;
1176 bool hasRemoveTransitionAsTarget = false;
1177 if (d->transitioner) {
1178 hasRemoveTransition = d->transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
1179 hasRemoveTransitionAsTarget = d->transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: true);
1180 }
1181#endif
1182
1183 for (QList<FxViewItem*>::Iterator it = d->visibleItems.begin();
1184 it != d->visibleItems.end();) {
1185 FxViewItem *item = *it;
1186 if (item->index == -1 && (!item->attached || item->attached->delayRemove() == false)) {
1187#if QT_CONFIG(quick_viewtransitions)
1188 if (hasRemoveTransitionAsTarget) {
1189 // don't remove from visibleItems until next layout()
1190 d->runDelayedRemoveTransition = true;
1191 QObject::disconnect(sender: item->attached, SIGNAL(delayRemoveChanged()), receiver: this, SLOT(destroyRemoved()));
1192 ++it;
1193 } else {
1194 if (hasRemoveTransition)
1195 d->runDelayedRemoveTransition = true;
1196#endif
1197 d->releaseItem(item, reusableFlag: d->reusableFlag);
1198 it = d->visibleItems.erase(pos: it);
1199#if QT_CONFIG(quick_viewtransitions)
1200 }
1201#endif
1202 } else {
1203 ++it;
1204 }
1205 }
1206
1207 // Correct the positioning of the items
1208 d->forceLayoutPolish();
1209}
1210
1211void QQuickItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
1212{
1213 Q_D(QQuickItemView);
1214 if (reset) {
1215 cancelFlick();
1216#if QT_CONFIG(quick_viewtransitions)
1217 if (d->transitioner)
1218 d->transitioner->setPopulateTransitionEnabled(true);
1219#endif
1220 d->moveReason = QQuickItemViewPrivate::SetIndex;
1221 d->regenerate();
1222 if (d->highlight && d->currentItem) {
1223 if (d->autoHighlight)
1224 d->resetHighlightPosition();
1225 d->updateTrackedItem();
1226 }
1227 d->moveReason = QQuickItemViewPrivate::Other;
1228 emit countChanged();
1229#if QT_CONFIG(quick_viewtransitions)
1230 if (d->transitioner && d->transitioner->populateTransition)
1231 d->forceLayoutPolish();
1232#endif
1233 } else {
1234 if (d->inLayout) {
1235 d->bufferedChanges.prepare(currentIndex: d->currentIndex, count: d->itemCount);
1236 d->bufferedChanges.applyChanges(changeSet);
1237 } else {
1238 if (d->bufferedChanges.hasPendingChanges()) {
1239 d->currentChanges.applyBufferedChanges(other: d->bufferedChanges);
1240 d->bufferedChanges.reset();
1241 }
1242 d->currentChanges.prepare(currentIndex: d->currentIndex, count: d->itemCount);
1243 d->currentChanges.applyChanges(changeSet);
1244 }
1245 polish();
1246 }
1247}
1248
1249void QQuickItemView::animStopped()
1250{
1251 Q_D(QQuickItemView);
1252 d->bufferMode = QQuickItemViewPrivate::BufferBefore | QQuickItemViewPrivate::BufferAfter;
1253 d->refillOrLayout();
1254 if (d->haveHighlightRange && d->highlightRange == QQuickItemView::StrictlyEnforceRange)
1255 d->updateHighlight();
1256}
1257
1258
1259void QQuickItemView::trackedPositionChanged()
1260{
1261 Q_D(QQuickItemView);
1262 if (!d->trackedItem || !d->currentItem)
1263 return;
1264
1265 if (d->inLayout) {
1266 polish();
1267 return;
1268 }
1269
1270 if (d->moveReason == QQuickItemViewPrivate::SetIndex) {
1271 qreal trackedPos = d->trackedItem->position();
1272 qreal trackedSize = d->trackedItem->size();
1273 qreal viewPos = d->isContentFlowReversed() ? -d->position()-d->size() : d->position();
1274 qreal pos = viewPos;
1275 if (d->haveHighlightRange) {
1276 if (trackedPos > pos + d->highlightRangeEnd - trackedSize)
1277 pos = trackedPos - d->highlightRangeEnd + trackedSize;
1278 if (trackedPos < pos + d->highlightRangeStart)
1279 pos = trackedPos - d->highlightRangeStart;
1280 if (d->highlightRange != StrictlyEnforceRange) {
1281 qreal maxExtent = d->calculatedMaxExtent();
1282 if (pos > maxExtent)
1283 pos = maxExtent;
1284 qreal minExtent = d->calculatedMinExtent();
1285 if (pos < minExtent)
1286 pos = minExtent;
1287 }
1288 } else {
1289 if (d->trackedItem != d->currentItem) {
1290 // also make section header visible
1291 trackedPos -= d->currentItem->sectionSize();
1292 trackedSize += d->currentItem->sectionSize();
1293 }
1294 qreal trackedEndPos = d->trackedItem->endPosition();
1295 qreal toItemPos = d->currentItem->position();
1296 qreal toItemEndPos = d->currentItem->endPosition();
1297 if (d->showHeaderForIndex(index: d->currentIndex)) {
1298 qreal startOffset = -d->contentStartOffset();
1299 trackedPos -= startOffset;
1300 trackedEndPos -= startOffset;
1301 toItemPos -= startOffset;
1302 toItemEndPos -= startOffset;
1303 } else if (d->showFooterForIndex(index: d->currentIndex)) {
1304 qreal endOffset = d->footerSize();
1305 if (d->layoutOrientation() == Qt::Vertical) {
1306 if (d->isContentFlowReversed())
1307 endOffset += d->vData.startMargin;
1308 else
1309 endOffset += d->vData.endMargin;
1310 } else {
1311 if (d->isContentFlowReversed())
1312 endOffset += d->hData.startMargin;
1313 else
1314 endOffset += d->hData.endMargin;
1315 }
1316 trackedPos += endOffset;
1317 trackedEndPos += endOffset;
1318 toItemPos += endOffset;
1319 toItemEndPos += endOffset;
1320 }
1321
1322 if (trackedEndPos >= viewPos + d->size()
1323 && toItemEndPos >= viewPos + d->size()) {
1324 if (trackedEndPos <= toItemEndPos) {
1325 pos = trackedEndPos - d->size();
1326 if (trackedSize > d->size())
1327 pos = trackedPos;
1328 } else {
1329 pos = toItemEndPos - d->size();
1330 if (d->currentItem->size() > d->size())
1331 pos = d->currentItem->position();
1332 }
1333 }
1334 if (trackedPos < pos && toItemPos < pos)
1335 pos = qMax(a: trackedPos, b: toItemPos);
1336 }
1337 if (viewPos != pos) {
1338 d->calcVelocity = true;
1339 d->setPosition(pos);
1340 d->calcVelocity = false;
1341 }
1342 }
1343}
1344
1345void QQuickItemView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
1346{
1347 Q_D(QQuickItemView);
1348 d->markExtentsDirty();
1349 if (isComponentComplete() && (d->isValid() || !d->visibleItems.isEmpty()))
1350 d->forceLayoutPolish();
1351 QQuickFlickable::geometryChange(newGeometry, oldGeometry);
1352}
1353
1354qreal QQuickItemView::minYExtent() const
1355{
1356 Q_D(const QQuickItemView);
1357 if (d->layoutOrientation() == Qt::Horizontal)
1358 return QQuickFlickable::minYExtent();
1359
1360 if (d->vData.minExtentDirty) {
1361 d->minExtent = d->minExtentForAxis(axisData: d->vData, forXAxis: false);
1362 d->vData.minExtentDirty = false;
1363 }
1364
1365 return d->minExtent;
1366}
1367
1368qreal QQuickItemView::maxYExtent() const
1369{
1370 Q_D(const QQuickItemView);
1371 if (d->layoutOrientation() == Qt::Horizontal)
1372 return QQuickFlickable::maxYExtent();
1373
1374 if (d->vData.maxExtentDirty) {
1375 d->maxExtent = d->maxExtentForAxis(axisData: d->vData, forXAxis: false);
1376 d->vData.maxExtentDirty = false;
1377 }
1378
1379 return d->maxExtent;
1380}
1381
1382qreal QQuickItemView::minXExtent() const
1383{
1384 Q_D(const QQuickItemView);
1385 if (d->layoutOrientation() == Qt::Vertical)
1386 return QQuickFlickable::minXExtent();
1387
1388 if (d->hData.minExtentDirty) {
1389 d->minExtent = d->minExtentForAxis(axisData: d->hData, forXAxis: true);
1390 d->hData.minExtentDirty = false;
1391 }
1392
1393 return d->minExtent;
1394}
1395
1396qreal QQuickItemView::maxXExtent() const
1397{
1398 Q_D(const QQuickItemView);
1399 if (d->layoutOrientation() == Qt::Vertical)
1400 return QQuickFlickable::maxXExtent();
1401
1402 if (d->hData.maxExtentDirty) {
1403 d->maxExtent = d->maxExtentForAxis(axisData: d->hData, forXAxis: true);
1404 d->hData.maxExtentDirty = false;
1405 }
1406
1407 return d->maxExtent;
1408}
1409
1410void QQuickItemView::setContentX(qreal pos)
1411{
1412 Q_D(QQuickItemView);
1413 // Positioning the view manually should override any current movement state
1414 d->moveReason = QQuickItemViewPrivate::Other;
1415 QQuickFlickable::setContentX(pos);
1416}
1417
1418void QQuickItemView::setContentY(qreal pos)
1419{
1420 Q_D(QQuickItemView);
1421 // Positioning the view manually should override any current movement state
1422 d->moveReason = QQuickItemViewPrivate::Other;
1423 QQuickFlickable::setContentY(pos);
1424}
1425
1426qreal QQuickItemView::originX() const
1427{
1428 Q_D(const QQuickItemView);
1429 if (d->layoutOrientation() == Qt::Horizontal
1430 && effectiveLayoutDirection() == Qt::RightToLeft
1431 && contentWidth() < width()) {
1432 return -d->lastPosition() - d->footerSize();
1433 }
1434 return QQuickFlickable::originX();
1435}
1436
1437qreal QQuickItemView::originY() const
1438{
1439 Q_D(const QQuickItemView);
1440 if (d->layoutOrientation() == Qt::Vertical
1441 && d->verticalLayoutDirection == QQuickItemView::BottomToTop
1442 && contentHeight() < height()) {
1443 return -d->lastPosition() - d->footerSize();
1444 }
1445 return QQuickFlickable::originY();
1446}
1447
1448void QQuickItemView::updatePolish()
1449{
1450 Q_D(QQuickItemView);
1451 QQuickFlickable::updatePolish();
1452 d->layout();
1453}
1454
1455void QQuickItemView::componentComplete()
1456{
1457 Q_D(QQuickItemView);
1458 if (d->model && d->ownModel)
1459 static_cast<QQmlDelegateModel *>(d->model.data())->componentComplete();
1460
1461 QQuickFlickable::componentComplete();
1462
1463 d->updateSectionCriteria();
1464 d->updateHeader();
1465 d->updateFooter();
1466 d->updateViewport();
1467 d->setPosition(d->contentStartOffset());
1468#if QT_CONFIG(quick_viewtransitions)
1469 if (d->transitioner)
1470 d->transitioner->setPopulateTransitionEnabled(true);
1471#endif
1472
1473 if (d->isValid()) {
1474 d->refill();
1475 d->moveReason = QQuickItemViewPrivate::SetIndex;
1476 if (d->currentIndex < 0 && !d->currentIndexCleared)
1477 d->updateCurrent(modelIndex: 0);
1478 else
1479 d->updateCurrent(modelIndex: d->currentIndex);
1480 if (d->highlight && d->currentItem) {
1481 if (d->autoHighlight)
1482 d->resetHighlightPosition();
1483 d->updateTrackedItem();
1484 }
1485 d->moveReason = QQuickItemViewPrivate::Other;
1486 d->fixupPosition();
1487 }
1488 if (d->model && d->model->count())
1489 emit countChanged();
1490}
1491
1492
1493
1494QQuickItemViewPrivate::QQuickItemViewPrivate()
1495 : itemCount(0)
1496 , buffer(QML_VIEW_DEFAULTCACHEBUFFER), bufferMode(BufferBefore | BufferAfter)
1497 , displayMarginBeginning(0), displayMarginEnd(0)
1498 , layoutDirection(Qt::LeftToRight), verticalLayoutDirection(QQuickItemView::TopToBottom)
1499 , visibleIndex(0)
1500 , currentIndex(-1), currentItem(nullptr)
1501 , trackedItem(nullptr), requestedIndex(-1)
1502 , highlightComponent(nullptr), highlight(nullptr)
1503 , highlightRange(QQuickItemView::NoHighlightRange)
1504 , highlightRangeStart(0), highlightRangeEnd(0)
1505 , highlightMoveDuration(150)
1506 , headerComponent(nullptr), header(nullptr), footerComponent(nullptr), footer(nullptr)
1507#if QT_CONFIG(quick_viewtransitions)
1508 , transitioner(nullptr)
1509#endif
1510 , minExtent(0), maxExtent(0)
1511 , ownModel(false), wrap(false)
1512 , keyNavigationEnabled(true)
1513 , explicitKeyNavigationEnabled(false)
1514 , inLayout(false), inViewportMoved(false), forceLayout(false), currentIndexCleared(false)
1515 , haveHighlightRange(false), autoHighlight(true), highlightRangeStartValid(false), highlightRangeEndValid(false)
1516 , fillCacheBuffer(false), inRequest(false)
1517#if QT_CONFIG(quick_viewtransitions)
1518 , runDelayedRemoveTransition(false)
1519#endif
1520 , delegateValidated(false), isClearing(false)
1521{
1522 bufferPause.addAnimationChangeListener(listener: this, QAbstractAnimationJob::Completion);
1523 bufferPause.setLoopCount(1);
1524 bufferPause.setDuration(16);
1525}
1526
1527QQuickItemViewPrivate::~QQuickItemViewPrivate()
1528{
1529#if QT_CONFIG(quick_viewtransitions)
1530 if (transitioner)
1531 transitioner->setChangeListener(nullptr);
1532 delete transitioner;
1533#endif
1534}
1535
1536bool QQuickItemViewPrivate::isValid() const
1537{
1538 return model && model->count() && model->isValid();
1539}
1540
1541qreal QQuickItemViewPrivate::position() const
1542{
1543 Q_Q(const QQuickItemView);
1544 return layoutOrientation() == Qt::Vertical ? q->contentY() : q->contentX();
1545}
1546
1547qreal QQuickItemViewPrivate::size() const
1548{
1549 Q_Q(const QQuickItemView);
1550 return layoutOrientation() == Qt::Vertical ? q->height() : q->width();
1551}
1552
1553qreal QQuickItemViewPrivate::startPosition() const
1554{
1555 return isContentFlowReversed() ? -lastPosition() : originPosition();
1556}
1557
1558qreal QQuickItemViewPrivate::endPosition() const
1559{
1560 return isContentFlowReversed() ? -originPosition() : lastPosition();
1561}
1562
1563qreal QQuickItemViewPrivate::contentStartOffset() const
1564{
1565 qreal pos = -headerSize();
1566 if (layoutOrientation() == Qt::Vertical) {
1567 if (isContentFlowReversed())
1568 pos -= vData.endMargin;
1569 else
1570 pos -= vData.startMargin;
1571 } else {
1572 if (isContentFlowReversed())
1573 pos -= hData.endMargin;
1574 else
1575 pos -= hData.startMargin;
1576 }
1577 return pos;
1578}
1579
1580int QQuickItemViewPrivate::findLastVisibleIndex(int defaultValue) const
1581{
1582 for (auto it = visibleItems.rbegin(), end = visibleItems.rend(); it != end; ++it) {
1583 auto item = *it;
1584 if (item->index != -1)
1585 return item->index;
1586 }
1587 return defaultValue;
1588}
1589
1590FxViewItem *QQuickItemViewPrivate::visibleItem(int modelIndex) const {
1591 if (modelIndex >= visibleIndex && modelIndex < visibleIndex + visibleItems.size()) {
1592 for (int i = modelIndex - visibleIndex; i < visibleItems.size(); ++i) {
1593 FxViewItem *item = visibleItems.at(i);
1594 if (item->index == modelIndex)
1595 return item;
1596 }
1597 }
1598 return nullptr;
1599}
1600
1601FxViewItem *QQuickItemViewPrivate::firstItemInView() const {
1602 const qreal pos = isContentFlowReversed() ? -position()-size() : position();
1603 for (FxViewItem *item : visibleItems) {
1604 if (item->index != -1 && item->endPosition() > pos)
1605 return item;
1606 }
1607 return visibleItems.size() ? visibleItems.first() : 0;
1608}
1609
1610int QQuickItemViewPrivate::findLastIndexInView() const
1611{
1612 const qreal viewEndPos = isContentFlowReversed() ? -position() : position() + size();
1613 for (auto it = visibleItems.rbegin(), end = visibleItems.rend(); it != end; ++it) {
1614 auto item = *it;
1615 if (item->index != -1 && item->position() <= viewEndPos)
1616 return item->index;
1617 }
1618 return -1;
1619}
1620
1621// Map a model index to visibleItems list index.
1622// These may differ if removed items are still present in the visible list,
1623// e.g. doing a removal animation
1624int QQuickItemViewPrivate::mapFromModel(int modelIndex) const
1625{
1626 if (modelIndex < visibleIndex || modelIndex >= visibleIndex + visibleItems.size())
1627 return -1;
1628 for (int i = 0; i < visibleItems.size(); ++i) {
1629 FxViewItem *item = visibleItems.at(i);
1630 if (item->index == modelIndex)
1631 return i;
1632 if (item->index > modelIndex)
1633 return -1;
1634 }
1635 return -1; // Not in visibleList
1636}
1637
1638void QQuickItemViewPrivate::init()
1639{
1640 Q_Q(QQuickItemView);
1641 q->setFlag(flag: QQuickItem::ItemIsFocusScope);
1642 QObject::connect(sender: q, SIGNAL(movementEnded()), receiver: q, SLOT(animStopped()));
1643 QObject::connect(sender: q, signal: &QQuickFlickable::interactiveChanged, context: q, slot: &QQuickItemView::keyNavigationEnabledChanged);
1644 q->setFlickableDirection(QQuickFlickable::VerticalFlick);
1645}
1646
1647void QQuickItemViewPrivate::updateCurrent(int modelIndex)
1648{
1649 Q_Q(QQuickItemView);
1650 applyPendingChanges();
1651 if (!q->isComponentComplete() || !isValid() || modelIndex < 0 || modelIndex >= model->count()) {
1652 if (currentItem) {
1653 if (currentItem->attached)
1654 currentItem->attached->setIsCurrentItem(false);
1655 releaseItem(item: currentItem, reusableFlag);
1656 currentItem = nullptr;
1657 currentIndex = modelIndex;
1658 emit q->currentIndexChanged();
1659 emit q->currentItemChanged();
1660 updateHighlight();
1661 } else if (currentIndex != modelIndex) {
1662 currentIndex = modelIndex;
1663 emit q->currentIndexChanged();
1664 }
1665 return;
1666 }
1667
1668 if (currentItem && currentIndex == modelIndex) {
1669 updateHighlight();
1670 return;
1671 }
1672
1673 FxViewItem *oldCurrentItem = currentItem;
1674 int oldCurrentIndex = currentIndex;
1675 currentIndex = modelIndex;
1676 currentItem = createItem(modelIndex, incubationMode: QQmlIncubator::AsynchronousIfNested);
1677 if (oldCurrentItem && oldCurrentItem->attached && (!currentItem || oldCurrentItem->item != currentItem->item))
1678 oldCurrentItem->attached->setIsCurrentItem(false);
1679 if (currentItem) {
1680 currentItem->item->setFocus(true);
1681 if (currentItem->attached)
1682 currentItem->attached->setIsCurrentItem(true);
1683 initializeCurrentItem();
1684 }
1685
1686 updateHighlight();
1687 if (oldCurrentIndex != currentIndex)
1688 emit q->currentIndexChanged();
1689 if (oldCurrentItem != currentItem
1690 && (!oldCurrentItem || !currentItem || oldCurrentItem->item != currentItem->item))
1691 emit q->currentItemChanged();
1692 releaseItem(item: oldCurrentItem, reusableFlag);
1693}
1694
1695void QQuickItemViewPrivate::clear(bool onDestruction)
1696{
1697 Q_Q(QQuickItemView);
1698
1699 isClearing = true;
1700 auto cleanup = qScopeGuard(f: [this] { isClearing = false; });
1701
1702 currentChanges.reset();
1703 bufferedChanges.reset();
1704 timeline.clear();
1705
1706 releaseVisibleItems(reusableFlag: QQmlInstanceModel::NotReusable);
1707 visibleIndex = 0;
1708
1709#if QT_CONFIG(quick_viewtransitions)
1710 for (FxViewItem *item : std::as_const(t&: releasePendingTransition)) {
1711 item->releaseAfterTransition = false;
1712 releaseItem(item, reusableFlag: QQmlInstanceModel::NotReusable);
1713 }
1714 releasePendingTransition.clear();
1715#endif
1716
1717 auto oldCurrentItem = currentItem;
1718 releaseItem(item: currentItem, reusableFlag: QQmlDelegateModel::NotReusable);
1719 currentItem = nullptr;
1720 if (oldCurrentItem)
1721 emit q->currentItemChanged();
1722 createHighlight(onDestruction);
1723 trackedItem = nullptr;
1724
1725 if (requestedIndex >= 0) {
1726 if (model)
1727 model->cancel(requestedIndex);
1728 requestedIndex = -1;
1729 }
1730
1731 markExtentsDirty();
1732 itemCount = 0;
1733}
1734
1735
1736void QQuickItemViewPrivate::mirrorChange()
1737{
1738 Q_Q(QQuickItemView);
1739 regenerate();
1740 emit q->effectiveLayoutDirectionChanged();
1741}
1742
1743void QQuickItemViewPrivate::animationFinished(QAbstractAnimationJob *)
1744{
1745 Q_Q(QQuickItemView);
1746 fillCacheBuffer = true;
1747 q->polish();
1748}
1749
1750void QQuickItemViewPrivate::refill()
1751{
1752 qreal s = qMax(a: size(), b: qreal(0.));
1753 const auto pos = position();
1754 if (isContentFlowReversed())
1755 refill(from: -pos - displayMarginBeginning-s, to: -pos + displayMarginEnd);
1756 else
1757 refill(from: pos - displayMarginBeginning, to: pos + displayMarginEnd+s);
1758}
1759
1760void QQuickItemViewPrivate::refill(qreal from, qreal to)
1761{
1762 Q_Q(QQuickItemView);
1763 if (!model || !model->isValid() || !q->isComponentComplete())
1764 return;
1765 if (q->size().isEmpty() && visibleItems.isEmpty())
1766 return;
1767 if (!model->count()) {
1768 updateHeader();
1769 updateFooter();
1770 updateViewport();
1771 return;
1772 }
1773
1774 do {
1775 bufferPause.stop();
1776 if (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges() || currentChanges.active) {
1777 currentChanges.reset();
1778 bufferedChanges.reset();
1779 releaseVisibleItems(reusableFlag);
1780 }
1781
1782 int prevCount = itemCount;
1783 itemCount = model->count();
1784 qreal bufferFrom = from - buffer;
1785 qreal bufferTo = to + buffer;
1786 qreal fillFrom = from;
1787 qreal fillTo = to;
1788
1789 bool added = addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, doBuffer: false);
1790 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
1791
1792 if (requestedIndex == -1 && buffer && bufferMode != NoBuffer) {
1793 if (added) {
1794 // We've already created a new delegate this frame.
1795 // Just schedule a buffer refill.
1796 bufferPause.start();
1797 } else {
1798 if (bufferMode & BufferAfter)
1799 fillTo = bufferTo;
1800 if (bufferMode & BufferBefore)
1801 fillFrom = bufferFrom;
1802 added |= addVisibleItems(fillFrom, fillTo, bufferFrom, bufferTo, doBuffer: true);
1803 }
1804 }
1805
1806 if (added || removed) {
1807 markExtentsDirty();
1808 updateBeginningEnd();
1809 visibleItemsChanged();
1810 updateHeader();
1811 updateFooter();
1812 updateViewport();
1813 }
1814
1815 if (prevCount != itemCount)
1816 emit q->countChanged();
1817 } while (currentChanges.hasPendingChanges() || bufferedChanges.hasPendingChanges());
1818 storeFirstVisibleItemPosition();
1819}
1820
1821void QQuickItemViewPrivate::regenerate(bool orientationChanged)
1822{
1823 Q_Q(QQuickItemView);
1824 if (q->isComponentComplete()) {
1825 if (orientationChanged) {
1826 delete header;
1827 header = nullptr;
1828 delete footer;
1829 footer = nullptr;
1830 }
1831 clear();
1832 updateHeader();
1833 updateFooter();
1834 updateViewport();
1835 setPosition(contentStartOffset());
1836 refill();
1837 updateCurrent(modelIndex: currentIndex);
1838 }
1839}
1840
1841void QQuickItemViewPrivate::updateViewport()
1842{
1843 Q_Q(QQuickItemView);
1844 qreal extra = headerSize() + footerSize();
1845 qreal contentSize = isValid() || !visibleItems.isEmpty() ? (endPosition() - startPosition()) : 0.0;
1846 if (layoutOrientation() == Qt::Vertical)
1847 q->setContentHeight(contentSize + extra);
1848 else
1849 q->setContentWidth(contentSize + extra);
1850}
1851
1852void QQuickItemViewPrivate::layout()
1853{
1854 Q_Q(QQuickItemView);
1855 if (inLayout)
1856 return;
1857
1858 inLayout = true;
1859
1860 // viewBounds contains bounds before any add/remove/move operation to the view
1861 QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
1862
1863 if (!isValid() && !visibleItems.size()) {
1864 clear();
1865 setPosition(contentStartOffset());
1866 updateViewport();
1867#if QT_CONFIG(quick_viewtransitions)
1868 if (transitioner)
1869 transitioner->setPopulateTransitionEnabled(false);
1870#endif
1871 inLayout = false;
1872 return;
1873 }
1874
1875#if QT_CONFIG(quick_viewtransitions)
1876 if (runDelayedRemoveTransition && transitioner
1877 && transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false)) {
1878 // assume that any items moving now are moving due to the remove - if they schedule
1879 // a different transition, that will override this one anyway
1880 for (int i=0; i<visibleItems.size(); i++)
1881 visibleItems[i]->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
1882 }
1883#endif
1884
1885 ChangeResult insertionPosChanges;
1886 ChangeResult removalPosChanges;
1887 if (!applyModelChanges(insertionResult: &insertionPosChanges, removalResult: &removalPosChanges) && !forceLayout) {
1888 if (fillCacheBuffer) {
1889 fillCacheBuffer = false;
1890 refill();
1891 }
1892 inLayout = false;
1893 return;
1894 }
1895 forceLayout = false;
1896
1897#if QT_CONFIG(quick_viewtransitions)
1898 if (transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true)) {
1899 // Give the view one more chance to refill itself,
1900 // in case its size is changed such that more delegates become visible after component completed
1901 refill();
1902 for (FxViewItem *item : std::as_const(t&: visibleItems)) {
1903 if (!item->transitionScheduledOrRunning())
1904 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::PopulateTransition, asTarget: true);
1905 }
1906 }
1907#endif
1908
1909 updateSections();
1910 layoutVisibleItems();
1911 storeFirstVisibleItemPosition();
1912
1913#if QT_CONFIG(quick_viewtransitions)
1914 int lastIndexInView = findLastIndexInView();
1915#endif
1916 refill();
1917 markExtentsDirty();
1918 updateHighlight();
1919
1920 if (!q->isMoving() && !q->isFlicking() && !movingFromHighlight()) {
1921 fixupPosition();
1922 refill();
1923 }
1924
1925 updateHeader();
1926 updateFooter();
1927 updateViewport();
1928 updateUnrequestedPositions();
1929
1930#if QT_CONFIG(quick_viewtransitions)
1931 if (transitioner) {
1932 // items added in the last refill() may need to be transitioned in - e.g. a remove
1933 // causes items to slide up into view
1934 if (lastIndexInView != -1 &&
1935 (transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: false)
1936 || transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false))) {
1937 translateAndTransitionItemsAfter(afterIndex: lastIndexInView, insertionResult: insertionPosChanges, removalResult: removalPosChanges);
1938 }
1939
1940 prepareVisibleItemTransitions();
1941
1942 // We cannot use iterators here as erasing from a container invalidates them.
1943 for (int i = 0, count = releasePendingTransition.size(); i < count;) {
1944 auto success = prepareNonVisibleItemTransition(item: releasePendingTransition[i], viewBounds);
1945 // prepareNonVisibleItemTransition() may remove items while in fast flicking.
1946 // Invisible animating items are kicked in or out the viewPort.
1947 // Recheck count to test if the item got removed. In that case the same index points
1948 // to a different item now.
1949 const int old_count = count;
1950 count = releasePendingTransition.size();
1951 if (old_count > count)
1952 continue;
1953
1954 if (!success) {
1955 releaseItem(item: releasePendingTransition[i], reusableFlag);
1956 releasePendingTransition.remove(i);
1957 --count;
1958 } else {
1959 ++i;
1960 }
1961 }
1962
1963 for (int i=0; i<visibleItems.size(); i++)
1964 visibleItems[i]->startTransition(transitioner);
1965 for (int i=0; i<releasePendingTransition.size(); i++)
1966 releasePendingTransition[i]->startTransition(transitioner);
1967
1968 transitioner->setPopulateTransitionEnabled(false);
1969 transitioner->resetTargetLists();
1970 }
1971#endif
1972
1973 if (!currentItem)
1974 updateCurrent(modelIndex: currentIndex);
1975
1976#if QT_CONFIG(quick_viewtransitions)
1977 runDelayedRemoveTransition = false;
1978#endif
1979 inLayout = false;
1980}
1981
1982bool QQuickItemViewPrivate::applyModelChanges(ChangeResult *totalInsertionResult, ChangeResult *totalRemovalResult)
1983{
1984 Q_Q(QQuickItemView);
1985 if (!q->isComponentComplete() || !hasPendingChanges())
1986 return false;
1987
1988 if (bufferedChanges.hasPendingChanges()) {
1989 currentChanges.applyBufferedChanges(other: bufferedChanges);
1990 bufferedChanges.reset();
1991 }
1992
1993 updateUnrequestedIndexes();
1994
1995 FxViewItem *prevVisibleItemsFirst = visibleItems.size() ? *visibleItems.constBegin() : nullptr;
1996 int prevItemCount = itemCount;
1997 int prevVisibleItemsCount = visibleItems.size();
1998 bool visibleAffected = false;
1999 bool viewportChanged = !currentChanges.pendingChanges.removes().isEmpty()
2000 || !currentChanges.pendingChanges.inserts().isEmpty();
2001
2002 FxViewItem *prevFirstItemInView = firstItemInView();
2003 QQmlNullableValue<qreal> prevFirstItemInViewPos;
2004 int prevFirstItemInViewIndex = -1;
2005 if (prevFirstItemInView) {
2006 prevFirstItemInViewPos = prevFirstItemInView->position();
2007 prevFirstItemInViewIndex = prevFirstItemInView->index;
2008 }
2009 qreal prevVisibleItemsFirstPos = visibleItems.size() ? firstVisibleItemPosition : 0.0;
2010
2011 totalInsertionResult->visiblePos = prevFirstItemInViewPos;
2012 totalRemovalResult->visiblePos = prevFirstItemInViewPos;
2013
2014 const QVector<QQmlChangeSet::Change> &removals = currentChanges.pendingChanges.removes();
2015 const QVector<QQmlChangeSet::Change> &insertions = currentChanges.pendingChanges.inserts();
2016 ChangeResult insertionResult(prevFirstItemInViewPos);
2017 ChangeResult removalResult(prevFirstItemInViewPos);
2018
2019 int removedCount = 0;
2020 for (const QQmlChangeSet::Change &r : removals) {
2021 itemCount -= r.count;
2022 if (applyRemovalChange(removal: r, changeResult: &removalResult, removedCount: &removedCount))
2023 visibleAffected = true;
2024 if (!visibleAffected && needsRefillForAddedOrRemovedIndex(r.index))
2025 visibleAffected = true;
2026 const int correctedFirstVisibleIndex = prevFirstItemInViewIndex - removalResult.countChangeBeforeVisible;
2027 if (correctedFirstVisibleIndex >= 0 && r.index < correctedFirstVisibleIndex) {
2028 if (r.index + r.count < correctedFirstVisibleIndex)
2029 removalResult.countChangeBeforeVisible += r.count;
2030 else
2031 removalResult.countChangeBeforeVisible += (correctedFirstVisibleIndex - r.index);
2032 }
2033 }
2034#if QT_CONFIG(quick_viewtransitions)
2035 if (runDelayedRemoveTransition) {
2036 QQmlChangeSet::Change removal;
2037 for (QList<FxViewItem*>::Iterator it = visibleItems.begin(); it != visibleItems.end();) {
2038 FxViewItem *item = *it;
2039 if (item->index == -1 && (!item->attached || !item->attached->delayRemove())) {
2040 removeItem(item, removal, removeResult: &removalResult);
2041 removedCount++;
2042 it = visibleItems.erase(pos: it);
2043 } else {
2044 ++it;
2045 }
2046 }
2047 }
2048#endif
2049 *totalRemovalResult += removalResult;
2050 if (!removals.isEmpty()) {
2051 updateVisibleIndex();
2052
2053 // set positions correctly for the next insertion
2054 if (!insertions.isEmpty()) {
2055 repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible: prevFirstItemInView, insertionResult: &insertionResult, removalResult: &removalResult);
2056 layoutVisibleItems(fromModelIndex: removals.first().index);
2057 storeFirstVisibleItemPosition();
2058 }
2059 }
2060
2061 QList<FxViewItem *> newItems;
2062 QList<MovedItem> movingIntoView;
2063
2064 for (int i=0; i<insertions.size(); i++) {
2065 bool wasEmpty = visibleItems.isEmpty();
2066 if (applyInsertionChange(insert: insertions[i], changeResult: &insertionResult, newItems: &newItems, movingIntoView: &movingIntoView))
2067 visibleAffected = true;
2068 if (!visibleAffected && needsRefillForAddedOrRemovedIndex(insertions[i].index))
2069 visibleAffected = true;
2070 if (wasEmpty && !visibleItems.isEmpty())
2071 resetFirstItemPosition();
2072 *totalInsertionResult += insertionResult;
2073
2074 // set positions correctly for the next insertion
2075 if (i < insertions.size() - 1) {
2076 repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible: prevFirstItemInView, insertionResult: &insertionResult, removalResult: &removalResult);
2077 layoutVisibleItems(fromModelIndex: insertions[i].index);
2078 storeFirstVisibleItemPosition();
2079 }
2080 itemCount += insertions[i].count;
2081 }
2082 for (FxViewItem *item : std::as_const(t&: newItems)) {
2083 if (item->attached)
2084 item->attached->emitAdd();
2085 }
2086
2087#if QT_CONFIG(quick_viewtransitions)
2088 // for each item that was moved directly into the view as a result of a move(),
2089 // find the index it was moved from in order to set its initial position, so that we
2090 // can transition it from this "original" position to its new position in the view
2091 if (transitioner && transitioner->canTransition(type: QQuickItemViewTransitioner::MoveTransition, asTarget: true)) {
2092 for (const MovedItem &m : std::as_const(t&: movingIntoView)) {
2093 int fromIndex = findMoveKeyIndex(key: m.moveKey, changes: removals);
2094 if (fromIndex >= 0) {
2095 if (prevFirstItemInViewIndex >= 0 && fromIndex < prevFirstItemInViewIndex)
2096 repositionItemAt(item: m.item, index: fromIndex, sizeBuffer: -totalInsertionResult->sizeChangesAfterVisiblePos);
2097 else
2098 repositionItemAt(item: m.item, index: fromIndex, sizeBuffer: totalInsertionResult->sizeChangesAfterVisiblePos);
2099 m.item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: true);
2100 }
2101 }
2102 }
2103#endif
2104
2105 // reposition visibleItems.first() correctly so that the content y doesn't jump
2106 if (removedCount != prevVisibleItemsCount)
2107 repositionFirstItem(prevVisibleItemsFirst, prevVisibleItemsFirstPos, prevFirstVisible: prevFirstItemInView, insertionResult: &insertionResult, removalResult: &removalResult);
2108
2109 // Whatever removed/moved items remain are no longer visible items.
2110#if QT_CONFIG(quick_viewtransitions)
2111 prepareRemoveTransitions(removedItems: &currentChanges.removedItems);
2112#endif
2113 for (auto it = currentChanges.removedItems.begin();
2114 it != currentChanges.removedItems.end(); ++it) {
2115 releaseItem(item: it.value(), reusableFlag);
2116 }
2117 currentChanges.removedItems.clear();
2118
2119 if (currentChanges.currentChanged) {
2120 if (currentChanges.currentRemoved && currentItem) {
2121 if (currentItem->item && currentItem->attached)
2122 currentItem->attached->setIsCurrentItem(false);
2123 auto oldCurrentItem = currentItem;
2124 releaseItem(item: currentItem, reusableFlag);
2125 currentItem = nullptr;
2126 if (oldCurrentItem)
2127 emit q->currentItemChanged();
2128 }
2129 if (!currentIndexCleared)
2130 updateCurrent(modelIndex: currentChanges.newCurrentIndex);
2131 }
2132
2133 if (!visibleAffected)
2134 visibleAffected = !currentChanges.pendingChanges.changes().isEmpty();
2135 currentChanges.reset();
2136
2137 updateSections();
2138 if (prevItemCount != itemCount)
2139 emit q->countChanged();
2140 if (!visibleAffected && viewportChanged)
2141 updateViewport();
2142
2143 return visibleAffected;
2144}
2145
2146bool QQuickItemViewPrivate::applyRemovalChange(const QQmlChangeSet::Change &removal, ChangeResult *removeResult, int *removedCount)
2147{
2148 Q_Q(QQuickItemView);
2149 bool visibleAffected = false;
2150
2151 if (visibleItems.size() && removal.index + removal.count > visibleItems.constLast()->index) {
2152 if (removal.index > visibleItems.constLast()->index)
2153 removeResult->countChangeAfterVisibleItems += removal.count;
2154 else
2155 removeResult->countChangeAfterVisibleItems += ((removal.index + removal.count - 1) - visibleItems.constLast()->index);
2156 }
2157
2158 QList<FxViewItem*>::Iterator it = visibleItems.begin();
2159 while (it != visibleItems.end()) {
2160 FxViewItem *item = *it;
2161 if (item->index == -1 || item->index < removal.index) {
2162 // already removed, or before removed items
2163 if (!visibleAffected && item->index < removal.index)
2164 visibleAffected = true;
2165 ++it;
2166 } else if (item->index >= removal.index + removal.count) {
2167 // after removed items
2168 item->index -= removal.count;
2169#if QT_CONFIG(quick_viewtransitions)
2170 if (removal.isMove())
2171 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: false);
2172 else
2173 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false);
2174#endif
2175 ++it;
2176 } else {
2177 // removed item
2178 visibleAffected = true;
2179 if (!removal.isMove() && item->item && item->attached)
2180 item->attached->emitRemove();
2181
2182 if (item->item && item->attached && item->attached->delayRemove() && !removal.isMove()) {
2183 item->index = -1;
2184 QObject::connect(sender: item->attached, SIGNAL(delayRemoveChanged()), receiver: q, SLOT(destroyRemoved()), Qt::QueuedConnection);
2185 ++it;
2186 } else {
2187 removeItem(item, removal, removeResult);
2188 if (!removal.isMove())
2189 (*removedCount)++;
2190 it = visibleItems.erase(pos: it);
2191 }
2192 }
2193 }
2194
2195 return visibleAffected;
2196}
2197
2198void QQuickItemViewPrivate::removeItem(FxViewItem *item, const QQmlChangeSet::Change &removal, ChangeResult *removeResult)
2199{
2200 if (removeResult->visiblePos.isValid()) {
2201 if (item->position() < removeResult->visiblePos)
2202 updateSizeChangesBeforeVisiblePos(item, removeResult);
2203 else
2204 removeResult->sizeChangesAfterVisiblePos += item->size();
2205 }
2206 if (removal.isMove()) {
2207 currentChanges.removedItems.replace(key: removal.moveKey(index: item->index), value: item);
2208#if QT_CONFIG(quick_viewtransitions)
2209 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::MoveTransition, asTarget: true);
2210#endif
2211 } else {
2212 // track item so it is released later
2213 currentChanges.removedItems.insert(key: QQmlChangeSet::MoveKey(), value: item);
2214 }
2215 if (!removeResult->changedFirstItem && item == *visibleItems.constBegin())
2216 removeResult->changedFirstItem = true;
2217}
2218
2219void QQuickItemViewPrivate::updateSizeChangesBeforeVisiblePos(FxViewItem *item, ChangeResult *removeResult)
2220{
2221 removeResult->sizeChangesBeforeVisiblePos += item->size();
2222}
2223
2224void QQuickItemViewPrivate::repositionFirstItem(FxViewItem *prevVisibleItemsFirst,
2225 qreal prevVisibleItemsFirstPos,
2226 FxViewItem *prevFirstVisible,
2227 ChangeResult *insertionResult,
2228 ChangeResult *removalResult)
2229{
2230 const QQmlNullableValue<qreal> prevViewPos = insertionResult->visiblePos;
2231
2232 // reposition visibleItems.first() correctly so that the content y doesn't jump
2233 if (visibleItems.size()) {
2234 if (prevVisibleItemsFirst && insertionResult->changedFirstItem)
2235 resetFirstItemPosition(pos: prevVisibleItemsFirstPos);
2236
2237 if (prevFirstVisible && prevVisibleItemsFirst == prevFirstVisible
2238 && prevFirstVisible != *visibleItems.constBegin()) {
2239 // the previous visibleItems.first() was also the first visible item, and it has been
2240 // moved/removed, so move the new visibleItems.first() to the pos of the previous one
2241 if (!insertionResult->changedFirstItem)
2242 resetFirstItemPosition(pos: prevVisibleItemsFirstPos);
2243
2244 } else if (prevViewPos.isValid()) {
2245 qreal moveForwardsBy = 0;
2246 qreal moveBackwardsBy = 0;
2247
2248 // shift visibleItems.first() relative to the number of added/removed items
2249 const auto pos = visibleItems.constFirst()->position();
2250 if (pos > prevViewPos) {
2251 moveForwardsBy = insertionResult->sizeChangesAfterVisiblePos;
2252 moveBackwardsBy = removalResult->sizeChangesAfterVisiblePos;
2253 } else if (pos < prevViewPos) {
2254 moveForwardsBy = removalResult->sizeChangesBeforeVisiblePos;
2255 moveBackwardsBy = insertionResult->sizeChangesBeforeVisiblePos;
2256 }
2257 adjustFirstItem(forwards: moveForwardsBy, backwards: moveBackwardsBy, changeBeforeVisible: insertionResult->countChangeBeforeVisible - removalResult->countChangeBeforeVisible);
2258 }
2259 insertionResult->reset();
2260 removalResult->reset();
2261 }
2262}
2263
2264#if QT_CONFIG(quick_viewtransitions)
2265void QQuickItemViewPrivate::createTransitioner()
2266{
2267 if (!transitioner) {
2268 transitioner = new QQuickItemViewTransitioner;
2269 transitioner->setChangeListener(this);
2270 }
2271}
2272
2273void QQuickItemViewPrivate::prepareVisibleItemTransitions()
2274{
2275 Q_Q(QQuickItemView);
2276 if (!transitioner)
2277 return;
2278
2279 // must call for every visible item to init or discard transitions
2280 QRectF viewBounds(q->contentX(), q->contentY(), q->width(), q->height());
2281 for (int i=0; i<visibleItems.size(); i++)
2282 visibleItems[i]->prepareTransition(transitioner, viewBounds);
2283}
2284
2285void QQuickItemViewPrivate::prepareRemoveTransitions(QMultiHash<QQmlChangeSet::MoveKey, FxViewItem *> *removedItems)
2286{
2287 if (!transitioner)
2288 return;
2289
2290 if (transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: true)
2291 || transitioner->canTransition(type: QQuickItemViewTransitioner::RemoveTransition, asTarget: false)) {
2292 for (auto it = removedItems->begin(); it != removedItems->end(); ) {
2293 bool isRemove = it.key().moveId < 0;
2294 if (isRemove) {
2295 FxViewItem *item = *it;
2296 item->trackGeometry(track: false);
2297 item->releaseAfterTransition = true;
2298 releasePendingTransition.append(t: item);
2299 item->transitionNextReposition(transitioner, type: QQuickItemViewTransitioner::RemoveTransition, asTarget: true);
2300 it = removedItems->erase(it);
2301 } else {
2302 ++it;
2303 }
2304 }
2305 }
2306}
2307
2308bool QQuickItemViewPrivate::prepareNonVisibleItemTransition(FxViewItem *item, const QRectF &viewBounds)
2309{
2310 // Called for items that have been removed from visibleItems and may now be
2311 // transitioned out of the view. This applies to items that are being directly
2312 // removed, or moved to outside of the view, as well as those that are
2313 // displaced to a position outside of the view due to an insert or move.
2314
2315 if (!transitioner)
2316 return false;
2317
2318 if (item->scheduledTransitionType() == QQuickItemViewTransitioner::MoveTransition)
2319 repositionItemAt(item, index: item->index, sizeBuffer: 0);
2320
2321 bool success = false;
2322 ACTION_IF_DELETED(item, success = item->prepareTransition(transitioner, viewBounds), return success);
2323
2324 if (success) {
2325 item->releaseAfterTransition = true;
2326 return true;
2327 }
2328 return false;
2329}
2330
2331void QQuickItemViewPrivate::viewItemTransitionFinished(QQuickItemViewTransitionableItem *item)
2332{
2333 for (int i=0; i<releasePendingTransition.size(); i++) {
2334 if (releasePendingTransition.at(i)->transitionableItem == item) {
2335 releaseItem(item: releasePendingTransition.takeAt(i), reusableFlag);
2336 return;
2337 }
2338 }
2339}
2340#endif
2341
2342/*
2343 This may return 0 if the item is being created asynchronously.
2344 When the item becomes available, refill() will be called and the item
2345 will be returned on the next call to createItem().
2346*/
2347FxViewItem *QQuickItemViewPrivate::createItem(int modelIndex, QQmlIncubator::IncubationMode incubationMode)
2348{
2349 Q_Q(QQuickItemView);
2350
2351 if (requestedIndex == modelIndex && incubationMode == QQmlIncubator::Asynchronous)
2352 return nullptr;
2353
2354#if QT_CONFIG(quick_viewtransitions)
2355 for (int i=0; i<releasePendingTransition.size(); i++) {
2356 if (releasePendingTransition.at(i)->index == modelIndex
2357 && !releasePendingTransition.at(i)->isPendingRemoval()) {
2358 releasePendingTransition[i]->releaseAfterTransition = false;
2359 return releasePendingTransition.takeAt(i);
2360 }
2361 }
2362#endif
2363
2364 inRequest = true;
2365
2366 // The model will run this same range check internally but produce a warning and return nullptr.
2367 // Since we handle this result graciously in our code, we preempt this warning by checking the range ourselves.
2368 QObject* object = modelIndex < model->count() ? model->object(index: modelIndex, incubationMode) : nullptr;
2369 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
2370
2371 if (!item) {
2372 if (!object) {
2373 if (requestedIndex == -1 && model->incubationStatus(index: modelIndex) == QQmlIncubator::Loading) {
2374 // The reason we didn't receive an item is because it's incubating async. We keep track
2375 // of this by assigning the index we're waiting for to 'requestedIndex'. This will e.g. let
2376 // the view avoid unnecessary layout calls until the item has been loaded.
2377 requestedIndex = modelIndex;
2378 }
2379 } else {
2380 model->release(object);
2381 if (!delegateValidated) {
2382 delegateValidated = true;
2383 QObject* delegate = q->delegate();
2384 qmlWarning(me: delegate ? delegate : q) << QQuickItemView::tr(s: "Delegate must be of Item type");
2385 }
2386 }
2387 inRequest = false;
2388 return nullptr;
2389 } else {
2390 item->setParentItem(q->contentItem());
2391 if (requestedIndex == modelIndex)
2392 requestedIndex = -1;
2393 FxViewItem *viewItem = newViewItem(index: modelIndex, item);
2394 if (viewItem) {
2395 viewItem->index = modelIndex;
2396 // do other set up for the new item that should not happen
2397 // until after bindings are evaluated
2398 initializeViewItem(viewItem);
2399 unrequestedItems.remove(key: item);
2400 }
2401 inRequest = false;
2402 return viewItem;
2403 }
2404}
2405
2406void QQuickItemView::createdItem(int index, QObject* object)
2407{
2408 Q_D(QQuickItemView);
2409
2410 QQuickItem* item = qmlobject_cast<QQuickItem*>(object);
2411 if (!d->inRequest) {
2412 d->unrequestedItems.insert(key: item, value: index);
2413 d->requestedIndex = -1;
2414 if (d->hasPendingChanges())
2415 d->layout();
2416 else
2417 d->refill();
2418 if (d->unrequestedItems.contains(key: item))
2419 d->repositionPackageItemAt(item, index);
2420 else if (index == d->currentIndex)
2421 d->updateCurrent(modelIndex: index);
2422 }
2423}
2424
2425void QQuickItemView::initItem(int, QObject *object)
2426{
2427 QQuickItem* item = qmlobject_cast<QQuickItem*>(object);
2428 if (item) {
2429 if (qFuzzyIsNull(d: item->z()))
2430 item->setZ(1);
2431 item->setParentItem(contentItem());
2432 QQuickItemPrivate::get(item)->setCulled(true);
2433 }
2434}
2435
2436void QQuickItemView::destroyingItem(QObject *object)
2437{
2438 Q_D(QQuickItemView);
2439 QQuickItem* item = qmlobject_cast<QQuickItem*>(object);
2440 if (item) {
2441 item->setParentItem(nullptr);
2442 d->unrequestedItems.remove(key: item);
2443 }
2444}
2445
2446void QQuickItemView::onItemPooled(int modelIndex, QObject *object)
2447{
2448 Q_UNUSED(modelIndex);
2449
2450 if (auto *attached = d_func()->getAttachedObject(object))
2451 emit attached->pooled();
2452}
2453
2454void QQuickItemView::onItemReused(int modelIndex, QObject *object)
2455{
2456 Q_UNUSED(modelIndex);
2457
2458 if (auto *attached = d_func()->getAttachedObject(object))
2459 emit attached->reused();
2460}
2461
2462bool QQuickItemViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::ReusableFlag reusableFlag)
2463{
2464 Q_Q(QQuickItemView);
2465 if (!item)
2466 return true;
2467 if (trackedItem == item)
2468 trackedItem = nullptr;
2469 item->trackGeometry(track: false);
2470
2471 QQmlInstanceModel::ReleaseFlags flags = {};
2472 if (model && item->item) {
2473 flags = model->release(object: item->item, reusableFlag);
2474 if (!flags) {
2475 // item was not destroyed, and we no longer reference it.
2476 if (item->item->parentItem() == contentItem) {
2477 // Only cull the item if its parent item is still our contentItem.
2478 // One case where this can happen is moving an item out of one ObjectModel and into another.
2479 QQuickItemPrivate::get(item: item->item)->setCulled(true);
2480 }
2481 if (!isClearing)
2482 unrequestedItems.insert(key: item->item, value: model->indexOf(object: item->item, objectContext: q));
2483 } else if (flags & QQmlInstanceModel::Destroyed) {
2484 item->item->setParentItem(nullptr);
2485 } else if (flags & QQmlInstanceModel::Pooled) {
2486 item->setVisible(false);
2487 }
2488 }
2489 delete item;
2490 return flags != QQmlInstanceModel::Referenced;
2491}
2492
2493QQuickItem *QQuickItemViewPrivate::createHighlightItem() const
2494{
2495 return createComponentItem(component: highlightComponent, zValue: 0.0, createDefault: true);
2496}
2497
2498QQuickItem *QQuickItemViewPrivate::createComponentItem(QQmlComponent *component, qreal zValue, bool createDefault) const
2499{
2500 Q_Q(const QQuickItemView);
2501
2502 QQuickItem *item = nullptr;
2503 if (component) {
2504 QQmlContext *context = component->creationContext();
2505 if (!context)
2506 context = qmlContext(q);
2507
2508 if (QObject *nobj = component->beginCreate(context)) {
2509 item = qobject_cast<QQuickItem *>(o: nobj);
2510 if (!item)
2511 delete nobj;
2512 }
2513 } else if (createDefault) {
2514 item = new QQuickItem;
2515 }
2516 if (item) {
2517 if (qFuzzyIsNull(d: item->z()))
2518 item->setZ(zValue);
2519 QQml_setParent_noEvent(object: item, parent: q->contentItem());
2520 item->setParentItem(q->contentItem());
2521
2522 initializeComponentItem(item);
2523 }
2524 if (component)
2525 component->completeCreate();
2526 return item;
2527}
2528
2529/*!
2530 \internal
2531
2532 Allows derived classes to do any initialization required for \a item
2533 before completeCreate() is called on it. For example, any attached
2534 properties required by the item can be set.
2535
2536 This is similar to initItem(), but as that has logic specific to
2537 delegate items, we use a separate function for non-delegates.
2538*/
2539void QQuickItemViewPrivate::initializeComponentItem(QQuickItem *item) const
2540{
2541 Q_UNUSED(item);
2542}
2543
2544void QQuickItemViewPrivate::updateTrackedItem()
2545{
2546 Q_Q(QQuickItemView);
2547 FxViewItem *item = currentItem;
2548 if (highlight)
2549 item = highlight.get();
2550 trackedItem = item;
2551
2552 if (trackedItem)
2553 q->trackedPositionChanged();
2554}
2555
2556void QQuickItemViewPrivate::updateUnrequestedIndexes()
2557{
2558 Q_Q(QQuickItemView);
2559 for (QHash<QQuickItem*,int>::iterator it = unrequestedItems.begin(), end = unrequestedItems.end(); it != end; ++it)
2560 *it = model->indexOf(object: it.key(), objectContext: q);
2561}
2562
2563void QQuickItemViewPrivate::updateUnrequestedPositions()
2564{
2565 for (QHash<QQuickItem*,int>::const_iterator it = unrequestedItems.cbegin(), cend = unrequestedItems.cend(); it != cend; ++it) {
2566 if (it.value() >= 0)
2567 repositionPackageItemAt(item: it.key(), index: it.value());
2568 }
2569}
2570
2571void QQuickItemViewPrivate::updateVisibleIndex()
2572{
2573 typedef QList<FxViewItem*>::const_iterator FxViewItemListConstIt;
2574
2575 visibleIndex = 0;
2576 for (FxViewItemListConstIt it = visibleItems.constBegin(), cend = visibleItems.constEnd(); it != cend; ++it) {
2577 if ((*it)->index != -1) {
2578 visibleIndex = (*it)->index;
2579 break;
2580 }
2581 }
2582}
2583
2584QT_END_NAMESPACE
2585
2586#include "moc_qquickitemview_p.cpp"
2587

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