| 1 | // Copyright (C) 2019 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 "qqmltablemodel_p.h" | 
| 5 |  | 
| 6 | #include <QtCore/qloggingcategory.h> | 
| 7 | #include <QtQml/qqmlinfo.h> | 
| 8 | #include <QtQml/qqmlengine.h> | 
| 9 |  | 
| 10 | QT_BEGIN_NAMESPACE | 
| 11 |  | 
| 12 | Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel" ) | 
| 13 |  | 
| 14 | /*! | 
| 15 |     \qmltype TableModel | 
| 16 | //!    \nativetype QQmlTableModel | 
| 17 |     \inqmlmodule Qt.labs.qmlmodels | 
| 18 |     \brief Encapsulates a simple table model. | 
| 19 |     \since 5.14 | 
| 20 |  | 
| 21 |     The TableModel type stores JavaScript/JSON objects as data for a table | 
| 22 |     model that can be used with \l TableView. It is intended to support | 
| 23 |     very simple models without requiring the creation of a custom | 
| 24 |     QAbstractTableModel subclass in C++. | 
| 25 |  | 
| 26 |     \snippet qml/tablemodel/fruit-example-simpledelegate.qml file | 
| 27 |  | 
| 28 |     The model's initial row data is set with either the \l rows property or by | 
| 29 |     calling \l appendRow(). Each column in the model is specified by declaring | 
| 30 |     a \l TableModelColumn instance, where the order of each instance determines | 
| 31 |     its column index. Once the model's \l Component::completed() signal has been | 
| 32 |     emitted, the columns and roles will have been established and are then | 
| 33 |     fixed for the lifetime of the model. | 
| 34 |  | 
| 35 |     To access a specific row, the \l getRow() function can be used. | 
| 36 |     It's also possible to access the model's JavaScript data | 
| 37 |     directly via the \l rows property, but it is not possible to | 
| 38 |     modify the model data this way. | 
| 39 |  | 
| 40 |     To add new rows, use \l appendRow() and \l insertRow(). To modify | 
| 41 |     existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and | 
| 42 |     \l clear(). | 
| 43 |  | 
| 44 |     It is also possible to modify the model's data via the delegate, | 
| 45 |     as shown in the example above: | 
| 46 |  | 
| 47 |     \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate | 
| 48 |  | 
| 49 |     If the type of the data at the modified role does not match the type of the | 
| 50 |     data that is set, it will be automatically converted via | 
| 51 |     \l {QVariant::canConvert()}{QVariant}. | 
| 52 |  | 
| 53 |     \section1 Supported Row Data Structures | 
| 54 |  | 
| 55 |     TableModel is designed to work with JavaScript/JSON data, where each row | 
| 56 |     is a simple key-pair object: | 
| 57 |  | 
| 58 |     \code | 
| 59 |     { | 
| 60 |         // Each property is one cell/column. | 
| 61 |         checked: false, | 
| 62 |         amount: 1, | 
| 63 |         fruitType: "Apple", | 
| 64 |         fruitName: "Granny Smith", | 
| 65 |         fruitPrice: 1.50 | 
| 66 |     }, | 
| 67 |     // ... | 
| 68 |     \endcode | 
| 69 |  | 
| 70 |     As model manipulation in Qt is done via row and column indices, | 
| 71 |     and because object keys are unordered, each column must be specified via | 
| 72 |     TableModelColumn. This allows mapping Qt's built-in roles to any property | 
| 73 |     in each row object. | 
| 74 |  | 
| 75 |     Complex row structures are supported, but with limited functionality. | 
| 76 |     As TableModel has no way of knowing how each row is structured, | 
| 77 |     it cannot manipulate it. As a consequence of this, the copy of the | 
| 78 |     model data that TableModel has stored in \l rows is not kept in sync | 
| 79 |     with the source data that was set in QML. For these reasons, TableModel | 
| 80 |     relies on the user to handle simple data manipulation. | 
| 81 |  | 
| 82 |     For example, suppose you wanted to have several roles per column. One way | 
| 83 |     of doing this is to use a data source where each row is an array and each | 
| 84 |     cell is an object. To use this data source with TableModel, define a | 
| 85 |     getter and setter: | 
| 86 |  | 
| 87 |     \code | 
| 88 |     TableModel { | 
| 89 |         TableModelColumn { | 
| 90 |             display: function(modelIndex) { return rows[modelIndex.row][0].checked } | 
| 91 |             setDisplay: function(modelIndex, cellData) { | 
| 92 |                     rows[modelIndex.row][0].checked = cellData | 
| 93 |                 } | 
| 94 |         } | 
| 95 |         // ... | 
| 96 |  | 
| 97 |         rows: [ | 
| 98 |             [ | 
| 99 |                 { checked: false, checkable: true }, | 
| 100 |                 { amount: 1 }, | 
| 101 |                 { fruitType: "Apple" }, | 
| 102 |                 { fruitName: "Granny Smith" }, | 
| 103 |                 { fruitPrice: 1.50 } | 
| 104 |             ] | 
| 105 |             // ... | 
| 106 |         ] | 
| 107 |     } | 
| 108 |     \endcode | 
| 109 |  | 
| 110 |     The row above is one example of a complex row. | 
| 111 |  | 
| 112 |     \note Row manipulation functions such as \l appendRow(), \l removeRow(), | 
| 113 |     etc. are not supported when using complex rows. | 
| 114 |  | 
| 115 |     \section1 Using DelegateChooser with TableModel | 
| 116 |  | 
| 117 |     For most real world use cases, it is recommended to use DelegateChooser | 
| 118 |     as the delegate of a TableView that uses TableModel. This allows you to | 
| 119 |     use specific roles in the relevant delegates. For example, the snippet | 
| 120 |     above can be rewritten to use DelegateChooser like so: | 
| 121 |  | 
| 122 |     \snippet qml/tablemodel/fruit-example-delegatechooser.qml file | 
| 123 |  | 
| 124 |     The most specific delegates are declared first: the columns at index \c 0 | 
| 125 |     and \c 1 have \c bool and \c integer data types, so they use a | 
| 126 |     \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, | 
| 127 |     respectively. The remaining columns can simply use a | 
| 128 |     \l [QtQuickControls2]{TextField}, and so that delegate is declared | 
| 129 |     last as a fallback. | 
| 130 |  | 
| 131 |     \sa TableModelColumn, TableView, QAbstractTableModel | 
| 132 | */ | 
| 133 |  | 
| 134 | QQmlTableModel::QQmlTableModel(QObject *parent) | 
| 135 |     : QAbstractTableModel(parent) | 
| 136 | { | 
| 137 | } | 
| 138 |  | 
| 139 | QQmlTableModel::~QQmlTableModel() | 
| 140 | { | 
| 141 | } | 
| 142 |  | 
| 143 | /*! | 
| 144 |     \qmlproperty object TableModel::rows | 
| 145 |  | 
| 146 |     This property holds the model data in the form of an array of rows: | 
| 147 |  | 
| 148 |     \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows | 
| 149 |  | 
| 150 |     \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount | 
| 151 | */ | 
| 152 | QVariant QQmlTableModel::rows() const | 
| 153 | { | 
| 154 |     return mRows; | 
| 155 | } | 
| 156 |  | 
| 157 | void QQmlTableModel::setRows(const QVariant &rows) | 
| 158 | { | 
| 159 |     if (rows.userType() != qMetaTypeId<QJSValue>()) { | 
| 160 |         qmlWarning(me: this) << "setRows(): \"rows\" must be an array; actual type is "  << rows.typeName(); | 
| 161 |         return; | 
| 162 |     } | 
| 163 |  | 
| 164 |     const QJSValue rowsAsJSValue = rows.value<QJSValue>(); | 
| 165 |     const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); | 
| 166 |     if (rowsAsVariantList == mRows) { | 
| 167 |         // No change. | 
| 168 |         return; | 
| 169 |     } | 
| 170 |  | 
| 171 |     if (!componentCompleted) { | 
| 172 |         // Store the rows until we can call doSetRows() after component completion. | 
| 173 |         mRows = rowsAsVariantList; | 
| 174 |         return; | 
| 175 |     } | 
| 176 |  | 
| 177 |     doSetRows(rowsAsVariantList); | 
| 178 | } | 
| 179 |  | 
| 180 | void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList) | 
| 181 | { | 
| 182 |     Q_ASSERT(componentCompleted); | 
| 183 |  | 
| 184 |     // By now, all TableModelColumns should have been set. | 
| 185 |     if (mColumns.isEmpty()) { | 
| 186 |         qmlWarning(me: this) << "No TableModelColumns were set; model will be empty" ; | 
| 187 |         return; | 
| 188 |     } | 
| 189 |  | 
| 190 |     const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); | 
| 191 |     if (!firstTimeValidRowsHaveBeenSet) { | 
| 192 |         // This is not the first time rows have been set; validate each one. | 
| 193 |         for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { | 
| 194 |             // validateNewRow() expects a QVariant wrapping a QJSValue, so to | 
| 195 |             // simplify the code, just create one here. | 
| 196 |             const QVariant row = QVariant::fromValue(value: rowsAsVariantList.at(i: rowIndex)); | 
| 197 |             if (!validateNewRow(functionName: "setRows()" , row, rowIndex, operation: SetRowsOperation)) | 
| 198 |                 return; | 
| 199 |         } | 
| 200 |     } | 
| 201 |  | 
| 202 |     const int oldRowCount = mRowCount; | 
| 203 |     const int oldColumnCount = mColumnCount; | 
| 204 |  | 
| 205 |     beginResetModel(); | 
| 206 |  | 
| 207 |     // We don't clear the column or role data, because a TableModel should not be reused in that way. | 
| 208 |     // Once it has valid data, its columns and roles are fixed. | 
| 209 |     mRows = rowsAsVariantList; | 
| 210 |     mRowCount = mRows.size(); | 
| 211 |  | 
| 212 |     // Gather metadata the first time rows is set. | 
| 213 |     if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) | 
| 214 |         fetchColumnMetadata(); | 
| 215 |  | 
| 216 |     endResetModel(); | 
| 217 |  | 
| 218 |     emit rowsChanged(); | 
| 219 |  | 
| 220 |     if (mRowCount != oldRowCount) | 
| 221 |         emit rowCountChanged(); | 
| 222 |     if (mColumnCount != oldColumnCount) | 
| 223 |         emit columnCountChanged(); | 
| 224 | } | 
| 225 |  | 
| 226 | QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey, | 
| 227 |     QQmlTableModelColumn *tableModelColumn, int columnIndex) const | 
| 228 | { | 
| 229 |     const QVariant firstRow = mRows.first(); | 
| 230 |     ColumnRoleMetadata roleData; | 
| 231 |  | 
| 232 |     QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleName: roleNameKey); | 
| 233 |     if (columnRoleGetter.isUndefined()) { | 
| 234 |         // This role is not defined, which is fine; just skip it. | 
| 235 |         return roleData; | 
| 236 |     } | 
| 237 |  | 
| 238 |     if (columnRoleGetter.isString()) { | 
| 239 |         // The role is set as a string, so we assume the row is a simple object. | 
| 240 |         if (firstRow.userType() != QMetaType::QVariantMap) { | 
| 241 |             qmlWarning(me: this).quote() << "expected row for role "  | 
| 242 |                 << roleNameKey << " of TableModelColumn at index "  | 
| 243 |                 << columnIndex << " to be a simple object, but it's "  | 
| 244 |                 << firstRow.typeName() << " instead: "  << firstRow; | 
| 245 |             return roleData; | 
| 246 |         } | 
| 247 |         const QVariantMap firstRowAsMap = firstRow.toMap(); | 
| 248 |         const QString rolePropertyName = columnRoleGetter.toString(); | 
| 249 |         const QVariant roleProperty = firstRowAsMap.value(key: rolePropertyName); | 
| 250 |  | 
| 251 |         roleData.isStringRole = true; | 
| 252 |         roleData.name = rolePropertyName; | 
| 253 |         roleData.type = roleProperty.userType(); | 
| 254 |         roleData.typeName = QString::fromLatin1(ba: roleProperty.typeName()); | 
| 255 |     } else if (columnRoleGetter.isCallable()) { | 
| 256 |         // The role is provided via a function, which means the row is complex and | 
| 257 |         // the user needs to provide the data for it. | 
| 258 |         const auto modelIndex = index(row: 0, column: columnIndex); | 
| 259 |         const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(value: modelIndex); | 
| 260 |         const QVariant cellData = columnRoleGetter.call(args).toVariant(); | 
| 261 |  | 
| 262 |         // We don't know the property name since it's provided through the function. | 
| 263 |         // roleData.name = ??? | 
| 264 |         roleData.isStringRole = false; | 
| 265 |         roleData.type = cellData.userType(); | 
| 266 |         roleData.typeName = QString::fromLatin1(ba: cellData.typeName()); | 
| 267 |     } else { | 
| 268 |         // Invalid role. | 
| 269 |         qmlWarning(me: this) << "TableModelColumn role for column at index "  | 
| 270 |             << columnIndex << " must be either a string or a function; actual type is: "  | 
| 271 |             << columnRoleGetter.toString(); | 
| 272 |     } | 
| 273 |  | 
| 274 |     return roleData; | 
| 275 | } | 
| 276 |  | 
| 277 | void QQmlTableModel::fetchColumnMetadata() | 
| 278 | { | 
| 279 |     qCDebug(lcTableModel) << "gathering metadata for"  << mColumnCount << "columns from first row:" ; | 
| 280 |  | 
| 281 |     static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames(); | 
| 282 |  | 
| 283 |     // Since we support different data structures at the row level, we require that there | 
| 284 |     // is a TableModelColumn for each column. | 
| 285 |     // Collect and cache metadata for each column. This makes data lookup faster. | 
| 286 |     for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { | 
| 287 |         QQmlTableModelColumn *column = mColumns.at(i: columnIndex); | 
| 288 |         qCDebug(lcTableModel).nospace() << "- column "  << columnIndex << ":" ; | 
| 289 |  | 
| 290 |         ColumnMetadata metaData; | 
| 291 |         const auto builtInRoleKeys = supportedRoleNames.keys(); | 
| 292 |         for (const int builtInRoleKey : builtInRoleKeys) { | 
| 293 |             const QString builtInRoleName = supportedRoleNames.value(key: builtInRoleKey); | 
| 294 |             ColumnRoleMetadata roleData = fetchColumnRoleData(roleNameKey: builtInRoleName, tableModelColumn: column, columnIndex); | 
| 295 |             if (roleData.type == QMetaType::UnknownType) { | 
| 296 |                 // This built-in role was not specified in this column. | 
| 297 |                 continue; | 
| 298 |             } | 
| 299 |  | 
| 300 |             qCDebug(lcTableModel).nospace() << "  - added metadata for built-in role "  | 
| 301 |                 << builtInRoleName << " at column index "  << columnIndex | 
| 302 |                 << ": name="  << roleData.name << " typeName="  << roleData.typeName | 
| 303 |                 << " type="  << roleData.type; | 
| 304 |  | 
| 305 |             // This column now supports this specific built-in role. | 
| 306 |             metaData.roles.insert(key: builtInRoleName, value: roleData); | 
| 307 |             // Add it if it doesn't already exist. | 
| 308 |             mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1(); | 
| 309 |         } | 
| 310 |         mColumnMetadata.insert(i: columnIndex, t: metaData); | 
| 311 |     } | 
| 312 | } | 
| 313 |  | 
| 314 | /*! | 
| 315 |     \qmlmethod TableModel::appendRow(object row) | 
| 316 |  | 
| 317 |     Adds a new row to the end of the model, with the | 
| 318 |     values (cells) in \a row. | 
| 319 |  | 
| 320 |     \code | 
| 321 |         model.appendRow({ | 
| 322 |             checkable: true, | 
| 323 |             amount: 1, | 
| 324 |             fruitType: "Pear", | 
| 325 |             fruitName: "Williams", | 
| 326 |             fruitPrice: 1.50, | 
| 327 |         }) | 
| 328 |     \endcode | 
| 329 |  | 
| 330 |     \sa insertRow(), setRow(), removeRow() | 
| 331 | */ | 
| 332 | void QQmlTableModel::appendRow(const QVariant &row) | 
| 333 | { | 
| 334 |     if (!validateNewRow(functionName: "appendRow()" , row, rowIndex: -1, operation: AppendOperation)) | 
| 335 |         return; | 
| 336 |  | 
| 337 |     doInsert(rowIndex: mRowCount, row); | 
| 338 | } | 
| 339 |  | 
| 340 | /*! | 
| 341 |     \qmlmethod TableModel::clear() | 
| 342 |  | 
| 343 |     Removes all rows from the model. | 
| 344 |  | 
| 345 |     \sa removeRow() | 
| 346 | */ | 
| 347 | void QQmlTableModel::clear() | 
| 348 | { | 
| 349 |     QQmlEngine *engine = qmlEngine(this); | 
| 350 |     Q_ASSERT(engine); | 
| 351 |     setRows(QVariant::fromValue(value: engine->newArray())); | 
| 352 | } | 
| 353 |  | 
| 354 | /*! | 
| 355 |     \qmlmethod object TableModel::getRow(int rowIndex) | 
| 356 |  | 
| 357 |     Returns the row at \a rowIndex in the model. | 
| 358 |  | 
| 359 |     Note that this equivalent to accessing the row directly | 
| 360 |     through the \l rows property: | 
| 361 |  | 
| 362 |     \code | 
| 363 |     Component.onCompleted: { | 
| 364 |         // These two lines are equivalent. | 
| 365 |         console.log(model.getRow(0).display); | 
| 366 |         console.log(model.rows[0].fruitName); | 
| 367 |     } | 
| 368 |     \endcode | 
| 369 |  | 
| 370 |     \note the returned object cannot be used to modify the contents of the | 
| 371 |     model; use setRow() instead. | 
| 372 |  | 
| 373 |     \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() | 
| 374 | */ | 
| 375 | QVariant QQmlTableModel::getRow(int rowIndex) | 
| 376 | { | 
| 377 |     if (!validateRowIndex(functionName: "getRow()" , argumentName: "rowIndex" , rowIndex)) | 
| 378 |         return QVariant(); | 
| 379 |  | 
| 380 |     return mRows.at(i: rowIndex); | 
| 381 | } | 
| 382 |  | 
| 383 | /*! | 
| 384 |     \qmlmethod TableModel::insertRow(int rowIndex, object row) | 
| 385 |  | 
| 386 |     Adds a new row to the list model at position \a rowIndex, with the | 
| 387 |     values (cells) in \a row. | 
| 388 |  | 
| 389 |     \code | 
| 390 |         model.insertRow(2, { | 
| 391 |             checkable: true, checked: false, | 
| 392 |             amount: 1, | 
| 393 |             fruitType: "Pear", | 
| 394 |             fruitName: "Williams", | 
| 395 |             fruitPrice: 1.50, | 
| 396 |         }) | 
| 397 |     \endcode | 
| 398 |  | 
| 399 |     The \a rowIndex must be to an existing item in the list, or one past | 
| 400 |     the end of the list (equivalent to \l appendRow()). | 
| 401 |  | 
| 402 |     \sa appendRow(), setRow(), removeRow(), rowCount | 
| 403 | */ | 
| 404 | void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) | 
| 405 | { | 
| 406 |     if (!validateNewRow(functionName: "insertRow()" , row, rowIndex)) | 
| 407 |         return; | 
| 408 |  | 
| 409 |     doInsert(rowIndex, row); | 
| 410 | } | 
| 411 |  | 
| 412 | void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) | 
| 413 | { | 
| 414 |     beginInsertRows(parent: QModelIndex(), first: rowIndex, last: rowIndex); | 
| 415 |  | 
| 416 |     // Adding rowAsVariant.toList() will add each invidual variant in the list, | 
| 417 |     // which is definitely not what we want. | 
| 418 |     const QVariant rowAsVariant = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant(); | 
| 419 |  | 
| 420 |     mRows.insert(i: rowIndex, t: rowAsVariant); | 
| 421 |     ++mRowCount; | 
| 422 |  | 
| 423 |     qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index "  | 
| 424 |         << rowIndex << ":\n"  << rowAsVariant.toMap(); | 
| 425 |  | 
| 426 |     // Gather metadata the first time a row is added. | 
| 427 |     if (mColumnMetadata.isEmpty()) | 
| 428 |         fetchColumnMetadata(); | 
| 429 |  | 
| 430 |     endInsertRows(); | 
| 431 |     emit rowCountChanged(); | 
| 432 | } | 
| 433 |  | 
| 434 | void QQmlTableModel::classBegin() | 
| 435 | { | 
| 436 | } | 
| 437 |  | 
| 438 | void QQmlTableModel::componentComplete() | 
| 439 | { | 
| 440 |     componentCompleted = true; | 
| 441 |  | 
| 442 |     mColumnCount = mColumns.size(); | 
| 443 |     if (mColumnCount > 0) | 
| 444 |         emit columnCountChanged(); | 
| 445 |  | 
| 446 |     doSetRows(rowsAsVariantList: mRows); | 
| 447 | } | 
| 448 |  | 
| 449 | /*! | 
| 450 |     \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) | 
| 451 |  | 
| 452 |     Moves \a rows from the index at \a fromRowIndex to the index at | 
| 453 |     \a toRowIndex. | 
| 454 |  | 
| 455 |     The from and to ranges must exist; for example, to move the first 3 items | 
| 456 |     to the end of the list: | 
| 457 |  | 
| 458 |     \code | 
| 459 |         model.moveRow(0, model.rowCount - 3, 3) | 
| 460 |     \endcode | 
| 461 |  | 
| 462 |     \sa appendRow(), insertRow(), removeRow(), rowCount | 
| 463 | */ | 
| 464 | void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) | 
| 465 | { | 
| 466 |     if (fromRowIndex == toRowIndex) { | 
| 467 |         qmlWarning(me: this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\"" ; | 
| 468 |         return; | 
| 469 |     } | 
| 470 |  | 
| 471 |     if (rows <= 0) { | 
| 472 |         qmlWarning(me: this) << "moveRow(): \"rows\" is less than or equal to 0" ; | 
| 473 |         return; | 
| 474 |     } | 
| 475 |  | 
| 476 |     if (!validateRowIndex(functionName: "moveRow()" , argumentName: "fromRowIndex" , rowIndex: fromRowIndex)) | 
| 477 |         return; | 
| 478 |  | 
| 479 |     if (!validateRowIndex(functionName: "moveRow()" , argumentName: "toRowIndex" , rowIndex: toRowIndex)) | 
| 480 |         return; | 
| 481 |  | 
| 482 |     if (fromRowIndex + rows > mRowCount) { | 
| 483 |         qmlWarning(me: this) << "moveRow(): \"fromRowIndex\" ("  << fromRowIndex | 
| 484 |             << ") + \"rows\" ("  << rows << ") = "  << (fromRowIndex + rows) | 
| 485 |             << ", which is greater than rowCount() of "  << mRowCount; | 
| 486 |         return; | 
| 487 |     } | 
| 488 |  | 
| 489 |     if (toRowIndex + rows > mRowCount) { | 
| 490 |         qmlWarning(me: this) << "moveRow(): \"toRowIndex\" ("  << toRowIndex | 
| 491 |             << ") + \"rows\" ("  << rows << ") = "  << (toRowIndex + rows) | 
| 492 |             << ", which is greater than rowCount() of "  << mRowCount; | 
| 493 |         return; | 
| 494 |     } | 
| 495 |  | 
| 496 |     qCDebug(lcTableModel).nospace() << "moving "  << rows | 
| 497 |         << " row(s) from index "  << fromRowIndex | 
| 498 |         << " to index "  << toRowIndex; | 
| 499 |  | 
| 500 |     // Based on the same call in QQmlListModel::moveRow(). | 
| 501 |     beginMoveRows(sourceParent: QModelIndex(), sourceFirst: fromRowIndex, sourceLast: fromRowIndex + rows - 1, destinationParent: QModelIndex(), | 
| 502 |         destinationRow: toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); | 
| 503 |  | 
| 504 |     // Based on ListModel::moveRow(). | 
| 505 |     if (fromRowIndex > toRowIndex) { | 
| 506 |         // Only move forwards - flip if moving backwards. | 
| 507 |         const int from = fromRowIndex; | 
| 508 |         const int to = toRowIndex; | 
| 509 |         fromRowIndex = to; | 
| 510 |         toRowIndex = to + rows; | 
| 511 |         rows = from - to; | 
| 512 |     } | 
| 513 |  | 
| 514 |     QVector<QVariant> store; | 
| 515 |     store.reserve(size: rows); | 
| 516 |     for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) | 
| 517 |         store.append(t: mRows.at(i: fromRowIndex + rows + i)); | 
| 518 |     for (int i = 0; i < rows; ++i) | 
| 519 |         store.append(t: mRows.at(i: fromRowIndex + i)); | 
| 520 |     for (int i = 0; i < store.size(); ++i) | 
| 521 |         mRows[fromRowIndex + i] = store[i]; | 
| 522 |  | 
| 523 |     qCDebug(lcTableModel).nospace() << "after moving, rows are:\n"  << mRows; | 
| 524 |  | 
| 525 |     endMoveRows(); | 
| 526 | } | 
| 527 |  | 
| 528 | /*! | 
| 529 |     \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) | 
| 530 |  | 
| 531 |     Removes a number of \a rows at \a rowIndex from the model. | 
| 532 |  | 
| 533 |     \sa clear(), rowCount | 
| 534 | */ | 
| 535 | void QQmlTableModel::removeRow(int rowIndex, int rows) | 
| 536 | { | 
| 537 |     if (!validateRowIndex(functionName: "removeRow()" , argumentName: "rowIndex" , rowIndex)) | 
| 538 |         return; | 
| 539 |  | 
| 540 |     if (rows <= 0) { | 
| 541 |         qmlWarning(me: this) << "removeRow(): \"rows\" is less than or equal to zero" ; | 
| 542 |         return; | 
| 543 |     } | 
| 544 |  | 
| 545 |     if (rowIndex + rows - 1 >= mRowCount) { | 
| 546 |         qmlWarning(me: this) << "removeRow(): \"rows\" "  << rows | 
| 547 |             << " exceeds available rowCount() of "  << mRowCount | 
| 548 |             << " when removing from \"rowIndex\" "  << rowIndex; | 
| 549 |         return; | 
| 550 |     } | 
| 551 |  | 
| 552 |     beginRemoveRows(parent: QModelIndex(), first: rowIndex, last: rowIndex + rows - 1); | 
| 553 |  | 
| 554 |     auto firstIterator = mRows.begin() + rowIndex; | 
| 555 |     // The "last" argument to erase() is exclusive, so we go one past the last item. | 
| 556 |     auto lastIterator = firstIterator + rows; | 
| 557 |     mRows.erase(begin: firstIterator, end: lastIterator); | 
| 558 |     mRowCount -= rows; | 
| 559 |  | 
| 560 |     endRemoveRows(); | 
| 561 |     emit rowCountChanged(); | 
| 562 |  | 
| 563 |     qCDebug(lcTableModel).nospace() << "removed "  << rows | 
| 564 |         << " items from the model, starting at index "  << rowIndex; | 
| 565 | } | 
| 566 |  | 
| 567 | /*! | 
| 568 |     \qmlmethod TableModel::setRow(int rowIndex, object row) | 
| 569 |  | 
| 570 |     Changes the row at \a rowIndex in the model with \a row. | 
| 571 |  | 
| 572 |     All columns/cells must be present in \c row, and in the correct order. | 
| 573 |  | 
| 574 |     \code | 
| 575 |         model.setRow(0, { | 
| 576 |             checkable: true, | 
| 577 |             amount: 1, | 
| 578 |             fruitType: "Pear", | 
| 579 |             fruitName: "Williams", | 
| 580 |             fruitPrice: 1.50, | 
| 581 |         }) | 
| 582 |     \endcode | 
| 583 |  | 
| 584 |     If \a rowIndex is equal to \c rowCount(), then a new row is appended to the | 
| 585 |     model. Otherwise, \a rowIndex must point to an existing row in the model. | 
| 586 |  | 
| 587 |     \sa appendRow(), insertRow(), rowCount | 
| 588 | */ | 
| 589 | void QQmlTableModel::setRow(int rowIndex, const QVariant &row) | 
| 590 | { | 
| 591 |     if (!validateNewRow(functionName: "setRow()" , row, rowIndex)) | 
| 592 |         return; | 
| 593 |  | 
| 594 |     if (rowIndex != mRowCount) { | 
| 595 |         // Setting an existing row. | 
| 596 |         mRows[rowIndex] = row; | 
| 597 |  | 
| 598 |         // For now we just assume the whole row changed, as it's simpler. | 
| 599 |         const QModelIndex topLeftModelIndex(createIndex(arow: rowIndex, acolumn: 0)); | 
| 600 |         const QModelIndex bottomRightModelIndex(createIndex(arow: rowIndex, acolumn: mColumnCount - 1)); | 
| 601 |         emit dataChanged(topLeft: topLeftModelIndex, bottomRight: bottomRightModelIndex); | 
| 602 |     } else { | 
| 603 |         // Appending a row. | 
| 604 |         doInsert(rowIndex, row); | 
| 605 |     } | 
| 606 | } | 
| 607 |  | 
| 608 | QQmlListProperty<QQmlTableModelColumn> QQmlTableModel::columns() | 
| 609 | { | 
| 610 |     return QQmlListProperty<QQmlTableModelColumn>(this, nullptr, | 
| 611 |         &QQmlTableModel::columns_append, | 
| 612 |         &QQmlTableModel::columns_count, | 
| 613 |         &QQmlTableModel::columns_at, | 
| 614 |         &QQmlTableModel::columns_clear, | 
| 615 |         &QQmlTableModel::columns_replace, | 
| 616 |         &QQmlTableModel::columns_removeLast); | 
| 617 | } | 
| 618 |  | 
| 619 | void QQmlTableModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property, | 
| 620 |     QQmlTableModelColumn *value) | 
| 621 | { | 
| 622 |     QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 623 |     QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(object: value); | 
| 624 |     if (column) | 
| 625 |         model->mColumns.append(t: column); | 
| 626 | } | 
| 627 |  | 
| 628 | qsizetype QQmlTableModel::columns_count(QQmlListProperty<QQmlTableModelColumn> *property) | 
| 629 | { | 
| 630 |     const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 631 |     return model->mColumns.size(); | 
| 632 | } | 
| 633 |  | 
| 634 | QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty<QQmlTableModelColumn> *property, qsizetype index) | 
| 635 | { | 
| 636 |     const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 637 |     return model->mColumns.at(i: index); | 
| 638 | } | 
| 639 |  | 
| 640 | void QQmlTableModel::columns_clear(QQmlListProperty<QQmlTableModelColumn> *property) | 
| 641 | { | 
| 642 |     QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 643 |     return model->mColumns.clear(); | 
| 644 | } | 
| 645 |  | 
| 646 | void QQmlTableModel::columns_replace(QQmlListProperty<QQmlTableModelColumn> *property, qsizetype index, QQmlTableModelColumn *value) | 
| 647 | { | 
| 648 |     QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 649 |     if (QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(object: value)) | 
| 650 |         return model->mColumns.replace(i: index, t: column); | 
| 651 | } | 
| 652 |  | 
| 653 | void QQmlTableModel::columns_removeLast(QQmlListProperty<QQmlTableModelColumn> *property) | 
| 654 | { | 
| 655 |     QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); | 
| 656 |     model->mColumns.removeLast(); | 
| 657 | } | 
| 658 |  | 
| 659 | /*! | 
| 660 |     \qmlmethod QModelIndex TableModel::index(int row, int column) | 
| 661 |  | 
| 662 |     Returns a \l QModelIndex object referencing the given \a row and \a column, | 
| 663 |     which can be passed to the data() function to get the data from that cell, | 
| 664 |     or to setData() to edit the contents of that cell. | 
| 665 |  | 
| 666 |     \code | 
| 667 |     import QtQml 2.14 | 
| 668 |     import Qt.labs.qmlmodels 1.0 | 
| 669 |  | 
| 670 |     TableModel { | 
| 671 |         id: model | 
| 672 |  | 
| 673 |         TableModelColumn { display: "fruitType" } | 
| 674 |         TableModelColumn { display: "fruitPrice" } | 
| 675 |  | 
| 676 |         rows: [ | 
| 677 |             { fruitType: "Apple", fruitPrice: 1.50 }, | 
| 678 |             { fruitType: "Orange", fruitPrice: 2.50 } | 
| 679 |         ] | 
| 680 |  | 
| 681 |         Component.onCompleted: { | 
| 682 |             for (var r = 0; r < model.rowCount; ++r) { | 
| 683 |                 console.log("An " + model.data(model.index(r, 0)).display + | 
| 684 |                             " costs " + model.data(model.index(r, 1)).display.toFixed(2)) | 
| 685 |             } | 
| 686 |         } | 
| 687 |     } | 
| 688 |     \endcode | 
| 689 |  | 
| 690 |     \sa {QModelIndex and related Classes in QML}, data() | 
| 691 | */ | 
| 692 | // Note: we don't document the parent argument, because you never need it, because | 
| 693 | // cells in a TableModel don't have parents.  But it is there because this function is an override. | 
| 694 | QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const | 
| 695 | { | 
| 696 |     return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() | 
| 697 |         ? createIndex(arow: row, acolumn: column) | 
| 698 |         : QModelIndex(); | 
| 699 | } | 
| 700 |  | 
| 701 | /*! | 
| 702 |     \qmlproperty int TableModel::rowCount | 
| 703 |     \readonly | 
| 704 |  | 
| 705 |     This read-only property holds the number of rows in the model. | 
| 706 |  | 
| 707 |     This value changes whenever rows are added or removed from the model. | 
| 708 | */ | 
| 709 | int QQmlTableModel::rowCount(const QModelIndex &parent) const | 
| 710 | { | 
| 711 |     if (parent.isValid()) | 
| 712 |         return 0; | 
| 713 |  | 
| 714 |     return mRowCount; | 
| 715 | } | 
| 716 |  | 
| 717 | /*! | 
| 718 |     \qmlproperty int TableModel::columnCount | 
| 719 |     \readonly | 
| 720 |  | 
| 721 |     This read-only property holds the number of columns in the model. | 
| 722 |  | 
| 723 |     The number of columns is fixed for the lifetime of the model | 
| 724 |     after the \l rows property is set or \l appendRow() is called for the first | 
| 725 |     time. | 
| 726 | */ | 
| 727 | int QQmlTableModel::columnCount(const QModelIndex &parent) const | 
| 728 | { | 
| 729 |     if (parent.isValid()) | 
| 730 |         return 0; | 
| 731 |  | 
| 732 |     return mColumnCount; | 
| 733 | } | 
| 734 |  | 
| 735 | /*! | 
| 736 |     \qmlmethod variant TableModel::data(QModelIndex index, string role) | 
| 737 |  | 
| 738 |     Returns the data from the table cell at the given \a index belonging to the | 
| 739 |     given \a role. | 
| 740 |  | 
| 741 |     \sa index() | 
| 742 | */ | 
| 743 | QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const | 
| 744 | { | 
| 745 |     const int iRole = mRoleNames.key(value: role.toUtf8(), defaultKey: -1); | 
| 746 |     if (iRole >= 0) | 
| 747 |         return data(index, role: iRole); | 
| 748 |     return QVariant(); | 
| 749 | } | 
| 750 |  | 
| 751 | QVariant QQmlTableModel::data(const QModelIndex &index, int role) const | 
| 752 | { | 
| 753 |     const int row = index.row(); | 
| 754 |     if (row < 0 || row >= rowCount()) | 
| 755 |         return QVariant(); | 
| 756 |  | 
| 757 |     const int column = index.column(); | 
| 758 |     if (column < 0 || column >= columnCount()) | 
| 759 |         return QVariant(); | 
| 760 |  | 
| 761 |     const ColumnMetadata columnMetadata = mColumnMetadata.at(i: index.column()); | 
| 762 |     const QString roleName = QString::fromUtf8(ba: mRoleNames.value(key: role)); | 
| 763 |     if (!columnMetadata.roles.contains(key: roleName)) { | 
| 764 |         qmlWarning(me: this) << "setData(): no role named "  << roleName | 
| 765 |             << " at column index "  << column << ". The available roles for that column are: "  | 
| 766 |             << columnMetadata.roles.keys(); | 
| 767 |         return QVariant(); | 
| 768 |     } | 
| 769 |  | 
| 770 |     const ColumnRoleMetadata roleData = columnMetadata.roles.value(key: roleName); | 
| 771 |     if (roleData.isStringRole) { | 
| 772 |         // We know the data structure, so we can get the data for the user. | 
| 773 |         const QVariantMap rowData = mRows.at(i: row).toMap(); | 
| 774 |         const QString propertyName = columnMetadata.roles.value(key: roleName).name; | 
| 775 |         const QVariant value = rowData.value(key: propertyName); | 
| 776 |         return value; | 
| 777 |     } | 
| 778 |  | 
| 779 |     // We don't know the data structure, so the user has to modify their data themselves. | 
| 780 |     // First, find the getter for this column and role. | 
| 781 |     QJSValue getter = mColumns.at(i: column)->getterAtRole(roleName); | 
| 782 |  | 
| 783 |     // Then, call it and return what it returned. | 
| 784 |     const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(value: index); | 
| 785 |     return getter.call(args).toVariant(); | 
| 786 | } | 
| 787 |  | 
| 788 | /*! | 
| 789 |     \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value) | 
| 790 |  | 
| 791 |     Inserts or updates the data field named by \a role in the table cell at the | 
| 792 |     given \a index with \a value. Returns true if sucessful, false if not. | 
| 793 |  | 
| 794 |     \sa index() | 
| 795 | */ | 
| 796 | bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) | 
| 797 | { | 
| 798 |     const int intRole = mRoleNames.key(value: role.toUtf8(), defaultKey: -1); | 
| 799 |     if (intRole >= 0) | 
| 800 |         return setData(index, value, role: intRole); | 
| 801 |     return false; | 
| 802 | } | 
| 803 |  | 
| 804 | bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) | 
| 805 | { | 
| 806 |     const int row = index.row(); | 
| 807 |     if (row < 0 || row >= rowCount()) | 
| 808 |         return false; | 
| 809 |  | 
| 810 |     const int column = index.column(); | 
| 811 |     if (column < 0 || column >= columnCount()) | 
| 812 |         return false; | 
| 813 |  | 
| 814 |     const QString roleName = QString::fromUtf8(ba: mRoleNames.value(key: role)); | 
| 815 |  | 
| 816 |     qCDebug(lcTableModel).nospace() << "setData() called with index "  | 
| 817 |         << index << ", value "  << value << " and role "  << roleName; | 
| 818 |  | 
| 819 |     // Verify that the role exists for this column. | 
| 820 |     const ColumnMetadata columnMetadata = mColumnMetadata.at(i: index.column()); | 
| 821 |     if (!columnMetadata.roles.contains(key: roleName)) { | 
| 822 |         qmlWarning(me: this) << "setData(): no role named \""  << roleName | 
| 823 |             << "\" at column index "  << column << ". The available roles for that column are: "  | 
| 824 |             << columnMetadata.roles.keys(); | 
| 825 |         return false; | 
| 826 |     } | 
| 827 |  | 
| 828 |     // Verify that the type of the value is what we expect. | 
| 829 |     // If the value set is not of the expected type, we can try to convert it automatically. | 
| 830 |     const ColumnRoleMetadata roleData = columnMetadata.roles.value(key: roleName); | 
| 831 |     QVariant effectiveValue = value; | 
| 832 |     if (value.userType() != roleData.type) { | 
| 833 |         if (!value.canConvert(targetType: QMetaType(roleData.type))) { | 
| 834 |             qmlWarning(me: this).nospace() << "setData(): the value "  << value | 
| 835 |                 << " set at row "  << row << " column "  << column << " with role "  << roleName | 
| 836 |                 << " cannot be converted to "  << roleData.typeName; | 
| 837 |             return false; | 
| 838 |         } | 
| 839 |  | 
| 840 |         if (!effectiveValue.convert(type: QMetaType(roleData.type))) { | 
| 841 |             qmlWarning(me: this).nospace() << "setData(): failed converting value "  << value | 
| 842 |                 << " set at row "  << row << " column "  << column << " with role "  << roleName | 
| 843 |                 << " to "  << roleData.typeName; | 
| 844 |             return false; | 
| 845 |         } | 
| 846 |     } | 
| 847 |  | 
| 848 |     if (roleData.isStringRole) { | 
| 849 |         // We know the data structure, so we can set it for the user. | 
| 850 |         QVariantMap modifiedRow = mRows.at(i: row).toMap(); | 
| 851 |         modifiedRow[roleData.name] = value; | 
| 852 |  | 
| 853 |         mRows[row] = modifiedRow; | 
| 854 |     } else { | 
| 855 |         // We don't know the data structure, so the user has to modify their data themselves. | 
| 856 |         auto engine = qmlEngine(this); | 
| 857 |         auto args = QJSValueList() | 
| 858 |             // arg 0: modelIndex. | 
| 859 |             << engine->toScriptValue(value: index) | 
| 860 |             // arg 1: cellData. | 
| 861 |             << engine->toScriptValue(value); | 
| 862 |         // Do the actual setting. | 
| 863 |         QJSValue setter = mColumns.at(i: column)->setterAtRole(roleName); | 
| 864 |         setter.call(args); | 
| 865 |  | 
| 866 |         /* | 
| 867 |             The chain of events so far: | 
| 868 |  | 
| 869 |             - User did e.g.: model.edit = textInput.text | 
| 870 |               - setData() is called | 
| 871 |                 - setData() calls the setter | 
| 872 |                   (remember that we need to emit the dataChanged() signal, | 
| 873 |                    which is why the user can't just set the data directly in the delegate) | 
| 874 |  | 
| 875 |             Now the user's setter function has modified *their* copy of the | 
| 876 |             data, but *our* copy of the data is old. Imagine the getters and setters looked like this: | 
| 877 |  | 
| 878 |             display: function(modelIndex) { return rows[modelIndex.row][1].amount } | 
| 879 |             setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } | 
| 880 |  | 
| 881 |             We don't know the structure of the user's data, so we can't just do | 
| 882 |             what we do above for the isStringRole case: | 
| 883 |  | 
| 884 |             modifiedRow[column][roleName] = value | 
| 885 |  | 
| 886 |             This means that, besides getting the implicit row count when rows is initially set, | 
| 887 |             our copy of the data is unused when it comes to complex columns. | 
| 888 |  | 
| 889 |             Another point to note is that we can't pass rowData in to the getter as a convenience, | 
| 890 |             because we would be passing in *our* copy of the row, which is not up-to-date. | 
| 891 |             Since the user already has access to the data, it's not a big deal for them to do: | 
| 892 |  | 
| 893 |             display: function(modelIndex) { return rows[modelIndex.row][1].amount } | 
| 894 |  | 
| 895 |             instead of: | 
| 896 |  | 
| 897 |             display: function(modelIndex, rowData) { return rowData[1].amount } | 
| 898 |         */ | 
| 899 |     } | 
| 900 |  | 
| 901 |     QVector<int> rolesChanged; | 
| 902 |     rolesChanged.append(t: role); | 
| 903 |     emit dataChanged(topLeft: index, bottomRight: index, roles: rolesChanged); | 
| 904 |  | 
| 905 |     return true; | 
| 906 | } | 
| 907 |  | 
| 908 | QHash<int, QByteArray> QQmlTableModel::roleNames() const | 
| 909 | { | 
| 910 |     return mRoleNames; | 
| 911 | } | 
| 912 |  | 
| 913 | QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() | 
| 914 | { | 
| 915 | } | 
| 916 |  | 
| 917 | QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( | 
| 918 |     bool isStringRole, const QString &name, int type, const QString &typeName) : | 
| 919 |     isStringRole(isStringRole), | 
| 920 |     name(name), | 
| 921 |     type(type), | 
| 922 |     typeName(typeName) | 
| 923 | { | 
| 924 | } | 
| 925 |  | 
| 926 | bool QQmlTableModel::ColumnRoleMetadata::isValid() const | 
| 927 | { | 
| 928 |     return !name.isEmpty(); | 
| 929 | } | 
| 930 |  | 
| 931 | bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const | 
| 932 | { | 
| 933 |     if (!row.canConvert<QJSValue>()) { | 
| 934 |         qmlWarning(me: this) << functionName << ": expected \"row\" argument to be a QJSValue,"  | 
| 935 |             << " but got "  << row.typeName() << " instead:\n"  << row; | 
| 936 |         return false; | 
| 937 |     } | 
| 938 |  | 
| 939 |     const QJSValue rowAsJSValue = row.value<QJSValue>(); | 
| 940 |     if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { | 
| 941 |         qmlWarning(me: this) << functionName << ": expected \"row\" argument "  | 
| 942 |             << "to be an object or array, but got:\n"  << rowAsJSValue.toString(); | 
| 943 |         return false; | 
| 944 |     } | 
| 945 |  | 
| 946 |     return true; | 
| 947 | } | 
| 948 |  | 
| 949 | bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, | 
| 950 |     int rowIndex, NewRowOperationFlag operation) const | 
| 951 | { | 
| 952 |     if (mColumnMetadata.isEmpty()) { | 
| 953 |         // There is no column metadata, so we have nothing to validate the row against. | 
| 954 |         // Rows have to be added before we can gather metadata from them, so just this | 
| 955 |         // once we'll return true to allow the rows to be added. | 
| 956 |         return true; | 
| 957 |     } | 
| 958 |  | 
| 959 |     const bool isVariantMap = (row.userType() == QMetaType::QVariantMap); | 
| 960 |  | 
| 961 |     // Don't require each row to be a QJSValue when setting all rows, | 
| 962 |     // as they won't be; they'll be QVariantMap. | 
| 963 |     if (operation != SetRowsOperation && (!isVariantMap && !validateRowType(functionName, row))) | 
| 964 |         return false; | 
| 965 |  | 
| 966 |     if (operation == OtherOperation) { | 
| 967 |         // Inserting/setting. | 
| 968 |         if (rowIndex < 0) { | 
| 969 |             qmlWarning(me: this) << functionName << ": \"rowIndex\" cannot be negative" ; | 
| 970 |             return false; | 
| 971 |         } | 
| 972 |  | 
| 973 |         if (rowIndex > mRowCount) { | 
| 974 |             qmlWarning(me: this) << functionName << ": \"rowIndex\" "  << rowIndex | 
| 975 |                 << " is greater than rowCount() of "  << mRowCount; | 
| 976 |             return false; | 
| 977 |         } | 
| 978 |     } | 
| 979 |  | 
| 980 |     const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap | 
| 981 |         ? row : row.value<QJSValue>().toVariant(); | 
| 982 |     if (rowAsVariant.userType() != QMetaType::QVariantMap) { | 
| 983 |         qmlWarning(me: this) << functionName << ": row manipulation functions "  | 
| 984 |             << "do not support complex rows (row index: "  << rowIndex << ")" ; | 
| 985 |         return false; | 
| 986 |     } | 
| 987 |  | 
| 988 |     const QVariantMap rowAsMap = rowAsVariant.toMap(); | 
| 989 |     const int columnCount = rowAsMap.size(); | 
| 990 |     if (columnCount < mColumnCount) { | 
| 991 |         qmlWarning(me: this) << functionName << ": expected "  << mColumnCount | 
| 992 |             << " columns, but only got "  << columnCount; | 
| 993 |         return false; | 
| 994 |     } | 
| 995 |  | 
| 996 |     // We can't validate complex structures, but we can make sure that | 
| 997 |     // each simple string-based role in each column is correct. | 
| 998 |     for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { | 
| 999 |         QQmlTableModelColumn *column = mColumns.at(i: columnIndex); | 
| 1000 |         const QHash<QString, QJSValue> getters = column->getters(); | 
| 1001 |         const auto roleNames = getters.keys(); | 
| 1002 |         const ColumnMetadata columnMetadata = mColumnMetadata.at(i: columnIndex); | 
| 1003 |         for (const QString &roleName : roleNames) { | 
| 1004 |             const ColumnRoleMetadata roleData = columnMetadata.roles.value(key: roleName); | 
| 1005 |             if (!roleData.isStringRole) | 
| 1006 |                 continue; | 
| 1007 |  | 
| 1008 |             if (!rowAsMap.contains(key: roleData.name)) { | 
| 1009 |                 qmlWarning(me: this).quote() << functionName << ": expected a property named "  | 
| 1010 |                     << roleData.name << " in row at index "  << rowIndex << ", but couldn't find one" ; | 
| 1011 |                 return false; | 
| 1012 |             } | 
| 1013 |  | 
| 1014 |             const QVariant rolePropertyValue = rowAsMap.value(key: roleData.name); | 
| 1015 |  | 
| 1016 |             if (rolePropertyValue.userType() != roleData.type) { | 
| 1017 |                 if (!rolePropertyValue.canConvert(targetType: QMetaType(roleData.type))) { | 
| 1018 |                     qmlWarning(me: this).quote() << functionName << ": expected the property named "  | 
| 1019 |                         << roleData.name << " to be of type "  << roleData.typeName | 
| 1020 |                         << ", but got "  << QString::fromLatin1(ba: rolePropertyValue.typeName()) | 
| 1021 |                         << " instead" ; | 
| 1022 |                     return false; | 
| 1023 |                 } | 
| 1024 |  | 
| 1025 |                 QVariant effectiveValue = rolePropertyValue; | 
| 1026 |                 if (!effectiveValue.convert(type: QMetaType(roleData.type))) { | 
| 1027 |                     qmlWarning(me: this).nospace() << functionName << ": failed converting value "  | 
| 1028 |                         << rolePropertyValue << " set at column "  << columnIndex << " with role "  | 
| 1029 |                         << QString::fromLatin1(ba: rolePropertyValue.typeName()) << " to "  | 
| 1030 |                         << roleData.typeName; | 
| 1031 |                     return false; | 
| 1032 |                 } | 
| 1033 |             } | 
| 1034 |         } | 
| 1035 |     } | 
| 1036 |  | 
| 1037 |     return true; | 
| 1038 | } | 
| 1039 |  | 
| 1040 | bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const | 
| 1041 | { | 
| 1042 |     if (rowIndex < 0) { | 
| 1043 |         qmlWarning(me: this) << functionName << ": \""  << argumentName << "\" cannot be negative" ; | 
| 1044 |         return false; | 
| 1045 |     } | 
| 1046 |  | 
| 1047 |     if (rowIndex >= mRowCount) { | 
| 1048 |         qmlWarning(me: this) << functionName << ": \""  << argumentName | 
| 1049 |             << "\" "  << rowIndex << " is greater than or equal to rowCount() of "  << mRowCount; | 
| 1050 |         return false; | 
| 1051 |     } | 
| 1052 |  | 
| 1053 |     return true; | 
| 1054 | } | 
| 1055 |  | 
| 1056 | Qt::ItemFlags QQmlTableModel::flags(const QModelIndex &index) const | 
| 1057 | { | 
| 1058 |     Q_UNUSED(index) | 
| 1059 |     return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; | 
| 1060 | } | 
| 1061 |  | 
| 1062 | QT_END_NAMESPACE | 
| 1063 |  | 
| 1064 | #include "moc_qqmltablemodel_p.cpp" | 
| 1065 |  |