1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmllistcompositor_p.h"
5
6#include <QtCore/qvarlengtharray.h>
7
8//#define QT_QML_VERIFY_MINIMAL
9//#define QT_QML_VERIFY_INTEGRITY
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \class QQmlListCompositor
15 \brief The QQmlListCompositor class provides a lookup table for filtered, or re-ordered list
16 indexes.
17 \internal
18
19 QQmlListCompositor is intended as an aid for developing proxy models. It doesn't however
20 directly proxy a list or model, instead a range of indexes from one or many lists can be
21 inserted into the compositor and then categorized and shuffled around and it will manage the
22 task of translating from an index in the combined space into an index in a particular list.
23
24 Within a compositor indexes are categorized into groups where a group is a sub-set of the
25 total indexes referenced by the compositor, each with an address space ranging from 0 to
26 the number of indexes in the group - 1. Group memberships are independent of each other with
27 the one exception that items always retain the same order so if an index is moved within a
28 group, its position in other groups will change as well.
29
30 The iterator classes encapsulate information about a specific position in a compositor group.
31 This includes a source list, the index of an item within that list and the groups that item
32 is a member of. The iterator for a specific position in a group can be retrieved with the
33 find() function and the addition and subtraction operators of the iterators can be used to
34 navigate to adjacent items in the same group.
35
36 Items can be added to the compositor with the append() and insert() functions, group
37 membership can be changed with the setFlags() and clearFlags() functions, and the position
38 of items in the compositor can be changed with the move() function. Each of these functions
39 optionally returns a list of the changes made to indexes within each group which can then
40 be propagated to view so that it can correctly refresh its contents; e.g. 3 items
41 removed at index 6, and 5 items inserted at index 1. The notification changes are always
42 ordered from the start of the list to the end and accumulate, so if 5 items are removed at
43 index 4, one is skipped and then 3 move are removed, the changes returned are 5 items removed
44 at index 4, followed by 3 items removed at index 4.
45
46 When the contents of a source list change, the mappings within the compositor can be updated
47 with the listItemsInserted(), listItemsRemoved(), listItemsMoved(), and listItemsChanged()
48 functions. Like the direct manipulation functions these too return a list of group indexes
49 affected by the change. If items are removed from a source list they are also removed from
50 any groups they belong to with the one exception being items belonging to the \l Cache group.
51 When items belonging to this group are removed the list, index, and other group membership
52 information are discarded but Cache membership is retained until explicitly removed. This
53 allows the cache index to be retained until cached resources for that item are actually
54 released.
55
56 Internally the index mapping is stored as a list of Range objects, each has a list identifier,
57 a start index, a count, and a set of flags which represent group membership and some other
58 properties. The group index of a range is the sum of all preceding ranges that are members of
59 that group. To avoid the inefficiency of iterating over potentially all ranges when looking
60 for a specific index, each time a lookup is done the range and its indexes are cached and the
61 next lookup is done relative to this. This works out to near constant time in most relevant
62 use cases because successive index lookups are most frequently adjacent. The total number of
63 ranges is often quite small, which helps as well. If there is a need for faster random access
64 then a skip list like index may be an appropriate addition.
65
66 \sa DelegateModel
67*/
68
69#ifdef QT_QML_VERIFY_MINIMAL
70#define QT_QML_VERIFY_INTEGRITY
71/*
72 Diagnostic to verify there are no consecutive ranges, or that the compositor contains the
73 most compact representation possible.
74
75 Returns false and prints a warning if any range has a starting index equal to the end
76 (index + count) index of the previous range, and both ranges also have the same flags and list
77 property.
78
79 If there are no consecutive ranges this will return true.
80*/
81
82static bool qt_verifyMinimal(
83 const QQmlListCompositor::iterator &begin,
84 const QQmlListCompositor::iterator &end)
85{
86 bool minimal = true;
87 int index = 0;
88
89 for (const QQmlListCompositor::Range *range = begin->next; range != *end; range = range->next, ++index) {
90 if (range->previous->list == range->list
91 && range->previous->flags == (range->flags & ~QQmlListCompositor::AppendFlag)
92 && range->previous->end() == range->index) {
93 qWarning() << index << "Consecutive ranges";
94 qWarning() << *range->previous;
95 qWarning() << *range;
96 minimal = false;
97 }
98 }
99
100 return minimal;
101}
102
103#endif
104
105#ifdef QT_QML_VERIFY_INTEGRITY
106static bool qt_printInfo(const QQmlListCompositor &compositor)
107{
108 qWarning() << compositor;
109 return true;
110}
111
112/*
113 Diagnostic to verify the integrity of a compositor.
114
115 Per range this verifies there are no invalid range combinations, that non-append ranges have
116 positive non-zero counts, and that list ranges have non-negative indexes.
117
118 Accumulatively this verifies that the cached total group counts match the sum of counts
119 of member ranges.
120*/
121
122static bool qt_verifyIntegrity(
123 const QQmlListCompositor::iterator &begin,
124 const QQmlListCompositor::iterator &end,
125 const QQmlListCompositor::iterator &cachedIt)
126{
127 bool valid = true;
128
129 int index = 0;
130 QQmlListCompositor::iterator it;
131 for (it = begin; *it != *end; *it = it->next) {
132 if (it->count == 0 && !it->append()) {
133 qWarning() << index << "Empty non-append range";
134 valid = false;
135 }
136 if (it->count < 0) {
137 qWarning() << index << "Negative count";
138 valid = false;
139 }
140 if (it->list && it->flags != QQmlListCompositor::CacheFlag && it->index < 0) {
141 qWarning() << index <<"Negative index";
142 valid = false;
143 }
144 if (it->previous->next != it.range) {
145 qWarning() << index << "broken list: it->previous->next != it.range";
146 valid = false;
147 }
148 if (it->next->previous != it.range) {
149 qWarning() << index << "broken list: it->next->previous != it.range";
150 valid = false;
151 }
152 if (*it == *cachedIt) {
153 for (int i = 0; i < end.groupCount; ++i) {
154 int groupIndex = it.index[i];
155 if (cachedIt->flags & (1 << i))
156 groupIndex += cachedIt.offset;
157 if (groupIndex != cachedIt.index[i]) {
158 qWarning() << index
159 << "invalid cached index"
160 << QQmlListCompositor::Group(i)
161 << "Expected:"
162 << groupIndex
163 << "Actual"
164 << cachedIt.index[i]
165 << cachedIt;
166 valid = false;
167 }
168 }
169 }
170 it.incrementIndexes(it->count);
171 ++index;
172 }
173
174 for (int i = 0; i < end.groupCount; ++i) {
175 if (end.index[i] != it.index[i]) {
176 qWarning() << "Group" << i << "count invalid. Expected:" << end.index[i] << "Actual:" << it.index[i];
177 valid = false;
178 }
179 }
180 return valid;
181}
182#endif
183
184#if defined(QT_QML_VERIFY_MINIMAL)
185# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!(qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \
186 && qt_verifyMinimal(iterator(m_ranges.next, 0, Default, m_groupCount), m_end)) \
187 && qt_printInfo(*this)));
188#elif defined(QT_QML_VERIFY_INTEGRITY)
189# define QT_QML_VERIFY_LISTCOMPOSITOR Q_ASSERT(!(!qt_verifyIntegrity(iterator(m_ranges.next, 0, Default, m_groupCount), m_end, m_cacheIt) \
190 && qt_printInfo(*this)));
191#else
192# define QT_QML_VERIFY_LISTCOMPOSITOR
193#endif
194
195//#define QT_QML_TRACE_LISTCOMPOSITOR(args) qDebug() << m_end.index[1] << m_end.index[0] << Q_FUNC_INFO args;
196#define QT_QML_TRACE_LISTCOMPOSITOR(args)
197
198QQmlListCompositor::iterator &QQmlListCompositor::iterator::operator +=(int difference)
199{
200 // Update all indexes to the start of the range.
201 decrementIndexes(difference: offset);
202
203 // If the iterator group isn't a member of the current range ignore the current offset.
204 if (!(range->flags & groupFlag))
205 offset = 0;
206
207 offset += difference;
208
209 // Iterate backwards looking for a range with a positive offset.
210 while (offset <= 0 && range->previous->flags) {
211 range = range->previous;
212 if (range->flags & groupFlag)
213 offset += range->count;
214 decrementIndexes(difference: range->count);
215 }
216
217 // Iterate forwards looking for the first range which contains both the offset and the
218 // iterator group.
219 while (range->flags && (offset >= range->count || !(range->flags & groupFlag))) {
220 if (range->flags & groupFlag)
221 offset -= range->count;
222 incrementIndexes(difference: range->count);
223 range = range->next;
224 }
225
226 // Update all the indexes to inclue the remaining offset.
227 incrementIndexes(difference: offset);
228
229 return *this;
230}
231
232QQmlListCompositor::insert_iterator &QQmlListCompositor::insert_iterator::operator +=(int difference)
233{
234 iterator::operator +=(difference);
235
236 // If the previous range contains the append flag move the iterator to the tail of the previous
237 // range so that appended appear after the insert position.
238 if (offset == 0 && range->previous->append()) {
239 range = range->previous;
240 offset = range->inGroup() ? range->count : 0;
241 }
242
243 return *this;
244}
245
246
247/*!
248 Constructs an empty list compositor.
249*/
250
251QQmlListCompositor::QQmlListCompositor()
252 : m_end(m_ranges.next, 0, Default, 2)
253 , m_cacheIt(m_end)
254 , m_groupCount(2)
255 , m_defaultFlags(PrependFlag | DefaultFlag)
256 , m_removeFlags(AppendFlag | PrependFlag | GroupMask)
257 , m_moveId(0)
258{
259}
260
261/*!
262 Destroys a list compositor.
263*/
264
265QQmlListCompositor::~QQmlListCompositor()
266{
267 for (Range *next, *range = m_ranges.next; range != &m_ranges; range = next) {
268 next = range->next;
269 delete range;
270 }
271}
272
273/*!
274 Inserts a range with the given source \a list, start \a index, \a count and \a flags, in front
275 of the existing range \a before.
276*/
277
278inline QQmlListCompositor::Range *QQmlListCompositor::insert(
279 Range *before, void *list, int index, int count, uint flags)
280{
281 return new Range(before, list, index, count, flags);
282}
283
284/*!
285 Erases a \a range from the compositor.
286
287 Returns a pointer to the next range in the compositor.
288*/
289
290inline QQmlListCompositor::Range *QQmlListCompositor::erase(
291 Range *range)
292{
293 Range *next = range->next;
294 next->previous = range->previous;
295 next->previous->next = range->next;
296 delete range;
297 return next;
298}
299
300/*!
301 Sets the number (\a count) of possible groups that items may belong to in a compositor.
302*/
303
304void QQmlListCompositor::setGroupCount(int count)
305{
306 m_groupCount = count;
307 m_end = iterator(&m_ranges, 0, Default, m_groupCount);
308 m_cacheIt = m_end;
309}
310
311/*!
312 Returns the number of items that belong to a \a group.
313*/
314
315int QQmlListCompositor::count(Group group) const
316{
317 return m_end.index[group];
318}
319
320/*!
321 Returns an iterator representing the item at \a index in a \a group.
322
323 The index must be between 0 and count(group) - 1.
324*/
325
326QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index)
327{
328 QT_QML_TRACE_LISTCOMPOSITOR(<< group << index)
329 Q_ASSERT(index >=0 && index < count(group));
330 if (m_cacheIt == m_end) {
331 m_cacheIt = iterator(m_ranges.next, 0, group, m_groupCount);
332 m_cacheIt += index;
333 } else {
334 const int offset = index - m_cacheIt.index[group];
335 m_cacheIt.setGroup(group);
336 m_cacheIt += offset;
337 }
338 Q_ASSERT(m_cacheIt.index[group] == index);
339 Q_ASSERT(m_cacheIt->inGroup(group));
340 QT_QML_VERIFY_LISTCOMPOSITOR
341 return m_cacheIt;
342}
343
344/*!
345 Returns an iterator representing the item at \a index in a \a group.
346
347 The index must be between 0 and count(group) - 1.
348*/
349
350QQmlListCompositor::iterator QQmlListCompositor::find(Group group, int index) const
351{
352 return const_cast<QQmlListCompositor *>(this)->find(group, index);
353}
354
355/*!
356 Returns an iterator representing an insert position in front of the item at \a index in a
357 \a group.
358
359 The iterator for an insert position can sometimes resolve to a different Range than a regular
360 iterator. This is because when items are inserted on a boundary between Ranges, if the first
361 range has the Append flag set then the items should be inserted into that range to ensure
362 that the append position for the existing range remains after the insert position.
363
364 The index must be between 0 and count(group) - 1.
365*/
366
367QQmlListCompositor::insert_iterator QQmlListCompositor::findInsertPosition(Group group, int index)
368{
369 QT_QML_TRACE_LISTCOMPOSITOR(<< group << index)
370 Q_ASSERT(index >=0 && index <= count(group));
371 insert_iterator it;
372 if (m_cacheIt == m_end) {
373 it = iterator(m_ranges.next, 0, group, m_groupCount);
374 it += index;
375 } else {
376 const int offset = index - m_cacheIt.index[group];
377 it = m_cacheIt;
378 it.setGroup(group);
379 it += offset;
380 }
381 Q_ASSERT(it.index[group] == index);
382 return it;
383}
384
385/*!
386 Appends a range of \a count indexes starting at \a index from a \a list into a compositor
387 with the given \a flags.
388
389 If supplied the \a inserts list will be populated with the positions of the inserted items
390 in each group.
391*/
392
393void QQmlListCompositor::append(
394 void *list, int index, int count, uint flags, QVector<Insert> *inserts)
395{
396 QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count << flags)
397 insert(before: m_end, list, index, count, flags, inserts);
398}
399
400/*!
401 Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags
402 into a \a group at index \a before.
403
404 If supplied the \a inserts list will be populated with the positions of items inserted into
405 each group.
406*/
407
408void QQmlListCompositor::insert(
409 Group group, int before, void *list, int index, int count, uint flags, QVector<Insert> *inserts)
410{
411 QT_QML_TRACE_LISTCOMPOSITOR(<< group << before << list << index << count << flags)
412 insert(before: findInsertPosition(group, index: before), list, index, count, flags, inserts);
413}
414
415/*!
416 Inserts a range of \a count indexes starting at \a index from a \a list with the given \a flags
417 into a compositor at position \a before.
418
419 If supplied the \a inserts list will be populated with the positions of items inserted into
420 each group.
421*/
422
423QQmlListCompositor::iterator QQmlListCompositor::insert(
424 iterator before, void *list, int index, int count, uint flags, QVector<Insert> *inserts)
425{
426 QT_QML_TRACE_LISTCOMPOSITOR(<< before << list << index << count << flags)
427 if (inserts) {
428 inserts->append(t: Insert(before, count, flags & GroupMask));
429 }
430 if (before.offset > 0) {
431 // Inserting into the middle of a range. Split it two and update the iterator so it's
432 // positioned at the start of the second half.
433 *before = insert(
434 before: *before, list: before->list, index: before->index, count: before.offset, flags: before->flags & ~AppendFlag)->next;
435 before->index += before.offset;
436 before->count -= before.offset;
437 before.offset = 0;
438 }
439
440
441 if (!(flags & AppendFlag) && *before != m_ranges.next
442 && before->previous->list == list
443 && before->previous->flags == flags
444 && (!list || before->previous->end() == index)) {
445 // The insert arguments represent a continuation of the previous range so increment
446 // its count instead of inserting a new range.
447 before->previous->count += count;
448 before.incrementIndexes(difference: count, flags);
449 } else {
450 *before = insert(before: *before, list, index, count, flags);
451 before.offset = 0;
452 }
453
454 if (!(flags & AppendFlag) && before->next != &m_ranges
455 && before->list == before->next->list
456 && before->flags == before->next->flags
457 && (!list || before->end() == before->next->index)) {
458 // The current range and the next are continuous so add their counts and delete one.
459 before->next->index = before->index;
460 before->next->count += before->count;
461 *before = erase(range: *before);
462 }
463
464 m_end.incrementIndexes(difference: count, flags);
465 m_cacheIt = before;
466 QT_QML_VERIFY_LISTCOMPOSITOR
467 return before;
468}
469
470/*!
471 Sets the given flags \a flags on \a count items belonging to \a group starting at the position
472 identified by \a fromGroup and the index \a from.
473
474 If supplied the \a inserts list will be populated with insert notifications for affected groups.
475*/
476
477void QQmlListCompositor::setFlags(
478 Group fromGroup, int from, int count, Group group, int flags, QVector<Insert> *inserts)
479{
480 QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags)
481 setFlags(from: find(group: fromGroup, index: from), count, group, flags, inserts);
482}
483
484/*!
485 Sets the given flags \a flags on \a count items belonging to \a group starting at the position
486 \a from.
487
488 If supplied the \a inserts list will be populated with insert notifications for affected groups.
489*/
490
491void QQmlListCompositor::setFlags(
492 iterator from, int count, Group group, uint flags, QVector<Insert> *inserts)
493{
494 QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags)
495 if (!flags || !count)
496 return;
497
498 if (from != group) {
499 // Skip to the next full range if the start one is not a member of the target group.
500 from.incrementIndexes(difference: from->count - from.offset);
501 from.offset = 0;
502 *from = from->next;
503 } else if (from.offset > 0) {
504 // If the start position is mid range split off the portion unaffected.
505 *from = insert(before: *from, list: from->list, index: from->index, count: from.offset, flags: from->flags & ~AppendFlag)->next;
506 from->index += from.offset;
507 from->count -= from.offset;
508 from.offset = 0;
509 }
510
511 for (; count > 0; *from = from->next) {
512 if (from != from.group) {
513 // Skip ranges that are not members of the target group.
514 from.incrementIndexes(difference: from->count);
515 continue;
516 }
517 // Find the number of items affected in the current range.
518 const int difference = qMin(a: count, b: from->count);
519 count -= difference;
520
521 // Determine the actual changes made to the range and increment counts accordingly.
522 const uint insertFlags = ~from->flags & flags;
523 const uint setFlags = (from->flags | flags) & ~AppendFlag;
524 if (insertFlags && inserts)
525 inserts->append(t: Insert(from, difference, insertFlags | (from->flags & CacheFlag)));
526 m_end.incrementIndexes(difference, flags: insertFlags);
527 from.incrementIndexes(difference, flags: setFlags);
528
529 if (from->previous != &m_ranges
530 && from->previous->list == from->list
531 && (!from->list || from->previous->end() == from->index)
532 && from->previous->flags == setFlags) {
533 // If the additional flags make the current range a continuation of the previous
534 // then move the affected items over to the previous range.
535 from->previous->count += difference;
536 from->index += difference;
537 from->count -= difference;
538 if (from->count == 0) {
539 // Delete the current range if it is now empty, preserving the append flag
540 // in the previous range.
541 if (from->append())
542 from->previous->flags |= AppendFlag;
543 *from = erase(range: *from)->previous;
544 continue;
545 } else {
546 break;
547 }
548 } else if (!insertFlags) {
549 // No new flags, so roll onto the next range.
550 from.incrementIndexes(difference: from->count - difference);
551 continue;
552 } else if (difference < from->count) {
553 // Create a new range with the updated flags, and remove the affected items
554 // from the current range.
555 *from = insert(before: *from, list: from->list, index: from->index, count: difference, flags: setFlags)->next;
556 from->index += difference;
557 from->count -= difference;
558 } else {
559 // The whole range is affected so simply update the flags.
560 from->flags |= flags;
561 continue;
562 }
563 from.incrementIndexes(difference: from->count);
564 }
565
566 if (from->previous != &m_ranges
567 && from->previous->list == from->list
568 && (!from->list || from->previous->end() == from->index)
569 && from->previous->flags == (from->flags & ~AppendFlag)) {
570 // If the following range is now a continuation, merge it with its previous range.
571 from.offset = from->previous->count;
572 from->previous->count += from->count;
573 from->previous->flags = from->flags;
574 *from = erase(range: *from)->previous;
575 }
576 m_cacheIt = from;
577 QT_QML_VERIFY_LISTCOMPOSITOR
578}
579
580/*!
581 Clears the given flags \a flags on \a count items belonging to \a group starting at the position
582 \a from.
583
584 If supplied the \a removes list will be populated with remove notifications for affected groups.
585*/
586
587void QQmlListCompositor::clearFlags(
588 Group fromGroup, int from, int count, Group group, uint flags, QVector<Remove> *removes)
589{
590 QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << count << group << flags)
591 clearFlags(from: find(group: fromGroup, index: from), count, group, flags, removals: removes);
592}
593
594/*!
595 Clears the given flags \a flags on \a count items belonging to \a group starting at the position
596 identified by \a fromGroup and the index \a from.
597
598 If supplied the \a removes list will be populated with remove notifications for affected groups.
599*/
600
601void QQmlListCompositor::clearFlags(
602 iterator from, int count, Group group, uint flags, QVector<Remove> *removes)
603{
604 QT_QML_TRACE_LISTCOMPOSITOR(<< from << count << flags)
605 if (!flags || !count)
606 return;
607
608 const bool clearCache = flags & CacheFlag;
609
610 if (from != group) {
611 // Skip to the next full range if the start one is not a member of the target group.
612 from.incrementIndexes(difference: from->count - from.offset);
613 from.offset = 0;
614 *from = from->next;
615 } else if (from.offset > 0) {
616 // If the start position is mid range split off the portion unaffected.
617 *from = insert(before: *from, list: from->list, index: from->index, count: from.offset, flags: from->flags & ~AppendFlag)->next;
618 from->index += from.offset;
619 from->count -= from.offset;
620 from.offset = 0;
621 }
622
623 for (; count > 0; *from = from->next) {
624 if (from != group) {
625 // Skip ranges that are not members of the target group.
626 from.incrementIndexes(difference: from->count);
627 continue;
628 }
629 // Find the number of items affected in the current range.
630 const int difference = qMin(a: count, b: from->count);
631 count -= difference;
632
633
634 // Determine the actual changes made to the range and decrement counts accordingly.
635 const uint removeFlags = from->flags & flags & ~(AppendFlag | PrependFlag);
636 const uint clearedFlags = from->flags & ~(flags | AppendFlag | UnresolvedFlag);
637 if (removeFlags && removes) {
638 const int maskedFlags = clearCache
639 ? (removeFlags & ~CacheFlag)
640 : (removeFlags | (from->flags & CacheFlag));
641 if (maskedFlags)
642 removes->append(t: Remove(from, difference, maskedFlags));
643 }
644 m_end.decrementIndexes(difference, flags: removeFlags);
645 from.incrementIndexes(difference, flags: clearedFlags);
646
647 if (from->previous != &m_ranges
648 && from->previous->list == from->list
649 && (!from->list || clearedFlags == CacheFlag || from->previous->end() == from->index)
650 && from->previous->flags == clearedFlags) {
651 // If the removed flags make the current range a continuation of the previous
652 // then move the affected items over to the previous range.
653 from->previous->count += difference;
654 from->index += difference;
655 from->count -= difference;
656 if (from->count == 0) {
657 // Delete the current range if it is now empty, preserving the append flag
658 if (from->append())
659 from->previous->flags |= AppendFlag;
660 *from = erase(range: *from)->previous;
661 } else {
662 from.incrementIndexes(difference: from->count);
663 }
664 } else if (difference < from->count) {
665 // Create a new range with the reduced flags, and remove the affected items from
666 // the current range.
667 if (clearedFlags)
668 *from = insert(before: *from, list: from->list, index: from->index, count: difference, flags: clearedFlags)->next;
669 from->index += difference;
670 from->count -= difference;
671 from.incrementIndexes(difference: from->count);
672 } else if (clearedFlags) {
673 // The whole range is affected so simply update the flags.
674 from->flags &= ~flags;
675 } else {
676 // All flags have been removed from the range so remove it.
677 *from = erase(range: *from)->previous;
678 }
679 }
680
681 if (*from != &m_ranges && from->previous != &m_ranges
682 && from->previous->list == from->list
683 && (!from->list || from->previous->end() == from->index)
684 && from->previous->flags == (from->flags & ~AppendFlag)) {
685 // If the following range is now a continuation, merge it with its previous range.
686 from.offset = from->previous->count;
687 from->previous->count += from->count;
688 from->previous->flags = from->flags;
689 *from = erase(range: *from)->previous;
690 }
691 m_cacheIt = from;
692 QT_QML_VERIFY_LISTCOMPOSITOR
693}
694
695bool QQmlListCompositor::verifyMoveTo(
696 Group fromGroup, int from, Group toGroup, int to, int count, Group group) const
697{
698 if (group != toGroup) {
699 // determine how many items from the destination group intersect with the source group.
700 iterator fromIt = find(group: fromGroup, index: from);
701
702 int intersectingCount = 0;
703
704 for (; count > 0; *fromIt = fromIt->next) {
705 if (*fromIt == &m_ranges)
706 return false;
707 if (!fromIt->inGroup(group))
708 continue;
709 if (fromIt->inGroup(group: toGroup))
710 intersectingCount += qMin(a: count, b: fromIt->count - fromIt.offset);
711 count -= fromIt->count - fromIt.offset;
712 fromIt.offset = 0;
713 }
714 count = intersectingCount;
715 }
716
717 return to >= 0 && to + count <= m_end.index[toGroup];
718}
719
720/*!
721 \internal
722
723 Moves \a count items belonging to \a moveGroup from the index \a from in \a fromGroup
724 to the index \a to in \a toGroup.
725
726 If \a removes and \a inserts are not null they will be populated with per group notifications
727 of the items moved.
728 */
729
730void QQmlListCompositor::move(
731 Group fromGroup,
732 int from,
733 Group toGroup,
734 int to,
735 int count,
736 Group moveGroup,
737 QVector<Remove> *removes,
738 QVector<Insert> *inserts)
739{
740 QT_QML_TRACE_LISTCOMPOSITOR(<< fromGroup << from << toGroup << to << count)
741 Q_ASSERT(count > 0);
742 Q_ASSERT(from >=0);
743 Q_ASSERT(verifyMoveTo(fromGroup, from, toGroup, to, count, moveGroup));
744
745 // Find the position of the first item to move.
746 iterator fromIt = find(group: fromGroup, index: from);
747
748 if (fromIt != moveGroup) {
749 // If the range at the from index doesn't contain items from the move group; skip
750 // to the next range.
751 fromIt.incrementIndexes(difference: fromIt->count - fromIt.offset);
752 fromIt.offset = 0;
753 *fromIt = fromIt->next;
754 } else if (fromIt.offset > 0) {
755 // If the range at the from index contains items from the move group and the index isn't
756 // at the start of the range; split the range at the index and move the iterator to start
757 // of the second range.
758 *fromIt = insert(
759 before: *fromIt, list: fromIt->list, index: fromIt->index, count: fromIt.offset, flags: fromIt->flags & ~AppendFlag)->next;
760 fromIt->index += fromIt.offset;
761 fromIt->count -= fromIt.offset;
762 fromIt.offset = 0;
763 }
764
765 // Remove count items belonging to the move group from the list.
766 Range movedFlags;
767 for (int moveId = m_moveId; count > 0;) {
768 if (fromIt != moveGroup) {
769 // Skip ranges not containing items from the move group.
770 fromIt.incrementIndexes(difference: fromIt->count);
771 *fromIt = fromIt->next;
772 continue;
773 }
774 int difference = qMin(a: count, b: fromIt->count);
775
776 // Create a new static range containing the moved items from an existing range.
777 new Range(
778 &movedFlags,
779 fromIt->list,
780 fromIt->index,
781 difference,
782 fromIt->flags & ~(PrependFlag | AppendFlag));
783 // Remove moved items from the count, the existing range, and a remove notification.
784 if (removes)
785 removes->append(t: Remove(fromIt, difference, fromIt->flags, ++moveId));
786 count -= difference;
787 fromIt->count -= difference;
788
789 // If the existing range contains the prepend flag replace the removed items with
790 // a placeholder range for new items inserted into the source model.
791 int removeIndex = fromIt->index;
792 if (fromIt->prepend()
793 && fromIt->previous != &m_ranges
794 && fromIt->previous->flags == PrependFlag
795 && fromIt->previous->list == fromIt->list
796 && fromIt->previous->end() == fromIt->index) {
797 // Grow the previous range instead of creating a new one if possible.
798 fromIt->previous->count += difference;
799 } else if (fromIt->prepend()) {
800 *fromIt = insert(before: *fromIt, list: fromIt->list, index: removeIndex, count: difference, flags: PrependFlag)->next;
801 }
802 fromIt->index += difference;
803
804 if (fromIt->count == 0) {
805 // If the existing range has no items remaining; remove it from the list.
806 if (fromIt->append())
807 fromIt->previous->flags |= AppendFlag;
808 *fromIt = erase(range: *fromIt);
809
810 // If the ranges before and after the removed range can be joined, do so.
811 if (*fromIt != m_ranges.next && fromIt->flags == PrependFlag
812 && fromIt->previous != &m_ranges
813 && fromIt->previous->flags == PrependFlag
814 && fromIt->previous->list == fromIt->list
815 && fromIt->previous->end() == fromIt->index) {
816 fromIt.incrementIndexes(difference: fromIt->count);
817 fromIt->previous->count += fromIt->count;
818 *fromIt = erase(range: *fromIt);
819 }
820 } else if (count > 0) {
821 *fromIt = fromIt->next;
822 }
823 }
824
825 // Try and join the range following the removed items to the range preceding it.
826 if (*fromIt != m_ranges.next
827 && *fromIt != &m_ranges
828 && fromIt->previous->list == fromIt->list
829 && (!fromIt->list || fromIt->previous->end() == fromIt->index)
830 && fromIt->previous->flags == (fromIt->flags & ~AppendFlag)) {
831 if (fromIt == fromIt.group)
832 fromIt.offset = fromIt->previous->count;
833 fromIt.offset = fromIt->previous->count;
834 fromIt->previous->count += fromIt->count;
835 fromIt->previous->flags = fromIt->flags;
836 *fromIt = erase(range: *fromIt)->previous;
837 }
838
839 // Find the destination position of the move.
840 insert_iterator toIt = fromIt;
841 toIt.setGroup(toGroup);
842
843 const int difference = to - toIt.index[toGroup];
844 toIt += difference;
845
846 // If the insert position is part way through a range; split it and move the iterator to the
847 // start of the second range.
848 if (toIt.offset > 0) {
849 *toIt = insert(before: *toIt, list: toIt->list, index: toIt->index, count: toIt.offset, flags: toIt->flags & ~AppendFlag)->next;
850 toIt->index += toIt.offset;
851 toIt->count -= toIt.offset;
852 toIt.offset = 0;
853 }
854
855 // Insert the moved ranges before the insert iterator, growing the previous range if that
856 // is an option.
857 for (Range *range = movedFlags.previous; range != &movedFlags; range = range->previous) {
858 if (*toIt != &m_ranges
859 && range->list == toIt->list
860 && (!range->list || range->end() == toIt->index)
861 && range->flags == (toIt->flags & ~AppendFlag)) {
862 toIt->index -= range->count;
863 toIt->count += range->count;
864 } else {
865 *toIt = insert(before: *toIt, list: range->list, index: range->index, count: range->count, flags: range->flags);
866 }
867 }
868
869 // Try and join the range after the inserted ranges to the last range inserted.
870 if (*toIt != m_ranges.next
871 && toIt->previous->list == toIt->list
872 && (!toIt->list || (toIt->previous->end() == toIt->index && toIt->previous->flags == (toIt->flags & ~AppendFlag)))) {
873 toIt.offset = toIt->previous->count;
874 toIt->previous->count += toIt->count;
875 toIt->previous->flags = toIt->flags;
876 *toIt = erase(range: *toIt)->previous;
877 }
878 // Create insert notification for the ranges moved.
879 Insert insert(toIt, 0, 0, 0);
880 for (Range *next, *range = movedFlags.next; range != &movedFlags; range = next) {
881 insert.count = range->count;
882 insert.flags = range->flags;
883 if (inserts) {
884 insert.moveId = ++m_moveId;
885 inserts->append(t: insert);
886 }
887 for (int i = 0; i < m_groupCount; ++i) {
888 if (insert.inGroup(group: i))
889 insert.index[i] += range->count;
890 }
891
892 next = range->next;
893 delete range;
894 }
895
896 m_cacheIt = toIt;
897
898 QT_QML_VERIFY_LISTCOMPOSITOR
899}
900
901/*!
902 Clears the contents of a compositor.
903*/
904
905void QQmlListCompositor::clear()
906{
907 QT_QML_TRACE_LISTCOMPOSITOR("")
908 for (Range *range = m_ranges.next; range != &m_ranges; range = erase(range)) {}
909 m_end = iterator(m_ranges.next, 0, Default, m_groupCount);
910 m_cacheIt = m_end;
911}
912
913void QQmlListCompositor::listItemsInserted(
914 QVector<Insert> *translatedInsertions,
915 void *list,
916 const QVector<QQmlChangeSet::Change> &insertions,
917 const QVector<MovedFlags> *movedFlags)
918{
919 QT_QML_TRACE_LISTCOMPOSITOR(<< list << insertions)
920 for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
921 if (it->list != list || it->flags == CacheFlag) {
922 // Skip ranges that don't reference list.
923 it.incrementIndexes(difference: it->count);
924 continue;
925 } else if (it->flags & MovedFlag) {
926 // Skip ranges that were already moved in listItemsRemoved.
927 it->flags &= ~MovedFlag;
928 it.incrementIndexes(difference: it->count);
929 continue;
930 }
931 for (const QQmlChangeSet::Change &insertion : insertions) {
932 int offset = insertion.index - it->index;
933 if ((offset > 0 && offset < it->count)
934 || (offset == 0 && it->prepend())
935 || (offset == it->count && it->append())) {
936 // The insert index is within the current range.
937 if (it->prepend()) {
938 // The range has the prepend flag set so we insert new items into the range.
939 uint flags = m_defaultFlags;
940 if (insertion.isMove()) {
941 // If the insert was part of a move replace the default flags with
942 // the flags from the source range.
943 for (QVector<MovedFlags>::const_iterator move = movedFlags->begin();
944 move != movedFlags->end();
945 ++move) {
946 if (move->moveId == insertion.moveId) {
947 flags = move->flags;
948 break;
949 }
950 }
951 }
952 if (flags & ~(AppendFlag | PrependFlag)) {
953 // If any items are added to groups append an insert notification.
954 Insert translatedInsert(it, insertion.count, flags, insertion.moveId);
955 for (int i = 0; i < m_groupCount; ++i) {
956 if (it->inGroup(group: i))
957 translatedInsert.index[i] += offset;
958 }
959 translatedInsertions->append(t: translatedInsert);
960 }
961 if ((it->flags & ~AppendFlag) == flags) {
962 // Accumulate items on the current range it its flags are the same as
963 // the insert flags.
964 it->count += insertion.count;
965 } else if (offset == 0
966 && it->previous != &m_ranges
967 && it->previous->list == list
968 && it->previous->end() == insertion.index
969 && it->previous->flags == flags) {
970 // Attempt to append to the previous range if the insert position is at
971 // the start of the current range.
972 it->previous->count += insertion.count;
973 it->index += insertion.count;
974 it.incrementIndexes(difference: insertion.count);
975 } else {
976 if (offset > 0) {
977 // Divide the current range at the insert position.
978 it.incrementIndexes(difference: offset);
979 *it = insert(before: *it, list: it->list, index: it->index, count: offset, flags: it->flags & ~AppendFlag)->next;
980 }
981 // Insert a new range, and increment the start index of the current range.
982 *it = insert(before: *it, list: it->list, index: insertion.index, count: insertion.count, flags)->next;
983 it.incrementIndexes(difference: insertion.count, flags);
984 it->index += offset + insertion.count;
985 it->count -= offset;
986 }
987 m_end.incrementIndexes(difference: insertion.count, flags);
988 } else {
989 // The range doesn't have the prepend flag set so split the range into parts;
990 // one before the insert position and one after the last inserted item.
991 if (offset > 0) {
992 *it = insert(before: *it, list: it->list, index: it->index, count: offset, flags: it->flags)->next;
993 it->index += offset;
994 it->count -= offset;
995 }
996 it->index += insertion.count;
997 }
998 } else if (offset <= 0) {
999 // The insert position was before the current range so increment the start index.
1000 it->index += insertion.count;
1001 }
1002 }
1003 it.incrementIndexes(difference: it->count);
1004 }
1005 m_cacheIt = m_end;
1006 QT_QML_VERIFY_LISTCOMPOSITOR
1007}
1008
1009/*!
1010 Updates the contents of a compositor when \a count items are inserted into a \a list at
1011 \a index.
1012
1013 This corrects the indexes of each range for that list in the compositor, splitting the range
1014 in two if the insert index is in the middle of that range. If a range at the insert position
1015 has the Prepend flag set then a new range will be inserted at that position with the groups
1016 specified in defaultGroups(). If the insert index corresponds to the end of a range with
1017 the Append flag set a new range will be inserted before the end of the append range.
1018
1019 The \a translatedInsertions list is populated with insert notifications for affected
1020 groups.
1021*/
1022
1023void QQmlListCompositor::listItemsInserted(
1024 void *list, int index, int count, QVector<Insert> *translatedInsertions)
1025{
1026 QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
1027 Q_ASSERT(count > 0);
1028
1029 QVector<QQmlChangeSet::Change> insertions;
1030 insertions.append(t: QQmlChangeSet::Change(index, count));
1031
1032 listItemsInserted(translatedInsertions, list, insertions);
1033}
1034
1035void QQmlListCompositor::listItemsRemoved(
1036 QVector<Remove> *translatedRemovals,
1037 void *list,
1038 QVector<QQmlChangeSet::Change> *removals,
1039 QVector<QQmlChangeSet::Change> *insertions,
1040 QVector<MovedFlags> *movedFlags)
1041{
1042 QT_QML_TRACE_LISTCOMPOSITOR(<< list << *removals)
1043
1044 for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
1045 if (it->list != list || it->flags == CacheFlag) {
1046 // Skip ranges that don't reference list.
1047 it.incrementIndexes(difference: it->count);
1048 continue;
1049 }
1050 bool removed = false;
1051 for (QVector<QQmlChangeSet::Change>::iterator removal = removals->begin();
1052 !removed && removal != removals->end();
1053 ++removal) {
1054 int relativeIndex = removal->index - it->index;
1055 int itemsRemoved = removal->count;
1056 if (relativeIndex + removal->count > 0 && relativeIndex < it->count) {
1057 // If the current range intersects the remove; remove the intersecting items.
1058 const int offset = qMax(a: 0, b: relativeIndex);
1059 int removeCount = qMin(a: it->count, b: relativeIndex + removal->count) - offset;
1060 it->count -= removeCount;
1061 int removeFlags = it->flags & m_removeFlags;
1062 Remove translatedRemoval(it, removeCount, it->flags);
1063 for (int i = 0; i < m_groupCount; ++i) {
1064 if (it->inGroup(group: i))
1065 translatedRemoval.index[i] += offset;
1066 }
1067 if (removal->isMove()) {
1068 // If the removal was part of a move find the corresponding insert.
1069 QVector<QQmlChangeSet::Change>::iterator insertion = insertions->begin();
1070 for (; insertion != insertions->end() && insertion->moveId != removal->moveId;
1071 ++insertion) {}
1072 Q_ASSERT(insertion != insertions->end());
1073 Q_ASSERT(insertion->count == removal->count);
1074
1075 if (relativeIndex < 0) {
1076 // If the remove started before the current range, split it and the
1077 // corresponding insert so we're only working with intersecting part.
1078 int splitMoveId = ++m_moveId;
1079 removal = removals->insert(before: removal, t: QQmlChangeSet::Change(
1080 removal->index, -relativeIndex, splitMoveId));
1081 ++removal;
1082 removal->count -= -relativeIndex;
1083 insertion = insertions->insert(before: insertion, t: QQmlChangeSet::Change(
1084 insertion->index, -relativeIndex, splitMoveId));
1085 ++insertion;
1086 insertion->index += -relativeIndex;
1087 insertion->count -= -relativeIndex;
1088 }
1089
1090 if (it->prepend()) {
1091 // If the current range has the prepend flag preserve its flags to transfer
1092 // to its new location.
1093 removeFlags |= it->flags & CacheFlag;
1094 translatedRemoval.moveId = ++m_moveId;
1095 movedFlags->append(t: MovedFlags(m_moveId, it->flags & ~AppendFlag));
1096
1097 if (removeCount < removal->count) {
1098 // If the remove doesn't encompass all of the current range,
1099 // split it and the corresponding insert.
1100 removal = removals->insert(before: removal, t: QQmlChangeSet::Change(
1101 removal->index, removeCount, translatedRemoval.moveId));
1102 ++removal;
1103 insertion = insertions->insert(before: insertion, t: QQmlChangeSet::Change(
1104 insertion->index, removeCount, translatedRemoval.moveId));
1105 ++insertion;
1106
1107 removal->count -= removeCount;
1108 insertion->index += removeCount;
1109 insertion->count -= removeCount;
1110 } else {
1111 // If there's no need to split the move simply replace its moveId
1112 // with that of the translated move.
1113 removal->moveId = translatedRemoval.moveId;
1114 insertion->moveId = translatedRemoval.moveId;
1115 }
1116 } else {
1117 // If the current range doesn't have prepend flags then insert a new range
1118 // with list indexes from the corresponding insert location. The MoveFlag
1119 // is to notify listItemsInserted that it can skip evaluation of that range.
1120 if (offset > 0) {
1121 *it = insert(before: *it, list: it->list, index: it->index, count: offset, flags: it->flags & ~AppendFlag)->next;
1122 it->index += offset;
1123 it->count -= offset;
1124 it.incrementIndexes(difference: offset);
1125 }
1126 if (it->previous != &m_ranges
1127 && it->previous->list == it->list
1128 && it->end() == insertion->index
1129 && it->previous->flags == (it->flags | MovedFlag)) {
1130 it->previous->count += removeCount;
1131 } else {
1132 *it = insert(before: *it, list: it->list, index: insertion->index, count: removeCount, flags: it->flags | MovedFlag)->next;
1133 }
1134 // Clear the changed flags as the item hasn't been removed.
1135 translatedRemoval.flags = 0;
1136 removeFlags = 0;
1137 }
1138 } else if (it->inCache()) {
1139 // If not moving and the current range has the cache flag, insert a new range
1140 // with just the cache flag set to retain caching information for the removed
1141 // range.
1142 if (offset > 0) {
1143 *it = insert(before: *it, list: it->list, index: it->index, count: offset, flags: it->flags & ~AppendFlag)->next;
1144 it->index += offset;
1145 it->count -= offset;
1146 it.incrementIndexes(difference: offset);
1147 }
1148 if (it->previous != &m_ranges
1149 && it->previous->list == it->list
1150 && it->previous->flags == CacheFlag) {
1151 it->previous->count += removeCount;
1152 } else {
1153 *it = insert(before: *it, list: it->list, index: -1, count: removeCount, flags: CacheFlag)->next;
1154 }
1155 it.index[Cache] += removeCount;
1156 }
1157 if (removeFlags & GroupMask)
1158 translatedRemovals->append(t: translatedRemoval);
1159 m_end.decrementIndexes(difference: removeCount, flags: removeFlags);
1160 if (it->count == 0 && !it->append()) {
1161 // Erase empty non-append ranges.
1162 *it = erase(range: *it)->previous;
1163 removed = true;
1164 } else if (relativeIndex <= 0) {
1165 // If the remove started before the current range move the start index of
1166 // the range to the remove index.
1167 it->index = removal->index;
1168 }
1169 } else if (relativeIndex < 0) {
1170 // If the remove was before the current range decrement the start index by the
1171 // number of items removed.
1172 it->index -= itemsRemoved;
1173
1174 if (it->previous != &m_ranges
1175 && it->previous->list == it->list
1176 && it->previous->end() == it->index
1177 && it->previous->flags == (it->flags & ~AppendFlag)) {
1178 // Compress ranges made continuous by the removal of separating ranges.
1179 it.decrementIndexes(difference: it->previous->count);
1180 it->previous->count += it->count;
1181 it->previous->flags = it->flags;
1182 *it = erase(range: *it)->previous;
1183 }
1184 }
1185 }
1186 if (it->flags == CacheFlag && it->next->flags == CacheFlag && it->next->list == it->list) {
1187 // Compress consecutive cache only ranges.
1188 it.index[Cache] += it->next->count;
1189 it->count += it->next->count;
1190 erase(range: it->next);
1191 } else if (!removed) {
1192 it.incrementIndexes(difference: it->count);
1193 }
1194 }
1195 m_cacheIt = m_end;
1196 QT_QML_VERIFY_LISTCOMPOSITOR
1197}
1198
1199
1200/*!
1201 Updates the contents of a compositor when \a count items are removed from a \a list at
1202 \a index.
1203
1204 Ranges that intersect the specified range are removed from the compositor and the indexes of
1205 ranges after index + count are updated.
1206
1207 The \a translatedRemovals list is populated with remove notifications for the affected
1208 groups.
1209*/
1210
1211
1212void QQmlListCompositor::listItemsRemoved(
1213 void *list, int index, int count, QVector<Remove> *translatedRemovals)
1214{
1215 QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
1216 Q_ASSERT(count >= 0);
1217
1218 QVector<QQmlChangeSet::Change> removals;
1219 removals.append(t: QQmlChangeSet::Change(index, count));
1220 listItemsRemoved(translatedRemovals, list, removals: &removals, insertions: nullptr, movedFlags: nullptr);
1221}
1222
1223/*!
1224 Updates the contents of a compositor when \a count items are removed from a \a list at
1225 \a index.
1226
1227 Ranges that intersect the specified range are removed from the compositor and the indexes of
1228 ranges after index + count are updated.
1229
1230 The \a translatedRemovals and translatedInserts lists are populated with move
1231 notifications for the affected groups.
1232*/
1233
1234void QQmlListCompositor::listItemsMoved(
1235 void *list,
1236 int from,
1237 int to,
1238 int count,
1239 QVector<Remove> *translatedRemovals,
1240 QVector<Insert> *translatedInsertions)
1241{
1242 QT_QML_TRACE_LISTCOMPOSITOR(<< list << from << to << count)
1243 Q_ASSERT(count >= 0);
1244
1245 QVector<QQmlChangeSet::Change> removals;
1246 QVector<QQmlChangeSet::Change> insertions;
1247 QVector<MovedFlags> movedFlags;
1248 removals.append(t: QQmlChangeSet::Change(from, count, 0));
1249 insertions.append(t: QQmlChangeSet::Change(to, count, 0));
1250
1251 listItemsRemoved(translatedRemovals, list, removals: &removals, insertions: &insertions, movedFlags: &movedFlags);
1252 listItemsInserted(translatedInsertions, list, insertions, movedFlags: &movedFlags);
1253}
1254
1255void QQmlListCompositor::listItemsChanged(
1256 QVector<Change> *translatedChanges,
1257 void *list,
1258 const QVector<QQmlChangeSet::Change> &changes)
1259{
1260 QT_QML_TRACE_LISTCOMPOSITOR(<< list << changes)
1261 for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
1262 if (it->list != list || it->flags == CacheFlag) {
1263 it.incrementIndexes(difference: it->count);
1264 continue;
1265 } else if (!it->inGroup()) {
1266 continue;
1267 }
1268 for (const QQmlChangeSet::Change &change : changes) {
1269 const int offset = change.index - it->index;
1270 if (offset + change.count > 0 && offset < it->count) {
1271 const int changeOffset = qMax(a: 0, b: offset);
1272 const int changeCount = qMin(a: it->count, b: offset + change.count) - changeOffset;
1273
1274 Change translatedChange(it, changeCount, it->flags);
1275 for (int i = 0; i < m_groupCount; ++i) {
1276 if (it->inGroup(group: i))
1277 translatedChange.index[i] += changeOffset;
1278 }
1279 translatedChanges->append(t: translatedChange);
1280 }
1281 }
1282 it.incrementIndexes(difference: it->count);
1283 }
1284}
1285
1286
1287/*!
1288 Translates the positions of \a count changed items at \a index in a \a list.
1289
1290 The \a translatedChanges list is populated with change notifications for the
1291 affected groups.
1292*/
1293
1294void QQmlListCompositor::listItemsChanged(
1295 void *list, int index, int count, QVector<Change> *translatedChanges)
1296{
1297 QT_QML_TRACE_LISTCOMPOSITOR(<< list << index << count)
1298 Q_ASSERT(count >= 0);
1299 QVector<QQmlChangeSet::Change> changes;
1300 changes.append(t: QQmlChangeSet::Change(index, count));
1301 listItemsChanged(translatedChanges, list, changes);
1302}
1303
1304void QQmlListCompositor::transition(
1305 Group from,
1306 Group to,
1307 QVector<QQmlChangeSet::Change> *removes,
1308 QVector<QQmlChangeSet::Change> *inserts)
1309{
1310 int removeCount = 0;
1311 for (iterator it(m_ranges.next, 0, Default, m_groupCount); *it != &m_ranges; *it = it->next) {
1312 if (it == from && it != to) {
1313 removes->append(t: QQmlChangeSet::Change(it.index[from]- removeCount, it->count));
1314 removeCount += it->count;
1315 } else if (it != from && it == to) {
1316 inserts->append(t: QQmlChangeSet::Change(it.index[to], it->count));
1317 }
1318 it.incrementIndexes(difference: it->count);
1319 }
1320}
1321
1322/*!
1323 \internal
1324 Writes the name of \a group to \a debug.
1325*/
1326
1327QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group)
1328{
1329 switch (group) {
1330 case QQmlListCompositor::Cache: return debug << "Cache";
1331 case QQmlListCompositor::Default: return debug << "Default";
1332 default: return (debug.nospace() << "Group" << int(group)).space();
1333 }
1334
1335}
1336
1337/*!
1338 \internal
1339 Writes the contents of \a range to \a debug.
1340*/
1341
1342QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range)
1343{
1344 (debug.nospace()
1345 << "Range("
1346 << range.list) << ' '
1347 << range.index << ' '
1348 << range.count << ' '
1349 << (range.isUnresolved() ? 'U' : '0')
1350 << (range.append() ? 'A' : '0')
1351 << (range.prepend() ? 'P' : '0');
1352 for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i)
1353 debug << (range.inGroup(group: i) ? '1' : '0');
1354 return (debug
1355 << (range.inGroup(group: QQmlListCompositor::Default) ? 'D' : '0')
1356 << (range.inGroup(group: QQmlListCompositor::Cache) ? 'C' : '0'));
1357}
1358
1359static void qt_print_indexes(QDebug &debug, int count, const int *indexes)
1360{
1361 for (int i = count - 1; i >= 0; --i)
1362 debug << indexes[i];
1363}
1364
1365/*!
1366 \internal
1367 Writes the contents of \a it to \a debug.
1368*/
1369
1370QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it)
1371{
1372 (debug.nospace() << "iterator(" << it.group).space() << "offset:" << it.offset;
1373 qt_print_indexes(debug, count: it.groupCount, indexes: it.index);
1374 return ((debug << **it).nospace() << ')').space();
1375}
1376
1377static QDebug qt_print_change(QDebug debug, const char *name, const QQmlListCompositor::Change &change)
1378{
1379 debug.nospace() << name << '(' << change.moveId << ' ' << change.count << ' ';
1380 for (int i = QQmlListCompositor::MaximumGroupCount - 1; i >= 2; --i)
1381 debug << (change.inGroup(group: i) ? '1' : '0');
1382 debug << (change.inGroup(group: QQmlListCompositor::Default) ? 'D' : '0')
1383 << (change.inGroup(group: QQmlListCompositor::Cache) ? 'C' : '0');
1384 int i = QQmlListCompositor::MaximumGroupCount - 1;
1385 for (; i >= 0 && !change.inGroup(group: i); --i) {}
1386 for (; i >= 0; --i)
1387 debug << ' ' << change.index[i];
1388 return (debug << ')').maybeSpace();
1389}
1390
1391/*!
1392 \internal
1393 Writes the contents of \a change to \a debug.
1394*/
1395
1396QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change)
1397{
1398 return qt_print_change(debug, name: "Change", change);
1399}
1400
1401/*!
1402 \internal
1403 Writes the contents of \a remove to \a debug.
1404*/
1405
1406QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove)
1407{
1408 return qt_print_change(debug, name: "Remove", change: remove);
1409}
1410
1411/*!
1412 \internal
1413 Writes the contents of \a insert to \a debug.
1414*/
1415
1416QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert)
1417{
1418 return qt_print_change(debug, name: "Insert", change: insert);
1419}
1420
1421/*!
1422 \internal
1423 Writes the contents of \a list to \a debug.
1424*/
1425
1426QDebug operator <<(QDebug debug, const QQmlListCompositor &list)
1427{
1428 int indexes[QQmlListCompositor::MaximumGroupCount];
1429 for (int i = 0; i < QQmlListCompositor::MaximumGroupCount; ++i)
1430 indexes[i] = 0;
1431 debug.nospace() << "QQmlListCompositor(";
1432 qt_print_indexes(debug, count: list.m_groupCount, indexes: list.m_end.index);
1433 for (QQmlListCompositor::Range *range = list.m_ranges.next; range != &list.m_ranges; range = range->next) {
1434 (debug << '\n').space();
1435 qt_print_indexes(debug, count: list.m_groupCount, indexes);
1436 debug << ' ' << *range;
1437
1438 for (int i = 0; i < list.m_groupCount; ++i) {
1439 if (range->inGroup(group: i))
1440 indexes[i] += range->count;
1441 }
1442 }
1443 return (debug << ')').maybeSpace();
1444}
1445
1446QT_END_NAMESPACE
1447

source code of qtdeclarative/src/qmlmodels/qqmllistcompositor.cpp