| 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 | |
| 4 | #include "qqmltreemodel_p.h" |
| 5 | #include "qqmltreerow_p.h" |
| 6 | |
| 7 | #include <QtCore/qloggingcategory.h> |
| 8 | |
| 9 | #include <QtQml/qqmlinfo.h> |
| 10 | #include <QtQml/qqmlengine.h> |
| 11 | |
| 12 | QT_BEGIN_NAMESPACE |
| 13 | |
| 14 | using namespace Qt::StringLiterals; |
| 15 | |
| 16 | static const QString ROWS_PROPERTY_NAME = u"rows"_s ; |
| 17 | |
| 18 | /*! |
| 19 | \qmltype TreeModel |
| 20 | //! \nativetype QQmlTreeModel |
| 21 | \inqmlmodule Qt.labs.qmlmodels |
| 22 | \brief Encapsulates a simple tree model. |
| 23 | \since 6.10 |
| 24 | |
| 25 | The TreeModel type stores JavaScript/JSON objects as data for a tree |
| 26 | model that can be used with \l TreeView. It is intended to support |
| 27 | very simple models without requiring the creation of a custom |
| 28 | \l QAbstractItemModel subclass in C++. |
| 29 | |
| 30 | \snippet qml/treemodel/treemodel-filesystem-basic.qml file |
| 31 | |
| 32 | The model's initial data is set with either the \l rows property or by |
| 33 | calling \l appendRow(). Each column in the model is specified by declaring |
| 34 | a \l TableModelColumn instance, where the order of each instance determines |
| 35 | its column index. Once the model's \l Component::completed() signal has been |
| 36 | emitted, the columns and roles will have been established and are then |
| 37 | fixed for the lifetime of the model. |
| 38 | |
| 39 | \section1 Supported Row Data Structures |
| 40 | |
| 41 | Each row represents a node in the tree. Each node has the same type of |
| 42 | columns. The TreeModel is designed to work with JavaScript/JSON data so |
| 43 | each row is a list of simple key-value pairs: |
| 44 | |
| 45 | \snippet qml/treemodel/treemodel-filesystem-basic.qml rows |
| 46 | |
| 47 | A node can have child nodes and these will be stored in an array |
| 48 | associated with the "rows" key. "rows" is reserved for this purpose: only |
| 49 | the list of child nodes should be associated with this key. |
| 50 | |
| 51 | The model is manipulated via \l {QModelIndex} {QModelIndices}. To access |
| 52 | a specific row/node, the \l getRow() function can be used. It's also |
| 53 | possible to access the model's JavaScript data directly via the \l rows |
| 54 | property, but it is not possible to modify the model data this way. |
| 55 | |
| 56 | To add new rows, use \l appendRow(). To modify existing rows, use |
| 57 | \l setRow(), \l removeRow() and \l clear(). |
| 58 | */ |
| 59 | |
| 60 | QQmlTreeModel::QQmlTreeModel(QObject *parent) |
| 61 | : QQmlAbstractColumnModel(parent) |
| 62 | { |
| 63 | } |
| 64 | |
| 65 | QQmlTreeModel::~QQmlTreeModel() = default; |
| 66 | |
| 67 | /*! |
| 68 | \qmlproperty object TreeModel::rows |
| 69 | |
| 70 | This property holds the model data in the form of an array of rows. |
| 71 | |
| 72 | \sa getRow(), setRow(), appendRow(), clear(), columnCount |
| 73 | */ |
| 74 | QVariant QQmlTreeModel::rows() const |
| 75 | { |
| 76 | QVariantList rowsAsVariant; |
| 77 | for (const auto &row : mRows) |
| 78 | rowsAsVariant.append(t: row->toVariant()); |
| 79 | |
| 80 | return rowsAsVariant; |
| 81 | } |
| 82 | |
| 83 | void QQmlTreeModel::setRows(const QVariant &rows) |
| 84 | { |
| 85 | if (rows.userType() != qMetaTypeId<QJSValue>()) { |
| 86 | qmlWarning(me: this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | const auto rowsAsJSValue = rows.value<QJSValue>(); |
| 91 | const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); |
| 92 | |
| 93 | if (!mComponentCompleted) { |
| 94 | // Store the rows until we can call setRowsPrivate() after component completion. |
| 95 | mInitialRows = rowsAsVariantList; |
| 96 | return; |
| 97 | } |
| 98 | |
| 99 | setRowsPrivate(rowsAsVariantList); |
| 100 | } |
| 101 | |
| 102 | void QQmlTreeModel::setRowsPrivate(const QVariantList &rowsAsVariantList) |
| 103 | { |
| 104 | Q_ASSERT(mComponentCompleted); |
| 105 | |
| 106 | // By now, all TableModelColumns should have been set. |
| 107 | if (mColumns.isEmpty()) { |
| 108 | qmlWarning(me: this) << "No TableModelColumns were set; model will be empty" ; |
| 109 | return; |
| 110 | } |
| 111 | |
| 112 | const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); |
| 113 | if (!firstTimeValidRowsHaveBeenSet) { |
| 114 | // This is not the first time rows have been set; validate each one. |
| 115 | for (const auto &row : rowsAsVariantList) { |
| 116 | // validateNewRow() expects a QVariant wrapping a QJSValue, so to |
| 117 | // simplify the code, just create one here. |
| 118 | const QVariant wrappedRow = QVariant::fromValue(value: row); |
| 119 | if (!validateNewRow(functionName: "TreeModel::setRows"_L1 , row: wrappedRow, SetRowsOperation)) |
| 120 | return; |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | beginResetModel(); |
| 125 | |
| 126 | // We don't clear the column or role data, because a TreeModel should not be reused in that way. |
| 127 | // Once it has valid data, its columns and roles are fixed. |
| 128 | mRows.clear(); |
| 129 | |
| 130 | for (const auto &rowAsVariant : rowsAsVariantList) |
| 131 | mRows.push_back(x: std::make_unique<QQmlTreeRow>(args: rowAsVariant)); |
| 132 | |
| 133 | // Gather metadata the first time rows is set. |
| 134 | // If we call setrows on an empty model, mInitialRows will be empty, but mRows is not |
| 135 | if (firstTimeValidRowsHaveBeenSet && (!mRows.empty() || !mInitialRows.isEmpty())) |
| 136 | fetchColumnMetadata(); |
| 137 | |
| 138 | endResetModel(); |
| 139 | emit rowsChanged(); |
| 140 | } |
| 141 | |
| 142 | QVariant QQmlTreeModel::dataPrivate(const QModelIndex &index, const QString &roleName) const |
| 143 | { |
| 144 | const ColumnMetadata columnMetadata = mColumnMetadata.at(i: index.column()); |
| 145 | const QString propertyName = columnMetadata.roles.value(key: roleName).name; |
| 146 | const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer()); |
| 147 | return thisRow->data(key: propertyName); |
| 148 | } |
| 149 | |
| 150 | void QQmlTreeModel::setDataPrivate(const QModelIndex &index, const QString &roleName, QVariant value) |
| 151 | { |
| 152 | auto *row = static_cast<QQmlTreeRow *>(index.internalPointer()); |
| 153 | row->setField(key: roleName, value); |
| 154 | } |
| 155 | |
| 156 | // TODO: Turn this into a snippet that compiles in CI |
| 157 | /*! |
| 158 | \qmlmethod TreeModel::appendRow(QModelIndex parent, object treeRow) |
| 159 | |
| 160 | Appends a new treeRow to \a parent, with the values (cells) in \a treeRow. |
| 161 | |
| 162 | \code |
| 163 | treeModel.appendRow(index, { |
| 164 | checked: false, |
| 165 | size: "-", |
| 166 | type: "folder", |
| 167 | name: "Orders", |
| 168 | lastModified: "2025-07-02", |
| 169 | rows: [ |
| 170 | { |
| 171 | checked: true, |
| 172 | size: "38 KB", |
| 173 | type: "file", |
| 174 | name: "monitors.xlsx", |
| 175 | lastModified: "2025-07-02" |
| 176 | }, |
| 177 | { |
| 178 | checked: true, |
| 179 | size: "54 KB", |
| 180 | type: "file", |
| 181 | name: "notebooks.xlsx", |
| 182 | lastModified: "2025-07-02" |
| 183 | } |
| 184 | ] |
| 185 | }); |
| 186 | \endcode |
| 187 | |
| 188 | If \a parent is invalid, \a treeRow gets appended to the root node. |
| 189 | |
| 190 | \sa setRow(), removeRow() |
| 191 | */ |
| 192 | void QQmlTreeModel::appendRow(QModelIndex parent, const QVariant &row) |
| 193 | { |
| 194 | if (!validateNewRow(functionName: "TreeModel::appendRow"_L1 , row)) |
| 195 | return; |
| 196 | |
| 197 | const QVariant data = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant(); |
| 198 | |
| 199 | if (parent.isValid()) { |
| 200 | auto *parentRow = static_cast<QQmlTreeRow *>(parent.internalPointer()); |
| 201 | auto *newChild = new QQmlTreeRow(data); |
| 202 | |
| 203 | beginInsertRows(parent, first: static_cast<int>(parentRow->rowCount()), last: static_cast<int>(parentRow->rowCount())); |
| 204 | parentRow->addChild(child: newChild); |
| 205 | |
| 206 | // Gather metadata the first time a row is added. |
| 207 | if (mColumnMetadata.isEmpty()) |
| 208 | fetchColumnMetadata(); |
| 209 | |
| 210 | endInsertRows(); |
| 211 | } else { |
| 212 | qmlWarning(me: this) << "append: could not find any node at the specified index" |
| 213 | << " - the new row will be appended to root" ; |
| 214 | |
| 215 | beginInsertRows(parent: QModelIndex(), |
| 216 | first: static_cast<int>(mRows.size()), |
| 217 | last: static_cast<int>(mRows.size())); |
| 218 | |
| 219 | mRows.push_back(x: std::make_unique<QQmlTreeRow>(args: data)); |
| 220 | |
| 221 | // Gather metadata the first time a row is added. |
| 222 | if (mColumnMetadata.isEmpty()) |
| 223 | fetchColumnMetadata(); |
| 224 | |
| 225 | endInsertRows(); |
| 226 | } |
| 227 | |
| 228 | emit rowsChanged(); |
| 229 | } |
| 230 | |
| 231 | /*! |
| 232 | \qmlmethod TreeModel::appendRow(object treeRow) |
| 233 | |
| 234 | Appends \a treeRow to the root node. |
| 235 | |
| 236 | \sa setRow(), removeRow() |
| 237 | */ |
| 238 | void QQmlTreeModel::appendRow(const QVariant &row) |
| 239 | { |
| 240 | appendRow(parent: {}, row); |
| 241 | } |
| 242 | |
| 243 | /*! |
| 244 | \qmlmethod TreeModel::clear() |
| 245 | |
| 246 | Removes all rows from the model. |
| 247 | |
| 248 | \sa removeRow() |
| 249 | */ |
| 250 | void QQmlTreeModel::clear() |
| 251 | { |
| 252 | QQmlEngine *engine = qmlEngine(this); |
| 253 | Q_ASSERT(engine); |
| 254 | setRows(QVariant::fromValue(value: engine->newArray())); |
| 255 | } |
| 256 | |
| 257 | /*! |
| 258 | \qmlmethod object TreeModel::getRow(const QModelIndex &rowIndex) |
| 259 | |
| 260 | Returns the treeRow at \a rowIndex in the model. |
| 261 | |
| 262 | \note the returned object cannot be used to modify the contents of the |
| 263 | model; use setTreeRow() instead. |
| 264 | |
| 265 | \sa setRow(), appendRow(), removeRow() |
| 266 | */ |
| 267 | QVariant QQmlTreeModel::getRow(const QModelIndex &rowIndex) const |
| 268 | { |
| 269 | if (rowIndex.isValid()) |
| 270 | return static_cast<QQmlTreeRow*>(rowIndex.internalPointer())->toVariant(); |
| 271 | |
| 272 | qmlWarning(me: this) << "getRow: could not find any node at the specified index" ; |
| 273 | return {}; |
| 274 | } |
| 275 | |
| 276 | QVariant QQmlTreeModel::firstRow() const |
| 277 | { |
| 278 | return mRows.front().get()->data(); |
| 279 | } |
| 280 | |
| 281 | void QQmlTreeModel::setInitialRows() |
| 282 | { |
| 283 | setRowsPrivate(mInitialRows); |
| 284 | } |
| 285 | |
| 286 | /*! |
| 287 | \qmlmethod TreeModel::removeRow(QModelIndex rowIndex) |
| 288 | |
| 289 | Removes the TreeRow referenced by \a rowIndex from the model. |
| 290 | |
| 291 | \code |
| 292 | treeModel.removeTreeRow(rowIndex) |
| 293 | \endcode |
| 294 | |
| 295 | \sa clear() |
| 296 | */ |
| 297 | void QQmlTreeModel::removeRow(QModelIndex rowIndex) |
| 298 | { |
| 299 | if (rowIndex.isValid()) { |
| 300 | QModelIndex mIndexParent = rowIndex.parent(); |
| 301 | |
| 302 | beginRemoveRows(parent: mIndexParent, first: rowIndex.row(), last: rowIndex.row()); |
| 303 | |
| 304 | if (mIndexParent.isValid()) { |
| 305 | auto *parent = static_cast<QQmlTreeRow *>(mIndexParent.internalPointer()); |
| 306 | parent->removeChildAt(i: rowIndex.row()); |
| 307 | } else { |
| 308 | mRows.erase(position: std::next(x: mRows.begin(), n: rowIndex.row())); |
| 309 | } |
| 310 | |
| 311 | endRemoveRows(); |
| 312 | } else { |
| 313 | qmlWarning(me: this) << "TreeModel::removeRow could not find any node at the specified index" ; |
| 314 | return; |
| 315 | } |
| 316 | |
| 317 | emit rowsChanged(); |
| 318 | } |
| 319 | |
| 320 | // TODO: Turn this into a snippet that compiles in CI |
| 321 | |
| 322 | /*! |
| 323 | \qmlmethod TreeModel::setRow(QModelIndex rowIndex, object treeRow) |
| 324 | |
| 325 | Replaces the TreeRow at \a rowIndex in the model with \a treeRow. |
| 326 | A row with child rows will be rejected. |
| 327 | |
| 328 | All columns/cells must be present in \c treeRow, and in the correct order. |
| 329 | The child rows of the row remain unaffected. |
| 330 | |
| 331 | \code |
| 332 | treeModel.setRow(rowIndex, { |
| 333 | checked: true, |
| 334 | size: "-", |
| 335 | type: "folder", |
| 336 | name: "Subtitles", |
| 337 | lastModified: "2025-07-07", |
| 338 | iconColor: "blue" |
| 339 | }); |
| 340 | \endcode |
| 341 | |
| 342 | \sa appendRow() |
| 343 | */ |
| 344 | void QQmlTreeModel::setRow(QModelIndex rowIndex, const QVariant &rowData) |
| 345 | { |
| 346 | if (!rowIndex.isValid()) { |
| 347 | qmlWarning(me: this) << "TreeModel::setRow: invalid modelIndex" ; |
| 348 | return; |
| 349 | } |
| 350 | |
| 351 | const QVariantMap rowAsMap = rowData.toMap(); |
| 352 | if (rowAsMap.contains(key: ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList) { |
| 353 | qmlWarning(me: this) << "TreeModel::setRow: child rows are not allowed" ; |
| 354 | return; |
| 355 | } |
| 356 | |
| 357 | if (!validateNewRow(functionName: "TreeModel::setRow"_L1 , row: rowData)) |
| 358 | return; |
| 359 | |
| 360 | const QVariant rowAsVariant = rowData.userType() == QMetaType::QVariantMap ? rowData : rowData.value<QJSValue>().toVariant(); |
| 361 | auto *row = static_cast<QQmlTreeRow *>(rowIndex.internalPointer()); |
| 362 | row->setData(rowAsVariant); |
| 363 | |
| 364 | const QModelIndex topLeftModelIndex(createIndex(arow: rowIndex.row(), acolumn: 0, adata: rowIndex.internalPointer())); |
| 365 | const QModelIndex bottomRightModelIndex(createIndex(arow: rowIndex.row(), acolumn: mColumnCount-1, adata: rowIndex.internalPointer())); |
| 366 | |
| 367 | emit dataChanged(topLeft: topLeftModelIndex, bottomRight: bottomRightModelIndex); |
| 368 | emit rowsChanged(); |
| 369 | } |
| 370 | |
| 371 | /*! |
| 372 | \qmlmethod QModelIndex TreeModel::index(int row, int column, object parent) |
| 373 | |
| 374 | Returns a \l QModelIndex object referencing the given \a row and \a column of |
| 375 | a given \a parent which can be passed to the data() function to get the data |
| 376 | from that cell, or to setData() to edit the contents of that cell. |
| 377 | |
| 378 | \sa {QModelIndex and related Classes in QML}, data() |
| 379 | */ |
| 380 | QModelIndex QQmlTreeModel::index(int row, int column, const QModelIndex &parent) const |
| 381 | { |
| 382 | if (!parent.isValid()){ |
| 383 | if (static_cast<size_t>(row) >= mRows.size()) |
| 384 | return {}; |
| 385 | |
| 386 | return createIndex(arow: row, acolumn: column, adata: mRows.at(n: row).get()); |
| 387 | } |
| 388 | |
| 389 | const auto *treeRow = static_cast<const QQmlTreeRow *>(parent.internalPointer()); |
| 390 | if (treeRow->rowCount() <= static_cast<size_t>(row)) |
| 391 | return {}; |
| 392 | |
| 393 | return createIndex(arow: row, acolumn: column, adata: treeRow->getRow(i: row)); |
| 394 | } |
| 395 | |
| 396 | /*! |
| 397 | \qmlmethod QModelIndex TreeModel::index(list<int> treeIndex, int column) |
| 398 | |
| 399 | Returns a \l QModelIndex object referencing the given \a treeIndex and \a column, |
| 400 | which can be passed to the data() function to get the data from that cell, |
| 401 | or to setData() to edit the contents of that cell. |
| 402 | |
| 403 | The first parameter \a treeIndex represents a path of row numbers tracing from |
| 404 | the root to the desired row and is used for navigation inside the tree. |
| 405 | This is best explained through an example. |
| 406 | |
| 407 | \table |
| 408 | |
| 409 | \row \li \inlineimage treemodel.svg |
| 410 | \li \list |
| 411 | |
| 412 | \li The root of the tree is special, as it can be referenced by an invalid |
| 413 | \l QModelIndex. |
| 414 | |
| 415 | \li Node A is the first child of the root and the corresponding \a treeIndex is \c [0]. |
| 416 | |
| 417 | \li Node B is the first child of node A. Since the \a treeIndex of A is \c [0] |
| 418 | the \a treeIndex of B will be \c [0,0]. |
| 419 | |
| 420 | \li Node C is the second child of A and its \a treeIndex is \c [0,1]. |
| 421 | |
| 422 | \li Node D is the third child of A and its \a treeIndex is \c [0,2]. |
| 423 | |
| 424 | \li Node E is the second child of the root and its \a treeIndex is \c [1]. |
| 425 | |
| 426 | \li Node F is the third child of the root and its \a treeIndex is \c [2]. |
| 427 | |
| 428 | \endlist |
| 429 | |
| 430 | \endtable |
| 431 | |
| 432 | With this overload it is possible to obtain a \l QModelIndex to a node without |
| 433 | having a \l QModelIndex to its parent node. |
| 434 | |
| 435 | If no node is found by the list specified, an invalid model index is returned. |
| 436 | Please note that an invalid model index is referencing the root of the node. |
| 437 | |
| 438 | \sa {QModelIndex and related Classes in QML}, data() |
| 439 | */ |
| 440 | QModelIndex QQmlTreeModel::index(const std::vector<int> &treeIndex, int column) |
| 441 | { |
| 442 | QModelIndex mIndex; |
| 443 | QQmlTreeRow *row = getPointerToTreeRow(index&: mIndex, rowIndex: treeIndex); |
| 444 | |
| 445 | if (row) |
| 446 | return createIndex(arow: treeIndex.back(), acolumn: column, adata: row); |
| 447 | |
| 448 | qmlWarning(me: this) << "TreeModel::index: could not find any node at the specified index" ; |
| 449 | return {}; |
| 450 | } |
| 451 | |
| 452 | QModelIndex QQmlTreeModel::parent(const QModelIndex &index) const |
| 453 | { |
| 454 | if (!index.isValid()) |
| 455 | return {}; |
| 456 | |
| 457 | const auto *thisRow = static_cast<const QQmlTreeRow *>(index.internalPointer()); |
| 458 | const QQmlTreeRow *parentRow = thisRow->parent(); |
| 459 | |
| 460 | if (!parentRow) // parent is root |
| 461 | return {}; |
| 462 | |
| 463 | const QQmlTreeRow *grandparentRow = parentRow->parent(); |
| 464 | |
| 465 | if (!grandparentRow) {// grandparent is root, parent is in mRows |
| 466 | for (size_t i = 0; i < mRows.size(); i++) { |
| 467 | if (mRows[i].get() == parentRow) |
| 468 | return createIndex(arow: static_cast<int>(i), acolumn: 0, adata: parentRow); |
| 469 | } |
| 470 | Q_UNREACHABLE_RETURN(QModelIndex()); |
| 471 | } |
| 472 | |
| 473 | for (size_t i = 0; i < grandparentRow->rowCount(); i++) { |
| 474 | if (grandparentRow->getRow(i: static_cast<int>(i)) == parentRow) |
| 475 | return createIndex(arow: static_cast<int>(i), acolumn: 0, adata: parentRow); |
| 476 | } |
| 477 | Q_UNREACHABLE_RETURN(QModelIndex()); |
| 478 | } |
| 479 | |
| 480 | |
| 481 | int QQmlTreeModel::rowCount(const QModelIndex &parent) const |
| 482 | { |
| 483 | if (!parent.isValid()) |
| 484 | return static_cast<int>(mRows.size()); |
| 485 | |
| 486 | const auto *row = static_cast<const QQmlTreeRow *>(parent.internalPointer()); |
| 487 | return static_cast<int>(row->rowCount()); |
| 488 | } |
| 489 | |
| 490 | /*! |
| 491 | \qmlproperty int TreeModel::columnCount |
| 492 | \readonly |
| 493 | |
| 494 | This read-only property holds the number of columns in the model. |
| 495 | |
| 496 | The number of columns is fixed for the lifetime of the model |
| 497 | after the \l rows property is set or \l appendRow() is called for the first |
| 498 | time. |
| 499 | */ |
| 500 | int QQmlTreeModel::columnCount(const QModelIndex &parent) const |
| 501 | { |
| 502 | Q_UNUSED(parent); |
| 503 | |
| 504 | return mColumnCount; |
| 505 | } |
| 506 | |
| 507 | /*! |
| 508 | \qmlmethod variant TreeModel::data(QModelIndex index, string role) |
| 509 | |
| 510 | Returns the data from the TreeModel at the given \a index belonging to the |
| 511 | given \a role. |
| 512 | |
| 513 | \sa index(), setData() |
| 514 | */ |
| 515 | |
| 516 | /*! |
| 517 | \qmlmethod bool TreeModel::setData(QModelIndex index, variant value, string role) |
| 518 | |
| 519 | Inserts or updates the data field named by \a role in the TreeRow at the |
| 520 | given \a index with \a value. Returns true if sucessful, false if not. |
| 521 | |
| 522 | \sa data(), index() |
| 523 | */ |
| 524 | |
| 525 | bool QQmlTreeModel::validateNewRow(QLatin1StringView functionName, const QVariant &row, |
| 526 | NewRowOperationFlag operation) const |
| 527 | { |
| 528 | const bool isVariantMap = (row.userType() == QMetaType::QVariantMap); |
| 529 | const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap |
| 530 | ? row : row.value<QJSValue>().toVariant(); |
| 531 | const QVariantMap rowAsMap = rowAsVariant.toMap(); |
| 532 | if (rowAsMap.contains(key: ROWS_PROPERTY_NAME) && rowAsMap[ROWS_PROPERTY_NAME].userType() == QMetaType::Type::QVariantList) |
| 533 | { |
| 534 | const QList<QVariant> variantList = rowAsMap[ROWS_PROPERTY_NAME].toList(); |
| 535 | for (const QVariant &rowAsVariant : variantList) |
| 536 | if (!validateNewRow(functionName, row: rowAsVariant)) |
| 537 | return false; |
| 538 | } |
| 539 | |
| 540 | return QQmlAbstractColumnModel::validateNewRow(functionName, row, operation); |
| 541 | } |
| 542 | |
| 543 | int QQmlTreeModel::treeSize() const |
| 544 | { |
| 545 | int treeSize = 0; |
| 546 | |
| 547 | for (const auto &treeRow : mRows) |
| 548 | treeSize += treeRow->subTreeSize(); |
| 549 | |
| 550 | return treeSize; |
| 551 | } |
| 552 | |
| 553 | QQmlTreeRow *QQmlTreeModel::getPointerToTreeRow(QModelIndex &modIndex, |
| 554 | const std::vector<int> &rowIndex) const |
| 555 | { |
| 556 | for (int r : rowIndex) { |
| 557 | modIndex = index(row: r, column: 0, parent: modIndex); |
| 558 | if (!modIndex.isValid()) |
| 559 | return nullptr; |
| 560 | } |
| 561 | |
| 562 | return static_cast<QQmlTreeRow*>(modIndex.internalPointer()); |
| 563 | } |
| 564 | |
| 565 | QT_END_NAMESPACE |
| 566 | |
| 567 | #include "moc_qqmltreemodel_p.cpp" |
| 568 | |