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
15using namespace Qt::StringLiterals;
16
17Q_STATIC_LOGGING_CATEGORY(lcTreeModel, "qt.qml.treemodel")
18
19static const QString ROWS_PROPERTY_NAME = u"rows"_s;
20
21/*!
22 \qmltype TreeModel
23//! \nativetype QQmlTreeModel
24 \inqmlmodule Qt.labs.qmlmodels
25 \brief Encapsulates a simple tree model.
26 \since 6.10
27
28 The TreeModel type stores JavaScript/JSON objects as data for a tree
29 model that can be used with \l TreeView. It is intended to support
30 very simple models without requiring the creation of a custom
31 \l QAbstractItemModel subclass in C++.
32
33 \snippet qml/treemodel/treemodel-filesystem-basic.qml file
34
35 The model's initial data is set with either the \l rows property or by
36 calling \l appendRow(). Each column in the model is specified by declaring
37 a \l TableModelColumn instance, where the order of each instance determines
38 its column index. Once the model's \l Component::completed() signal has been
39 emitted, the columns and roles will have been established and are then
40 fixed for the lifetime of the model.
41
42 \section1 Supported Row Data Structures
43
44 Each row represents a node in the tree. Each node has the same type of
45 columns. The TreeModel is designed to work with JavaScript/JSON data so
46 each row is a list of simple key-value pairs:
47
48 \snippet qml/treemodel/treemodel-filesystem-basic.qml rows
49
50 A node can have child nodes and these will be stored in an array
51 associated with the "rows" key. "rows" is reserved for this purpose: only
52 the list of child nodes should be associated with this key.
53
54 The model is manipulated via \l {QModelIndex} {QModelIndices}. To access
55 a specific row/node, the \l getRow() function can be used. It's also
56 possible to access the model's JavaScript data directly via the \l rows
57 property, but it is not possible to modify the model data this way.
58
59 To add new rows, use \l appendRow(). To modify existing rows, use
60 \l setRow(), \l removeRow() and \l clear().
61
62 \section1 Using the TreeModel with External JSON Sources
63
64 While the intended use of the TreeModel is to define the JSON data in-place in
65 the QML file, you can use any JSON object with the model as long as
66 its structure matches the structure defined by the TreeModel. If all the rows
67 of the JSON object conform to the columns defined by the model, you can
68 assign it directly through the rows property.
69
70 The source of the JSON object can be a file. For example, if the model is
71 defined as
72
73 \snippet setRowsViaJSON.qml model
74
75 and the JSON object stored in the file and looks like
76
77 \snippet TreeData.js file
78
79 then this object can be provided by importing the JavaScript file as a
80
81 module
82
83 \snippet setRowsViaJSON.qml import
84
85 and then it can be assigned directly
86
87 \snippet setRowsViaJSON.qml assignment
88
89 \warning Ensure that the JSON data comes from a trusted source. Since the
90 model dinamically populates its rows based on the input, malformed or
91 untrusted JSON can lead to unexpected behavior or performance issues.
92
93 See \l TableModel for an another example where a JSON object is parsed
94 into and assigned as a JavaScript array.
95
96 \sa TableModelColumn, TreeView
97*/
98
99QQmlTreeModel::QQmlTreeModel(QObject *parent)
100 : QQmlAbstractColumnModel(parent)
101{
102}
103
104QQmlTreeModel::~QQmlTreeModel() = default;
105
106/*!
107 \qmlproperty var TreeModel::rows
108
109 This property holds the model data in the form of an array of rows.
110
111 \sa getRow(), setRow(), appendRow(), clear(), columnCount
112*/
113QVariant QQmlTreeModel::rows() const
114{
115 QVariantList rowsAsVariant;
116 for (const auto &row : mRows)
117 rowsAsVariant.append(row->toVariant());
118
119 return rowsAsVariant;
120}
121
122void QQmlTreeModel::setRows(const QVariant &rows)
123{
124 const std::optional<QVariantList> validated = validateRowsArgument(rows);
125 if (!validated)
126 return;
127
128 const QVariantList rowsAsVariantList = *validated;
129
130 if (!mComponentCompleted) {
131 // Store the rows until we can call setRowsPrivate() after component completion.
132 mInitialRows = rowsAsVariantList;
133 return;
134 }
135
136 setRowsPrivate(rowsAsVariantList);
137}
138
139void QQmlTreeModel::setRowsPrivate(const QVariantList &rowsAsVariantList)
140{
141 Q_ASSERT(mComponentCompleted);
142
143 // By now, all TableModelColumns should have been set.
144 if (mColumns.isEmpty()) {
145 qmlWarning(this) << "No TableModelColumns were set; model will be empty";
146 return;
147 }
148
149 const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty();
150 if (!firstTimeValidRowsHaveBeenSet) {
151 // This is not the first time rows have been set; validate each one.
152 for (const auto &row : rowsAsVariantList) {
153 // validateNewRow() expects a QVariant wrapping a QJSValue, so to
154 // simplify the code, just create one here.
155 const QVariant wrappedRow = QVariant::fromValue(row);
156 if (!validateNewRow("TreeModel::setRows"_L1, wrappedRow, SetRowsOperation))
157 return;
158 }
159 }
160
161 beginResetModel();
162
163 // In case the model is empty and we cannot insert any new rows in the loop below,
164 // we don't want to emit rowsChanged
165 const bool wasEmpty = mRows.empty();
166
167 // We don't clear the column or role data, because a TreeModel should not be reused in that way.
168 // Once it has valid data, its columns and roles are fixed.
169 mRows.clear();
170
171 for (const auto &rowAsVariant : rowsAsVariantList) {
172 if (rowAsVariant.canConvert<QVariantMap>())
173 mRows.push_back(std::make_unique<QQmlTreeRow>(rowAsVariant));
174 else
175 qmlWarning(this) << "Cannot create tree row as the row does not contain "
176 << "key-value pairs";
177 }
178
179 // Gather metadata the first time rows is set.
180 // If we call setrows on an empty model, mInitialRows will be empty, but mRows is not
181 if (firstTimeValidRowsHaveBeenSet && (!mRows.empty() || !mInitialRows.isEmpty()))
182 fetchColumnMetadata();
183
184 endResetModel();
185
186 // was empty, still empty => no emit
187 // was empty, now non-empty => emit
188 // was not empty, now empty => emit
189 // was not empty, now non-empty => emit (there was a clear in-between)
190
191 if (!wasEmpty || !mRows.empty())
192 emit rowsChanged();
193}
194
195QVariant QQmlTreeModel::dataPrivate(const QModelIndex &index, const QString &roleName) const
196{
197 const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column());
198 const QString propertyName = columnMetadata.roles.value(roleName).name;
199 const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer());
200 return thisRow->data(propertyName);
201}
202
203void QQmlTreeModel::setDataPrivate(const QModelIndex &index, const QString &roleName, QVariant value)
204{
205 auto *row = static_cast<QQmlTreeRow *>(index.internalPointer());
206 row->setField(roleName, value);
207}
208
209// TODO: Turn this into a snippet that compiles in CI
210/*!
211 \qmlmethod void TreeModel::appendRow(parent, var treeRow)
212
213 Appends a new treeRow to \a parent, with the values (cells) in \a treeRow.
214
215 \code
216 treeModel.appendRow(index, {
217 checked: false,
218 size: "-",
219 type: "folder",
220 name: "Orders",
221 lastModified: "2025-07-02",
222 rows: [
223 {
224 checked: true,
225 size: "38 KB",
226 type: "file",
227 name: "monitors.xlsx",
228 lastModified: "2025-07-02"
229 },
230 {
231 checked: true,
232 size: "54 KB",
233 type: "file",
234 name: "notebooks.xlsx",
235 lastModified: "2025-07-02"
236 }
237 ]
238 });
239 \endcode
240
241 \a parent is an anonymous QML type backed by \l QModelIndex.
242 If \a parent is invalid, \a treeRow gets appended to the root node.
243
244 \sa setRow(), removeRow()
245*/
246void QQmlTreeModel::appendRow(QModelIndex parent, const QVariant &row)
247{
248 if (!validateNewRow("TreeModel::appendRow"_L1, row))
249 return;
250
251 const QVariant data = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant();
252
253 if (parent.isValid()) {
254 auto *parentRow = static_cast<QQmlTreeRow *>(parent.internalPointer());
255 auto *newChild = new QQmlTreeRow(data);
256
257 beginInsertRows(parent, static_cast<int>(parentRow->rowCount()), static_cast<int>(parentRow->rowCount()));
258 parentRow->addChild(newChild);
259
260 // Gather metadata the first time a row is added.
261 if (mColumnMetadata.isEmpty())
262 fetchColumnMetadata();
263
264 endInsertRows();
265 } else {
266 qmlWarning(this) << "append: could not find any node at the specified index"
267 << " - the new row will be appended to root";
268
269 if (data.canConvert<QVariantMap>()) {
270 beginInsertRows(QModelIndex(),
271 static_cast<int>(mRows.size()),
272 static_cast<int>(mRows.size()));
273
274 mRows.push_back(std::make_unique<QQmlTreeRow>(data));
275
276 // Gather metadata the first time a row is added.
277 if (mColumnMetadata.isEmpty())
278 fetchColumnMetadata();
279
280 endInsertRows();
281 } else {
282 qmlWarning(this) << "Cannot create tree row as the row does not contain "
283 << "key-value pairs";
284 return;
285 }
286 }
287
288 emit rowsChanged();
289}
290
291/*!
292 \qmlmethod void TreeModel::appendRow(var treeRow)
293
294 Appends \a treeRow to the root node.
295
296 \sa setRow(), removeRow()
297*/
298void QQmlTreeModel::appendRow(const QVariant &row)
299{
300 appendRow({}, row);
301}
302
303/*!
304 \qmlmethod void TreeModel::clear()
305
306 Removes all rows from the model.
307
308 \sa removeRow()
309*/
310void QQmlTreeModel::clear()
311{
312 QQmlEngine *engine = qmlEngine(this);
313 Q_ASSERT(engine);
314 setRows(QVariant::fromValue(engine->newArray()));
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 = new QQmlTreeRow(data);
399 if (parent.isValid())
400 static_cast<QQmlTreeRow *>(parent.internalPointer())->insertChild(rowIndex, newChild);
401 else
402 mRows.insert(mRows.begin() + rowIndex, std::unique_ptr<QQmlTreeRow>(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 return mRows.front().get()->data();
419}
420
421void QQmlTreeModel::setInitialRows()
422{
423 setRowsPrivate(mInitialRows);
424}
425
426/*!
427 \qmlmethod void TreeModel::removeRow(rowIndex)
428
429 Removes the TreeRow referenced by \a rowIndex from the model.
430 \a rowIndex is an anonymous QML type backed by \l QModelIndex.
431
432 \code
433 treeModel.removeTreeRow(rowIndex)
434 \endcode
435
436\sa clear()
437*/
438void QQmlTreeModel::removeRow(QModelIndex rowIndex)
439{
440 if (rowIndex.isValid()) {
441 QModelIndex mIndexParent = rowIndex.parent();
442
443 beginRemoveRows(mIndexParent, rowIndex.row(), rowIndex.row());
444
445 if (mIndexParent.isValid()) {
446 auto *parent = static_cast<QQmlTreeRow *>(mIndexParent.internalPointer());
447 parent->removeChildAt(rowIndex.row());
448 } else {
449 mRows.erase(std::next(mRows.begin(), rowIndex.row()));
450 }
451
452 endRemoveRows();
453 } else {
454 qmlWarning(this) << "TreeModel::removeRow could not find any node at the specified index";
455 return;
456 }
457
458 emit rowsChanged();
459}
460
461// TODO: Turn this into a snippet that compiles in CI
462
463/*!
464 \qmlmethod void TreeModel::setRow(rowIndex, var treeRow)
465
466 Replaces the TreeRow at \a rowIndex in the model with \a treeRow.
467 \a rowIndex is an anonymous QML type back by \l QModelIndex.
468
469 A row with child rows will be rejected.
470
471 All columns/cells must be present in \c treeRow, and in the correct order.
472 The child rows of the row remain unaffected.
473
474 \code
475 treeModel.setRow(rowIndex, {
476 checked: true,
477 size: "-",
478 type: "folder",
479 name: "Subtitles",
480 lastModified: "2025-07-07",
481 iconColor: "blue"
482 });
483 \endcode
484
485 \sa appendRow()
486*/
487void QQmlTreeModel::setRow(QModelIndex rowIndex, const QVariant &rowData)
488{
489 if (!rowIndex.isValid()) {
490 qmlWarning(this) << "TreeModel::setRow: invalid modelIndex";
491 return;
492 }
493
494 const QVariantMap rowAsMap = rowData.toMap();
495 if (rowAsMap.contains(ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList) {
496 qmlWarning(this) << "TreeModel::setRow: child rows are not allowed";
497 return;
498 }
499
500 if (!validateNewRow("TreeModel::setRow"_L1, rowData))
501 return;
502
503 const QVariant rowAsVariant = rowData.userType() == QMetaType::QVariantMap ? rowData : rowData.value<QJSValue>().toVariant();
504 auto *row = static_cast<QQmlTreeRow *>(rowIndex.internalPointer());
505 row->setData(rowAsVariant);
506
507 const QModelIndex topLeftModelIndex(createIndex(rowIndex.row(), 0, rowIndex.internalPointer()));
508 const QModelIndex bottomRightModelIndex(createIndex(rowIndex.row(), mColumnCount-1, rowIndex.internalPointer()));
509
510 emit dataChanged(topLeftModelIndex, bottomRightModelIndex);
511 emit rowsChanged();
512}
513
514/*!
515 \qmlmethod QModelIndex TreeModel::index(int row, int column, var parent)
516
517 Returns an object referencing the given \a row and \a column of
518 a given \a parent which can be passed to the data() function to get the data
519 from that cell, or to setData() to edit the contents of that cell.
520
521 The returned object is of an anonymous QML type backed by \l QModelIndex.
522
523 \sa {QModelIndex and related Classes in QML}, data()
524*/
525QModelIndex QQmlTreeModel::index(int row, int column, const QModelIndex &parent) const
526{
527 if (!parent.isValid()){
528 if (static_cast<size_t>(row) >= mRows.size())
529 return {};
530
531 return createIndex(row, column, mRows.at(row).get());
532 }
533
534 const auto *treeRow = static_cast<const QQmlTreeRow *>(parent.internalPointer());
535 if (treeRow->rowCount() <= static_cast<size_t>(row))
536 return {};
537
538 return createIndex(row, column, treeRow->getRow(row));
539}
540
541/*!
542 \qmlmethod QModelIndex TreeModel::index(list<int> treeIndex, int column)
543
544 Returns an object referencing the given \a treeIndex and \a column,
545 which can be passed to the data() function to get the data from that cell,
546 or to setData() to edit the contents of that cell.
547
548 The returned object is of an anonymous QML type backed by \l QModelIndex.
549
550 The first parameter \a treeIndex represents a path of row numbers tracing from
551 the root to the desired row and is used for navigation inside the tree.
552 This is best explained through an example.
553
554 \table
555
556 \row \li \inlineimage treemodel.svg
557 {Tree diagram showing nodes A through F with index paths}
558 \li \list
559
560 \li The root of the tree is special, as it can be referenced by an invalid
561 \l QModelIndex.
562
563 \li Node A is the first child of the root and the corresponding \a treeIndex is \c [0].
564
565 \li Node B is the first child of node A. Since the \a treeIndex of A is \c [0]
566 the \a treeIndex of B will be \c [0,0].
567
568 \li Node C is the second child of A and its \a treeIndex is \c [0,1].
569
570 \li Node D is the third child of A and its \a treeIndex is \c [0,2].
571
572 \li Node E is the second child of the root and its \a treeIndex is \c [1].
573
574 \li Node F is the third child of the root and its \a treeIndex is \c [2].
575
576 \endlist
577
578 \endtable
579
580 With this overload it is possible to obtain a \l QModelIndex to a node without
581 having a \l QModelIndex to its parent node.
582
583 If no node is found by the list specified, an invalid model index is returned.
584 Please note that an invalid model index is referencing the root of the node.
585
586 \sa {QModelIndex and related Classes in QML}, data()
587*/
588QModelIndex QQmlTreeModel::index(const std::vector<int> &treeIndex, int column)
589{
590 QModelIndex mIndex;
591 QQmlTreeRow *row = getPointerToTreeRow(mIndex, treeIndex);
592
593 if (row)
594 return createIndex(treeIndex.back(), column, row);
595
596 qmlWarning(this) << "TreeModel::index: could not find any node at the specified index: "
597 << treeIndex;
598 return {};
599}
600
601QModelIndex QQmlTreeModel::parent(const QModelIndex &index) const
602{
603 if (!index.isValid())
604 return {};
605
606 const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer());
607 const QQmlTreeRow *parentRow = thisRow->parent();
608
609 if (!parentRow) // parent is root
610 return {};
611
612 const QQmlTreeRow *grandparentRow = parentRow->parent();
613
614 if (!grandparentRow) {// grandparent is root, parent is in mRows
615 for (size_t i = 0; i < mRows.size(); i++) {
616 if (mRows[i].get() == parentRow)
617 return createIndex(static_cast<int>(i), 0, parentRow);
618 }
619 Q_UNREACHABLE_RETURN(QModelIndex());
620 }
621
622 for (size_t i = 0; i < grandparentRow->rowCount(); i++) {
623 if (grandparentRow->getRow(static_cast<int>(i)) == parentRow)
624 return createIndex(static_cast<int>(i), 0, parentRow);
625 }
626 Q_UNREACHABLE_RETURN(QModelIndex());
627}
628
629
630int QQmlTreeModel::rowCount(const QModelIndex &parent) const
631{
632 if (!parent.isValid())
633 return static_cast<int>(mRows.size());
634
635 const auto *row = static_cast<const QQmlTreeRow *>(parent.internalPointer());
636 return static_cast<int>(row->rowCount());
637}
638
639/*!
640 \qmlproperty int TreeModel::columnCount
641 \readonly
642
643 This read-only property holds the number of columns in the model.
644
645 The number of columns is fixed for the lifetime of the model
646 after the \l rows property is set or \l appendRow() is called for the first
647 time.
648*/
649int QQmlTreeModel::columnCount(const QModelIndex &parent) const
650{
651 Q_UNUSED(parent);
652
653 return mColumnCount;
654}
655
656/*!
657 \qmlmethod variant TreeModel::data(index, string role)
658
659 Returns the data from the TreeModel at the given \a index belonging to the
660 given \a role.
661
662 \a index is an anonymous QML type backed by \l QModelIndex.
663
664 \sa index(), setData()
665*/
666
667/*!
668 \qmlmethod bool TreeModel::setData(index, variant value, string role)
669
670 Inserts or updates the data field named by \a role in the TreeRow at the
671 given \a index with \a value. Returns true if successful, false if not.
672
673 \a index is an anonymous QML type backed by \l QModelIndex.
674
675 \sa data(), index()
676*/
677
678bool QQmlTreeModel::validateNewRow(QLatin1StringView functionName, const QVariant &row,
679 NewRowOperationFlag operation) const
680{
681 const bool isVariantMap = (row.userType() == QMetaType::QVariantMap);
682 const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap
683 ? row : row.value<QJSValue>().toVariant();
684 const QVariantMap rowAsMap = rowAsVariant.toMap();
685 if (rowAsMap.contains(ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList)
686 {
687 const QList<QVariant> variantList = rowAsMap[ROWS_PROPERTY_NAME].toList();
688 for (const QVariant &rowAsVariant : variantList)
689 if (!validateNewRow(functionName, rowAsVariant))
690 return false;
691 }
692
693 return QQmlAbstractColumnModel::validateNewRow(functionName, row, operation);
694}
695
696int QQmlTreeModel::treeSize() const
697{
698 int treeSize = 0;
699
700 for (const auto &treeRow : mRows)
701 treeSize += treeRow->subTreeSize();
702
703 return treeSize;
704}
705
706QQmlTreeRow *QQmlTreeModel::getPointerToTreeRow(QModelIndex &modIndex,
707 const std::vector<int> &rowIndex) const
708{
709 for (int r : rowIndex) {
710 modIndex = index(r, 0, modIndex);
711 if (!modIndex.isValid())
712 return nullptr;
713 }
714
715 return static_cast<QQmlTreeRow*>(modIndex.internalPointer());
716}
717
718QT_END_NAMESPACE
719
720#include "moc_qqmltreemodel_p.cpp"
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
static const QString ROWS_PROPERTY_NAME