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

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