Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltreemodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qloggingcategory.h>
9
10#include <QtQml/qqmlinfo.h>
11#include <QtQml/qqmlengine.h>
12
14
15Q_STATIC_LOGGING_CATEGORY(lcTreeModel, "qt.qml.treemodel")
16
17/*!
18 \qmltype TreeModel
19//! \nativetype QQmlTreeModel
20 \inqmlmodule Qt.labs.qmlmodels
21 \brief Encapsulates a simple tree model.
22 \since 6.10
23
24 The TreeModel type stores JavaScript/JSON objects as data for a tree
25 model that can be used with \l TreeView. It is intended to support
26 very simple models without requiring the creation of a custom
27 \l QAbstractItemModel subclass in C++.
28
29 \snippet qml/treemodel/treemodel-filesystem-basic.qml file
30
31 The model's initial data is set with either the \l rows property or by
32 calling \l appendRow(). Each column in the model is specified by declaring
33 a \l TableModelColumn instance, where the order of each instance determines
34 its column index. Once the model's \l Component::completed() signal has been
35 emitted, the columns and roles will have been established and are then
36 fixed for the lifetime of the model.
37
38 \section1 Supported Row Data Structures
39
40 Each row represents a node in the tree. Each node has the same type of
41 columns. The TreeModel is designed to work with JavaScript/JSON data so
42 each row is a list of simple key-value pairs:
43
44 \snippet qml/treemodel/treemodel-filesystem-basic.qml rows
45
46 A node can have child nodes and these will be stored in an array
47 associated with the "rows" key. "rows" is reserved for this purpose: only
48 the list of child nodes should be associated with this key.
49
50 The model is manipulated via \l {QModelIndex} {QModelIndices}. To access
51 a specific row/node, the \l getRow() function can be used. It's also
52 possible to access the model's JavaScript data directly via the \l rows
53 property, but it is not possible to modify the model data this way.
54
55 To add new rows, use \l appendRow(). To modify existing rows, use
56 \l setRow(), \l removeRow() and \l clear().
57
58 \section1 Using the TreeModel with External JSON Sources
59
60 While the intended use of the TreeModel is to define the JSON data in-place in
61 the QML file, you can use any JSON object with the model as long as
62 its structure matches the structure defined by the TreeModel. If all the rows
63 of the JSON object conform to the columns defined by the model, you can
64 assign it directly through the rows property.
65
66 The source of the JSON object can be a file. For example, if the model is
67 defined as
68
69 \snippet setRowsViaJSON.qml model
70
71 and the JSON object stored in the file and looks like
72
73 \snippet TreeData.js file
74
75 then this object can be provided by importing the JavaScript file as a
76
77 module
78
79 \snippet setRowsViaJSON.qml import
80
81 and then it can be assigned directly
82
83 \snippet setRowsViaJSON.qml assignment
84
85 \warning Ensure that the JSON data comes from a trusted source. Since the
86 model dynamically populates its rows based on the input, malformed or
87 untrusted JSON can lead to unexpected behavior or performance issues.
88
89 See \l TableModel for an another example where a JSON object is parsed
90 into and assigned as a JavaScript array.
91
92 \sa TableModelColumn, TreeView
93*/
94
95QQmlTreeModel::QQmlTreeModel(QObject *parent)
96 : QQmlAbstractColumnModel(parent)
97{
98}
99
100QQmlTreeModel::~QQmlTreeModel() = default;
101
102/*!
103 \qmlproperty var TreeModel::rows
104
105 This property holds the model data in the form of an array of rows.
106
107 \sa getRow(), setRow(), appendRow(), clear(), columnCount
108*/
109QVariant QQmlTreeModel::rows() const
110{
111 QVariantList rowsAsVariant;
112 for (const auto &row : mRows)
113 rowsAsVariant.append(row->toVariant());
114
115 return rowsAsVariant;
116}
117
118void QQmlTreeModel::setRows(const QVariant &rows)
119{
120 const std::optional<QVariantList> validated = validateRowsArgument(rows);
121 if (!validated)
122 return;
123
124 const QVariantList rowsAsVariantList = *validated;
125
126 if (!mComponentCompleted) {
127 // Store the rows until we can call setRowsPrivate() after component completion.
128 mInitialRows = rowsAsVariantList;
129 return;
130 }
131
132 setRowsPrivate(rowsAsVariantList);
133}
134
135void QQmlTreeModel::setRowsPrivate(const QVariantList &rowsAsVariantList)
136{
137 Q_ASSERT(mComponentCompleted);
138
139 // By now, all TableModelColumns should have been set.
140 if (mColumns.isEmpty()) {
141 qmlWarning(this) << "No TableModelColumns were set; model will be empty";
142 return;
143 }
144
145 const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty();
146 if (!firstTimeValidRowsHaveBeenSet) {
147 // This is not the first time rows have been set; validate each one.
148 for (const auto &row : rowsAsVariantList) {
149 // validateNewRow() expects a QVariant wrapping a QJSValue, so to
150 // simplify the code, just create one here.
151 const QVariant wrappedRow = QVariant::fromValue(row);
152 if (!validateNewRow("TreeModel::setRows"_L1, wrappedRow, SetRowsOperation))
153 return;
154 }
155 }
156
157 beginResetModel();
158
159 // In case the model is empty and we cannot insert any new rows in the loop below,
160 // we don't want to emit rowsChanged
161 const bool wasEmpty = mRows.empty();
162
163 // We don't clear the column or role data, because a TreeModel should not be reused in that way.
164 // Once it has valid data, its columns and roles are fixed.
165 mRows.clear();
166
167 for (const auto &rowAsVariant : rowsAsVariantList) {
168 if (rowAsVariant.canConvert<QVariantMap>())
169 mRows.push_back(std::make_unique<QQmlTreeRow>(rowAsVariant));
170 else
171 qmlWarning(this) << "Cannot create tree row as the row does not contain "
172 << "key-value pairs";
173 }
174
175 // Gather metadata the first time rows is set.
176 // If we call setrows on an empty model, mInitialRows will be empty, but mRows is not
177 if (firstTimeValidRowsHaveBeenSet && (!mRows.empty() || !mInitialRows.isEmpty()))
178 fetchColumnMetadata();
179
180 endResetModel();
181
182 // was empty, still empty => no emit
183 // was empty, now non-empty => emit
184 // was not empty, now empty => emit
185 // was not empty, now non-empty => emit (there was a clear in-between)
186
187 if (!wasEmpty || !mRows.empty())
188 emit rowsChanged();
189}
190
191QVariant QQmlTreeModel::dataPrivate(const QModelIndex &index, const QString &roleName) const
192{
193 const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column());
194 const QString propertyName = columnMetadata.roles.value(roleName).name;
195 const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer());
196 return thisRow->data(propertyName);
197}
198
199void QQmlTreeModel::setDataPrivate(const QModelIndex &index, const QString &roleName, QVariant value)
200{
201 auto *row = static_cast<QQmlTreeRow *>(index.internalPointer());
202 row->setField(roleName, value);
203}
204
205// TODO: Turn this into a snippet that compiles in CI
206/*!
207 \qmlmethod void TreeModel::appendRow(parent, var treeRow)
208
209 Appends a new treeRow to \a parent, with the values (cells) in \a treeRow.
210
211 \code
212 treeModel.appendRow(index, {
213 checked: false,
214 size: "-",
215 type: "folder",
216 name: "Orders",
217 lastModified: "2025-07-02",
218 rows: [
219 {
220 checked: true,
221 size: "38 KB",
222 type: "file",
223 name: "monitors.xlsx",
224 lastModified: "2025-07-02"
225 },
226 {
227 checked: true,
228 size: "54 KB",
229 type: "file",
230 name: "notebooks.xlsx",
231 lastModified: "2025-07-02"
232 }
233 ]
234 });
235 \endcode
236
237 \a parent is an anonymous QML type backed by \l QModelIndex.
238 If \a parent is invalid, \a treeRow gets appended to the root node.
239
240 \sa setRow(), removeRow()
241*/
242void QQmlTreeModel::appendRow(QModelIndex parent, const QVariant &row)
243{
244 if (!validateNewRow("TreeModel::appendRow"_L1, row))
245 return;
246
247 const QVariant data = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant();
248
249 if (parent.isValid()) {
250 auto *parentRow = static_cast<QQmlTreeRow *>(parent.internalPointer());
251 auto *newChild = new QQmlTreeRow(data);
252
253 beginInsertRows(parent, static_cast<int>(parentRow->rowCount()), static_cast<int>(parentRow->rowCount()));
254 parentRow->addChild(newChild);
255
256 // Gather metadata the first time a row is added.
257 if (mColumnMetadata.isEmpty())
258 fetchColumnMetadata();
259
260 endInsertRows();
261 } else {
262 qmlWarning(this) << "append: could not find any node at the specified index"
263 << " - the new row will be appended to root";
264
265 if (data.canConvert<QVariantMap>()) {
266 beginInsertRows(QModelIndex(),
267 static_cast<int>(mRows.size()),
268 static_cast<int>(mRows.size()));
269
270 mRows.push_back(std::make_unique<QQmlTreeRow>(data));
271
272 // Gather metadata the first time a row is added.
273 if (mColumnMetadata.isEmpty())
274 fetchColumnMetadata();
275
276 endInsertRows();
277 } else {
278 qmlWarning(this) << "Cannot create tree row as the row does not contain "
279 << "key-value pairs";
280 return;
281 }
282 }
283
284 emit rowsChanged();
285}
286
287/*!
288 \qmlmethod void TreeModel::appendRow(var treeRow)
289
290 Appends \a treeRow to the root node.
291
292 \sa setRow(), removeRow()
293*/
294void QQmlTreeModel::appendRow(const QVariant &row)
295{
296 appendRow({}, row);
297}
298
299/*!
300 \qmlmethod void TreeModel::clear()
301
302 Removes all rows from the model.
303
304 \sa removeRow()
305*/
306void QQmlTreeModel::clear()
307{
308 if (mRows.empty())
309 return;
310
311 beginResetModel();
312 mRows.clear();
313 endResetModel();
314 emit rowsChanged();
315}
316
317/*!
318 \qmlmethod var TreeModel::getRow(rowIndex)
319
320 Returns the treeRow at specified index in the model.
321 \a rowIndex is an anonymous QML type backed by \l QModelIndex.
322
323 \note the returned object cannot be used to modify the contents of the
324 model; use setTreeRow() instead.
325
326 \sa setRow(), appendRow(), removeRow()
327*/
328QVariant QQmlTreeModel::getRow(const QModelIndex &rowIndex) const
329{
330 if (rowIndex.isValid())
331 return static_cast<QQmlTreeRow*>(rowIndex.internalPointer())->toVariant();
332
333 qmlWarning(this) << "getRow: could not find any node at the specified index";
334 return {};
335}
336
337/*!
338 \qmlmethod void TreeModel::insertRow(int rowIndex, QModelIndex parent, object row)
339 \since 6.12
340
341 Inserts a new row to the \a parent at position \a rowIndex, with the
342 values (cells) in \a row.
343
344 \code
345 model.insertRow(2, parentIndex, {
346 checkable: true, checked: false,
347 amount: 1,
348 fruitType: "Pear",
349 fruitName: "Williams",
350 fruitPrice: 1.50,
351 })
352 \endcode
353
354 \sa appendRow(), setRow(), removeRow()
355*/
356
357void QQmlTreeModel::insertRow(int rowIndex, QModelIndex parent, const QVariant &row)
358{
359 if (rowIndex < 0) {
360 qmlWarning(this).noquote() << "insertRow(): rowIndex cannot be negative";
361 return;
362 }
363 if (rowIndex > rowCount(parent)) {
364 qmlWarning(this).noquote() << "insertRow(): rowIndex " << rowIndex
365 << " is greater than rowCount() of "
366 << rowCount(parent);
367 return;
368 }
369 if (!validateNewRow("insertRow()"_L1, row))
370 return;
371
372 doInsert(parent, rowIndex, row);
373}
374
375/*!
376 \qmlmethod void TreeModel::insertRow(int rowIndex, object row)
377 \since 6.12
378
379 Inserts a new row to the root item at position \a rowIndex, with the
380 values (cells) in \a row.
381
382 \sa appendRow(), setRow(), removeRow()
383*/
384void QQmlTreeModel::insertRow(int rowIndex, const QVariant &row)
385{
386 insertRow(rowIndex, {}, row);
387}
388
389void QQmlTreeModel::doInsert(const QModelIndex &parent, int rowIndex, const QVariant &row)
390{
391 beginInsertRows(parent, rowIndex, rowIndex);
392
393 const QVariant data =
394 row.userType() == QMetaType::QVariantMap
395 ? row
396 : row.value<QJSValue>().toVariant();
397
398 auto newChild = std::make_unique<QQmlTreeRow>(data);
399 if (parent.isValid())
400 static_cast<QQmlTreeRow *>(parent.internalPointer())->insertChild(rowIndex, std::move(newChild));
401 else
402 mRows.insert(mRows.begin() + rowIndex, std::move(newChild));
403
404 qCDebug(lcTreeModel).nospace() << "inserted the following row to the row "
405 << parent << " at index "
406 << rowIndex << ":\n" << data.toMap();
407
408 // Gather metadata the first time a row is added.
409 if (mColumnMetadata.isEmpty())
410 fetchColumnMetadata();
411
412 endInsertRows();
413 emit rowsChanged();
414}
415
416QVariant QQmlTreeModel::firstRow() const
417{
418 if (mRows.empty()) {
419 qmlWarning(this) << "TreeModel::firstRow(): model is empty";
420 return {};
421 }
422
423 return mRows.front().get()->data();
424}
425
426void QQmlTreeModel::setInitialRows()
427{
428 setRowsPrivate(mInitialRows);
429}
430
431/*!
432 \qmlmethod void TreeModel::removeRow(rowIndex)
433
434 Removes the TreeRow referenced by \a rowIndex from the model.
435 \a rowIndex is an anonymous QML type backed by \l QModelIndex.
436
437 \code
438 treeModel.removeTreeRow(rowIndex)
439 \endcode
440
441\sa clear()
442*/
443void QQmlTreeModel::removeRow(QModelIndex rowIndex)
444{
445 if (rowIndex.isValid()) {
446 QModelIndex mIndexParent = rowIndex.parent();
447
448 beginRemoveRows(mIndexParent, rowIndex.row(), rowIndex.row());
449
450 if (mIndexParent.isValid()) {
451 auto *parent = static_cast<QQmlTreeRow *>(mIndexParent.internalPointer());
452 parent->removeChildAt(rowIndex.row());
453 } else {
454 mRows.erase(std::next(mRows.begin(), rowIndex.row()));
455 }
456
457 endRemoveRows();
458 } else {
459 qmlWarning(this) << "TreeModel::removeRow could not find any node at the specified index";
460 return;
461 }
462
463 emit rowsChanged();
464}
465
466// TODO: Turn this into a snippet that compiles in CI
467
468/*!
469 \qmlmethod void TreeModel::setRow(rowIndex, var treeRow)
470
471 Replaces the TreeRow at \a rowIndex in the model with \a treeRow.
472 \a rowIndex is an anonymous QML type back by \l QModelIndex.
473
474 A row with child rows will be rejected.
475
476 All columns/cells must be present in \c treeRow, and in the correct order.
477 The child rows of the row remain unaffected.
478
479 \code
480 treeModel.setRow(rowIndex, {
481 checked: true,
482 size: "-",
483 type: "folder",
484 name: "Subtitles",
485 lastModified: "2025-07-07",
486 iconColor: "blue"
487 });
488 \endcode
489
490 \sa appendRow()
491*/
492void QQmlTreeModel::setRow(QModelIndex rowIndex, const QVariant &rowData)
493{
494 if (!rowIndex.isValid()) {
495 qmlWarning(this) << "TreeModel::setRow: invalid modelIndex";
496 return;
497 }
498
499 const QVariantMap rowAsMap = rowData.toMap();
500 if (rowAsMap.contains(ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList) {
501 qmlWarning(this) << "TreeModel::setRow: child rows are not allowed";
502 return;
503 }
504
505 if (!validateNewRow("TreeModel::setRow"_L1, rowData))
506 return;
507
508 const QVariant rowAsVariant = rowData.userType() == QMetaType::QVariantMap ? rowData : rowData.value<QJSValue>().toVariant();
509 auto *row = static_cast<QQmlTreeRow *>(rowIndex.internalPointer());
510 row->setData(rowAsVariant);
511
512 const QModelIndex topLeftModelIndex(createIndex(rowIndex.row(), 0, rowIndex.internalPointer()));
513 const QModelIndex bottomRightModelIndex(createIndex(rowIndex.row(), mColumnCount-1, rowIndex.internalPointer()));
514
515 emit dataChanged(topLeftModelIndex, bottomRightModelIndex);
516 emit rowsChanged();
517}
518
519/*!
520 \qmlmethod QModelIndex TreeModel::index(int row, int column, var parent)
521
522 Returns an object referencing the given \a row and \a column of
523 a given \a parent which can be passed to the data() function to get the data
524 from that cell, or to setData() to edit the contents of that cell.
525
526 The returned object is of an anonymous QML type backed by \l QModelIndex.
527
528 \sa {QModelIndex and related Classes in QML}, data()
529*/
530QModelIndex QQmlTreeModel::index(int row, int column, const QModelIndex &parent) const
531{
532 if (!parent.isValid()){
533 if (static_cast<size_t>(row) >= mRows.size())
534 return {};
535
536 return createIndex(row, column, mRows.at(row).get());
537 }
538
539 const auto *treeRow = static_cast<const QQmlTreeRow *>(parent.internalPointer());
540 if (treeRow->rowCount() <= static_cast<size_t>(row))
541 return {};
542
543 return createIndex(row, column, treeRow->getRow(row));
544}
545
546/*!
547 \qmlmethod QModelIndex TreeModel::index(list<int> treeIndex, int column)
548
549 Returns an object referencing the given \a treeIndex and \a column,
550 which can be passed to the data() function to get the data from that cell,
551 or to setData() to edit the contents of that cell.
552
553 The returned object is of an anonymous QML type backed by \l QModelIndex.
554
555 The first parameter \a treeIndex represents a path of row numbers tracing from
556 the root to the desired row and is used for navigation inside the tree.
557 This is best explained through an example.
558
559 \table
560
561 \row \li \inlineimage treemodel.svg
562 {Tree diagram showing nodes A through F with index paths}
563 \li \list
564
565 \li The root of the tree is special, as it can be referenced by an invalid
566 \l QModelIndex.
567
568 \li Node A is the first child of the root and the corresponding \a treeIndex is \c [0].
569
570 \li Node B is the first child of node A. Since the \a treeIndex of A is \c [0]
571 the \a treeIndex of B will be \c [0,0].
572
573 \li Node C is the second child of A and its \a treeIndex is \c [0,1].
574
575 \li Node D is the third child of A and its \a treeIndex is \c [0,2].
576
577 \li Node E is the second child of the root and its \a treeIndex is \c [1].
578
579 \li Node F is the third child of the root and its \a treeIndex is \c [2].
580
581 \endlist
582
583 \endtable
584
585 With this overload it is possible to obtain a \l QModelIndex to a node without
586 having a \l QModelIndex to its parent node.
587
588 If no node is found by the list specified, an invalid model index is returned.
589 Please note that an invalid model index is referencing the root of the node.
590
591 \sa {QModelIndex and related Classes in QML}, data()
592*/
593QModelIndex QQmlTreeModel::index(const std::vector<int> &treeIndex, int column)
594{
595 QModelIndex mIndex;
596 QQmlTreeRow *row = getPointerToTreeRow(mIndex, treeIndex);
597
598 if (row)
599 return createIndex(treeIndex.back(), column, row);
600
601 qmlWarning(this) << "TreeModel::index: could not find any node at the specified index: "
602 << treeIndex;
603 return {};
604}
605
606QModelIndex QQmlTreeModel::parent(const QModelIndex &index) const
607{
608 if (!index.isValid())
609 return {};
610
611 const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer());
612 const QQmlTreeRow *parentRow = thisRow->parent();
613
614 if (!parentRow) // parent is root
615 return {};
616
617 const QQmlTreeRow *grandparentRow = parentRow->parent();
618
619 if (!grandparentRow) {// grandparent is root, parent is in mRows
620 for (size_t i = 0; i < mRows.size(); i++) {
621 if (mRows[i].get() == parentRow)
622 return createIndex(static_cast<int>(i), 0, parentRow);
623 }
624 Q_UNREACHABLE_RETURN(QModelIndex());
625 }
626
627 for (size_t i = 0; i < grandparentRow->rowCount(); i++) {
628 if (grandparentRow->getRow(static_cast<int>(i)) == parentRow)
629 return createIndex(static_cast<int>(i), 0, parentRow);
630 }
631 Q_UNREACHABLE_RETURN(QModelIndex());
632}
633
634
635int QQmlTreeModel::rowCount(const QModelIndex &parent) const
636{
637 if (!parent.isValid())
638 return static_cast<int>(mRows.size());
639
640 const auto *row = static_cast<const QQmlTreeRow *>(parent.internalPointer());
641 return static_cast<int>(row->rowCount());
642}
643
644/*!
645 \qmlproperty int TreeModel::columnCount
646 \readonly
647
648 This read-only property holds the number of columns in the model.
649
650 The number of columns is fixed for the lifetime of the model
651 after the \l rows property is set or \l appendRow() is called for the first
652 time.
653*/
654int QQmlTreeModel::columnCount(const QModelIndex &parent) const
655{
656 Q_UNUSED(parent);
657
658 return mColumnCount;
659}
660
661/*!
662 \qmlmethod variant TreeModel::data(index, string role)
663
664 Returns the data from the TreeModel at the given \a index belonging to the
665 given \a role.
666
667 \a index is an anonymous QML type backed by \l QModelIndex.
668
669 \sa index(), setData()
670*/
671
672/*!
673 \qmlmethod bool TreeModel::setData(index, variant value, string role)
674
675 Inserts or updates the data field named by \a role in the TreeRow at the
676 given \a index with \a value. Returns true if successful, false if not.
677
678 \a index is an anonymous QML type backed by \l QModelIndex.
679
680 \sa data(), index()
681*/
682
683bool QQmlTreeModel::validateNewRow(QLatin1StringView functionName, const QVariant &row,
684 NewRowOperationFlag operation) const
685{
686 const bool isVariantMap = (row.userType() == QMetaType::QVariantMap);
687 const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap
688 ? row : row.value<QJSValue>().toVariant();
689 const QVariantMap rowAsMap = rowAsVariant.toMap();
690 if (rowAsMap.contains(ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList)
691 {
692 const QList<QVariant> variantList = rowAsMap[ROWS_PROPERTY_NAME].toList();
693 for (const QVariant &rowAsVariant : variantList)
694 if (!validateNewRow(functionName, rowAsVariant))
695 return false;
696 }
697
698 return QQmlAbstractColumnModel::validateNewRow(functionName, row, operation);
699}
700
701int QQmlTreeModel::treeSize() const
702{
703 int treeSize = 0;
704
705 for (const auto &treeRow : mRows)
706 treeSize += treeRow->subTreeSize();
707
708 return treeSize;
709}
710
711QQmlTreeRow *QQmlTreeModel::getPointerToTreeRow(QModelIndex &modIndex,
712 const std::vector<int> &rowIndex) const
713{
714 for (int r : rowIndex) {
715 modIndex = index(r, 0, modIndex);
716 if (!modIndex.isValid())
717 return nullptr;
718 }
719
720 return static_cast<QQmlTreeRow*>(modIndex.internalPointer());
721}
722
723/*!
724 \qmlmethod bool TreeModel::moveRows(QModelIndex sourceParent, int sourceRow, int count,
725 QModelIndex destinationParent, int destinationChild)
726 \since 6.12
727 Moves \a count rows starting at \a sourceRow from \a sourceParent to
728 \a destinationParent, inserting before \a destinationChild.
729
730 The \a destinationChild refers to the position in the \a destinationParent
731 \e before the move (pre-removal index), consistent with
732 QAbstractItemModel::beginMoveRows().
733
734 The source and destination ranges must exist, and \a sourceParent and
735 \a destinationParent must not overlap such that a parent would be moved
736 under its own child.
737
738 Returns \c true if the rows were successfully moved; otherwise returns
739 \c false.
740
741 \sa appendRow(), insertRow(), removeRow()
742*/
743bool QQmlTreeModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
744 const QModelIndex &destinationParent, int destinationChild)
745{
746 if (count <= 0)
747 return false;
748
749 // beginMoveRows does not validate bounds against rowCount,
750 // so we must check before calling it.
751 if (sourceRow < 0 || sourceRow + count > rowCount(sourceParent))
752 return false;
753 if (destinationChild < 0 || destinationChild > rowCount(destinationParent))
754 return false;
755
756 if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
757 destinationParent, destinationChild))
758 return false;
759
760 // Root-level children live in mRows on the model, not in a QQmlTreeRow,
761 // so internalPointer() is nullptr for the root QModelIndex.
762 auto takeChild = [this](const QModelIndex &parent, int row) -> std::unique_ptr<QQmlTreeRow> {
763 if (parent.isValid())
764 return static_cast<QQmlTreeRow *>(parent.internalPointer())->takeChild(row);
765 auto it = std::next(mRows.begin(), row);
766 std::unique_ptr<QQmlTreeRow> child = std::move(*it);
767 mRows.erase(it);
768 return child;
769 };
770
771 auto insertChild = [this](const QModelIndex &parent, int row, std::unique_ptr<QQmlTreeRow> child) {
772 if (parent.isValid()) {
773 static_cast<QQmlTreeRow *>(parent.internalPointer())->insertChild(row, std::move(child));
774 } else {
775 child->setParent(nullptr);
776 mRows.insert(std::next(mRows.begin(), row), std::move(child));
777 }
778 };
779
780 std::vector<std::unique_ptr<QQmlTreeRow>> buffer;
781 buffer.reserve(count);
782 for (int i = 0; i < count; ++i)
783 buffer.push_back(takeChild(sourceParent, sourceRow));
784
785 // beginMoveRows uses pre-removal indexing, but after takeChild the
786 // parent's child vector has already shrunk. Adjust for same-parent
787 // backward moves.
788 int insertAt = destinationChild;
789 if (sourceParent == destinationParent && destinationChild > sourceRow)
790 insertAt -= count;
791
792 for (int i = 0; i < count; ++i)
793 insertChild(destinationParent, insertAt + i, std::move(buffer[i]));
794
795 endMoveRows();
796 emit rowsChanged();
797 return true;
798}
799
800QT_END_NAMESPACE
801
802#include "moc_qqmltreemodel_p.cpp"
Combined button and popup list for selecting options.