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 || rowConversionFailed("TreeModel::setRows"_L1))
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 // Convert before validating: validation of incompletely converted rows would fail with a
245 // misleading warning.
246 const QVariant data =
247 row.userType() == QMetaType::QVariantMap
248 ? row
249 : row.value<QJSValue>().toVariant();
250
251 if (rowConversionFailed("TreeModel::appendRow"_L1))
252 return;
253
254 if (!validateNewRow("TreeModel::appendRow"_L1, row))
255 return;
256
257 if (parent.isValid()) {
258 auto *parentRow = static_cast<QQmlTreeRow *>(parent.internalPointer());
259 auto *newChild = new QQmlTreeRow(data);
260
261 beginInsertRows(parent, static_cast<int>(parentRow->rowCount()), static_cast<int>(parentRow->rowCount()));
262 parentRow->addChild(newChild);
263
264 // Gather metadata the first time a row is added.
265 if (mColumnMetadata.isEmpty())
266 fetchColumnMetadata();
267
268 endInsertRows();
269 } else {
270 qmlWarning(this) << "append: could not find any node at the specified index"
271 << " - the new row will be appended to root";
272
273 if (data.canConvert<QVariantMap>()) {
274 beginInsertRows(QModelIndex(),
275 static_cast<int>(mRows.size()),
276 static_cast<int>(mRows.size()));
277
278 mRows.push_back(std::make_unique<QQmlTreeRow>(data));
279
280 // Gather metadata the first time a row is added.
281 if (mColumnMetadata.isEmpty())
282 fetchColumnMetadata();
283
284 endInsertRows();
285 } else {
286 qmlWarning(this) << "Cannot create tree row as the row does not contain "
287 << "key-value pairs";
288 return;
289 }
290 }
291
292 emit rowsChanged();
293}
294
295/*!
296 \qmlmethod void TreeModel::appendRow(var treeRow)
297
298 Appends \a treeRow to the root node.
299
300 \sa setRow(), removeRow()
301*/
302void QQmlTreeModel::appendRow(const QVariant &row)
303{
304 appendRow({}, row);
305}
306
307/*!
308 \qmlmethod void TreeModel::clear()
309
310 Removes all rows from the model.
311
312 \sa removeRow()
313*/
314void QQmlTreeModel::clear()
315{
316 if (mRows.empty())
317 return;
318
319 beginResetModel();
320 mRows.clear();
321 endResetModel();
322 emit rowsChanged();
323}
324
325/*!
326 \qmlmethod var TreeModel::getRow(rowIndex)
327
328 Returns the treeRow at specified index in the model.
329 \a rowIndex is an anonymous QML type backed by \l QModelIndex.
330
331 \note the returned object cannot be used to modify the contents of the
332 model; use setTreeRow() instead.
333
334 \sa setRow(), appendRow(), removeRow()
335*/
336QVariant QQmlTreeModel::getRow(const QModelIndex &rowIndex) const
337{
338 if (rowIndex.isValid())
339 return static_cast<QQmlTreeRow*>(rowIndex.internalPointer())->toVariant();
340
341 qmlWarning(this) << "getRow: could not find any node at the specified index";
342 return {};
343}
344
345/*!
346 \qmlmethod void TreeModel::insertRow(int rowIndex, QModelIndex parent, object row)
347 \since 6.12
348
349 Inserts a new row to the \a parent at position \a rowIndex, with the
350 values (cells) in \a row.
351
352 \code
353 model.insertRow(2, parentIndex, {
354 checkable: true, checked: false,
355 amount: 1,
356 fruitType: "Pear",
357 fruitName: "Williams",
358 fruitPrice: 1.50,
359 })
360 \endcode
361
362 \sa appendRow(), setRow(), removeRow()
363*/
364
365void QQmlTreeModel::insertRow(int rowIndex, QModelIndex parent, const QVariant &row)
366{
367 if (rowIndex < 0) {
368 qmlWarning(this).noquote() << "insertRow(): rowIndex cannot be negative";
369 return;
370 }
371 if (rowIndex > rowCount(parent)) {
372 qmlWarning(this).noquote() << "insertRow(): rowIndex " << rowIndex
373 << " is greater than rowCount() of "
374 << rowCount(parent);
375 return;
376 }
377
378 // Convert here rather than in doInsert(), which is too late to reject the
379 // row, and before validating: validation of incompletely converted rows
380 // would fail with a misleading warning.
381 const QVariant data =
382 row.userType() == QMetaType::QVariantMap
383 ? row
384 : row.value<QJSValue>().toVariant();
385 if (rowConversionFailed("insertRow()"_L1))
386 return;
387
388 if (!validateNewRow("insertRow()"_L1, row))
389 return;
390
391 doInsert(parent, rowIndex, data);
392}
393
394/*!
395 \qmlmethod void TreeModel::insertRow(int rowIndex, object row)
396 \since 6.12
397
398 Inserts a new row to the root item at position \a rowIndex, with the
399 values (cells) in \a row.
400
401 \sa appendRow(), setRow(), removeRow()
402*/
403void QQmlTreeModel::insertRow(int rowIndex, const QVariant &row)
404{
405 insertRow(rowIndex, {}, row);
406}
407
408void QQmlTreeModel::doInsert(const QModelIndex &parent, int rowIndex, const QVariant &row)
409{
410 beginInsertRows(parent, rowIndex, rowIndex);
411
412 const QVariant data =
413 row.userType() == QMetaType::QVariantMap
414 ? row
415 : row.value<QJSValue>().toVariant();
416
417 auto newChild = std::make_unique<QQmlTreeRow>(data);
418 if (parent.isValid())
419 static_cast<QQmlTreeRow *>(parent.internalPointer())->insertChild(rowIndex, std::move(newChild));
420 else
421 mRows.insert(mRows.begin() + rowIndex, std::move(newChild));
422
423 qCDebug(lcTreeModel).nospace() << "inserted the following row to the row "
424 << parent << " at index "
425 << rowIndex << " (child rows omitted):\n"
426 << [&data] { QVariantMap map = data.toMap(); map.remove(ROWS_PROPERTY_NAME); return map; }();
427
428 // Gather metadata the first time a row is added.
429 if (mColumnMetadata.isEmpty())
430 fetchColumnMetadata();
431
432 endInsertRows();
433 emit rowsChanged();
434}
435
436QVariant QQmlTreeModel::firstRow() const
437{
438 if (mRows.empty()) {
439 qmlWarning(this) << "TreeModel::firstRow(): model is empty";
440 return {};
441 }
442
443 return mRows.front().get()->data();
444}
445
446void QQmlTreeModel::setInitialRows()
447{
448 setRowsPrivate(mInitialRows);
449}
450
451/*!
452 \qmlmethod void TreeModel::removeRow(rowIndex)
453
454 Removes the TreeRow referenced by \a rowIndex from the model.
455 \a rowIndex is an anonymous QML type backed by \l QModelIndex.
456
457 \code
458 treeModel.removeTreeRow(rowIndex)
459 \endcode
460
461\sa clear()
462*/
463void QQmlTreeModel::removeRow(QModelIndex rowIndex)
464{
465 if (rowIndex.isValid()) {
466 QModelIndex mIndexParent = rowIndex.parent();
467
468 beginRemoveRows(mIndexParent, rowIndex.row(), rowIndex.row());
469
470 if (mIndexParent.isValid()) {
471 auto *parent = static_cast<QQmlTreeRow *>(mIndexParent.internalPointer());
472 parent->removeChildAt(rowIndex.row());
473 } else {
474 mRows.erase(std::next(mRows.begin(), rowIndex.row()));
475 }
476
477 endRemoveRows();
478 } else {
479 qmlWarning(this) << "TreeModel::removeRow could not find any node at the specified index";
480 return;
481 }
482
483 emit rowsChanged();
484}
485
486// TODO: Turn this into a snippet that compiles in CI
487
488/*!
489 \qmlmethod void TreeModel::setRow(rowIndex, var treeRow)
490
491 Replaces the TreeRow at \a rowIndex in the model with \a treeRow.
492 \a rowIndex is an anonymous QML type back by \l QModelIndex.
493
494 A row with child rows will be rejected.
495
496 All columns/cells must be present in \c treeRow, and in the correct order.
497 The child rows of the row remain unaffected.
498
499 \code
500 treeModel.setRow(rowIndex, {
501 checked: true,
502 size: "-",
503 type: "folder",
504 name: "Subtitles",
505 lastModified: "2025-07-07",
506 iconColor: "blue"
507 });
508 \endcode
509
510 \sa appendRow()
511*/
512void QQmlTreeModel::setRow(QModelIndex rowIndex, const QVariant &rowData)
513{
514 if (!rowIndex.isValid()) {
515 qmlWarning(this) << "TreeModel::setRow: invalid modelIndex";
516 return;
517 }
518
519 const QVariantMap rowAsMap = rowData.toMap();
520 if (rowAsMap.contains(ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList) {
521 qmlWarning(this) << "TreeModel::setRow: child rows are not allowed";
522 return;
523 }
524
525 if (!validateNewRow("TreeModel::setRow"_L1, rowData))
526 return;
527
528 const QVariant rowAsVariant = rowData.userType() == QMetaType::QVariantMap ? rowData : rowData.value<QJSValue>().toVariant();
529 if (rowConversionFailed("TreeModel::setRow"_L1))
530 return;
531
532 auto *row = static_cast<QQmlTreeRow *>(rowIndex.internalPointer());
533 row->setData(rowAsVariant);
534
535 const QModelIndex topLeftModelIndex(createIndex(rowIndex.row(), 0, rowIndex.internalPointer()));
536 const QModelIndex bottomRightModelIndex(createIndex(rowIndex.row(), mColumnCount-1, rowIndex.internalPointer()));
537
538 emit dataChanged(topLeftModelIndex, bottomRightModelIndex);
539 emit rowsChanged();
540}
541
542/*!
543 \qmlmethod QModelIndex TreeModel::index(int row, int column, var parent)
544
545 Returns an object referencing the given \a row and \a column of
546 a given \a parent which can be passed to the data() function to get the data
547 from that cell, or to setData() to edit the contents of that cell.
548
549 The returned object is of an anonymous QML type backed by \l QModelIndex.
550
551 \sa {QModelIndex and related Classes in QML}, data()
552*/
553QModelIndex QQmlTreeModel::index(int row, int column, const QModelIndex &parent) const
554{
555 if (!parent.isValid()){
556 if (static_cast<size_t>(row) >= mRows.size())
557 return {};
558
559 return createIndex(row, column, mRows.at(row).get());
560 }
561
562 const auto *treeRow = static_cast<const QQmlTreeRow *>(parent.internalPointer());
563 if (treeRow->rowCount() <= static_cast<size_t>(row))
564 return {};
565
566 return createIndex(row, column, treeRow->getRow(row));
567}
568
569/*!
570 \qmlmethod QModelIndex TreeModel::index(list<int> treeIndex, int column)
571
572 Returns an object referencing the given \a treeIndex and \a column,
573 which can be passed to the data() function to get the data from that cell,
574 or to setData() to edit the contents of that cell.
575
576 The returned object is of an anonymous QML type backed by \l QModelIndex.
577
578 The first parameter \a treeIndex represents a path of row numbers tracing from
579 the root to the desired row and is used for navigation inside the tree.
580 This is best explained through an example.
581
582 \table
583
584 \row \li \inlineimage treemodel.svg
585 {Tree diagram showing nodes A through F with index paths}
586 \li \list
587
588 \li The root of the tree is special, as it can be referenced by an invalid
589 \l QModelIndex.
590
591 \li Node A is the first child of the root and the corresponding \a treeIndex is \c [0].
592
593 \li Node B is the first child of node A. Since the \a treeIndex of A is \c [0]
594 the \a treeIndex of B will be \c [0,0].
595
596 \li Node C is the second child of A and its \a treeIndex is \c [0,1].
597
598 \li Node D is the third child of A and its \a treeIndex is \c [0,2].
599
600 \li Node E is the second child of the root and its \a treeIndex is \c [1].
601
602 \li Node F is the third child of the root and its \a treeIndex is \c [2].
603
604 \endlist
605
606 \endtable
607
608 With this overload it is possible to obtain a \l QModelIndex to a node without
609 having a \l QModelIndex to its parent node.
610
611 If no node is found by the list specified, an invalid model index is returned.
612 Please note that an invalid model index is referencing the root of the node.
613
614 \sa {QModelIndex and related Classes in QML}, data()
615*/
616QModelIndex QQmlTreeModel::index(const std::vector<int> &treeIndex, int column)
617{
618 QModelIndex mIndex;
619 QQmlTreeRow *row = getPointerToTreeRow(mIndex, treeIndex);
620
621 if (row)
622 return createIndex(treeIndex.back(), column, row);
623
624 qmlWarning(this) << "TreeModel::index: could not find any node at the specified index: "
625 << treeIndex;
626 return {};
627}
628
629QModelIndex QQmlTreeModel::parent(const QModelIndex &index) const
630{
631 if (!index.isValid())
632 return {};
633
634 const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer());
635 const QQmlTreeRow *parentRow = thisRow->parent();
636
637 if (!parentRow) // parent is root
638 return {};
639
640 const QQmlTreeRow *grandparentRow = parentRow->parent();
641
642 if (!grandparentRow) {// grandparent is root, parent is in mRows
643 for (size_t i = 0; i < mRows.size(); i++) {
644 if (mRows[i].get() == parentRow)
645 return createIndex(static_cast<int>(i), 0, parentRow);
646 }
647 Q_UNREACHABLE_RETURN(QModelIndex());
648 }
649
650 for (size_t i = 0; i < grandparentRow->rowCount(); i++) {
651 if (grandparentRow->getRow(static_cast<int>(i)) == parentRow)
652 return createIndex(static_cast<int>(i), 0, parentRow);
653 }
654 Q_UNREACHABLE_RETURN(QModelIndex());
655}
656
657
658int QQmlTreeModel::rowCount(const QModelIndex &parent) const
659{
660 if (!parent.isValid())
661 return static_cast<int>(mRows.size());
662
663 const auto *row = static_cast<const QQmlTreeRow *>(parent.internalPointer());
664 return static_cast<int>(row->rowCount());
665}
666
667/*!
668 \qmlproperty int TreeModel::columnCount
669 \readonly
670
671 This read-only property holds the number of columns in the model.
672
673 The number of columns is fixed for the lifetime of the model
674 after the \l rows property is set or \l appendRow() is called for the first
675 time.
676*/
677int QQmlTreeModel::columnCount(const QModelIndex &parent) const
678{
679 Q_UNUSED(parent);
680
681 return mColumnCount;
682}
683
684/*!
685 \qmlmethod variant TreeModel::data(index, string role)
686
687 Returns the data from the TreeModel at the given \a index belonging to the
688 given \a role.
689
690 \a index is an anonymous QML type backed by \l QModelIndex.
691
692 \sa index(), setData()
693*/
694
695/*!
696 \qmlmethod bool TreeModel::setData(index, variant value, string role)
697
698 Inserts or updates the data field named by \a role in the TreeRow at the
699 given \a index with \a value. Returns true if successful, false if not.
700
701 \a index is an anonymous QML type backed by \l QModelIndex.
702
703 \sa data(), index()
704*/
705
706bool QQmlTreeModel::validateNewRow(QLatin1StringView functionName, const QVariant &row,
707 NewRowOperationFlag operation) const
708{
709 // Validates the row and all its descendants, children before their parent,
710 // in the same order as a recursive traversal would, but without recursing
711 // once per level of the nested rows, which can be arbitrarily deep.
712 struct Frame
713 {
714 QVariant row;
715 NewRowOperationFlag operation;
716 QVariantList children;
717 qsizetype nextChild = 0;
718 };
719
720 std::vector<Frame> pending;
721 const auto push = [&pending](const QVariant &newRow, NewRowOperationFlag newRowOperation) {
722 const bool isVariantMap = (newRow.userType() == QMetaType::QVariantMap);
723 const QVariant rowAsVariant = newRowOperation == SetRowsOperation || isVariantMap
724 ? newRow : newRow.value<QJSValue>().toVariant();
725 const QVariantMap rowAsMap = rowAsVariant.toMap();
726
727 QVariantList children;
728 const auto it = rowAsMap.constFind(ROWS_PROPERTY_NAME);
729 if (it != rowAsMap.cend() && it->userType() == QMetaType::Type::QVariantList)
730 children = it->toList();
731
732 pending.push_back({ newRow, newRowOperation, std::move(children) });
733 };
734
735 push(row, operation);
736 while (!pending.empty()) {
737 Frame &top = pending.back();
738 if (top.nextChild < top.children.size()) {
739 const QVariant child = top.children.at(top.nextChild++);
740 push(child, OtherOperation); // invalidates top
741 continue;
742 }
743
744 if (!QQmlAbstractColumnModel::validateNewRow(functionName, top.row, top.operation))
745 return false;
746 pending.pop_back();
747 }
748
749 return true;
750}
751
752/*
753 QJSValue::toVariant() stops converting when it runs out of stack space: it
754 returns an incomplete result and raises a RangeError on the engine. Rows
755 converted that way must not end up in the model.
756*/
757bool QQmlTreeModel::rowConversionFailed(QLatin1StringView functionName) const
758{
759 const QJSEngine *engine = qjsEngine(this);
760 if (!engine || !engine->hasError())
761 return false;
762
763 qmlWarning(this).nospace() << functionName
764 << ": rows are nested too deeply to be converted";
765 return true;
766}
767
768int QQmlTreeModel::treeSize() const
769{
770 int treeSize = 0;
771
772 for (const auto &treeRow : mRows)
773 treeSize += treeRow->subTreeSize();
774
775 return treeSize;
776}
777
778QQmlTreeRow *QQmlTreeModel::getPointerToTreeRow(QModelIndex &modIndex,
779 const std::vector<int> &rowIndex) const
780{
781 for (int r : rowIndex) {
782 modIndex = index(r, 0, modIndex);
783 if (!modIndex.isValid())
784 return nullptr;
785 }
786
787 return static_cast<QQmlTreeRow*>(modIndex.internalPointer());
788}
789
790/*!
791 \qmlmethod bool TreeModel::moveRows(QModelIndex sourceParent, int sourceRow, int count,
792 QModelIndex destinationParent, int destinationChild)
793 \since 6.12
794 Moves \a count rows starting at \a sourceRow from \a sourceParent to
795 \a destinationParent, inserting before \a destinationChild.
796
797 The \a destinationChild refers to the position in the \a destinationParent
798 \e before the move (pre-removal index), consistent with
799 QAbstractItemModel::beginMoveRows().
800
801 The source and destination ranges must exist, and \a sourceParent and
802 \a destinationParent must not overlap such that a parent would be moved
803 under its own child.
804
805 Returns \c true if the rows were successfully moved; otherwise returns
806 \c false.
807
808 \sa appendRow(), insertRow(), removeRow()
809*/
810bool QQmlTreeModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
811 const QModelIndex &destinationParent, int destinationChild)
812{
813 if (count <= 0)
814 return false;
815
816 // beginMoveRows does not validate bounds against rowCount,
817 // so we must check before calling it.
818 if (sourceRow < 0 || sourceRow + count > rowCount(sourceParent))
819 return false;
820 if (destinationChild < 0 || destinationChild > rowCount(destinationParent))
821 return false;
822
823 if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
824 destinationParent, destinationChild))
825 return false;
826
827 // Root-level children live in mRows on the model, not in a QQmlTreeRow,
828 // so internalPointer() is nullptr for the root QModelIndex.
829 auto takeChild = [this](const QModelIndex &parent, int row) -> std::unique_ptr<QQmlTreeRow> {
830 if (parent.isValid())
831 return static_cast<QQmlTreeRow *>(parent.internalPointer())->takeChild(row);
832 auto it = std::next(mRows.begin(), row);
833 std::unique_ptr<QQmlTreeRow> child = std::move(*it);
834 mRows.erase(it);
835 return child;
836 };
837
838 auto insertChild = [this](const QModelIndex &parent, int row, std::unique_ptr<QQmlTreeRow> child) {
839 if (parent.isValid()) {
840 static_cast<QQmlTreeRow *>(parent.internalPointer())->insertChild(row, std::move(child));
841 } else {
842 child->setParent(nullptr);
843 mRows.insert(std::next(mRows.begin(), row), std::move(child));
844 }
845 };
846
847 std::vector<std::unique_ptr<QQmlTreeRow>> buffer;
848 buffer.reserve(count);
849 for (int i = 0; i < count; ++i)
850 buffer.push_back(takeChild(sourceParent, sourceRow));
851
852 // beginMoveRows uses pre-removal indexing, but after takeChild the
853 // parent's child vector has already shrunk. Adjust for same-parent
854 // backward moves.
855 int insertAt = destinationChild;
856 if (sourceParent == destinationParent && destinationChild > sourceRow)
857 insertAt -= count;
858
859 for (int i = 0; i < count; ++i)
860 insertChild(destinationParent, insertAt + i, std::move(buffer[i]));
861
862 endMoveRows();
863 emit rowsChanged();
864 return true;
865}
866
867QT_END_NAMESPACE
868
869#include "moc_qqmltreemodel_p.cpp"
Combined button and popup list for selecting options.