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