| 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 | |
| 8 | #include <QtQml/qqmlinfo.h> |
| 9 | #include <QtQml/qqmlengine.h> |
| 10 | |
| 11 | QT_BEGIN_NAMESPACE |
| 12 | |
| 13 | using namespace Qt::StringLiterals; |
| 14 | |
| 15 | Q_STATIC_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel" ) |
| 16 | |
| 17 | /*! |
| 18 | \qmltype TableModel |
| 19 | //! \nativetype QQmlTableModel |
| 20 | \inqmlmodule Qt.labs.qmlmodels |
| 21 | \brief Encapsulates a simple table model. |
| 22 | \since 5.14 |
| 23 | |
| 24 | The TableModel type stores JavaScript/JSON objects as data for a table |
| 25 | model that can be used with \l TableView. It is intended to support |
| 26 | very simple models without requiring the creation of a custom |
| 27 | QAbstractTableModel subclass in C++. |
| 28 | |
| 29 | \snippet qml/tablemodel/fruit-example-simpledelegate.qml file |
| 30 | |
| 31 | The model's initial row data is set with either the \l rows property or by |
| 32 | calling \l appendRow(). Each column in the model is specified by declaring |
| 33 | a \l TableModelColumn instance, where the order of each instance determines |
| 34 | its column index. Once the model's \l Component::completed() signal has been |
| 35 | emitted, the columns and roles will have been established and are then |
| 36 | fixed for the lifetime of the model. |
| 37 | |
| 38 | To access a specific row, the \l getRow() function can be used. |
| 39 | It's also possible to access the model's JavaScript data |
| 40 | directly via the \l rows property, but it is not possible to |
| 41 | modify the model data this way. |
| 42 | |
| 43 | To add new rows, use \l appendRow() and \l insertRow(). To modify |
| 44 | existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and |
| 45 | \l clear(). |
| 46 | |
| 47 | It is also possible to modify the model's data via the delegate, |
| 48 | as shown in the example above: |
| 49 | |
| 50 | \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate |
| 51 | |
| 52 | If the type of the data at the modified role does not match the type of the |
| 53 | data that is set, it will be automatically converted via |
| 54 | \l {QVariant::canConvert()}{QVariant}. |
| 55 | |
| 56 | \section1 Supported Row Data Structures |
| 57 | |
| 58 | TableModel is designed to work with JavaScript/JSON data, where each row |
| 59 | is a list of simple key-value pairs: |
| 60 | |
| 61 | \code |
| 62 | { |
| 63 | // Each property is one cell/column. |
| 64 | checked: false, |
| 65 | amount: 1, |
| 66 | fruitType: "Apple", |
| 67 | fruitName: "Granny Smith", |
| 68 | fruitPrice: 1.50 |
| 69 | }, |
| 70 | // ... |
| 71 | \endcode |
| 72 | |
| 73 | As model manipulation in Qt is done via row and column indices, |
| 74 | and because object keys are unordered, each column must be specified via |
| 75 | TableModelColumn. This allows mapping Qt's built-in roles to any property |
| 76 | in each row object. |
| 77 | |
| 78 | Complex row structures are supported, but with limited functionality. |
| 79 | As TableModel has no way of knowing how each row is structured, |
| 80 | it cannot manipulate it. As a consequence of this, the copy of the |
| 81 | model data that TableModel has stored in \l rows is not kept in sync |
| 82 | with the source data that was set in QML. For these reasons, manipulation |
| 83 | of the data is not supported. |
| 84 | |
| 85 | For example, suppose you wanted to use a data source where each row is an |
| 86 | array and each cell is an object. To use this data source with TableModel, |
| 87 | define a getter: |
| 88 | |
| 89 | \code |
| 90 | TableModel { |
| 91 | TableModelColumn { |
| 92 | display: function(modelIndex) { return rows[modelIndex.row][0].checked } |
| 93 | } |
| 94 | // ... |
| 95 | |
| 96 | rows: [ |
| 97 | [ |
| 98 | { checked: false, checkable: true }, |
| 99 | { amount: 1 }, |
| 100 | { fruitType: "Apple" }, |
| 101 | { fruitName: "Granny Smith" }, |
| 102 | { fruitPrice: 1.50 } |
| 103 | ] |
| 104 | // ... |
| 105 | ] |
| 106 | } |
| 107 | \endcode |
| 108 | |
| 109 | The row above is one example of a complex row. |
| 110 | |
| 111 | \note Row manipulation functions such as \l appendRow(), \l removeRow(), |
| 112 | etc. are not supported when using complex rows. |
| 113 | |
| 114 | \section1 Using DelegateChooser with TableModel |
| 115 | |
| 116 | For most real world use cases, it is recommended to use DelegateChooser |
| 117 | as the delegate of a TableView that uses TableModel. This allows you to |
| 118 | use specific roles in the relevant delegates. For example, the snippet |
| 119 | above can be rewritten to use DelegateChooser like so: |
| 120 | |
| 121 | \snippet qml/tablemodel/fruit-example-delegatechooser.qml file |
| 122 | |
| 123 | The most specific delegates are declared first: the columns at index \c 0 |
| 124 | and \c 1 have \c bool and \c integer data types, so they use a |
| 125 | \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, |
| 126 | respectively. The remaining columns can simply use a |
| 127 | \l [QtQuickControls2]{TextField}, and so that delegate is declared |
| 128 | last as a fallback. |
| 129 | |
| 130 | \sa TableModelColumn, TableView, QAbstractTableModel |
| 131 | */ |
| 132 | |
| 133 | QQmlTableModel::QQmlTableModel(QObject *parent) |
| 134 | : QQmlAbstractColumnModel(parent) |
| 135 | { |
| 136 | } |
| 137 | |
| 138 | QQmlTableModel::~QQmlTableModel() |
| 139 | = default; |
| 140 | |
| 141 | /*! |
| 142 | \qmlproperty object TableModel::rows |
| 143 | |
| 144 | This property holds the model data in the form of an array of rows: |
| 145 | |
| 146 | \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows |
| 147 | |
| 148 | \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount |
| 149 | */ |
| 150 | QVariant QQmlTableModel::rows() const |
| 151 | { |
| 152 | return mRows; |
| 153 | } |
| 154 | |
| 155 | void QQmlTableModel::setRows(const QVariant &rows) |
| 156 | { |
| 157 | if (rows.userType() != qMetaTypeId<QJSValue>()) { |
| 158 | qmlWarning(me: this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); |
| 159 | return; |
| 160 | } |
| 161 | |
| 162 | const auto rowsAsJSValue = rows.value<QJSValue>(); |
| 163 | const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); |
| 164 | if (rowsAsVariantList == mRows) { |
| 165 | // No change. |
| 166 | return; |
| 167 | } |
| 168 | |
| 169 | if (!mComponentCompleted) { |
| 170 | // Store the rows until we can call setRowsPrivate() after component completion. |
| 171 | mRows = rowsAsVariantList; |
| 172 | return; |
| 173 | } |
| 174 | |
| 175 | setRowsPrivate(rowsAsVariantList); |
| 176 | } |
| 177 | |
| 178 | void QQmlTableModel::setRowsPrivate(const QVariantList &rowsAsVariantList) |
| 179 | { |
| 180 | Q_ASSERT(mComponentCompleted); |
| 181 | |
| 182 | // By now, all TableModelColumns should have been set. |
| 183 | if (mColumns.isEmpty()) { |
| 184 | qmlWarning(me: this) << "No TableModelColumns were set; model will be empty" ; |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); |
| 189 | if (!firstTimeValidRowsHaveBeenSet) { |
| 190 | // This is not the first time rows have been set; validate each one. |
| 191 | for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { |
| 192 | // validateNewRow() expects a QVariant wrapping a QJSValue, so to |
| 193 | // simplify the code, just create one here. |
| 194 | const QVariant row = QVariant::fromValue(value: rowsAsVariantList.at(i: rowIndex)); |
| 195 | if (!validateNewRow(functionName: "setRows()"_L1 , row, operation: SetRowsOperation)) |
| 196 | return; |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | const int oldRowCount = mRowCount; |
| 201 | |
| 202 | beginResetModel(); |
| 203 | |
| 204 | // We don't clear the column or role data, because a TableModel should not be reused in that way. |
| 205 | // Once it has valid data, its columns and roles are fixed. |
| 206 | mRows = rowsAsVariantList; |
| 207 | mRowCount = mRows.size(); |
| 208 | |
| 209 | // Gather metadata the first time rows is set. |
| 210 | if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) |
| 211 | fetchColumnMetadata(); |
| 212 | |
| 213 | endResetModel(); |
| 214 | emit rowsChanged(); |
| 215 | |
| 216 | if (mRowCount != oldRowCount) |
| 217 | emit rowCountChanged(); |
| 218 | } |
| 219 | |
| 220 | QVariant QQmlTableModel::dataPrivate(const QModelIndex &index, const QString &roleName) const |
| 221 | { |
| 222 | const ColumnMetadata columnMetadata = mColumnMetadata.at(i: index.column()); |
| 223 | const QString propertyName = columnMetadata.roles.value(key: roleName).name; |
| 224 | const QVariantMap rowData = mRows.at(i: index.row()).toMap(); |
| 225 | return rowData.value(key: propertyName); |
| 226 | } |
| 227 | |
| 228 | void QQmlTableModel::setDataPrivate(const QModelIndex &index, const QString &roleName, QVariant value) |
| 229 | { |
| 230 | int row = index.row(); |
| 231 | QVariantMap modifiedRow = mRows.at(i: row).toMap(); |
| 232 | modifiedRow[roleName] = value; |
| 233 | mRows[row] = modifiedRow; |
| 234 | } |
| 235 | |
| 236 | // TODO: Turn this into a snippet that compiles in CI |
| 237 | /*! |
| 238 | \qmlmethod TableModel::appendRow(object row) |
| 239 | |
| 240 | Adds a new row to the end of the model, with the |
| 241 | values (cells) in \a row. |
| 242 | |
| 243 | \code |
| 244 | model.appendRow({ |
| 245 | checkable: true, |
| 246 | amount: 1, |
| 247 | fruitType: "Pear", |
| 248 | fruitName: "Williams", |
| 249 | fruitPrice: 1.50, |
| 250 | }) |
| 251 | \endcode |
| 252 | |
| 253 | \sa insertRow(), setRow(), removeRow() |
| 254 | */ |
| 255 | void QQmlTableModel::appendRow(const QVariant &row) |
| 256 | { |
| 257 | if (!validateNewRow(functionName: "appendRow()"_L1 , row, operation: AppendOperation)) |
| 258 | return; |
| 259 | |
| 260 | doInsert(rowIndex: mRowCount, row); |
| 261 | } |
| 262 | |
| 263 | /*! |
| 264 | \qmlmethod TableModel::clear() |
| 265 | |
| 266 | Removes all rows from the model. |
| 267 | |
| 268 | \sa removeRow() |
| 269 | */ |
| 270 | void QQmlTableModel::clear() |
| 271 | { |
| 272 | QQmlEngine *engine = qmlEngine(this); |
| 273 | Q_ASSERT(engine); |
| 274 | setRows(QVariant::fromValue(value: engine->newArray())); |
| 275 | } |
| 276 | |
| 277 | /*! |
| 278 | \qmlmethod object TableModel::getRow(int rowIndex) |
| 279 | |
| 280 | Returns the row at \a rowIndex in the model. |
| 281 | |
| 282 | Note that this equivalent to accessing the row directly |
| 283 | through the \l rows property: |
| 284 | |
| 285 | \code |
| 286 | Component.onCompleted: { |
| 287 | // These two lines are equivalent. |
| 288 | console.log(model.getRow(0).display); |
| 289 | console.log(model.rows[0].fruitName); |
| 290 | } |
| 291 | \endcode |
| 292 | |
| 293 | \note the returned object cannot be used to modify the contents of the |
| 294 | model; use setRow() instead. |
| 295 | |
| 296 | \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() |
| 297 | */ |
| 298 | QVariant QQmlTableModel::getRow(int rowIndex) |
| 299 | { |
| 300 | if (!validateRowIndex(functionName: "getRow()"_L1 , argumentName: "rowIndex"_L1 , rowIndex, operation: NeedsExisting)) |
| 301 | return QVariant(); |
| 302 | return mRows.at(i: rowIndex); |
| 303 | } |
| 304 | |
| 305 | /*! |
| 306 | \qmlmethod TableModel::insertRow(int rowIndex, object row) |
| 307 | |
| 308 | Adds a new row to the model at position \a rowIndex, with the |
| 309 | values (cells) in \a row. |
| 310 | |
| 311 | \code |
| 312 | model.insertRow(2, { |
| 313 | checkable: true, checked: false, |
| 314 | amount: 1, |
| 315 | fruitType: "Pear", |
| 316 | fruitName: "Williams", |
| 317 | fruitPrice: 1.50, |
| 318 | }) |
| 319 | \endcode |
| 320 | |
| 321 | The \a rowIndex must point to an existing item in the table, or one past |
| 322 | the end of the table (equivalent to \l appendRow()). |
| 323 | |
| 324 | \sa appendRow(), setRow(), removeRow(), rowCount |
| 325 | */ |
| 326 | void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) |
| 327 | { |
| 328 | if (!validateNewRow(functionName: "insertRow()"_L1 , row) || |
| 329 | !validateRowIndex(functionName: "insertRow()"_L1 , argumentName: "rowIndex"_L1 , rowIndex, operation: CanAppend)) |
| 330 | return; |
| 331 | |
| 332 | doInsert(rowIndex, row); |
| 333 | } |
| 334 | |
| 335 | void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) |
| 336 | { |
| 337 | beginInsertRows(parent: QModelIndex(), first: rowIndex, last: rowIndex); |
| 338 | |
| 339 | // Adding rowAsVariant.toList() will add each invidual variant in the list, |
| 340 | // which is definitely not what we want. |
| 341 | const QVariant rowAsVariant = row.userType() == QMetaType::QVariantMap ? row : row.value<QJSValue>().toVariant(); |
| 342 | |
| 343 | mRows.insert(i: rowIndex, t: rowAsVariant); |
| 344 | ++mRowCount; |
| 345 | |
| 346 | qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index " |
| 347 | << rowIndex << ":\n" << rowAsVariant.toMap(); |
| 348 | |
| 349 | // Gather metadata the first time a row is added. |
| 350 | if (mColumnMetadata.isEmpty()) |
| 351 | fetchColumnMetadata(); |
| 352 | |
| 353 | endInsertRows(); |
| 354 | emit rowCountChanged(); |
| 355 | emit rowsChanged(); |
| 356 | } |
| 357 | |
| 358 | /*! |
| 359 | \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) |
| 360 | |
| 361 | Moves \a rows from the index at \a fromRowIndex to the index at |
| 362 | \a toRowIndex. |
| 363 | |
| 364 | The from and to ranges must exist; for example, to move the first 3 items |
| 365 | to the end of the list: |
| 366 | |
| 367 | \code |
| 368 | model.moveRow(0, model.rowCount - 3, 3) |
| 369 | \endcode |
| 370 | |
| 371 | \sa appendRow(), insertRow(), removeRow(), rowCount |
| 372 | */ |
| 373 | void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) |
| 374 | { |
| 375 | if (fromRowIndex == toRowIndex) { |
| 376 | qmlWarning(me: this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\"" ; |
| 377 | return; |
| 378 | } |
| 379 | |
| 380 | if (rows <= 0) { |
| 381 | qmlWarning(me: this) << "moveRow(): \"rows\" is less than or equal to 0" ; |
| 382 | return; |
| 383 | } |
| 384 | |
| 385 | if (!validateRowIndex(functionName: "moveRow()"_L1 , argumentName: "fromRowIndex"_L1 , rowIndex: fromRowIndex, operation: NeedsExisting)) |
| 386 | return; |
| 387 | |
| 388 | if (!validateRowIndex(functionName: "moveRow()"_L1 , argumentName: "toRowIndex"_L1 , rowIndex: toRowIndex, operation: NeedsExisting)) |
| 389 | return; |
| 390 | |
| 391 | if (fromRowIndex + rows > mRowCount) { |
| 392 | qmlWarning(me: this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex |
| 393 | << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows) |
| 394 | << ", which is greater than rowCount() of " << mRowCount; |
| 395 | return; |
| 396 | } |
| 397 | |
| 398 | if (toRowIndex + rows > mRowCount) { |
| 399 | qmlWarning(me: this) << "moveRow(): \"toRowIndex\" (" << toRowIndex |
| 400 | << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows) |
| 401 | << ", which is greater than rowCount() of " << mRowCount; |
| 402 | return; |
| 403 | } |
| 404 | |
| 405 | qCDebug(lcTableModel).nospace() << "moving " << rows |
| 406 | << " row(s) from index " << fromRowIndex |
| 407 | << " to index " << toRowIndex; |
| 408 | |
| 409 | // Based on the same call in QQmlListModel::moveRow(). |
| 410 | beginMoveRows(sourceParent: QModelIndex(), sourceFirst: fromRowIndex, sourceLast: fromRowIndex + rows - 1, destinationParent: QModelIndex(), |
| 411 | destinationRow: toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); |
| 412 | |
| 413 | // Based on ListModel::moveRow(). |
| 414 | if (fromRowIndex > toRowIndex) { |
| 415 | // Only move forwards - flip if moving backwards. |
| 416 | const int from = fromRowIndex; |
| 417 | const int to = toRowIndex; |
| 418 | fromRowIndex = to; |
| 419 | toRowIndex = to + rows; |
| 420 | rows = from - to; |
| 421 | } |
| 422 | |
| 423 | QVector<QVariant> store; |
| 424 | store.reserve(size: rows); |
| 425 | for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) |
| 426 | store.append(t: mRows.at(i: fromRowIndex + rows + i)); |
| 427 | for (int i = 0; i < rows; ++i) |
| 428 | store.append(t: mRows.at(i: fromRowIndex + i)); |
| 429 | for (int i = 0; i < store.size(); ++i) |
| 430 | mRows[fromRowIndex + i] = store[i]; |
| 431 | |
| 432 | qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows; |
| 433 | |
| 434 | endMoveRows(); |
| 435 | emit rowsChanged(); |
| 436 | } |
| 437 | |
| 438 | /*! |
| 439 | \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) |
| 440 | |
| 441 | Removes a number of \a rows at \a rowIndex from the model. |
| 442 | |
| 443 | \sa clear(), rowCount |
| 444 | */ |
| 445 | void QQmlTableModel::removeRow(int rowIndex, int rows) |
| 446 | { |
| 447 | if (!validateRowIndex(functionName: "removeRow()"_L1 , argumentName: "rowIndex"_L1 , rowIndex, operation: NeedsExisting)) |
| 448 | return; |
| 449 | |
| 450 | if (rows <= 0) { |
| 451 | qmlWarning(me: this) << "removeRow(): \"rows\" is less than or equal to zero" ; |
| 452 | return; |
| 453 | } |
| 454 | |
| 455 | if (rowIndex + rows - 1 >= mRowCount) { |
| 456 | qmlWarning(me: this) << "removeRow(): \"rows\" " << rows |
| 457 | << " exceeds available rowCount() of " << mRowCount |
| 458 | << " when removing from \"rowIndex\" " << rowIndex; |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | beginRemoveRows(parent: QModelIndex(), first: rowIndex, last: rowIndex + rows - 1); |
| 463 | |
| 464 | auto firstIterator = mRows.begin() + rowIndex; |
| 465 | // The "last" argument to erase() is exclusive, so we go one past the last item. |
| 466 | auto lastIterator = firstIterator + rows; |
| 467 | mRows.erase(begin: firstIterator, end: lastIterator); |
| 468 | mRowCount -= rows; |
| 469 | |
| 470 | endRemoveRows(); |
| 471 | emit rowCountChanged(); |
| 472 | emit rowsChanged(); |
| 473 | |
| 474 | qCDebug(lcTableModel).nospace() << "removed " << rows |
| 475 | << " items from the model, starting at index " << rowIndex; |
| 476 | } |
| 477 | |
| 478 | /*! |
| 479 | \qmlmethod TableModel::setRow(int rowIndex, object row) |
| 480 | |
| 481 | Changes the row at \a rowIndex in the model with \a row. |
| 482 | |
| 483 | All columns/cells must be present in \c row, and in the correct order. |
| 484 | |
| 485 | \code |
| 486 | model.setRow(0, { |
| 487 | checkable: true, |
| 488 | amount: 1, |
| 489 | fruitType: "Pear", |
| 490 | fruitName: "Williams", |
| 491 | fruitPrice: 1.50, |
| 492 | }) |
| 493 | \endcode |
| 494 | |
| 495 | If \a rowIndex is equal to \c rowCount(), then a new row is appended to the |
| 496 | model. Otherwise, \a rowIndex must point to an existing row in the model. |
| 497 | |
| 498 | \sa appendRow(), insertRow(), rowCount |
| 499 | */ |
| 500 | void QQmlTableModel::setRow(int rowIndex, const QVariant &row) |
| 501 | { |
| 502 | if (!validateNewRow(functionName: "setRow()"_L1 , row) || |
| 503 | !validateRowIndex(functionName: "setRow()"_L1 , argumentName: "rowIndex"_L1 , rowIndex, operation: CanAppend)) |
| 504 | return; |
| 505 | |
| 506 | if (rowIndex != mRowCount) { |
| 507 | // Setting an existing row. |
| 508 | mRows[rowIndex] = row; |
| 509 | |
| 510 | // For now we just assume the whole row changed, as it's simpler. |
| 511 | const QModelIndex topLeftModelIndex(createIndex(arow: rowIndex, acolumn: 0)); |
| 512 | const QModelIndex bottomRightModelIndex(createIndex(arow: rowIndex, acolumn: mColumnCount - 1)); |
| 513 | emit dataChanged(topLeft: topLeftModelIndex, bottomRight: bottomRightModelIndex); |
| 514 | emit rowsChanged(); |
| 515 | } else { |
| 516 | // Appending a row. |
| 517 | doInsert(rowIndex, row); |
| 518 | } |
| 519 | } |
| 520 | |
| 521 | |
| 522 | QVariant QQmlTableModel::firstRow() const |
| 523 | { |
| 524 | return mRows.first(); |
| 525 | } |
| 526 | |
| 527 | void QQmlTableModel::setInitialRows() |
| 528 | { |
| 529 | setRowsPrivate(mRows); |
| 530 | } |
| 531 | |
| 532 | /*! |
| 533 | \qmlmethod QModelIndex TableModel::index(int row, int column) |
| 534 | |
| 535 | Returns a \l QModelIndex object referencing the given \a row and \a column, |
| 536 | which can be passed to the data() function to get the data from that cell, |
| 537 | or to setData() to edit the contents of that cell. |
| 538 | |
| 539 | \code |
| 540 | import QtQml 2.14 |
| 541 | import Qt.labs.qmlmodels 1.0 |
| 542 | |
| 543 | TableModel { |
| 544 | id: model |
| 545 | |
| 546 | TableModelColumn { display: "fruitType" } |
| 547 | TableModelColumn { display: "fruitPrice" } |
| 548 | |
| 549 | rows: [ |
| 550 | { fruitType: "Apple", fruitPrice: 1.50 }, |
| 551 | { fruitType: "Orange", fruitPrice: 2.50 } |
| 552 | ] |
| 553 | |
| 554 | Component.onCompleted: { |
| 555 | for (var r = 0; r < model.rowCount; ++r) { |
| 556 | console.log("An " + model.data(model.index(r, 0)).display + |
| 557 | " costs " + model.data(model.index(r, 1)).display.toFixed(2)) |
| 558 | } |
| 559 | } |
| 560 | } |
| 561 | \endcode |
| 562 | |
| 563 | \sa {QModelIndex and related Classes in QML}, data() |
| 564 | */ |
| 565 | // Note: we don't document the parent argument, because you never need it, because |
| 566 | // cells in a TableModel don't have parents. But it is there because this function is an override. |
| 567 | QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const |
| 568 | { |
| 569 | return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() |
| 570 | ? createIndex(arow: row, acolumn: column) |
| 571 | : QModelIndex(); |
| 572 | } |
| 573 | |
| 574 | QModelIndex QQmlTableModel::parent(const QModelIndex &index) const |
| 575 | { |
| 576 | Q_UNUSED(index); |
| 577 | return {}; |
| 578 | } |
| 579 | |
| 580 | /*! |
| 581 | \qmlproperty int TableModel::rowCount |
| 582 | \readonly |
| 583 | |
| 584 | This read-only property holds the number of rows in the model. |
| 585 | |
| 586 | This value changes whenever rows are added or removed from the model. |
| 587 | */ |
| 588 | int QQmlTableModel::rowCount(const QModelIndex &parent) const |
| 589 | { |
| 590 | if (parent.isValid()) |
| 591 | return 0; |
| 592 | |
| 593 | return mRowCount; |
| 594 | } |
| 595 | |
| 596 | /*! |
| 597 | \qmlproperty int TableModel::columnCount |
| 598 | \readonly |
| 599 | |
| 600 | This read-only property holds the number of columns in the model. |
| 601 | |
| 602 | The number of columns is fixed for the lifetime of the model |
| 603 | after the \l rows property is set or \l appendRow() is called for the first |
| 604 | time. |
| 605 | */ |
| 606 | int QQmlTableModel::columnCount(const QModelIndex &parent) const |
| 607 | { |
| 608 | Q_UNUSED(parent); |
| 609 | |
| 610 | return mColumnCount; |
| 611 | } |
| 612 | |
| 613 | /*! |
| 614 | \qmlmethod variant TableModel::data(QModelIndex index, string role) |
| 615 | |
| 616 | Returns the data from the table cell at the given \a index belonging to the |
| 617 | given \a role. |
| 618 | |
| 619 | \sa index(), setData() |
| 620 | */ |
| 621 | |
| 622 | /*! |
| 623 | \qmlmethod bool TableModel::setData(QModelIndex index, variant value, string role) |
| 624 | |
| 625 | Inserts or updates the data field named by \a role in the table cell at the |
| 626 | given \a index with \a value. Returns true if sucessful, false if not. |
| 627 | |
| 628 | \sa data(), index() |
| 629 | */ |
| 630 | |
| 631 | bool QQmlTableModel::validateRowIndex(QLatin1StringView functionName, QLatin1StringView argumentName, |
| 632 | int rowIndex, RowOption operation) const |
| 633 | { |
| 634 | if (rowIndex < 0) { |
| 635 | qmlWarning(me: this).noquote() << functionName << ": \"" << argumentName << "\" cannot be negative" ; |
| 636 | return false; |
| 637 | } |
| 638 | |
| 639 | if (operation == NeedsExisting) { |
| 640 | if (rowIndex >= mRowCount) { |
| 641 | qmlWarning(me: this).noquote() << functionName << ": \"" << argumentName |
| 642 | << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount; |
| 643 | return false; |
| 644 | } |
| 645 | } else { |
| 646 | if (rowIndex > mRowCount) { |
| 647 | qmlWarning(me: this).noquote() << functionName << ": \"" << argumentName |
| 648 | << "\" " << rowIndex << " is greater than rowCount() of " << mRowCount; |
| 649 | return false; |
| 650 | } |
| 651 | } |
| 652 | |
| 653 | return true; |
| 654 | } |
| 655 | |
| 656 | QT_END_NAMESPACE |
| 657 | |
| 658 | #include "moc_qqmltablemodel_p.cpp" |
| 659 | |