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 | |
45 | QT_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 | |
54 | QQuickTreeModelAdaptor1::QQuickTreeModelAdaptor1(QObject *parent) : |
55 | QAbstractListModel(parent), m_model(0), m_lastItemIndex(0), |
56 | m_visibleRowsMoved(false), m_signalAggregatorStack(0) |
57 | { |
58 | } |
59 | |
60 | QAbstractItemModel *QQuickTreeModelAdaptor1::model() const |
61 | { |
62 | return m_model; |
63 | } |
64 | |
65 | void 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 | |
119 | void QQuickTreeModelAdaptor1::clearModelData() |
120 | { |
121 | beginResetModel(); |
122 | m_items.clear(); |
123 | m_expandedItems.clear(); |
124 | endResetModel(); |
125 | } |
126 | |
127 | const QModelIndex &QQuickTreeModelAdaptor1::rootIndex() const |
128 | { |
129 | return m_rootIndex; |
130 | } |
131 | |
132 | void 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 | |
145 | void QQuickTreeModelAdaptor1::resetRootIndex() |
146 | { |
147 | setRootIndex(QModelIndex()); |
148 | } |
149 | |
150 | QHash<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 | |
164 | int QQuickTreeModelAdaptor1::rowCount(const QModelIndex &) const |
165 | { |
166 | return m_items.count(); |
167 | } |
168 | |
169 | QVariant 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 | |
192 | bool 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 | |
211 | int 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 | |
253 | bool QQuickTreeModelAdaptor1::isVisible(const QModelIndex &index) |
254 | { |
255 | return itemIndex(index) != -1; |
256 | } |
257 | |
258 | bool QQuickTreeModelAdaptor1::childrenVisible(const QModelIndex &index) |
259 | { |
260 | return (index == m_rootIndex && !m_items.isEmpty()) |
261 | || (m_expandedItems.contains(value: index) && isVisible(index)); |
262 | } |
263 | |
264 | const QModelIndex &QQuickTreeModelAdaptor1::mapToModel(const QModelIndex &index) const |
265 | { |
266 | return m_items.at(i: index.row()).index; |
267 | } |
268 | |
269 | QPersistentModelIndex 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 | |
278 | QItemSelection 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 | |
333 | void 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 | |
347 | void 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 | |
396 | void 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 | |
417 | void 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 | |
438 | bool 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 | |
447 | bool QQuickTreeModelAdaptor1::isExpanded(int row) const |
448 | { |
449 | return m_items.at(i: row).expanded; |
450 | } |
451 | |
452 | void 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 | |
469 | void 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 | |
489 | void 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 | |
510 | int 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 | |
528 | void 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 | |
550 | void 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 | |
557 | void QQuickTreeModelAdaptor1::modelHasBeenReset() |
558 | { |
559 | clearModelData(); |
560 | |
561 | showModelTopLevelItems(); |
562 | ASSERT_CONSISTENCY(); |
563 | } |
564 | |
565 | void 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 | |
603 | void QQuickTreeModelAdaptor1::modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint) |
604 | { |
605 | ASSERT_CONSISTENCY(); |
606 | Q_UNUSED(parents); |
607 | Q_UNUSED(hint); |
608 | } |
609 | |
610 | void 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 | |
637 | void 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 | |
645 | void 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 | |
668 | void 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 | |
696 | void 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 | |
710 | void 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 | |
821 | void 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 | |
846 | void 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 | |
866 | bool 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 | |
931 | void QQuickTreeModelAdaptor1::enableSignalAggregation() { |
932 | m_signalAggregatorStack++; |
933 | } |
934 | |
935 | void QQuickTreeModelAdaptor1::disableSignalAggregation() { |
936 | m_signalAggregatorStack--; |
937 | Q_ASSERT(m_signalAggregatorStack >= 0); |
938 | if (m_signalAggregatorStack == 0) { |
939 | emitQueuedSignals(); |
940 | } |
941 | } |
942 | |
943 | void 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 | |
954 | void 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 | |
997 | QT_END_NAMESPACE |
998 | |