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 Qt Quick Controls 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 <math.h>
41#include "qquicktreemodeladaptor_p.h"
42#include <QtCore/qstack.h>
43#include <QtCore/qdebug.h>
44
45QT_BEGIN_NAMESPACE
46
47//#define QQUICKTREEMODELADAPTOR_DEBUG
48#if defined(QQUICKTREEMODELADAPTOR_DEBUG) && !defined(QT_TESTLIB_LIB)
49# define ASSERT_CONSISTENCY() Q_ASSERT_X(testConsistency(true /* dumpOnFail */), Q_FUNC_INFO, "Consistency test failed")
50#else
51# define ASSERT_CONSISTENCY qt_noop
52#endif
53
54QQuickTreeModelAdaptor1::QQuickTreeModelAdaptor1(QObject *parent) :
55 QAbstractListModel(parent), m_model(0), m_lastItemIndex(0),
56 m_visibleRowsMoved(false), m_signalAggregatorStack(0)
57{
58}
59
60QAbstractItemModel *QQuickTreeModelAdaptor1::model() const
61{
62 return m_model;
63}
64
65void QQuickTreeModelAdaptor1::setModel(QAbstractItemModel *arg)
66{
67 struct Cx {
68 const char *signal;
69 const char *slot;
70 };
71 static const Cx connections[] = {
72 { SIGNAL(destroyed(QObject*)),
73 SLOT(modelHasBeenDestroyed()) },
74 { SIGNAL(modelReset()),
75 SLOT(modelHasBeenReset()) },
76 { SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
77 SLOT(modelDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)) },
78
79 { SIGNAL(layoutAboutToBeChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
80 SLOT(modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)) },
81 { SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
82 SLOT(modelLayoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)) },
83
84 { SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)),
85 SLOT(modelRowsAboutToBeInserted(const QModelIndex &, int, int)) },
86 { SIGNAL(rowsInserted(const QModelIndex&, int, int)),
87 SLOT(modelRowsInserted(const QModelIndex&, int, int)) },
88 { SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
89 SLOT(modelRowsAboutToBeRemoved(const QModelIndex&, int, int)) },
90 { SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
91 SLOT(modelRowsRemoved(const QModelIndex&, int, int)) },
92 { SIGNAL(rowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
93 SLOT(modelRowsAboutToBeMoved(const QModelIndex&, int, int, const QModelIndex&, int)) },
94 { SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)),
95 SLOT(modelRowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)) },
96 { .signal: 0, .slot: 0 }
97 };
98
99 if (m_model != arg) {
100 if (m_model) {
101 for (const Cx *c = &connections[0]; c->signal; c++)
102 disconnect(sender: m_model, signal: c->signal, receiver: this, member: c->slot);
103 }
104
105 clearModelData();
106 m_model = arg;
107
108 if (m_model) {
109 for (const Cx *c = &connections[0]; c->signal; c++)
110 connect(sender: m_model, signal: c->signal, receiver: this, member: c->slot);
111
112 showModelTopLevelItems();
113 }
114
115 emit modelChanged(model: arg);
116 }
117}
118
119void QQuickTreeModelAdaptor1::clearModelData()
120{
121 beginResetModel();
122 m_items.clear();
123 m_expandedItems.clear();
124 endResetModel();
125}
126
127const QModelIndex &QQuickTreeModelAdaptor1::rootIndex() const
128{
129 return m_rootIndex;
130}
131
132void QQuickTreeModelAdaptor1::setRootIndex(const QModelIndex &idx)
133{
134 if (m_rootIndex == idx)
135 return;
136
137 if (m_model)
138 clearModelData();
139 m_rootIndex = idx;
140 if (m_model)
141 showModelTopLevelItems();
142 emit rootIndexChanged();
143}
144
145void QQuickTreeModelAdaptor1::resetRootIndex()
146{
147 setRootIndex(QModelIndex());
148}
149
150QHash<int, QByteArray> QQuickTreeModelAdaptor1::roleNames() const
151{
152 if (!m_model)
153 return QHash<int, QByteArray>();
154
155 QHash<int, QByteArray> modelRoleNames = m_model->roleNames();
156 modelRoleNames.insert(akey: DepthRole, avalue: "_q_TreeView_ItemDepth");
157 modelRoleNames.insert(akey: ExpandedRole, avalue: "_q_TreeView_ItemExpanded");
158 modelRoleNames.insert(akey: HasChildrenRole, avalue: "_q_TreeView_HasChildren");
159 modelRoleNames.insert(akey: HasSiblingRole, avalue: "_q_TreeView_HasSibling");
160 modelRoleNames.insert(akey: ModelIndexRole, avalue: "_q_TreeView_ModelIndex");
161 return modelRoleNames;
162}
163
164int QQuickTreeModelAdaptor1::rowCount(const QModelIndex &) const
165{
166 return m_items.count();
167}
168
169QVariant QQuickTreeModelAdaptor1::data(const QModelIndex &index, int role) const
170{
171 if (!m_model)
172 return QVariant();
173
174 const QModelIndex &modelIndex = mapToModel(index);
175
176 switch (role) {
177 case DepthRole:
178 return m_items.at(i: index.row()).depth;
179 case ExpandedRole:
180 return isExpanded(row: index.row());
181 case HasChildrenRole:
182 return !(modelIndex.flags() & Qt::ItemNeverHasChildren) && m_model->hasChildren(parent: modelIndex);
183 case HasSiblingRole:
184 return modelIndex.row() != m_model->rowCount(parent: modelIndex.parent()) - 1;
185 case ModelIndexRole:
186 return modelIndex;
187 default:
188 return m_model->data(index: modelIndex, role);
189 }
190}
191
192bool QQuickTreeModelAdaptor1::setData(const QModelIndex &index, const QVariant &value, int role)
193{
194 if (!m_model)
195 return false;
196
197 switch (role) {
198 case DepthRole:
199 case ExpandedRole:
200 case HasChildrenRole:
201 case HasSiblingRole:
202 case ModelIndexRole:
203 return false;
204 default: {
205 const QModelIndex &pmi = mapToModel(index);
206 return m_model->setData(index: pmi, value, role);
207 }
208 }
209}
210
211int QQuickTreeModelAdaptor1::itemIndex(const QModelIndex &index) const
212{
213 // This is basically a plagiarism of QTreeViewPrivate::viewIndex()
214 if (!index.isValid() || index == m_rootIndex || m_items.isEmpty())
215 return -1;
216
217 const int totalCount = m_items.count();
218
219 // We start nearest to the lastViewedItem
220 int localCount = qMin(a: m_lastItemIndex - 1, b: totalCount - m_lastItemIndex);
221 for (int i = 0; i < localCount; ++i) {
222 const TreeItem &item1 = m_items.at(i: m_lastItemIndex + i);
223 if (item1.index == index) {
224 m_lastItemIndex = m_lastItemIndex + i;
225 return m_lastItemIndex;
226 }
227 const TreeItem &item2 = m_items.at(i: m_lastItemIndex - i - 1);
228 if (item2.index == index) {
229 m_lastItemIndex = m_lastItemIndex - i - 1;
230 return m_lastItemIndex;
231 }
232 }
233
234 for (int j = qMax(a: 0, b: m_lastItemIndex + localCount); j < totalCount; ++j) {
235 const TreeItem &item = m_items.at(i: j);
236 if (item.index == index) {
237 m_lastItemIndex = j;
238 return j;
239 }
240 }
241 for (int j = qMin(a: totalCount, b: m_lastItemIndex - localCount) - 1; j >= 0; --j) {
242 const TreeItem &item = m_items.at(i: j);
243 if (item.index == index) {
244 m_lastItemIndex = j;
245 return j;
246 }
247 }
248
249 // nothing found
250 return -1;
251}
252
253bool QQuickTreeModelAdaptor1::isVisible(const QModelIndex &index)
254{
255 return itemIndex(index) != -1;
256}
257
258bool QQuickTreeModelAdaptor1::childrenVisible(const QModelIndex &index)
259{
260 return (index == m_rootIndex && !m_items.isEmpty())
261 || (m_expandedItems.contains(value: index) && isVisible(index));
262}
263
264const QModelIndex &QQuickTreeModelAdaptor1::mapToModel(const QModelIndex &index) const
265{
266 return m_items.at(i: index.row()).index;
267}
268
269QPersistentModelIndex QQuickTreeModelAdaptor1::mapRowToModelIndex(int row) const
270{
271 if (!m_model)
272 return QModelIndex();
273 if (row < 0 || row >= m_items.count())
274 return QModelIndex();
275 return m_items.at(i: row).index;
276}
277
278QItemSelection QQuickTreeModelAdaptor1::selectionForRowRange(const QModelIndex &fromIndex, const QModelIndex &toIndex) const
279{
280 int from = itemIndex(index: fromIndex);
281 int to = itemIndex(index: toIndex);
282 if (from == -1) {
283 if (to == -1)
284 return QItemSelection();
285 return QItemSelection(toIndex, toIndex);
286 }
287
288 to = qMax(a: to, b: 0);
289 if (from > to)
290 qSwap(value1&: from, value2&: to);
291
292 typedef QPair<QModelIndex, QModelIndex> MIPair;
293 typedef QHash<QModelIndex, MIPair> MI2MIPairHash;
294 MI2MIPairHash ranges;
295 QModelIndex firstIndex = m_items.at(i: from).index;
296 QModelIndex lastIndex = firstIndex;
297 QModelIndex previousParent = firstIndex.parent();
298 bool selectLastRow = false;
299 for (int i = from + 1; i <= to || (selectLastRow = true); i++) {
300 // We run an extra iteration to make sure the last row is
301 // added to the selection. (And also to avoid duplicating
302 // the insertion code.)
303 QModelIndex index;
304 QModelIndex parent;
305 if (!selectLastRow) {
306 index = m_items.at(i).index;
307 parent = index.parent();
308 }
309 if (selectLastRow || previousParent != parent) {
310 const MI2MIPairHash::iterator &it = ranges.find(akey: previousParent);
311 if (it == ranges.end())
312 ranges.insert(akey: previousParent, avalue: MIPair(firstIndex, lastIndex));
313 else
314 it->second = lastIndex;
315
316 if (selectLastRow)
317 break;
318
319 firstIndex = index;
320 previousParent = parent;
321 }
322 lastIndex = index;
323 }
324
325 QItemSelection sel;
326 sel.reserve(alloc: ranges.count());
327 for (const MIPair &pair : qAsConst(t&: ranges))
328 sel.append(t: QItemSelectionRange(pair.first, pair.second));
329
330 return sel;
331}
332
333void QQuickTreeModelAdaptor1::showModelTopLevelItems(bool doInsertRows)
334{
335 if (!m_model)
336 return;
337
338 if (m_model->hasChildren(parent: m_rootIndex) && m_model->canFetchMore(parent: m_rootIndex))
339 m_model->fetchMore(parent: m_rootIndex);
340 const long topLevelRowCount = m_model->rowCount(parent: m_rootIndex);
341 if (topLevelRowCount == 0)
342 return;
343
344 showModelChildItems(parent: TreeItem(m_rootIndex), start: 0, end: topLevelRowCount - 1, doInsertRows);
345}
346
347void QQuickTreeModelAdaptor1::showModelChildItems(const TreeItem &parentItem, int start, int end, bool doInsertRows, bool doExpandPendingRows)
348{
349 const QModelIndex &parentIndex = parentItem.index;
350 int rowIdx = parentIndex.isValid() && parentIndex != m_rootIndex ? itemIndex(index: parentIndex) + 1 : 0;
351 Q_ASSERT(rowIdx == 0 || parentItem.expanded);
352 if (parentIndex.isValid() && parentIndex != m_rootIndex && (rowIdx == 0 || !parentItem.expanded))
353 return;
354
355 if (m_model->rowCount(parent: parentIndex) == 0) {
356 if (m_model->hasChildren(parent: parentIndex) && m_model->canFetchMore(parent: parentIndex))
357 m_model->fetchMore(parent: parentIndex);
358 return;
359 }
360
361 int insertCount = end - start + 1;
362 int startIdx;
363 if (start == 0) {
364 startIdx = rowIdx;
365 } else {
366 // Prefer to insert before next sibling instead of after last child of previous, as
367 // the latter is potentially buggy, see QTBUG-66062
368 const QModelIndex &nextSiblingIdx = m_model->index(row: end + 1, column: 0, parent: parentIndex);
369 if (nextSiblingIdx.isValid()) {
370 startIdx = itemIndex(index: nextSiblingIdx);
371 } else {
372 const QModelIndex &prevSiblingIdx = m_model->index(row: start - 1, column: 0, parent: parentIndex);
373 startIdx = lastChildIndex(index: prevSiblingIdx) + 1;
374 }
375 }
376
377 int rowDepth = rowIdx == 0 ? 0 : parentItem.depth + 1;
378 if (doInsertRows)
379 beginInsertRows(parent: QModelIndex(), first: startIdx, last: startIdx + insertCount - 1);
380 m_items.reserve(alloc: m_items.count() + insertCount);
381 for (int i = 0; i < insertCount; i++) {
382 const QModelIndex &cmi = m_model->index(row: start + i, column: 0, parent: parentIndex);
383 bool expanded = m_expandedItems.contains(value: cmi);
384 m_items.insert(i: startIdx + i, t: TreeItem(cmi, rowDepth, expanded));
385 if (expanded)
386 m_itemsToExpand.append(t: &m_items[startIdx + i]);
387 }
388 if (doInsertRows)
389 endInsertRows();
390
391 if (doExpandPendingRows)
392 expandPendingRows(doInsertRows);
393}
394
395
396void QQuickTreeModelAdaptor1::expand(const QModelIndex &idx)
397{
398 ASSERT_CONSISTENCY();
399 if (!m_model)
400 return;
401 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
402 if (!idx.isValid() || !m_model->hasChildren(parent: idx))
403 return;
404 if (m_expandedItems.contains(value: idx))
405 return;
406
407 int row = itemIndex(index: idx);
408 if (row != -1)
409 expandRow(n: row);
410 else
411 m_expandedItems.insert(value: idx);
412 ASSERT_CONSISTENCY();
413
414 emit expanded(index: idx);
415}
416
417void QQuickTreeModelAdaptor1::collapse(const QModelIndex &idx)
418{
419 ASSERT_CONSISTENCY();
420 if (!m_model)
421 return;
422 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
423 if (!idx.isValid() || !m_model->hasChildren(parent: idx))
424 return;
425 if (!m_expandedItems.contains(value: idx))
426 return;
427
428 int row = itemIndex(index: idx);
429 if (row != -1)
430 collapseRow(n: row);
431 else
432 m_expandedItems.remove(value: idx);
433 ASSERT_CONSISTENCY();
434
435 emit collapsed(index: idx);
436}
437
438bool QQuickTreeModelAdaptor1::isExpanded(const QModelIndex &index) const
439{
440 ASSERT_CONSISTENCY();
441 if (!m_model)
442 return false;
443 Q_ASSERT(!index.isValid() || index.model() == m_model);
444 return !index.isValid() || m_expandedItems.contains(value: index);
445}
446
447bool QQuickTreeModelAdaptor1::isExpanded(int row) const
448{
449 return m_items.at(i: row).expanded;
450}
451
452void QQuickTreeModelAdaptor1::expandRow(int n)
453{
454 if (!m_model || isExpanded(row: n))
455 return;
456
457 TreeItem &item = m_items[n];
458 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(parent: item.index))
459 return;
460 item.expanded = true;
461 m_expandedItems.insert(value: item.index);
462 QVector<int> changedRole(1, ExpandedRole);
463 emit dataChanged(topLeft: index(row: n), bottomRight: index(row: n), roles: changedRole);
464
465 m_itemsToExpand.append(t: &item);
466 expandPendingRows();
467}
468
469void QQuickTreeModelAdaptor1::expandPendingRows(bool doInsertRows)
470{
471 while (!m_itemsToExpand.isEmpty()) {
472 TreeItem *item = m_itemsToExpand.takeFirst();
473 Q_ASSERT(item->expanded);
474 const QModelIndex &index = item->index;
475 int childrenCount = m_model->rowCount(parent: index);
476 if (childrenCount == 0) {
477 if (m_model->hasChildren(parent: index) && m_model->canFetchMore(parent: index))
478 m_model->fetchMore(parent: index);
479 continue;
480 }
481
482 // TODO Pre-compute the total number of items made visible
483 // so that we only call a single beginInsertRows()/endInsertRows()
484 // pair per expansion (same as we do for collapsing).
485 showModelChildItems(parentItem: *item, start: 0, end: childrenCount - 1, doInsertRows, doExpandPendingRows: false);
486 }
487}
488
489void QQuickTreeModelAdaptor1::collapseRow(int n)
490{
491 if (!m_model || !isExpanded(row: n))
492 return;
493
494 SignalFreezer aggregator(this);
495
496 TreeItem &item = m_items[n];
497 item.expanded = false;
498 m_expandedItems.remove(value: item.index);
499 QVector<int> changedRole(1, ExpandedRole);
500 queueDataChanged(topLeft: index(row: n), bottomRight: index(row: n), roles: changedRole);
501 int childrenCount = m_model->rowCount(parent: item.index);
502 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(parent: item.index) || childrenCount == 0)
503 return;
504
505 const QModelIndex &emi = m_model->index(row: childrenCount - 1, column: 0, parent: item.index);
506 int lastIndex = lastChildIndex(index: emi);
507 removeVisibleRows(startIndex: n + 1, endIndex: lastIndex);
508}
509
510int QQuickTreeModelAdaptor1::lastChildIndex(const QModelIndex &index)
511{
512 if (!m_expandedItems.contains(value: index))
513 return itemIndex(index);
514
515 QModelIndex parent = index.parent();
516 QModelIndex nextSiblingIndex;
517 while (parent.isValid()) {
518 nextSiblingIndex = parent.sibling(arow: parent.row() + 1, acolumn: 0);
519 if (nextSiblingIndex.isValid())
520 break;
521 parent = parent.parent();
522 }
523
524 int firstIndex = nextSiblingIndex.isValid() ? itemIndex(index: nextSiblingIndex) : m_items.count();
525 return firstIndex - 1;
526}
527
528void QQuickTreeModelAdaptor1::removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows)
529{
530 if (startIndex < 0 || endIndex < 0 || startIndex > endIndex)
531 return;
532
533 if (doRemoveRows)
534 beginRemoveRows(parent: QModelIndex(), first: startIndex, last: endIndex);
535 m_items.erase(afirst: m_items.begin() + startIndex, alast: m_items.begin() + endIndex + 1);
536 if (doRemoveRows) {
537 endRemoveRows();
538
539 /* We need to update the model index for all the items below the removed ones */
540 int lastIndex = m_items.count() - 1;
541 if (startIndex <= lastIndex) {
542 const QModelIndex &topLeft = index(row: startIndex, column: 0, parent: QModelIndex());
543 const QModelIndex &bottomRight = index(row: lastIndex, column: 0, parent: QModelIndex());
544 const QVector<int> changedRole(1, ModelIndexRole);
545 queueDataChanged(topLeft, bottomRight, roles: changedRole);
546 }
547 }
548}
549
550void QQuickTreeModelAdaptor1::modelHasBeenDestroyed()
551{
552 // The model has been deleted. This should behave as if no model was set
553 clearModelData();
554 emit modelChanged(model: nullptr);
555}
556
557void QQuickTreeModelAdaptor1::modelHasBeenReset()
558{
559 clearModelData();
560
561 showModelTopLevelItems();
562 ASSERT_CONSISTENCY();
563}
564
565void QQuickTreeModelAdaptor1::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRigth, const QVector<int> &roles)
566{
567 Q_ASSERT(topLeft.parent() == bottomRigth.parent());
568 const QModelIndex &parent = topLeft.parent();
569 if (parent.isValid() && !childrenVisible(index: parent)) {
570 ASSERT_CONSISTENCY();
571 return;
572 }
573
574 int topIndex = itemIndex(index: topLeft);
575 if (topIndex == -1) // 'parent' is not visible anymore, though it's been expanded previously
576 return;
577 for (int i = topLeft.row(); i <= bottomRigth.row(); i++) {
578 // Group items with same parent to minize the number of 'dataChanged()' emits
579 int bottomIndex = topIndex;
580 while (bottomIndex < m_items.count()) {
581 const QModelIndex &idx = m_items.at(i: bottomIndex).index;
582 if (idx.parent() != parent) {
583 --bottomIndex;
584 break;
585 }
586 if (idx.row() == bottomRigth.row())
587 break;
588 ++bottomIndex;
589 }
590 emit dataChanged(topLeft: index(row: topIndex), bottomRight: index(row: bottomIndex), roles);
591
592 i += bottomIndex - topIndex;
593 if (i == bottomRigth.row())
594 break;
595 topIndex = bottomIndex + 1;
596 while (topIndex < m_items.count()
597 && m_items.at(i: topIndex).index.parent() != parent)
598 topIndex++;
599 }
600 ASSERT_CONSISTENCY();
601}
602
603void QQuickTreeModelAdaptor1::modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
604{
605 ASSERT_CONSISTENCY();
606 Q_UNUSED(parents);
607 Q_UNUSED(hint);
608}
609
610void QQuickTreeModelAdaptor1::modelLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
611{
612 Q_UNUSED(hint);
613 if (parents.isEmpty()) {
614 m_items.clear();
615 showModelTopLevelItems(doInsertRows: false /*doInsertRows*/);
616 emit dataChanged(topLeft: index(row: 0), bottomRight: index(row: m_items.count() - 1));
617 }
618
619 for (const QPersistentModelIndex &pmi : parents) {
620 if (m_expandedItems.contains(value: pmi)) {
621 int row = itemIndex(index: pmi);
622 if (row != -1) {
623 int rowCount = m_model->rowCount(parent: pmi);
624 if (rowCount > 0) {
625 const QModelIndex &lmi = m_model->index(row: rowCount - 1, column: 0, parent: pmi);
626 int lastRow = lastChildIndex(index: lmi);
627 removeVisibleRows(startIndex: row + 1, endIndex: lastRow, doRemoveRows: false /*doRemoveRows*/);
628 showModelChildItems(parentItem: m_items.at(i: row), start: 0, end: rowCount - 1, doInsertRows: false /*doInsertRows*/);
629 emit dataChanged(topLeft: index(row: row + 1), bottomRight: index(row: lastRow));
630 }
631 }
632 }
633 }
634 ASSERT_CONSISTENCY();
635}
636
637void QQuickTreeModelAdaptor1::modelRowsAboutToBeInserted(const QModelIndex & parent, int start, int end)
638{
639 Q_UNUSED(parent);
640 Q_UNUSED(start);
641 Q_UNUSED(end);
642 ASSERT_CONSISTENCY();
643}
644
645void QQuickTreeModelAdaptor1::modelRowsInserted(const QModelIndex & parent, int start, int end)
646{
647 TreeItem item;
648 int parentRow = itemIndex(index: parent);
649 if (parentRow >= 0) {
650 const QModelIndex& parentIndex = index(row: parentRow);
651 QVector<int> changedRole(1, HasChildrenRole);
652 queueDataChanged(topLeft: parentIndex, bottomRight: parentIndex, roles: changedRole);
653 item = m_items.at(i: parentRow);
654 if (!item.expanded) {
655 ASSERT_CONSISTENCY();
656 return;
657 }
658 } else if (parent == m_rootIndex) {
659 item = TreeItem(parent);
660 } else {
661 ASSERT_CONSISTENCY();
662 return;
663 }
664 showModelChildItems(parentItem: item, start, end);
665 ASSERT_CONSISTENCY();
666}
667
668void QQuickTreeModelAdaptor1::modelRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end)
669{
670 ASSERT_CONSISTENCY();
671 enableSignalAggregation();
672 if (parent == m_rootIndex || childrenVisible(index: parent)) {
673 const QModelIndex &smi = m_model->index(row: start, column: 0, parent);
674 int startIndex = itemIndex(index: smi);
675 const QModelIndex &emi = m_model->index(row: end, column: 0, parent);
676 int endIndex = -1;
677 if (isExpanded(index: emi)) {
678 int rowCount = m_model->rowCount(parent: emi);
679 if (rowCount > 0) {
680 const QModelIndex &idx = m_model->index(row: rowCount - 1, column: 0, parent: emi);
681 endIndex = lastChildIndex(index: idx);
682 }
683 }
684 if (endIndex == -1)
685 endIndex = itemIndex(index: emi);
686
687 removeVisibleRows(startIndex, endIndex);
688 }
689
690 for (int r = start; r <= end; r++) {
691 const QModelIndex &cmi = m_model->index(row: r, column: 0, parent);
692 m_expandedItems.remove(value: cmi);
693 }
694}
695
696void QQuickTreeModelAdaptor1::modelRowsRemoved(const QModelIndex & parent, int start, int end)
697{
698 Q_UNUSED(start);
699 Q_UNUSED(end);
700 int parentRow = itemIndex(index: parent);
701 if (parentRow >= 0) {
702 const QModelIndex& parentIndex = index(row: parentRow);
703 QVector<int> changedRole(1, HasChildrenRole);
704 queueDataChanged(topLeft: parentIndex, bottomRight: parentIndex, roles: changedRole);
705 }
706 disableSignalAggregation();
707 ASSERT_CONSISTENCY();
708}
709
710void QQuickTreeModelAdaptor1::modelRowsAboutToBeMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
711{
712 ASSERT_CONSISTENCY();
713 enableSignalAggregation();
714 m_visibleRowsMoved = false;
715 if (!childrenVisible(index: sourceParent))
716 return; // Do nothing now. See modelRowsMoved() below.
717
718 if (!childrenVisible(index: destinationParent)) {
719 modelRowsAboutToBeRemoved(parent: sourceParent, start: sourceStart, end: sourceEnd);
720 /* If the destination parent has no children, we'll need to
721 * report a change on the HasChildrenRole */
722 if (isVisible(index: destinationParent) && m_model->rowCount(parent: destinationParent) == 0) {
723 const QModelIndex &topLeft = index(row: itemIndex(index: destinationParent), column: 0, parent: QModelIndex());
724 const QModelIndex &bottomRight = topLeft;
725 const QVector<int> changedRole(1, HasChildrenRole);
726 queueDataChanged(topLeft, bottomRight, roles: changedRole);
727 }
728 } else {
729 int depthDifference = -1;
730 if (destinationParent.isValid()) {
731 int destParentIndex = itemIndex(index: destinationParent);
732 depthDifference = m_items.at(i: destParentIndex).depth;
733 }
734 if (sourceParent.isValid()) {
735 int sourceParentIndex = itemIndex(index: sourceParent);
736 depthDifference -= m_items.at(i: sourceParentIndex).depth;
737 } else {
738 depthDifference++;
739 }
740
741 int startIndex = itemIndex(index: m_model->index(row: sourceStart, column: 0, parent: sourceParent));
742 const QModelIndex &emi = m_model->index(row: sourceEnd, column: 0, parent: sourceParent);
743 int endIndex = -1;
744 if (isExpanded(index: emi)) {
745 int rowCount = m_model->rowCount(parent: emi);
746 if (rowCount > 0)
747 endIndex = lastChildIndex(index: m_model->index(row: rowCount - 1, column: 0, parent: emi));
748 }
749 if (endIndex == -1)
750 endIndex = itemIndex(index: emi);
751
752 int destIndex = -1;
753 if (destinationRow == m_model->rowCount(parent: destinationParent)) {
754 const QModelIndex &emi = m_model->index(row: destinationRow - 1, column: 0, parent: destinationParent);
755 destIndex = lastChildIndex(index: emi) + 1;
756 } else {
757 destIndex = itemIndex(index: m_model->index(row: destinationRow, column: 0, parent: destinationParent));
758 }
759
760 int totalMovedCount = endIndex - startIndex + 1;
761
762 /* This beginMoveRows() is matched by a endMoveRows() in the
763 * modelRowsMoved() method below. */
764 m_visibleRowsMoved = startIndex != destIndex &&
765 beginMoveRows(sourceParent: QModelIndex(), sourceFirst: startIndex, sourceLast: endIndex, destinationParent: QModelIndex(), destinationRow: destIndex);
766
767 const QList<TreeItem> &buffer = m_items.mid(pos: startIndex, alength: totalMovedCount);
768 int bufferCopyOffset;
769 if (destIndex > endIndex) {
770 for (int i = endIndex + 1; i < destIndex; i++) {
771 m_items.swapItemsAt(i, j: i - totalMovedCount); // Fast move from 1st to 2nd position
772 }
773 bufferCopyOffset = destIndex - totalMovedCount;
774 } else {
775 // NOTE: we will not enter this loop if startIndex == destIndex
776 for (int i = startIndex - 1; i >= destIndex; i--) {
777 m_items.swapItemsAt(i, j: i + totalMovedCount); // Fast move from 1st to 2nd position
778 }
779 bufferCopyOffset = destIndex;
780 }
781 for (int i = 0; i < buffer.length(); i++) {
782 TreeItem item = buffer.at(i);
783 item.depth += depthDifference;
784 m_items.replace(i: bufferCopyOffset + i, t: item);
785 }
786
787 /* If both source and destination items are visible, the indexes of
788 * all the items in between will change. If they share the same
789 * parent, then this is all; however, if they belong to different
790 * parents, their bottom siblings will also get displaced, so their
791 * index also needs to be updated.
792 * Given that the bottom siblings of the top moved elements are
793 * already included in the update (since they lie between the
794 * source and the dest elements), we only need to worry about the
795 * siblings of the bottom moved element.
796 */
797 const int top = qMin(a: startIndex, b: bufferCopyOffset);
798 int bottom = qMax(a: endIndex, b: bufferCopyOffset + totalMovedCount - 1);
799 if (sourceParent != destinationParent) {
800 const QModelIndex &bottomParent =
801 bottom == endIndex ? sourceParent : destinationParent;
802
803 const int rowCount = m_model->rowCount(parent: bottomParent);
804 if (rowCount > 0)
805 bottom = qMax(a: bottom, b: lastChildIndex(index: m_model->index(row: rowCount - 1, column: 0, parent: bottomParent)));
806 }
807 const QModelIndex &topLeft = index(row: top, column: 0, parent: QModelIndex());
808 const QModelIndex &bottomRight = index(row: bottom, column: 0, parent: QModelIndex());
809 const QVector<int> changedRole(1, ModelIndexRole);
810 queueDataChanged(topLeft, bottomRight, roles: changedRole);
811
812 if (depthDifference != 0) {
813 const QModelIndex &topLeft = index(row: bufferCopyOffset, column: 0, parent: QModelIndex());
814 const QModelIndex &bottomRight = index(row: bufferCopyOffset + totalMovedCount - 1, column: 0, parent: QModelIndex());
815 const QVector<int> changedRole(1, DepthRole);
816 queueDataChanged(topLeft, bottomRight, roles: changedRole);
817 }
818 }
819}
820
821void QQuickTreeModelAdaptor1::modelRowsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
822{
823 if (!childrenVisible(index: sourceParent)) {
824 modelRowsInserted(parent: destinationParent, start: destinationRow, end: destinationRow + sourceEnd - sourceStart);
825 } else if (!childrenVisible(index: destinationParent)) {
826 modelRowsRemoved(parent: sourceParent, start: sourceStart, end: sourceEnd);
827 }
828
829 if (m_visibleRowsMoved)
830 endMoveRows();
831
832 if (isVisible(index: sourceParent) && m_model->rowCount(parent: sourceParent) == 0) {
833 int parentRow = itemIndex(index: sourceParent);
834 collapseRow(n: parentRow);
835 const QModelIndex &topLeft = index(row: parentRow, column: 0, parent: QModelIndex());
836 const QModelIndex &bottomRight = topLeft;
837 const QVector<int> changedRole { ExpandedRole, HasChildrenRole };
838 queueDataChanged(topLeft, bottomRight, roles: changedRole);
839 }
840
841 disableSignalAggregation();
842
843 ASSERT_CONSISTENCY();
844}
845
846void QQuickTreeModelAdaptor1::dump() const
847{
848 if (!m_model)
849 return;
850 int count = m_items.count();
851 if (count == 0)
852 return;
853 int countWidth = floor(x: log10(x: double(count))) + 1;
854 qInfo() << "Dumping" << this;
855 for (int i = 0; i < count; i++) {
856 const TreeItem &item = m_items.at(i);
857 bool hasChildren = m_model->hasChildren(parent: item.index);
858 int children = m_model->rowCount(parent: item.index);
859 qInfo().noquote().nospace()
860 << QString("%1 ").arg(a: i, fieldWidth: countWidth) << QString(4 * item.depth, QChar::fromLatin1(c: '.'))
861 << QLatin1String(!hasChildren ? ".. " : item.expanded ? " v " : " > ")
862 << item.index << children;
863 }
864}
865
866bool QQuickTreeModelAdaptor1::testConsistency(bool dumpOnFail) const
867{
868 if (!m_model) {
869 if (!m_items.isEmpty()) {
870 qWarning() << "Model inconsistency: No model but stored visible items";
871 return false;
872 }
873 if (!m_expandedItems.isEmpty()) {
874 qWarning() << "Model inconsistency: No model but stored expanded items";
875 return false;
876 }
877 return true;
878 }
879 QModelIndex parent = m_rootIndex;
880 QStack<QModelIndex> ancestors;
881 QModelIndex idx = m_model->index(row: 0, column: 0, parent);
882 for (int i = 0; i < m_items.count(); i++) {
883 bool isConsistent = true;
884 const TreeItem &item = m_items.at(i);
885 if (item.index != idx) {
886 qWarning() << "QModelIndex inconsistency" << i << item.index;
887 qWarning() << " expected" << idx;
888 isConsistent = false;
889 }
890 if (item.index.parent() != parent) {
891 qWarning() << "Parent inconsistency" << i << item.index;
892 qWarning() << " stored index parent" << item.index.parent() << "model parent" << parent;
893 isConsistent = false;
894 }
895 if (item.depth != ancestors.count()) {
896 qWarning() << "Depth inconsistency" << i << item.index;
897 qWarning() << " item depth" << item.depth << "ancestors stack" << ancestors.count();
898 isConsistent = false;
899 }
900 if (item.expanded && !m_expandedItems.contains(value: item.index)) {
901 qWarning() << "Expanded inconsistency" << i << item.index;
902 qWarning() << " set" << m_expandedItems.contains(value: item.index) << "item" << item.expanded;
903 isConsistent = false;
904 }
905 if (!isConsistent) {
906 if (dumpOnFail)
907 dump();
908 return false;
909 }
910 QModelIndex firstChildIndex;
911 if (item.expanded)
912 firstChildIndex = m_model->index(row: 0, column: 0, parent: idx);
913 if (firstChildIndex.isValid()) {
914 ancestors.push(t: parent);
915 parent = idx;
916 idx = m_model->index(row: 0, column: 0, parent);
917 } else {
918 while (idx.row() == m_model->rowCount(parent) - 1) {
919 if (ancestors.isEmpty())
920 break;
921 idx = parent;
922 parent = ancestors.pop();
923 }
924 idx = m_model->index(row: idx.row() + 1, column: 0, parent);
925 }
926 }
927
928 return true;
929}
930
931void QQuickTreeModelAdaptor1::enableSignalAggregation() {
932 m_signalAggregatorStack++;
933}
934
935void QQuickTreeModelAdaptor1::disableSignalAggregation() {
936 m_signalAggregatorStack--;
937 Q_ASSERT(m_signalAggregatorStack >= 0);
938 if (m_signalAggregatorStack == 0) {
939 emitQueuedSignals();
940 }
941}
942
943void QQuickTreeModelAdaptor1::queueDataChanged(const QModelIndex &topLeft,
944 const QModelIndex &bottomRight,
945 const QVector<int> &roles)
946{
947 if (isAggregatingSignals()) {
948 m_queuedDataChanged.append(t: DataChangedParams { .topLeft: topLeft, .bottomRight: bottomRight, .roles: roles });
949 } else {
950 emit dataChanged(topLeft, bottomRight, roles);
951 }
952}
953
954void QQuickTreeModelAdaptor1::emitQueuedSignals()
955{
956 QVector<DataChangedParams> combinedUpdates;
957 /* First, iterate through the queued updates and merge the overlapping ones
958 * to reduce the number of updates.
959 * We don't merge adjacent updates, because they are typically filed with a
960 * different role (a parent row is next to its children).
961 */
962 for (const DataChangedParams &dataChange : m_queuedDataChanged) {
963 int startRow = dataChange.topLeft.row();
964 int endRow = dataChange.bottomRight.row();
965 bool merged = false;
966 for (DataChangedParams &combined : combinedUpdates) {
967 int combinedStartRow = combined.topLeft.row();
968 int combinedEndRow = combined.bottomRight.row();
969 if ((startRow <= combinedStartRow && endRow >= combinedStartRow) ||
970 (startRow <= combinedEndRow && endRow >= combinedEndRow)) {
971 if (startRow < combinedStartRow) {
972 combined.topLeft = dataChange.topLeft;
973 }
974 if (endRow > combinedEndRow) {
975 combined.bottomRight = dataChange.bottomRight;
976 }
977 for (int role : dataChange.roles) {
978 if (!combined.roles.contains(t: role))
979 combined.roles.append(t: role);
980 }
981 merged = true;
982 break;
983 }
984 }
985 if (!merged) {
986 combinedUpdates.append(t: dataChange);
987 }
988 }
989
990 /* Finally, emit the dataChanged signals */
991 for (const DataChangedParams &dataChange : combinedUpdates) {
992 emit dataChanged(topLeft: dataChange.topLeft, bottomRight: dataChange.bottomRight, roles: dataChange.roles);
993 }
994 m_queuedDataChanged.clear();
995}
996
997QT_END_NAMESPACE
998

source code of qtquickcontrols/src/controls/Private/qquicktreemodeladaptor.cpp