Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qqmltreemodeltotablemodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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 <math.h>
5#include <QtCore/qstack.h>
6#include <QtCore/qdebug.h>
7
9
11
12//#define QQMLTREEMODELADAPTOR_DEBUG
13#if defined(QQMLTREEMODELADAPTOR_DEBUG) && !defined(QT_TESTLIB_LIB)
14# define ASSERT_CONSISTENCY() Q_ASSERT_X(testConsistency(true /* dumpOnFail */), Q_FUNC_INFO, "Consistency test failed")
15#else
16# define ASSERT_CONSISTENCY qt_noop
17#endif
18
23
25{
26 return m_model;
27}
28
29void QQmlTreeModelToTableModel::connectToModel()
30{
31 m_connections = {
33 this, &QQmlTreeModelToTableModel::modelHasBeenDestroyed),
35 this, &QQmlTreeModelToTableModel::modelHasBeenReset),
37 this, &QQmlTreeModelToTableModel::modelDataChanged),
38
40 this, &QQmlTreeModelToTableModel::modelLayoutAboutToBeChanged),
42 this, &QQmlTreeModelToTableModel::modelLayoutChanged),
43
45 this, &QQmlTreeModelToTableModel::modelRowsAboutToBeInserted),
47 this, &QQmlTreeModelToTableModel::modelRowsInserted),
49 this, &QQmlTreeModelToTableModel::modelRowsAboutToBeRemoved),
51 this, &QQmlTreeModelToTableModel::modelRowsRemoved),
53 this, &QQmlTreeModelToTableModel::modelRowsAboutToBeMoved),
55 this, &QQmlTreeModelToTableModel::modelRowsMoved),
56
58 this, &QQmlTreeModelToTableModel::modelColumnsAboutToBeInserted),
60 this, &QQmlTreeModelToTableModel::modelColumnsAboutToBeRemoved),
62 this, &QQmlTreeModelToTableModel::modelColumnsInserted),
64 this, &QQmlTreeModelToTableModel::modelColumnsRemoved)
65 };
66}
67
69{
70 if (m_model != arg) {
71 if (m_model) {
72 for (const auto &c : m_connections)
74 m_connections.fill({});
75 }
76
78 m_model = arg;
79
80 if (m_rootIndex.isValid() && m_rootIndex.model() != m_model)
81 m_rootIndex = QModelIndex();
82
83 if (m_model) {
84 connectToModel();
86 }
87
89 }
90}
91
93{
95 m_items.clear();
96 m_expandedItems.clear();
98}
99
101{
102 return m_rootIndex;
103}
104
106{
107 if (m_rootIndex == idx)
108 return;
109
110 if (m_model)
112 m_rootIndex = idx;
113 if (m_model)
116}
117
122
127
133
134QHash<int, QByteArray> QQmlTreeModelToTableModel::roleNames() const
135{
136 if (!m_model)
137 return QHash<int, QByteArray>();
138 return m_model->roleNames();
139}
140
142{
143 if (!m_model)
144 return 0;
145 return m_items.size();
146}
147
149{
150 if (!m_model)
151 return 0;
152 return m_model->columnCount(parent);
153}
154
156{
157 if (!m_model)
158 return QVariant();
159
160 return m_model->data(mapToModel(index), role);
161}
162
164{
165 if (!m_model)
166 return false;
167
168 return m_model->setData(mapToModel(index), value, role);
169}
170
171QVariant QQmlTreeModelToTableModel::headerData(int section, Qt::Orientation orientation, int role) const
172{
173 return m_model->headerData(section, orientation, role);
174}
175
177{
178 return m_model->flags(mapToModel(index));
179}
180
182{
183 if (row < 0 || row >= m_items.size())
184 return 0;
185 return m_items.at(row).depth;
186}
187
189{
190 // This is basically a plagiarism of QTreeViewPrivate::viewIndex()
191 if (!index.isValid() || index == m_rootIndex || m_items.isEmpty())
192 return -1;
193
194 const int totalCount = m_items.size();
195
196 // We start nearest to the lastViewedItem
197 int localCount = qMin(m_lastItemIndex - 1, totalCount - m_lastItemIndex);
198
199 for (int i = 0; i < localCount; ++i) {
200 const TreeItem &item1 = m_items.at(m_lastItemIndex + i);
201 if (item1.index == index) {
202 m_lastItemIndex = m_lastItemIndex + i;
203 return m_lastItemIndex;
204 }
205 const TreeItem &item2 = m_items.at(m_lastItemIndex - i - 1);
206 if (item2.index == index) {
207 m_lastItemIndex = m_lastItemIndex - i - 1;
208 return m_lastItemIndex;
209 }
210 }
211
212 for (int j = qMax(0, m_lastItemIndex + localCount); j < totalCount; ++j) {
213 const TreeItem &item = m_items.at(j);
214 if (item.index == index) {
215 m_lastItemIndex = j;
216 return j;
217 }
218 }
219
220 for (int j = qMin(totalCount, m_lastItemIndex - localCount) - 1; j >= 0; --j) {
221 const TreeItem &item = m_items.at(j);
222 if (item.index == index) {
223 m_lastItemIndex = j;
224 return j;
225 }
226 }
227
228 // nothing found
229 return -1;
230}
231
233{
234 return itemIndex(index) != -1;
235}
236
238{
239 return (index == m_rootIndex && !m_items.isEmpty())
240 || (m_expandedItems.contains(index) && isVisible(index));
241}
242
244{
245 if (!index.isValid())
246 return QModelIndex();
247
248 const int row = index.row();
249 if (row < 0 || row > m_items.size() - 1)
250 return QModelIndex();
251
252 const QModelIndex sourceIndex = m_items.at(row).index;
253 return m_model->index(sourceIndex.row(), index.column(), sourceIndex.parent());
254}
255
257{
258 if (!index.isValid())
259 return QModelIndex();
260
261 int row = -1;
262 for (int i = 0; i < m_items.size(); ++i) {
263 const QModelIndex proxyIndex = m_items[i].index;
264 if (proxyIndex.row() == index.row() && proxyIndex.parent() == index.parent()) {
265 row = i;
266 break;
267 }
268 }
269
270 if (row == -1)
271 return QModelIndex();
272
273 return this->index(row, index.column());
274}
275
277{
278 if (row < 0 || row >= m_items.size())
279 return QModelIndex();
280 return m_items.at(row).index;
281}
282
284{
285 int from = itemIndex(fromIndex);
286 int to = itemIndex(toIndex);
287 if (from == -1) {
288 if (to == -1)
289 return QItemSelection();
291 }
292
293 to = qMax(to, 0);
294 if (from > to)
295 qSwap(from, to);
296
297 typedef QPair<QModelIndex, QModelIndex> MIPair;
298 typedef QHash<QModelIndex, MIPair> MI2MIPairHash;
299 MI2MIPairHash ranges;
300 QModelIndex firstIndex = m_items.at(from).index;
301 QModelIndex lastIndex = firstIndex;
302 QModelIndex previousParent = firstIndex.parent();
303 bool selectLastRow = false;
304 for (int i = from + 1; i <= to || (selectLastRow = true); i++) {
305 // We run an extra iteration to make sure the last row is
306 // added to the selection. (And also to avoid duplicating
307 // the insertion code.)
310 if (!selectLastRow) {
311 index = m_items.at(i).index;
312 parent = index.parent();
313 }
314 if (selectLastRow || previousParent != parent) {
315 const MI2MIPairHash::iterator &it = ranges.find(previousParent);
316 if (it == ranges.end())
317 ranges.insert(previousParent, MIPair(firstIndex, lastIndex));
318 else
319 it->second = lastIndex;
320
321 if (selectLastRow)
322 break;
323
324 firstIndex = index;
325 previousParent = parent;
326 }
327 lastIndex = index;
328 }
329
330 QItemSelection sel;
331 sel.reserve(ranges.size());
332 for (const MIPair &pair : std::as_const(ranges))
333 sel.append(QItemSelectionRange(pair.first, pair.second));
334
335 return sel;
336}
337
339{
340 if (!m_model)
341 return;
342
343 if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex))
344 m_model->fetchMore(m_rootIndex);
345 const long topLevelRowCount = m_model->rowCount(m_rootIndex);
346 if (topLevelRowCount == 0)
347 return;
348
349 showModelChildItems(TreeItem(m_rootIndex), 0, topLevelRowCount - 1, doInsertRows);
350}
351
352void QQmlTreeModelToTableModel::showModelChildItems(const TreeItem &parentItem, int start, int end, bool doInsertRows, bool doExpandPendingRows)
353{
354 const QModelIndex &parentIndex = parentItem.index;
355 int rowIdx = parentIndex.isValid() && parentIndex != m_rootIndex ? itemIndex(parentIndex) + 1 : 0;
356 Q_ASSERT(rowIdx == 0 || parentItem.expanded);
357 if (parentIndex.isValid() && parentIndex != m_rootIndex && (rowIdx == 0 || !parentItem.expanded))
358 return;
359
360 if (m_model->rowCount(parentIndex) == 0) {
361 if (m_model->hasChildren(parentIndex) && m_model->canFetchMore(parentIndex))
362 m_model->fetchMore(parentIndex);
363 return;
364 }
365
366 int insertCount = end - start + 1;
367 int startIdx;
368 if (start == 0) {
369 startIdx = rowIdx;
370 } else {
371 // Prefer to insert before next sibling instead of after last child of previous, as
372 // the latter is potentially buggy, see QTBUG-66062
373 const QModelIndex &nextSiblingIdx = m_model->index(end + 1, 0, parentIndex);
374 if (nextSiblingIdx.isValid()) {
375 startIdx = itemIndex(nextSiblingIdx);
376 } else {
377 const QModelIndex &prevSiblingIdx = m_model->index(start - 1, 0, parentIndex);
378 startIdx = lastChildIndex(prevSiblingIdx) + 1;
379 }
380 }
381
382 int rowDepth = rowIdx == 0 ? 0 : parentItem.depth + 1;
383 if (doInsertRows)
384 beginInsertRows(QModelIndex(), startIdx, startIdx + insertCount - 1);
385 m_items.reserve(m_items.size() + insertCount);
386
387 for (int i = 0; i < insertCount; i++) {
388 const QModelIndex &cmi = m_model->index(start + i, 0, parentIndex);
389 const bool expanded = m_expandedItems.contains(cmi);
390 const TreeItem treeItem(cmi, rowDepth, expanded);
391 m_items.insert(startIdx + i, treeItem);
392
393 if (expanded)
394 m_itemsToExpand.append(treeItem);
395 }
396
397 if (doInsertRows)
399
400 if (doExpandPendingRows)
401 expandPendingRows(doInsertRows);
402}
403
404
406{
408 if (!m_model)
409 return;
410
411 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
412
413 if (!idx.isValid() || !m_model->hasChildren(idx))
414 return;
415 if (m_expandedItems.contains(idx))
416 return;
417
418 int row = itemIndex(idx);
419 if (row != -1)
420 expandRow(row);
421 else
422 m_expandedItems.insert(idx);
424
425 emit expanded(idx);
426}
427
429{
431 if (!m_model)
432 return;
433
434 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
435
436 if (!idx.isValid() || !m_model->hasChildren(idx))
437 return;
438 if (!m_expandedItems.contains(idx))
439 return;
440
441 int row = itemIndex(idx);
442 if (row != -1)
444 else
445 m_expandedItems.remove(idx);
447
448 emit collapsed(idx);
449}
450
452{
454 if (!m_model)
455 return false;
456
457 Q_ASSERT(!index.isValid() || index.model() == m_model);
458 return !index.isValid() || m_expandedItems.contains(index);
459}
460
462{
463 if (row < 0 || row >= m_items.size())
464 return false;
465 return m_items.at(row).expanded;
466}
467
469{
470 if (row < 0 || row >= m_items.size())
471 return false;
472 return m_model->hasChildren(m_items[row].index);
473}
474
476{
478 return index.row() != m_model->rowCount(index.parent()) - 1;
479}
480
482{
483 if (!m_model || isExpanded(n))
484 return;
485
486 TreeItem &item = m_items[n];
487 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index))
488 return;
489 item.expanded = true;
490 m_expandedItems.insert(item.index);
491 QVector<int> changedRole(1, ExpandedRole);
492 emit dataChanged(index(n, m_column), index(n, m_column), changedRole);
493
494 m_itemsToExpand.append(item);
496}
497
499{
500 Q_ASSERT(depth == -1 || depth > 0);
501 const int startDepth = depthAtRow(row);
502
503 auto expandHelp = [this, depth, startDepth] (const auto expandHelp, const QModelIndex &index) -> void {
504 const int rowToExpand = itemIndex(index);
505 if (!m_expandedItems.contains(index))
506 expandRow(rowToExpand);
507
508 if (depth != -1 && depthAtRow(rowToExpand) == startDepth + depth - 1)
509 return;
510
511 const int childCount = m_model->rowCount(index);
512 for (int childRow = 0; childRow < childCount; ++childRow) {
513 const QModelIndex childIndex = m_model->index(childRow, 0, index);
514 if (m_model->hasChildren(childIndex))
515 expandHelp(expandHelp, childIndex);
516 }
517 };
518
519 const QModelIndex index = m_items[row].index;
520 if (index.isValid())
521 expandHelp(expandHelp, index);
522}
523
525{
526 while (!m_itemsToExpand.isEmpty()) {
527 const TreeItem item = m_itemsToExpand.takeFirst();
528 Q_ASSERT(item.expanded);
529 const QModelIndex &index = item.index;
530 int childrenCount = m_model->rowCount(index);
531 if (childrenCount == 0) {
532 if (m_model->hasChildren(index) && m_model->canFetchMore(index))
533 m_model->fetchMore(index);
534 continue;
535 }
536
537 // TODO Pre-compute the total number of items made visible
538 // so that we only call a single beginInsertRows()/endInsertRows()
539 // pair per expansion (same as we do for collapsing).
540 showModelChildItems(item, 0, childrenCount - 1, doInsertRows, false);
541 }
542}
543
545{
546 auto collapseHelp = [this] (const auto collapseHelp, const QModelIndex &index) -> void {
547 if (m_expandedItems.contains(index)) {
548 const int rowToCollapse = itemIndex(index);
549 if (rowToCollapse != -1)
550 collapseRow(rowToCollapse);
551 else
552 m_expandedItems.remove(index);
553 }
554
555 const int childCount = m_model->rowCount(index);
556 for (int childRow = 0; childRow < childCount; ++childRow) {
557 const QModelIndex childIndex = m_model->index(childRow, 0, index);
558 if (m_model->hasChildren(childIndex))
559 collapseHelp(collapseHelp, childIndex);
560 }
561 };
562
563 const QModelIndex index = m_items[row].index;
564 if (index.isValid())
565 collapseHelp(collapseHelp, index);
566}
567
569{
570 if (!m_model || !isExpanded(n))
571 return;
572
573 SignalFreezer aggregator(this);
574
575 TreeItem &item = m_items[n];
576 item.expanded = false;
577 m_expandedItems.remove(item.index);
578 QVector<int> changedRole(1, ExpandedRole);
579 queueDataChanged(index(n, m_column), index(n, m_column), changedRole);
580 int childrenCount = m_model->rowCount(item.index);
581 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index) || childrenCount == 0)
582 return;
583
584 const QModelIndex &emi = m_model->index(childrenCount - 1, 0, item.index);
585 int lastIndex = lastChildIndex(emi);
586 removeVisibleRows(n + 1, lastIndex);
587}
588
590{
591 // The purpose of this function is to return the row of the last decendant of a node N.
592 // But note: index should point to the last child of N, and not N itself!
593 // This means that if index is not expanded, the last child will simply be index itself.
594 // Otherwise, since the tree underneath index can be of any depth, it will instead find
595 // the first sibling of N, get its table row, and simply return the row above.
596 if (!m_expandedItems.contains(index))
597 return itemIndex(index);
598
600 QModelIndex nextSiblingIndex;
601 while (parent.isValid()) {
602 nextSiblingIndex = parent.sibling(parent.row() + 1, 0);
603 if (nextSiblingIndex.isValid())
604 break;
605 parent = parent.parent();
606 }
607
608 int firstIndex = nextSiblingIndex.isValid() ? itemIndex(nextSiblingIndex) : m_items.size();
609 return firstIndex - 1;
610}
611
612void QQmlTreeModelToTableModel::removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows)
613{
614 if (startIndex < 0 || endIndex < 0 || startIndex > endIndex)
615 return;
616
617 if (doRemoveRows)
618 beginRemoveRows(QModelIndex(), startIndex, endIndex);
619 m_items.erase(m_items.begin() + startIndex, m_items.begin() + endIndex + 1);
620 if (doRemoveRows) {
622
623 /* We need to update the model index for all the items below the removed ones */
624 int lastIndex = m_items.size() - 1;
625 if (startIndex <= lastIndex) {
626 const QModelIndex &topLeft = index(startIndex, 0, QModelIndex());
627 const QModelIndex &bottomRight = index(lastIndex, 0, QModelIndex());
628 const QVector<int> changedRole(1, ModelIndexRole);
629 queueDataChanged(topLeft, bottomRight, changedRole);
630 }
631 }
632}
633
634void QQmlTreeModelToTableModel::modelHasBeenDestroyed()
635{
636 // The model has been deleted. This should behave as if no model was set
638 emit modelChanged(nullptr);
639}
640
641void QQmlTreeModelToTableModel::modelHasBeenReset()
642{
644
647}
648
649void QQmlTreeModelToTableModel::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
650{
651 Q_ASSERT(topLeft.parent() == bottomRight.parent());
652 const QModelIndex &parent = topLeft.parent();
655 return;
656 }
657
658 int topIndex = itemIndex(topLeft.siblingAtColumn(0));
659 if (topIndex == -1) // 'parent' is not visible anymore, though it's been expanded previously
660 return;
661 for (int i = topLeft.row(); i <= bottomRight.row(); i++) {
662 // Group items with same parent to minize the number of 'dataChanged()' emits
663 int bottomIndex = topIndex;
664 while (bottomIndex < m_items.size()) {
665 const QModelIndex &idx = m_items.at(bottomIndex).index;
666 if (idx.parent() != parent) {
667 --bottomIndex;
668 break;
669 }
670 if (idx.row() == bottomRight.row())
671 break;
672 ++bottomIndex;
673 }
674 emit dataChanged(index(topIndex, topLeft.column()), index(bottomIndex, bottomRight.column()), roles);
675
676 i += bottomIndex - topIndex;
677 if (i == bottomRight.row())
678 break;
679 topIndex = bottomIndex + 1;
680 while (topIndex < m_items.size()
681 && m_items.at(topIndex).index.parent() != parent)
682 topIndex++;
683 }
685}
686
687void QQmlTreeModelToTableModel::modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
688{
690
691 // Since the m_items is a list of TreeItems that contains QPersistentModelIndexes, we
692 // cannot wait until we get a modelLayoutChanged() before we remove the affected rows
693 // from that list. After the layout has changed, the list (or, the persistent indexes
694 // that it contains) is no longer in sync with the model (after all, that is what we're
695 // supposed to correct in modelLayoutChanged()).
696 // This means that vital functions, like itemIndex(index), cannot be trusted at that point.
697 // Therefore we need to do the update in two steps; First remove all the affected rows
698 // from here (while we're still in sync with the model), and then add back the
699 // affected rows, and notify about it, from modelLayoutChanged().
700 m_modelLayoutChanged = false;
701
702 if (parents.isEmpty() || !parents[0].isValid()) {
703 // Update entire model
705 m_modelLayoutChanged = true;
706 m_items.clear();
707 return;
708 }
709
710 for (const QPersistentModelIndex &pmi : parents) {
711 if (!m_expandedItems.contains(pmi))
712 continue;
713 const int row = itemIndex(pmi);
714 if (row == -1)
715 continue;
716 const int rowCount = m_model->rowCount(pmi);
717 if (rowCount == 0)
718 continue;
719
720 if (!m_modelLayoutChanged) {
722 m_modelLayoutChanged = true;
723 }
724
725 const QModelIndex &lmi = m_model->index(rowCount - 1, 0, pmi);
726 const int lastRow = lastChildIndex(lmi);
727 removeVisibleRows(row + 1, lastRow, false /*doRemoveRows*/);
728 }
729
731}
732
733void QQmlTreeModelToTableModel::modelLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
734{
736
737 if (!m_modelLayoutChanged) {
738 // No relevant changes done from modelLayoutAboutToBeChanged()
739 return;
740 }
741
742 if (m_items.isEmpty()) {
743 // Entire model has changed. Add back all rows.
744 showModelTopLevelItems(false /*doInsertRows*/);
745 const QModelIndex &mi = m_model->index(0, 0);
746 const int columnCount = m_model->columnCount(mi);
747 emit dataChanged(index(0, 0), index(m_items.size() - 1, columnCount - 1));
749 return;
750 }
751
752 for (const QPersistentModelIndex &pmi : parents) {
753 if (!m_expandedItems.contains(pmi))
754 continue;
755 const int row = itemIndex(pmi);
756 if (row == -1)
757 continue;
758 const int rowCount = m_model->rowCount(pmi);
759 if (rowCount == 0)
760 continue;
761
762 const QModelIndex &lmi = m_model->index(rowCount - 1, 0, pmi);
763 const int columnCount = m_model->columnCount(lmi);
764 showModelChildItems(m_items.at(row), 0, rowCount - 1, false /*doInsertRows*/);
765 const int lastRow = lastChildIndex(lmi);
766 emit dataChanged(index(row + 1, 0), index(lastRow, columnCount - 1));
767 }
768
770
772}
773
774void QQmlTreeModelToTableModel::modelRowsAboutToBeInserted(const QModelIndex & parent, int start, int end)
775{
780}
781
782void QQmlTreeModelToTableModel::modelRowsInserted(const QModelIndex & parent, int start, int end)
783{
784 TreeItem item;
785 int parentRow = itemIndex(parent);
786 if (parentRow >= 0) {
787 const QModelIndex& parentIndex = index(parentRow, m_column);
788 QVector<int> changedRole(1, HasChildrenRole);
789 queueDataChanged(parentIndex, parentIndex, changedRole);
790 item = m_items.at(parentRow);
791 if (!item.expanded) {
793 return;
794 }
795 } else if (parent == m_rootIndex) {
796 item = TreeItem(parent);
797 } else {
799 return;
800 }
803}
804
805void QQmlTreeModelToTableModel::modelRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end)
806{
808 enableSignalAggregation();
809 if (parent == m_rootIndex || childrenVisible(parent)) {
810 const QModelIndex &smi = m_model->index(start, 0, parent);
811 int startIndex = itemIndex(smi);
812 const QModelIndex &emi = m_model->index(end, 0, parent);
813 int endIndex = -1;
814 if (isExpanded(emi)) {
815 int rowCount = m_model->rowCount(emi);
816 if (rowCount > 0) {
817 const QModelIndex &idx = m_model->index(rowCount - 1, 0, emi);
818 endIndex = lastChildIndex(idx);
819 }
820 }
821 if (endIndex == -1)
822 endIndex = itemIndex(emi);
823
824 removeVisibleRows(startIndex, endIndex);
825 }
826
827 for (int r = start; r <= end; r++) {
828 const QModelIndex &cmi = m_model->index(r, 0, parent);
829 m_expandedItems.remove(cmi);
830 }
831}
832
833void QQmlTreeModelToTableModel::modelRowsRemoved(const QModelIndex & parent, int start, int end)
834{
837 int parentRow = itemIndex(parent);
838 if (parentRow >= 0) {
839 const QModelIndex& parentIndex = index(parentRow, m_column);
840 QVector<int> changedRole(1, HasChildrenRole);
841 queueDataChanged(parentIndex, parentIndex, changedRole);
842 }
843 disableSignalAggregation();
845}
846
847void QQmlTreeModelToTableModel::modelRowsAboutToBeMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
848{
850 enableSignalAggregation();
851 m_visibleRowsMoved = false;
852 if (!childrenVisible(sourceParent))
853 return; // Do nothing now. See modelRowsMoved() below.
854
856 modelRowsAboutToBeRemoved(sourceParent, sourceStart, sourceEnd);
857 /* If the destination parent has no children, we'll need to
858 * report a change on the HasChildrenRole */
859 if (isVisible(destinationParent) && m_model->rowCount(destinationParent) == 0) {
861 const QModelIndex &bottomRight = topLeft;
862 const QVector<int> changedRole(1, HasChildrenRole);
863 queueDataChanged(topLeft, bottomRight, changedRole);
864 }
865 } else {
866 int depthDifference = -1;
868 int destParentIndex = itemIndex(destinationParent);
869 depthDifference = m_items.at(destParentIndex).depth;
870 }
871 if (sourceParent.isValid()) {
872 int sourceParentIndex = itemIndex(sourceParent);
873 depthDifference -= m_items.at(sourceParentIndex).depth;
874 } else {
875 depthDifference++;
876 }
877
878 int startIndex = itemIndex(m_model->index(sourceStart, 0, sourceParent));
879 const QModelIndex &emi = m_model->index(sourceEnd, 0, sourceParent);
880 int endIndex = -1;
881 if (isExpanded(emi)) {
882 int rowCount = m_model->rowCount(emi);
883 if (rowCount > 0)
884 endIndex = lastChildIndex(m_model->index(rowCount - 1, 0, emi));
885 }
886 if (endIndex == -1)
887 endIndex = itemIndex(emi);
888
889 int destIndex = -1;
890 if (destinationRow == m_model->rowCount(destinationParent)) {
891 const QModelIndex &emi = m_model->index(destinationRow - 1, 0, destinationParent);
892 destIndex = lastChildIndex(emi) + 1;
893 } else {
894 destIndex = itemIndex(m_model->index(destinationRow, 0, destinationParent));
895 }
896
897 int totalMovedCount = endIndex - startIndex + 1;
898
899 /* This beginMoveRows() is matched by a endMoveRows() in the
900 * modelRowsMoved() method below. */
901 m_visibleRowsMoved = startIndex != destIndex &&
902 beginMoveRows(QModelIndex(), startIndex, endIndex, QModelIndex(), destIndex);
903
904 const QList<TreeItem> &buffer = m_items.mid(startIndex, totalMovedCount);
905 int bufferCopyOffset;
906 if (destIndex > endIndex) {
907 for (int i = endIndex + 1; i < destIndex; i++) {
908 m_items.swapItemsAt(i, i - totalMovedCount); // Fast move from 1st to 2nd position
909 }
910 bufferCopyOffset = destIndex - totalMovedCount;
911 } else {
912 // NOTE: we will not enter this loop if startIndex == destIndex
913 for (int i = startIndex - 1; i >= destIndex; i--) {
914 m_items.swapItemsAt(i, i + totalMovedCount); // Fast move from 1st to 2nd position
915 }
916 bufferCopyOffset = destIndex;
917 }
918 for (int i = 0; i < buffer.size(); i++) {
919 TreeItem item = buffer.at(i);
920 item.depth += depthDifference;
921 m_items.replace(bufferCopyOffset + i, item);
922 }
923
924 /* If both source and destination items are visible, the indexes of
925 * all the items in between will change. If they share the same
926 * parent, then this is all; however, if they belong to different
927 * parents, their bottom siblings will also get displaced, so their
928 * index also needs to be updated.
929 * Given that the bottom siblings of the top moved elements are
930 * already included in the update (since they lie between the
931 * source and the dest elements), we only need to worry about the
932 * siblings of the bottom moved element.
933 */
934 const int top = qMin(startIndex, bufferCopyOffset);
935 int bottom = qMax(endIndex, bufferCopyOffset + totalMovedCount - 1);
936 if (sourceParent != destinationParent) {
937 const QModelIndex &bottomParent =
938 bottom == endIndex ? sourceParent : destinationParent;
939
940 const int rowCount = m_model->rowCount(bottomParent);
941 if (rowCount > 0)
942 bottom = qMax(bottom, lastChildIndex(m_model->index(rowCount - 1, 0, bottomParent)));
943 }
944 const QModelIndex &topLeft = index(top, 0, QModelIndex());
945 const QModelIndex &bottomRight = index(bottom, 0, QModelIndex());
946 const QVector<int> changedRole(1, ModelIndexRole);
947 queueDataChanged(topLeft, bottomRight, changedRole);
948
949 if (depthDifference != 0) {
950 const QModelIndex &topLeft = index(bufferCopyOffset, 0, QModelIndex());
951 const QModelIndex &bottomRight = index(bufferCopyOffset + totalMovedCount - 1, 0, QModelIndex());
952 const QVector<int> changedRole(1, DepthRole);
953 queueDataChanged(topLeft, bottomRight, changedRole);
954 }
955 }
956}
957
958void QQmlTreeModelToTableModel::modelRowsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
959{
960 if (!childrenVisible(sourceParent)) {
961 modelRowsInserted(destinationParent, destinationRow, destinationRow + sourceEnd - sourceStart);
962 } else if (!childrenVisible(destinationParent)) {
963 modelRowsRemoved(sourceParent, sourceStart, sourceEnd);
964 }
965
966 if (m_visibleRowsMoved)
967 endMoveRows();
968
969 if (isVisible(sourceParent) && m_model->rowCount(sourceParent) == 0) {
970 int parentRow = itemIndex(sourceParent);
971 collapseRow(parentRow);
972 const QModelIndex &topLeft = index(parentRow, 0, QModelIndex());
973 const QModelIndex &bottomRight = topLeft;
974 const QVector<int> changedRole { ExpandedRole, HasChildrenRole };
975 queueDataChanged(topLeft, bottomRight, changedRole);
976 }
977
978 disableSignalAggregation();
979
981}
982
983void QQmlTreeModelToTableModel::modelColumnsAboutToBeInserted(const QModelIndex & parent, int start, int end)
984{
987}
988
989void QQmlTreeModelToTableModel::modelColumnsAboutToBeRemoved(const QModelIndex & parent, int start, int end)
990{
993}
994
995void QQmlTreeModelToTableModel::modelColumnsInserted(const QModelIndex & parent, int start, int end)
996{
999 Q_UNUSED(end);
1001 m_items.clear();
1004}
1005
1006void QQmlTreeModelToTableModel::modelColumnsRemoved(const QModelIndex & parent, int start, int end)
1007{
1009 Q_UNUSED(start);
1010 Q_UNUSED(end);
1012 m_items.clear();
1015}
1016
1018{
1019 if (!m_model)
1020 return;
1021 int count = m_items.size();
1022 if (count == 0)
1023 return;
1024 int countWidth = floor(log10(double(count))) + 1;
1025 qInfo() << "Dumping" << this;
1026 for (int i = 0; i < count; i++) {
1027 const TreeItem &item = m_items.at(i);
1028 bool hasChildren = m_model->hasChildren(item.index);
1029 int children = m_model->rowCount(item.index);
1030 qInfo().noquote().nospace()
1031 << QStringLiteral("%1 ").arg(i, countWidth) << QString(4 * item.depth, QChar::fromLatin1('.'))
1032 << QLatin1String(!hasChildren ? ".. " : item.expanded ? " v " : " > ")
1033 << item.index << children;
1034 }
1035}
1036
1038{
1039 if (!m_model) {
1040 if (!m_items.isEmpty()) {
1041 qWarning() << "Model inconsistency: No model but stored visible items";
1042 return false;
1043 }
1044 if (!m_expandedItems.isEmpty()) {
1045 qWarning() << "Model inconsistency: No model but stored expanded items";
1046 return false;
1047 }
1048 return true;
1049 }
1050 QModelIndex parent = m_rootIndex;
1051 QStack<QModelIndex> ancestors;
1052 QModelIndex idx = m_model->index(0, 0, parent);
1053 for (int i = 0; i < m_items.size(); i++) {
1054 bool isConsistent = true;
1055 const TreeItem &item = m_items.at(i);
1056 if (item.index != idx) {
1057 qWarning() << "QModelIndex inconsistency" << i << item.index;
1058 qWarning() << " expected" << idx;
1059 isConsistent = false;
1060 }
1061 if (item.index.parent() != parent) {
1062 qWarning() << "Parent inconsistency" << i << item.index;
1063 qWarning() << " stored index parent" << item.index.parent() << "model parent" << parent;
1064 isConsistent = false;
1065 }
1066 if (item.depth != ancestors.size()) {
1067 qWarning() << "Depth inconsistency" << i << item.index;
1068 qWarning() << " item depth" << item.depth << "ancestors stack" << ancestors.size();
1069 isConsistent = false;
1070 }
1071 if (item.expanded && !m_expandedItems.contains(item.index)) {
1072 qWarning() << "Expanded inconsistency" << i << item.index;
1073 qWarning() << " set" << m_expandedItems.contains(item.index) << "item" << item.expanded;
1074 isConsistent = false;
1075 }
1076 if (!isConsistent) {
1077 if (dumpOnFail)
1078 dump();
1079 return false;
1080 }
1081 QModelIndex firstChildIndex;
1082 if (item.expanded)
1083 firstChildIndex = m_model->index(0, 0, idx);
1084 if (firstChildIndex.isValid()) {
1085 ancestors.push(parent);
1086 parent = idx;
1087 idx = m_model->index(0, 0, parent);
1088 } else {
1089 while (idx.row() == m_model->rowCount(parent) - 1) {
1090 if (ancestors.isEmpty())
1091 break;
1092 idx = parent;
1093 parent = ancestors.pop();
1094 }
1095 idx = m_model->index(idx.row() + 1, 0, parent);
1096 }
1097 }
1098
1099 return true;
1100}
1101
1102void QQmlTreeModelToTableModel::enableSignalAggregation() {
1103 m_signalAggregatorStack++;
1104}
1105
1106void QQmlTreeModelToTableModel::disableSignalAggregation() {
1107 m_signalAggregatorStack--;
1108 Q_ASSERT(m_signalAggregatorStack >= 0);
1109 if (m_signalAggregatorStack == 0) {
1110 emitQueuedSignals();
1111 }
1112}
1113
1114void QQmlTreeModelToTableModel::queueDataChanged(const QModelIndex &topLeft,
1115 const QModelIndex &bottomRight,
1116 const QVector<int> &roles)
1117{
1118 if (isAggregatingSignals()) {
1119 m_queuedDataChanged.append(DataChangedParams { topLeft, bottomRight, roles });
1120 } else {
1121 emit dataChanged(topLeft, bottomRight, roles);
1122 }
1123}
1124
1125void QQmlTreeModelToTableModel::emitQueuedSignals()
1126{
1127 QVector<DataChangedParams> combinedUpdates;
1128 /* First, iterate through the queued updates and merge the overlapping ones
1129 * to reduce the number of updates.
1130 * We don't merge adjacent updates, because they are typically filed with a
1131 * different role (a parent row is next to its children).
1132 */
1133 for (const DataChangedParams &dataChange : std::as_const(m_queuedDataChanged)) {
1134 int startRow = dataChange.topLeft.row();
1135 int endRow = dataChange.bottomRight.row();
1136 bool merged = false;
1137 for (DataChangedParams &combined : combinedUpdates) {
1138 int combinedStartRow = combined.topLeft.row();
1139 int combinedEndRow = combined.bottomRight.row();
1140 if ((startRow <= combinedStartRow && endRow >= combinedStartRow) ||
1141 (startRow <= combinedEndRow && endRow >= combinedEndRow)) {
1142 if (startRow < combinedStartRow) {
1143 combined.topLeft = dataChange.topLeft;
1144 }
1145 if (endRow > combinedEndRow) {
1146 combined.bottomRight = dataChange.bottomRight;
1147 }
1148 for (int role : dataChange.roles) {
1149 if (!combined.roles.contains(role))
1150 combined.roles.append(role);
1151 }
1152 merged = true;
1153 break;
1154 }
1155 }
1156 if (!merged) {
1157 combinedUpdates.append(dataChange);
1158 }
1159 }
1160
1161 /* Finally, emit the dataChanged signals */
1162 for (const DataChangedParams &dataChange : combinedUpdates) {
1163 emit dataChanged(dataChange.topLeft, dataChange.bottomRight, dataChange.roles);
1164 }
1165 m_queuedDataChanged.clear();
1166}
1167
1169
1170#include "moc_qqmltreemodeltotablemodel_p_p.cpp"
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow, QPrivateSignal)
Q_INVOKABLE int const QModelIndex & parent
Returns the parent of the model item with the given index.
void endResetModel()
Completes a model reset operation.
bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast, const QModelIndex &destinationParent, int destinationRow)
void columnsRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after columns have been removed from the model.
LayoutChangeHint
This enum describes the way the model changes layout.
void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted just before rows are inserted into the model.
virtual Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const
Returns the data for the given role and section in the header with the specified orientation.
void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted just before columns are inserted into the model.
Q_INVOKABLE bool hasIndex(int row, int column, const QModelIndex &parent=QModelIndex()) const
Returns {true} if the model returns a valid QModelIndex for row and column with parent,...
virtual Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const
Returns the item flags for the given index.
void modelReset(QPrivateSignal)
void endRemoveRows()
Ends a row removal operation.
void endMoveRows()
Ends a row move operation.
void beginRemoveColumns(const QModelIndex &parent, int first, int last)
Begins a column removal operation.
Q_INVOKABLE int int const QModelIndex & destinationParent
void layoutAboutToBeChanged(const QList< QPersistentModelIndex > &parents=QList< QPersistentModelIndex >(), QAbstractItemModel::LayoutChangeHint hint=QAbstractItemModel::NoLayoutChangeHint)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles=QList< int >())
This signal is emitted whenever the data in an existing item changes.
virtual Q_INVOKABLE bool hasChildren(const QModelIndex &parent=QModelIndex()) const
Returns {true} if parent has any children; otherwise returns {false}.
virtual Q_INVOKABLE int rowCount(const QModelIndex &parent=QModelIndex()) const =0
Returns the number of rows under the given parent.
virtual Q_INVOKABLE void fetchMore(const QModelIndex &parent)
Fetches any available data for the items with the parent specified by the parent index.
void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted just before columns are removed from the model.
void layoutChanged(const QList< QPersistentModelIndex > &parents=QList< QPersistentModelIndex >(), QAbstractItemModel::LayoutChangeHint hint=QAbstractItemModel::NoLayoutChangeHint)
virtual Q_INVOKABLE bool canFetchMore(const QModelIndex &parent) const
Returns {true} if there is more data available for parent; otherwise returns {false}.
virtual Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole)
Sets the role data for the item at index to value.
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted just before rows are removed from the model.
virtual QHash< int, QByteArray > roleNames() const
void beginInsertColumns(const QModelIndex &parent, int first, int last)
Begins a column insertion operation.
void endInsertRows()
Ends a row insertion operation.
void beginResetModel()
Begins a model reset operation.
void endRemoveColumns()
Ends a column removal operation.
void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow, QPrivateSignal)
virtual Q_INVOKABLE int columnCount(const QModelIndex &parent=QModelIndex()) const =0
Returns the number of columns for the children of the given parent.
void rowsInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been inserted into the model.
QModelIndex createIndex(int row, int column, const void *data=nullptr) const
Creates a model index for the given row and column with the internal pointer ptr.
void endInsertColumns()
Ends a column insertion operation.
void columnsInserted(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after columns have been inserted into the model.
virtual Q_INVOKABLE QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const =0
Returns the data stored under the given role for the item referred to by the index.
void beginRemoveRows(const QModelIndex &parent, int first, int last)
Begins a row removal operation.
virtual Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const =0
Returns the index of the item in the model specified by the given row, column and parent index.
void beginInsertRows(const QModelIndex &parent, int first, int last)
Begins a row insertion operation.
void rowsRemoved(const QModelIndex &parent, int first, int last, QPrivateSignal)
This signal is emitted after rows have been removed from the model.
GraphicsItemFlags flags() const
Returns this item's flags.
\inmodule QtCore
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:889
iterator insert(qsizetype i, parameter_type t)
Definition qlist.h:488
void swapItemsAt(qsizetype i, qsizetype j)
Definition qlist.h:667
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
value_type takeFirst()
Definition qlist.h:566
QList< T > mid(qsizetype pos, qsizetype len=-1) const
Definition qlist.h:975
iterator begin()
Definition qlist.h:625
void reserve(qsizetype size)
Definition qlist.h:753
void replace(qsizetype i, parameter_type t)
Definition qlist.h:543
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
\inmodule QtCore
QModelIndex siblingAtColumn(int column) const
Returns the sibling at column for the current row.
constexpr int row() const noexcept
Returns the row this model index refers to.
QModelIndex parent() const
Returns the parent of the model index, or QModelIndex() if it has no parent.
constexpr const QAbstractItemModel * model() const noexcept
Returns a pointer to the model containing the item that this index refers to.
constexpr int column() const noexcept
Returns the column this model index refers to.
constexpr bool isValid() const noexcept
Returns {true} if this model index is valid; otherwise returns {false}.
QModelIndex sibling(int row, int column) const
Returns the sibling at row and column.
\inmodule QtCore
Definition qobject.h:103
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:201
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
const QAbstractItemModel * model() const
Returns the model that the index belongs to.
bool isValid() const
Returns {true} if this persistent model index is valid; otherwise returns {false}.
void modelChanged(QAbstractItemModel *model)
bool childrenVisible(const QModelIndex &index)
void removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows=true)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
Returns the data for the given role and section in the header with the specified orientation.
QHash< int, QByteArray > roleNames() const override
void setRootIndex(const QModelIndex &idx)
bool isExpanded(const QModelIndex &) const
QModelIndex mapToModel(const QModelIndex &index) const
Qt::ItemFlags flags(const QModelIndex &index) const override
Returns the item flags for the given index.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Returns the index of the item in the model specified by the given row, column and parent index.
void expandPendingRows(bool doInsertRows=true)
bool isVisible(const QModelIndex &index)
void showModelChildItems(const TreeItem &parent, int start, int end, bool doInsertRows=true, bool doExpandPendingRows=true)
Q_INVOKABLE QItemSelection selectionForRowRange(const QModelIndex &fromIndex, const QModelIndex &toIndex) const
bool setData(const QModelIndex &index, const QVariant &value, int role) override
Sets the role data for the item at index to value.
QModelIndex mapFromModel(const QModelIndex &index) const
bool testConsistency(bool dumpOnFail=false) const
QVariant data(const QModelIndex &, int role) const override
Returns the data stored under the given role for the item referred to by the index.
int lastChildIndex(const QModelIndex &index) const
void showModelTopLevelItems(bool doInsertRows=true)
int itemIndex(const QModelIndex &index) const
void expandRecursively(int row, int depth)
void setModel(QAbstractItemModel *model)
void collapsed(const QModelIndex &index)
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of columns for the children of the given parent.
void expanded(const QModelIndex &index)
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Returns the number of rows under the given parent.
QQmlTreeModelToTableModel(QObject *parent=nullptr)
bool remove(const T &value)
Definition qset.h:63
bool isEmpty() const
Definition qset.h:52
void clear()
Definition qset.h:61
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition qvariant.h:65
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Orientation
Definition qnamespace.h:98
@ ItemNeverHasChildren
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qInfo
Definition qlogging.h:165
#define qWarning
Definition qlogging.h:166
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLint GLenum GLsizei GLsizei GLsizei depth
GLuint index
[2]
GLboolean r
[2]
GLuint GLuint end
GLdouble GLdouble GLdouble GLdouble top
GLenum GLenum GLsizei count
GLenum GLuint buffer
GLint GLint bottom
GLuint start
GLfloat n
GLenum GLenum GLsizei void GLsizei void * column
const GLubyte * c
GLenum GLenum GLsizei void * row
#define ASSERT_CONSISTENCY
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int void * arg
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
static QT_BEGIN_NAMESPACE QVariant hint(QPlatformIntegration::StyleHint h)
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
#define emit
#define Q_UNUSED(x)
static uint toIndex(ExecutionEngine *e, const Value &v)
QGraphicsItem * item
edit isVisible()
QLayoutItem * child
[0]