Qt 6.x
The Qt SDK
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
21{
22}
23
25{
26 return m_model;
27}
28
30{
31 struct Cx {
32 const char *signal;
33 const char *slot;
34 };
35 const Cx connections[] = {
37 SLOT(modelHasBeenDestroyed()) },
38 { SIGNAL(modelReset()),
39 SLOT(modelHasBeenReset()) },
41 SLOT(modelDataChanged(QModelIndex,QModelIndex,QList<int>)) },
42
47
49 SLOT(modelRowsAboutToBeInserted(QModelIndex,int,int)) },
51 SLOT(modelRowsInserted(QModelIndex,int,int)) },
53 SLOT(modelRowsAboutToBeRemoved(QModelIndex,int,int)) },
54 { SIGNAL(rowsRemoved(QModelIndex,int,int)),
55 SLOT(modelRowsRemoved(QModelIndex,int,int)) },
57 SLOT(modelRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)) },
59 SLOT(modelRowsMoved(QModelIndex,int,int,QModelIndex,int)) },
60 { nullptr, nullptr }
61 };
62
63 if (m_model != arg) {
64 if (m_model) {
65 for (const Cx *c = &connections[0]; c->signal; c++)
66 disconnect(m_model, c->signal, this, c->slot);
67 }
68
70 m_model = arg;
71
72 if (m_rootIndex.isValid() && m_rootIndex.model() != m_model)
73 m_rootIndex = QModelIndex();
74
75 if (m_model) {
76 for (const Cx *c = &connections[0]; c->signal; c++)
77 connect(m_model, c->signal, this, c->slot);
78
80 }
81
83 }
84}
85
87{
89 m_items.clear();
90 m_expandedItems.clear();
92}
93
95{
96 return m_rootIndex;
97}
98
100{
101 if (m_rootIndex == idx)
102 return;
103
104 if (m_model)
106 m_rootIndex = idx;
107 if (m_model)
110}
111
113{
115}
116
118{
120}
121
123{
125 return QModelIndex();
126}
127
129{
130 if (!m_model)
131 return QHash<int, QByteArray>();
132 return m_model->roleNames();
133}
134
136{
137 if (!m_model)
138 return 0;
139 return m_items.size();
140}
141
143{
144 if (!m_model)
145 return 0;
146 return m_model->columnCount(parent);
147}
148
150{
151 if (!m_model)
152 return QVariant();
153
154 return m_model->data(mapToModel(index), role);
155}
156
158{
159 if (!m_model)
160 return false;
161
162 return m_model->setData(mapToModel(index), value, role);
163}
164
165QVariant QQmlTreeModelToTableModel::headerData(int section, Qt::Orientation orientation, int role) const
166{
167 return m_model->headerData(section, orientation, role);
168}
169
171{
172 return m_model->flags(mapToModel(index));
173}
174
176{
177 if (row < 0 || row >= m_items.size())
178 return 0;
179 return m_items.at(row).depth;
180}
181
183{
184 // This is basically a plagiarism of QTreeViewPrivate::viewIndex()
185 if (!index.isValid() || index == m_rootIndex || m_items.isEmpty())
186 return -1;
187
188 const int totalCount = m_items.size();
189
190 // We start nearest to the lastViewedItem
191 int localCount = qMin(m_lastItemIndex - 1, totalCount - m_lastItemIndex);
192
193 for (int i = 0; i < localCount; ++i) {
194 const TreeItem &item1 = m_items.at(m_lastItemIndex + i);
195 if (item1.index == index) {
196 m_lastItemIndex = m_lastItemIndex + i;
197 return m_lastItemIndex;
198 }
199 const TreeItem &item2 = m_items.at(m_lastItemIndex - i - 1);
200 if (item2.index == index) {
201 m_lastItemIndex = m_lastItemIndex - i - 1;
202 return m_lastItemIndex;
203 }
204 }
205
206 for (int j = qMax(0, m_lastItemIndex + localCount); j < totalCount; ++j) {
207 const TreeItem &item = m_items.at(j);
208 if (item.index == index) {
209 m_lastItemIndex = j;
210 return j;
211 }
212 }
213
214 for (int j = qMin(totalCount, m_lastItemIndex - localCount) - 1; j >= 0; --j) {
215 const TreeItem &item = m_items.at(j);
216 if (item.index == index) {
217 m_lastItemIndex = j;
218 return j;
219 }
220 }
221
222 // nothing found
223 return -1;
224}
225
227{
228 return itemIndex(index) != -1;
229}
230
232{
233 return (index == m_rootIndex && !m_items.isEmpty())
234 || (m_expandedItems.contains(index) && isVisible(index));
235}
236
238{
239 if (!index.isValid())
240 return QModelIndex();
241
242 const int row = index.row();
243 if (row < 0 || row > m_items.size() - 1)
244 return QModelIndex();
245
246 const QModelIndex sourceIndex = m_items.at(row).index;
247 return m_model->index(sourceIndex.row(), index.column(), sourceIndex.parent());
248}
249
251{
252 if (!index.isValid())
253 return QModelIndex();
254
255 int row = -1;
256 for (int i = 0; i < m_items.size(); ++i) {
257 const QModelIndex proxyIndex = m_items[i].index;
258 if (proxyIndex.row() == index.row() && proxyIndex.parent() == index.parent()) {
259 row = i;
260 break;
261 }
262 }
263
264 if (row == -1)
265 return QModelIndex();
266
267 return this->index(row, index.column());
268}
269
271{
272 if (row < 0 || row >= m_items.size())
273 return QModelIndex();
274 return m_items.at(row).index;
275}
276
278{
279 int from = itemIndex(fromIndex);
280 int to = itemIndex(toIndex);
281 if (from == -1) {
282 if (to == -1)
283 return QItemSelection();
285 }
286
287 to = qMax(to, 0);
288 if (from > to)
289 qSwap(from, to);
290
291 typedef QPair<QModelIndex, QModelIndex> MIPair;
292 typedef QHash<QModelIndex, MIPair> MI2MIPairHash;
293 MI2MIPairHash ranges;
294 QModelIndex firstIndex = m_items.at(from).index;
295 QModelIndex lastIndex = firstIndex;
296 QModelIndex previousParent = firstIndex.parent();
297 bool selectLastRow = false;
298 for (int i = from + 1; i <= to || (selectLastRow = true); i++) {
299 // We run an extra iteration to make sure the last row is
300 // added to the selection. (And also to avoid duplicating
301 // the insertion code.)
304 if (!selectLastRow) {
305 index = m_items.at(i).index;
306 parent = index.parent();
307 }
308 if (selectLastRow || previousParent != parent) {
309 const MI2MIPairHash::iterator &it = ranges.find(previousParent);
310 if (it == ranges.end())
311 ranges.insert(previousParent, MIPair(firstIndex, lastIndex));
312 else
313 it->second = lastIndex;
314
315 if (selectLastRow)
316 break;
317
318 firstIndex = index;
319 previousParent = parent;
320 }
321 lastIndex = index;
322 }
323
324 QItemSelection sel;
325 sel.reserve(ranges.size());
326 for (const MIPair &pair : std::as_const(ranges))
327 sel.append(QItemSelectionRange(pair.first, pair.second));
328
329 return sel;
330}
331
333{
334 if (!m_model)
335 return;
336
337 if (m_model->hasChildren(m_rootIndex) && m_model->canFetchMore(m_rootIndex))
338 m_model->fetchMore(m_rootIndex);
339 const long topLevelRowCount = m_model->rowCount(m_rootIndex);
340 if (topLevelRowCount == 0)
341 return;
342
343 showModelChildItems(TreeItem(m_rootIndex), 0, topLevelRowCount - 1, doInsertRows);
344}
345
346void QQmlTreeModelToTableModel::showModelChildItems(const TreeItem &parentItem, int start, int end, bool doInsertRows, bool doExpandPendingRows)
347{
348 const QModelIndex &parentIndex = parentItem.index;
349 int rowIdx = parentIndex.isValid() && parentIndex != m_rootIndex ? itemIndex(parentIndex) + 1 : 0;
350 Q_ASSERT(rowIdx == 0 || parentItem.expanded);
351 if (parentIndex.isValid() && parentIndex != m_rootIndex && (rowIdx == 0 || !parentItem.expanded))
352 return;
353
354 if (m_model->rowCount(parentIndex) == 0) {
355 if (m_model->hasChildren(parentIndex) && m_model->canFetchMore(parentIndex))
356 m_model->fetchMore(parentIndex);
357 return;
358 }
359
360 int insertCount = end - start + 1;
361 int startIdx;
362 if (start == 0) {
363 startIdx = rowIdx;
364 } else {
365 // Prefer to insert before next sibling instead of after last child of previous, as
366 // the latter is potentially buggy, see QTBUG-66062
367 const QModelIndex &nextSiblingIdx = m_model->index(end + 1, 0, parentIndex);
368 if (nextSiblingIdx.isValid()) {
369 startIdx = itemIndex(nextSiblingIdx);
370 } else {
371 const QModelIndex &prevSiblingIdx = m_model->index(start - 1, 0, parentIndex);
372 startIdx = lastChildIndex(prevSiblingIdx) + 1;
373 }
374 }
375
376 int rowDepth = rowIdx == 0 ? 0 : parentItem.depth + 1;
377 if (doInsertRows)
378 beginInsertRows(QModelIndex(), startIdx, startIdx + insertCount - 1);
379 m_items.reserve(m_items.size() + insertCount);
380
381 for (int i = 0; i < insertCount; i++) {
382 const QModelIndex &cmi = m_model->index(start + i, 0, parentIndex);
383 const bool expanded = m_expandedItems.contains(cmi);
384 const TreeItem treeItem(cmi, rowDepth, expanded);
385 m_items.insert(startIdx + i, treeItem);
386
387 if (expanded)
388 m_itemsToExpand.append(treeItem);
389 }
390
391 if (doInsertRows)
393
394 if (doExpandPendingRows)
395 expandPendingRows(doInsertRows);
396}
397
398
400{
402 if (!m_model)
403 return;
404
405 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
406
407 if (!idx.isValid() || !m_model->hasChildren(idx))
408 return;
409 if (m_expandedItems.contains(idx))
410 return;
411
412 int row = itemIndex(idx);
413 if (row != -1)
414 expandRow(row);
415 else
416 m_expandedItems.insert(idx);
418
419 emit expanded(idx);
420}
421
423{
425 if (!m_model)
426 return;
427
428 Q_ASSERT(!idx.isValid() || idx.model() == m_model);
429
430 if (!idx.isValid() || !m_model->hasChildren(idx))
431 return;
432 if (!m_expandedItems.contains(idx))
433 return;
434
435 int row = itemIndex(idx);
436 if (row != -1)
438 else
439 m_expandedItems.remove(idx);
441
442 emit collapsed(idx);
443}
444
446{
448 if (!m_model)
449 return false;
450
451 Q_ASSERT(!index.isValid() || index.model() == m_model);
452 return !index.isValid() || m_expandedItems.contains(index);
453}
454
456{
457 if (row < 0 || row >= m_items.size())
458 return false;
459 return m_items.at(row).expanded;
460}
461
463{
464 if (row < 0 || row >= m_items.size())
465 return false;
466 return m_model->hasChildren(m_items[row].index);
467}
468
470{
472 return index.row() != m_model->rowCount(index.parent()) - 1;
473}
474
476{
477 if (!m_model || isExpanded(n))
478 return;
479
480 TreeItem &item = m_items[n];
481 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index))
482 return;
483 item.expanded = true;
484 m_expandedItems.insert(item.index);
485 QVector<int> changedRole(1, ExpandedRole);
486 emit dataChanged(index(n, m_column), index(n, m_column), changedRole);
487
488 m_itemsToExpand.append(item);
490}
491
493{
494 Q_ASSERT(depth == -1 || depth > 0);
495 const int startDepth = depthAtRow(row);
496
497 auto expandHelp = [this, depth, startDepth] (const auto expandHelp, const QModelIndex &index) -> void {
498 const int rowToExpand = itemIndex(index);
499 if (!m_expandedItems.contains(index))
500 expandRow(rowToExpand);
501
502 if (depth != -1 && depthAtRow(rowToExpand) == startDepth + depth - 1)
503 return;
504
505 const int childCount = m_model->rowCount(index);
506 for (int childRow = 0; childRow < childCount; ++childRow) {
507 const QModelIndex childIndex = m_model->index(childRow, 0, index);
508 if (m_model->hasChildren(childIndex))
509 expandHelp(expandHelp, childIndex);
510 }
511 };
512
513 const QModelIndex index = m_items[row].index;
514 if (index.isValid())
515 expandHelp(expandHelp, index);
516}
517
519{
520 while (!m_itemsToExpand.isEmpty()) {
521 const TreeItem item = m_itemsToExpand.takeFirst();
522 Q_ASSERT(item.expanded);
523 const QModelIndex &index = item.index;
524 int childrenCount = m_model->rowCount(index);
525 if (childrenCount == 0) {
526 if (m_model->hasChildren(index) && m_model->canFetchMore(index))
527 m_model->fetchMore(index);
528 continue;
529 }
530
531 // TODO Pre-compute the total number of items made visible
532 // so that we only call a single beginInsertRows()/endInsertRows()
533 // pair per expansion (same as we do for collapsing).
534 showModelChildItems(item, 0, childrenCount - 1, doInsertRows, false);
535 }
536}
537
539{
540 auto collapseHelp = [this] (const auto collapseHelp, const QModelIndex &index) -> void {
541 if (m_expandedItems.contains(index)) {
542 const int rowToCollapse = itemIndex(index);
543 if (rowToCollapse != -1)
544 collapseRow(rowToCollapse);
545 else
546 m_expandedItems.remove(index);
547 }
548
549 const int childCount = m_model->rowCount(index);
550 for (int childRow = 0; childRow < childCount; ++childRow) {
551 const QModelIndex childIndex = m_model->index(childRow, 0, index);
552 if (m_model->hasChildren(childIndex))
553 collapseHelp(collapseHelp, childIndex);
554 }
555 };
556
557 const QModelIndex index = m_items[row].index;
558 if (index.isValid())
559 collapseHelp(collapseHelp, index);
560}
561
563{
564 if (!m_model || !isExpanded(n))
565 return;
566
567 SignalFreezer aggregator(this);
568
569 TreeItem &item = m_items[n];
570 item.expanded = false;
571 m_expandedItems.remove(item.index);
572 QVector<int> changedRole(1, ExpandedRole);
573 queueDataChanged(index(n, m_column), index(n, m_column), changedRole);
574 int childrenCount = m_model->rowCount(item.index);
575 if ((item.index.flags() & Qt::ItemNeverHasChildren) || !m_model->hasChildren(item.index) || childrenCount == 0)
576 return;
577
578 const QModelIndex &emi = m_model->index(childrenCount - 1, 0, item.index);
579 int lastIndex = lastChildIndex(emi);
580 removeVisibleRows(n + 1, lastIndex);
581}
582
584{
585 // The purpose of this function is to return the row of the last decendant of a node N.
586 // But note: index should point to the last child of N, and not N itself!
587 // This means that if index is not expanded, the last child will simply be index itself.
588 // Otherwise, since the tree underneath index can be of any depth, it will instead find
589 // the first sibling of N, get its table row, and simply return the row above.
590 if (!m_expandedItems.contains(index))
591 return itemIndex(index);
592
594 QModelIndex nextSiblingIndex;
595 while (parent.isValid()) {
596 nextSiblingIndex = parent.sibling(parent.row() + 1, 0);
597 if (nextSiblingIndex.isValid())
598 break;
599 parent = parent.parent();
600 }
601
602 int firstIndex = nextSiblingIndex.isValid() ? itemIndex(nextSiblingIndex) : m_items.size();
603 return firstIndex - 1;
604}
605
606void QQmlTreeModelToTableModel::removeVisibleRows(int startIndex, int endIndex, bool doRemoveRows)
607{
608 if (startIndex < 0 || endIndex < 0 || startIndex > endIndex)
609 return;
610
611 if (doRemoveRows)
612 beginRemoveRows(QModelIndex(), startIndex, endIndex);
613 m_items.erase(m_items.begin() + startIndex, m_items.begin() + endIndex + 1);
614 if (doRemoveRows) {
616
617 /* We need to update the model index for all the items below the removed ones */
618 int lastIndex = m_items.size() - 1;
619 if (startIndex <= lastIndex) {
620 const QModelIndex &topLeft = index(startIndex, 0, QModelIndex());
621 const QModelIndex &bottomRight = index(lastIndex, 0, QModelIndex());
622 const QVector<int> changedRole(1, ModelIndexRole);
623 queueDataChanged(topLeft, bottomRight, changedRole);
624 }
625 }
626}
627
628void QQmlTreeModelToTableModel::modelHasBeenDestroyed()
629{
630 // The model has been deleted. This should behave as if no model was set
632 emit modelChanged(nullptr);
633}
634
635void QQmlTreeModelToTableModel::modelHasBeenReset()
636{
638
641}
642
643void QQmlTreeModelToTableModel::modelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
644{
645 Q_ASSERT(topLeft.parent() == bottomRight.parent());
646 const QModelIndex &parent = topLeft.parent();
649 return;
650 }
651
652 int topIndex = itemIndex(topLeft.siblingAtColumn(0));
653 if (topIndex == -1) // 'parent' is not visible anymore, though it's been expanded previously
654 return;
655 for (int i = topLeft.row(); i <= bottomRight.row(); i++) {
656 // Group items with same parent to minize the number of 'dataChanged()' emits
657 int bottomIndex = topIndex;
658 while (bottomIndex < m_items.size()) {
659 const QModelIndex &idx = m_items.at(bottomIndex).index;
660 if (idx.parent() != parent) {
661 --bottomIndex;
662 break;
663 }
664 if (idx.row() == bottomRight.row())
665 break;
666 ++bottomIndex;
667 }
668 emit dataChanged(index(topIndex, topLeft.column()), index(bottomIndex, bottomRight.column()), roles);
669
670 i += bottomIndex - topIndex;
671 if (i == bottomRight.row())
672 break;
673 topIndex = bottomIndex + 1;
674 while (topIndex < m_items.size()
675 && m_items.at(topIndex).index.parent() != parent)
676 topIndex++;
677 }
679}
680
681void QQmlTreeModelToTableModel::modelLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
682{
684
685 // Since the m_items is a list of TreeItems that contains QPersistentModelIndexes, we
686 // cannot wait until we get a modelLayoutChanged() before we remove the affected rows
687 // from that list. After the layout has changed, the list (or, the persistent indexes
688 // that it contains) is no longer in sync with the model (after all, that is what we're
689 // supposed to correct in modelLayoutChanged()).
690 // This means that vital functions, like itemIndex(index), cannot be trusted at that point.
691 // Therefore we need to do the update in two steps; First remove all the affected rows
692 // from here (while we're still in sync with the model), and then add back the
693 // affected rows, and notify about it, from modelLayoutChanged().
694 m_modelLayoutChanged = false;
695
696 if (parents.isEmpty() || !parents[0].isValid()) {
697 // Update entire model
699 m_modelLayoutChanged = true;
700 m_items.clear();
701 return;
702 }
703
704 for (const QPersistentModelIndex &pmi : parents) {
705 if (!m_expandedItems.contains(pmi))
706 continue;
707 const int row = itemIndex(pmi);
708 if (row == -1)
709 continue;
710 const int rowCount = m_model->rowCount(pmi);
711 if (rowCount == 0)
712 continue;
713
714 if (!m_modelLayoutChanged) {
716 m_modelLayoutChanged = true;
717 }
718
719 const QModelIndex &lmi = m_model->index(rowCount - 1, 0, pmi);
720 const int lastRow = lastChildIndex(lmi);
721 removeVisibleRows(row + 1, lastRow, false /*doRemoveRows*/);
722 }
723
725}
726
727void QQmlTreeModelToTableModel::modelLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
728{
730
731 if (!m_modelLayoutChanged) {
732 // No relevant changes done from modelLayoutAboutToBeChanged()
733 return;
734 }
735
736 if (m_items.isEmpty()) {
737 // Entire model has changed. Add back all rows.
738 showModelTopLevelItems(false /*doInsertRows*/);
739 const QModelIndex &mi = m_model->index(0, 0);
740 const int columnCount = m_model->columnCount(mi);
741 emit dataChanged(index(0, 0), index(m_items.size() - 1, columnCount - 1));
743 return;
744 }
745
746 for (const QPersistentModelIndex &pmi : parents) {
747 if (!m_expandedItems.contains(pmi))
748 continue;
749 const int row = itemIndex(pmi);
750 if (row == -1)
751 continue;
752 const int rowCount = m_model->rowCount(pmi);
753 if (rowCount == 0)
754 continue;
755
756 const QModelIndex &lmi = m_model->index(rowCount - 1, 0, pmi);
757 const int columnCount = m_model->columnCount(lmi);
758 showModelChildItems(m_items.at(row), 0, rowCount - 1, false /*doInsertRows*/);
759 const int lastRow = lastChildIndex(lmi);
760 emit dataChanged(index(row + 1, 0), index(lastRow, columnCount - 1));
761 }
762
764
766}
767
768void QQmlTreeModelToTableModel::modelRowsAboutToBeInserted(const QModelIndex & parent, int start, int end)
769{
774}
775
776void QQmlTreeModelToTableModel::modelRowsInserted(const QModelIndex & parent, int start, int end)
777{
778 TreeItem item;
779 int parentRow = itemIndex(parent);
780 if (parentRow >= 0) {
781 const QModelIndex& parentIndex = index(parentRow, m_column);
782 QVector<int> changedRole(1, HasChildrenRole);
783 queueDataChanged(parentIndex, parentIndex, changedRole);
784 item = m_items.at(parentRow);
785 if (!item.expanded) {
787 return;
788 }
789 } else if (parent == m_rootIndex) {
790 item = TreeItem(parent);
791 } else {
793 return;
794 }
797}
798
799void QQmlTreeModelToTableModel::modelRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end)
800{
802 enableSignalAggregation();
803 if (parent == m_rootIndex || childrenVisible(parent)) {
804 const QModelIndex &smi = m_model->index(start, 0, parent);
805 int startIndex = itemIndex(smi);
806 const QModelIndex &emi = m_model->index(end, 0, parent);
807 int endIndex = -1;
808 if (isExpanded(emi)) {
809 int rowCount = m_model->rowCount(emi);
810 if (rowCount > 0) {
811 const QModelIndex &idx = m_model->index(rowCount - 1, 0, emi);
812 endIndex = lastChildIndex(idx);
813 }
814 }
815 if (endIndex == -1)
816 endIndex = itemIndex(emi);
817
818 removeVisibleRows(startIndex, endIndex);
819 }
820
821 for (int r = start; r <= end; r++) {
822 const QModelIndex &cmi = m_model->index(r, 0, parent);
823 m_expandedItems.remove(cmi);
824 }
825}
826
827void QQmlTreeModelToTableModel::modelRowsRemoved(const QModelIndex & parent, int start, int end)
828{
831 int parentRow = itemIndex(parent);
832 if (parentRow >= 0) {
833 const QModelIndex& parentIndex = index(parentRow, m_column);
834 QVector<int> changedRole(1, HasChildrenRole);
835 queueDataChanged(parentIndex, parentIndex, changedRole);
836 }
837 disableSignalAggregation();
839}
840
841void QQmlTreeModelToTableModel::modelRowsAboutToBeMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
842{
844 enableSignalAggregation();
845 m_visibleRowsMoved = false;
846 if (!childrenVisible(sourceParent))
847 return; // Do nothing now. See modelRowsMoved() below.
848
850 modelRowsAboutToBeRemoved(sourceParent, sourceStart, sourceEnd);
851 /* If the destination parent has no children, we'll need to
852 * report a change on the HasChildrenRole */
853 if (isVisible(destinationParent) && m_model->rowCount(destinationParent) == 0) {
855 const QModelIndex &bottomRight = topLeft;
856 const QVector<int> changedRole(1, HasChildrenRole);
857 queueDataChanged(topLeft, bottomRight, changedRole);
858 }
859 } else {
860 int depthDifference = -1;
862 int destParentIndex = itemIndex(destinationParent);
863 depthDifference = m_items.at(destParentIndex).depth;
864 }
865 if (sourceParent.isValid()) {
866 int sourceParentIndex = itemIndex(sourceParent);
867 depthDifference -= m_items.at(sourceParentIndex).depth;
868 } else {
869 depthDifference++;
870 }
871
872 int startIndex = itemIndex(m_model->index(sourceStart, 0, sourceParent));
873 const QModelIndex &emi = m_model->index(sourceEnd, 0, sourceParent);
874 int endIndex = -1;
875 if (isExpanded(emi)) {
876 int rowCount = m_model->rowCount(emi);
877 if (rowCount > 0)
878 endIndex = lastChildIndex(m_model->index(rowCount - 1, 0, emi));
879 }
880 if (endIndex == -1)
881 endIndex = itemIndex(emi);
882
883 int destIndex = -1;
884 if (destinationRow == m_model->rowCount(destinationParent)) {
885 const QModelIndex &emi = m_model->index(destinationRow - 1, 0, destinationParent);
886 destIndex = lastChildIndex(emi) + 1;
887 } else {
888 destIndex = itemIndex(m_model->index(destinationRow, 0, destinationParent));
889 }
890
891 int totalMovedCount = endIndex - startIndex + 1;
892
893 /* This beginMoveRows() is matched by a endMoveRows() in the
894 * modelRowsMoved() method below. */
895 m_visibleRowsMoved = startIndex != destIndex &&
896 beginMoveRows(QModelIndex(), startIndex, endIndex, QModelIndex(), destIndex);
897
898 const QList<TreeItem> &buffer = m_items.mid(startIndex, totalMovedCount);
899 int bufferCopyOffset;
900 if (destIndex > endIndex) {
901 for (int i = endIndex + 1; i < destIndex; i++) {
902 m_items.swapItemsAt(i, i - totalMovedCount); // Fast move from 1st to 2nd position
903 }
904 bufferCopyOffset = destIndex - totalMovedCount;
905 } else {
906 // NOTE: we will not enter this loop if startIndex == destIndex
907 for (int i = startIndex - 1; i >= destIndex; i--) {
908 m_items.swapItemsAt(i, i + totalMovedCount); // Fast move from 1st to 2nd position
909 }
910 bufferCopyOffset = destIndex;
911 }
912 for (int i = 0; i < buffer.size(); i++) {
913 TreeItem item = buffer.at(i);
914 item.depth += depthDifference;
915 m_items.replace(bufferCopyOffset + i, item);
916 }
917
918 /* If both source and destination items are visible, the indexes of
919 * all the items in between will change. If they share the same
920 * parent, then this is all; however, if they belong to different
921 * parents, their bottom siblings will also get displaced, so their
922 * index also needs to be updated.
923 * Given that the bottom siblings of the top moved elements are
924 * already included in the update (since they lie between the
925 * source and the dest elements), we only need to worry about the
926 * siblings of the bottom moved element.
927 */
928 const int top = qMin(startIndex, bufferCopyOffset);
929 int bottom = qMax(endIndex, bufferCopyOffset + totalMovedCount - 1);
930 if (sourceParent != destinationParent) {
931 const QModelIndex &bottomParent =
932 bottom == endIndex ? sourceParent : destinationParent;
933
934 const int rowCount = m_model->rowCount(bottomParent);
935 if (rowCount > 0)
936 bottom = qMax(bottom, lastChildIndex(m_model->index(rowCount - 1, 0, bottomParent)));
937 }
938 const QModelIndex &topLeft = index(top, 0, QModelIndex());
939 const QModelIndex &bottomRight = index(bottom, 0, QModelIndex());
940 const QVector<int> changedRole(1, ModelIndexRole);
941 queueDataChanged(topLeft, bottomRight, changedRole);
942
943 if (depthDifference != 0) {
944 const QModelIndex &topLeft = index(bufferCopyOffset, 0, QModelIndex());
945 const QModelIndex &bottomRight = index(bufferCopyOffset + totalMovedCount - 1, 0, QModelIndex());
946 const QVector<int> changedRole(1, DepthRole);
947 queueDataChanged(topLeft, bottomRight, changedRole);
948 }
949 }
950}
951
952void QQmlTreeModelToTableModel::modelRowsMoved(const QModelIndex & sourceParent, int sourceStart, int sourceEnd, const QModelIndex & destinationParent, int destinationRow)
953{
954 if (!childrenVisible(sourceParent)) {
955 modelRowsInserted(destinationParent, destinationRow, destinationRow + sourceEnd - sourceStart);
956 } else if (!childrenVisible(destinationParent)) {
957 modelRowsRemoved(sourceParent, sourceStart, sourceEnd);
958 }
959
960 if (m_visibleRowsMoved)
961 endMoveRows();
962
963 if (isVisible(sourceParent) && m_model->rowCount(sourceParent) == 0) {
964 int parentRow = itemIndex(sourceParent);
965 collapseRow(parentRow);
966 const QModelIndex &topLeft = index(parentRow, 0, QModelIndex());
967 const QModelIndex &bottomRight = topLeft;
968 const QVector<int> changedRole { ExpandedRole, HasChildrenRole };
969 queueDataChanged(topLeft, bottomRight, changedRole);
970 }
971
972 disableSignalAggregation();
973
975}
976
978{
979 if (!m_model)
980 return;
981 int count = m_items.size();
982 if (count == 0)
983 return;
984 int countWidth = floor(log10(double(count))) + 1;
985 qInfo() << "Dumping" << this;
986 for (int i = 0; i < count; i++) {
987 const TreeItem &item = m_items.at(i);
988 bool hasChildren = m_model->hasChildren(item.index);
989 int children = m_model->rowCount(item.index);
990 qInfo().noquote().nospace()
991 << QStringLiteral("%1 ").arg(i, countWidth) << QString(4 * item.depth, QChar::fromLatin1('.'))
992 << QLatin1String(!hasChildren ? ".. " : item.expanded ? " v " : " > ")
993 << item.index << children;
994 }
995}
996
998{
999 if (!m_model) {
1000 if (!m_items.isEmpty()) {
1001 qWarning() << "Model inconsistency: No model but stored visible items";
1002 return false;
1003 }
1004 if (!m_expandedItems.isEmpty()) {
1005 qWarning() << "Model inconsistency: No model but stored expanded items";
1006 return false;
1007 }
1008 return true;
1009 }
1010 QModelIndex parent = m_rootIndex;
1011 QStack<QModelIndex> ancestors;
1012 QModelIndex idx = m_model->index(0, 0, parent);
1013 for (int i = 0; i < m_items.size(); i++) {
1014 bool isConsistent = true;
1015 const TreeItem &item = m_items.at(i);
1016 if (item.index != idx) {
1017 qWarning() << "QModelIndex inconsistency" << i << item.index;
1018 qWarning() << " expected" << idx;
1019 isConsistent = false;
1020 }
1021 if (item.index.parent() != parent) {
1022 qWarning() << "Parent inconsistency" << i << item.index;
1023 qWarning() << " stored index parent" << item.index.parent() << "model parent" << parent;
1024 isConsistent = false;
1025 }
1026 if (item.depth != ancestors.size()) {
1027 qWarning() << "Depth inconsistency" << i << item.index;
1028 qWarning() << " item depth" << item.depth << "ancestors stack" << ancestors.size();
1029 isConsistent = false;
1030 }
1031 if (item.expanded && !m_expandedItems.contains(item.index)) {
1032 qWarning() << "Expanded inconsistency" << i << item.index;
1033 qWarning() << " set" << m_expandedItems.contains(item.index) << "item" << item.expanded;
1034 isConsistent = false;
1035 }
1036 if (!isConsistent) {
1037 if (dumpOnFail)
1038 dump();
1039 return false;
1040 }
1041 QModelIndex firstChildIndex;
1042 if (item.expanded)
1043 firstChildIndex = m_model->index(0, 0, idx);
1044 if (firstChildIndex.isValid()) {
1045 ancestors.push(parent);
1046 parent = idx;
1047 idx = m_model->index(0, 0, parent);
1048 } else {
1049 while (idx.row() == m_model->rowCount(parent) - 1) {
1050 if (ancestors.isEmpty())
1051 break;
1052 idx = parent;
1053 parent = ancestors.pop();
1054 }
1055 idx = m_model->index(idx.row() + 1, 0, parent);
1056 }
1057 }
1058
1059 return true;
1060}
1061
1062void QQmlTreeModelToTableModel::enableSignalAggregation() {
1063 m_signalAggregatorStack++;
1064}
1065
1066void QQmlTreeModelToTableModel::disableSignalAggregation() {
1067 m_signalAggregatorStack--;
1068 Q_ASSERT(m_signalAggregatorStack >= 0);
1069 if (m_signalAggregatorStack == 0) {
1070 emitQueuedSignals();
1071 }
1072}
1073
1074void QQmlTreeModelToTableModel::queueDataChanged(const QModelIndex &topLeft,
1075 const QModelIndex &bottomRight,
1076 const QVector<int> &roles)
1077{
1078 if (isAggregatingSignals()) {
1079 m_queuedDataChanged.append(DataChangedParams { topLeft, bottomRight, roles });
1080 } else {
1081 emit dataChanged(topLeft, bottomRight, roles);
1082 }
1083}
1084
1085void QQmlTreeModelToTableModel::emitQueuedSignals()
1086{
1087 QVector<DataChangedParams> combinedUpdates;
1088 /* First, iterate through the queued updates and merge the overlapping ones
1089 * to reduce the number of updates.
1090 * We don't merge adjacent updates, because they are typically filed with a
1091 * different role (a parent row is next to its children).
1092 */
1093 for (const DataChangedParams &dataChange : std::as_const(m_queuedDataChanged)) {
1094 int startRow = dataChange.topLeft.row();
1095 int endRow = dataChange.bottomRight.row();
1096 bool merged = false;
1097 for (DataChangedParams &combined : combinedUpdates) {
1098 int combinedStartRow = combined.topLeft.row();
1099 int combinedEndRow = combined.bottomRight.row();
1100 if ((startRow <= combinedStartRow && endRow >= combinedStartRow) ||
1101 (startRow <= combinedEndRow && endRow >= combinedEndRow)) {
1102 if (startRow < combinedStartRow) {
1103 combined.topLeft = dataChange.topLeft;
1104 }
1105 if (endRow > combinedEndRow) {
1106 combined.bottomRight = dataChange.bottomRight;
1107 }
1108 for (int role : dataChange.roles) {
1109 if (!combined.roles.contains(role))
1110 combined.roles.append(role);
1111 }
1112 merged = true;
1113 break;
1114 }
1115 }
1116 if (!merged) {
1117 combinedUpdates.append(dataChange);
1118 }
1119 }
1120
1121 /* Finally, emit the dataChanged signals */
1122 for (const DataChangedParams &dataChange : combinedUpdates) {
1123 emit dataChanged(dataChange.topLeft, dataChange.bottomRight, dataChange.roles);
1124 }
1125 m_queuedDataChanged.clear();
1126}
1127
1129
1130#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)
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.
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.
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 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 endInsertRows()
Ends a row insertion operation.
void beginResetModel()
Begins a model reset 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.
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.
static constexpr QChar fromLatin1(char c) noexcept
Converts the Latin-1 character c to its equivalent QChar.
Definition qchar.h:461
GraphicsItemFlags flags() const
Returns this item's flags.
\inmodule QtCore
Definition qhash.h:818
\inmodule QtCore
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:882
iterator insert(qsizetype i, parameter_type t)
Definition qlist.h:471
void swapItemsAt(qsizetype i, qsizetype j)
Definition qlist.h:664
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
value_type takeFirst()
Definition qlist.h:549
QList< T > mid(qsizetype pos, qsizetype len=-1) const
Definition qlist.h:968
iterator begin()
Definition qlist.h:608
void reserve(qsizetype size)
Definition qlist.h:746
void replace(qsizetype i, parameter_type t)
Definition qlist.h:526
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
\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:90
const QObjectList & children() const
Returns a list of child objects.
Definition qobject.h:171
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
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
iterator end()
Definition qset.h:140
bool isEmpty() const
Definition qset.h:52
void clear()
Definition qset.h:61
iterator find(const T &value)
Definition qset.h:159
bool contains(const T &value) const
Definition qset.h:71
iterator insert(const T &value)
Definition qset.h:155
\inmodule QtCore
Definition qstack.h:13
T pop()
Removes the top item from the stack and returns it.
Definition qstack.h:18
void push(const T &t)
Adds element t to the top of the stack.
Definition qstack.h:17
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
\inmodule QtCore
Definition qvariant.h:64
qSwap(pi, e)
QSet< QString >::iterator it
auto signal
Combined button and popup list for selecting options.
Orientation
Definition qnamespace.h:97
@ ItemNeverHasChildren
std::pair< T1, T2 > QPair
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qInfo
Definition qlogging.h:161
#define qWarning
Definition qlogging.h:162
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
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
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)
#define emit
#define Q_UNUSED(x)
static uint toIndex(ExecutionEngine *e, const Value &v)
myObject disconnect()
[26]
QGraphicsItem * item
edit isVisible()
QLayoutItem * child
[0]
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent