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