diff options
Diffstat (limited to 'src/qml/types/qqmltablemodel.cpp')
-rw-r--r-- | src/qml/types/qqmltablemodel.cpp | 1059 |
1 files changed, 0 insertions, 1059 deletions
diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp deleted file mode 100644 index 4a96e7a46b..0000000000 --- a/src/qml/types/qqmltablemodel.cpp +++ /dev/null @@ -1,1059 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qqmltablemodel_p.h" - -#include <QtCore/qloggingcategory.h> -#include <QtQml/qqmlinfo.h> -#include <QtQml/qqmlengine.h> - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel") - -/*! - \qmltype TableModel - \instantiates QQmlTableModel - \inqmlmodule Qt.labs.qmlmodels - \brief Encapsulates a simple table model. - \since 5.14 - - The TableModel type stores JavaScript/JSON objects as data for a table - model that can be used with \l TableView. It is intended to support - very simple models without requiring the creation of a custom - QAbstractTableModel subclass in C++. - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml file - - The model's initial row data is set with either the \l rows property or by - calling \l appendRow(). Each column in the model is specified by declaring - a \l TableModelColumn instance, where the order of each instance determines - its column index. Once the model's \l Component.completed() signal has been - emitted, the columns and roles will have been established and are then - fixed for the lifetime of the model. - - To access a specific row, the \l getRow() function can be used. - It's also possible to access the model's JavaScript data - directly via the \l rows property, but it is not possible to - modify the model data this way. - - To add new rows, use \l appendRow() and \l insertRow(). To modify - existing rows, use \l setRow(), \l moveRow(), \l removeRow(), and - \l clear(). - - It is also possible to modify the model's data via the delegate, - as shown in the example above: - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml delegate - - If the type of the data at the modified role does not match the type of the - data that is set, it will be automatically converted via - \l {QVariant::canConvert()}{QVariant}. - - \section1 Supported Row Data Structures - - TableModel is designed to work with JavaScript/JSON data, where each row - is a simple key-pair object: - - \code - { - // Each property is one cell/column. - checked: false, - amount: 1, - fruitType: "Apple", - fruitName: "Granny Smith", - fruitPrice: 1.50 - }, - // ... - \endcode - - As model manipulation in Qt is done via row and column indices, - and because object keys are unordered, each column must be specified via - TableModelColumn. This allows mapping Qt's built-in roles to any property - in each row object. - - Complex row structures are supported, but with limited functionality. - As TableModel has no way of knowing how each row is structured, - it cannot manipulate it. As a consequence of this, the copy of the - model data that TableModel has stored in \l rows is not kept in sync - with the source data that was set in QML. For these reasons, TableModel - relies on the user to handle simple data manipulation. - - For example, suppose you wanted to have several roles per column. One way - of doing this is to use a data source where each row is an array and each - cell is an object. To use this data source with TableModel, define a - getter and setter: - - \code - TableModel { - TableModelColumn { - display: function(modelIndex) { return rows[modelIndex.row][0].checked } - setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][0].checked = cellData } - } - // ... - - rows: [ - [ - { checked: false, checkable: true }, - { amount: 1 }, - { fruitType: "Apple" }, - { fruitName: "Granny Smith" }, - { fruitPrice: 1.50 } - ] - // ... - ] - } - \endcode - - The row above is one example of a complex row. - - \note Row manipulation functions such as \l appendRow(), \l removeRow(), - etc. are not supported when using complex rows. - - \section1 Using DelegateChooser with TableModel - - For most real world use cases, it is recommended to use DelegateChooser - as the delegate of a TableView that uses TableModel. This allows you to - use specific roles in the relevant delegates. For example, the snippet - above can be rewritten to use DelegateChooser like so: - - \snippet qml/tablemodel/fruit-example-delegatechooser.qml file - - The most specific delegates are declared first: the columns at index \c 0 - and \c 1 have \c bool and \c integer data types, so they use a - \l [QtQuickControls2]{CheckBox} and \l [QtQuickControls2]{SpinBox}, - respectively. The remaining columns can simply use a - \l [QtQuickControls2]{TextField}, and so that delegate is declared - last as a fallback. - - \sa TableModelColumn, TableView, QAbstractTableModel -*/ - -QQmlTableModel::QQmlTableModel(QObject *parent) - : QAbstractTableModel(parent) -{ -} - -QQmlTableModel::~QQmlTableModel() -{ -} - -/*! - \qmlproperty object TableModel::rows - - This property holds the model data in the form of an array of rows: - - \snippet qml/tablemodel/fruit-example-simpledelegate.qml rows - - \sa getRow(), setRow(), moveRow(), appendRow(), insertRow(), clear(), rowCount, columnCount -*/ -QVariant QQmlTableModel::rows() const -{ - return mRows; -} - -void QQmlTableModel::setRows(const QVariant &rows) -{ - if (rows.userType() != qMetaTypeId<QJSValue>()) { - qmlWarning(this) << "setRows(): \"rows\" must be an array; actual type is " << rows.typeName(); - return; - } - - const QJSValue rowsAsJSValue = rows.value<QJSValue>(); - const QVariantList rowsAsVariantList = rowsAsJSValue.toVariant().toList(); - if (rowsAsVariantList == mRows) { - // No change. - return; - } - - if (!componentCompleted) { - // Store the rows until we can call doSetRows() after component completion. - mRows = rowsAsVariantList; - return; - } - - doSetRows(rowsAsVariantList); -} - -void QQmlTableModel::doSetRows(const QVariantList &rowsAsVariantList) -{ - Q_ASSERT(componentCompleted); - - // By now, all TableModelColumns should have been set. - if (mColumns.isEmpty()) { - qmlWarning(this) << "No TableModelColumns were set; model will be empty"; - return; - } - - const bool firstTimeValidRowsHaveBeenSet = mColumnMetadata.isEmpty(); - if (!firstTimeValidRowsHaveBeenSet) { - // This is not the first time rows have been set; validate each one. - for (int rowIndex = 0; rowIndex < rowsAsVariantList.size(); ++rowIndex) { - // validateNewRow() expects a QVariant wrapping a QJSValue, so to - // simplify the code, just create one here. - const QVariant row = QVariant::fromValue(rowsAsVariantList.at(rowIndex)); - if (!validateNewRow("setRows()", row, rowIndex, SetRowsOperation)) - return; - } - } - - const int oldRowCount = mRowCount; - const int oldColumnCount = mColumnCount; - - beginResetModel(); - - // We don't clear the column or role data, because a TableModel should not be reused in that way. - // Once it has valid data, its columns and roles are fixed. - mRows = rowsAsVariantList; - mRowCount = mRows.size(); - - // Gather metadata the first time rows is set. - if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty()) - fetchColumnMetadata(); - - endResetModel(); - - emit rowsChanged(); - - if (mRowCount != oldRowCount) - emit rowCountChanged(); - if (mColumnCount != oldColumnCount) - emit columnCountChanged(); -} - -QQmlTableModel::ColumnRoleMetadata QQmlTableModel::fetchColumnRoleData(const QString &roleNameKey, - QQmlTableModelColumn *tableModelColumn, int columnIndex) const -{ - const QVariant firstRow = mRows.first(); - ColumnRoleMetadata roleData; - - QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey); - if (columnRoleGetter.isUndefined()) { - // This role is not defined, which is fine; just skip it. - return roleData; - } - - if (columnRoleGetter.isString()) { - // The role is set as a string, so we assume the row is a simple object. - if (firstRow.type() != QVariant::Map) { - qmlWarning(this).quote() << "expected row for role " - << roleNameKey << " of TableModelColumn at index " - << columnIndex << " to be a simple object, but it's " - << firstRow.typeName() << " instead: " << firstRow; - return roleData; - } - const QVariantMap firstRowAsMap = firstRow.toMap(); - const QString rolePropertyName = columnRoleGetter.toString(); - const QVariant roleProperty = firstRowAsMap.value(rolePropertyName); - - roleData.isStringRole = true; - roleData.name = rolePropertyName; - roleData.type = roleProperty.type(); - roleData.typeName = QString::fromLatin1(roleProperty.typeName()); - } else if (columnRoleGetter.isCallable()) { - // The role is provided via a function, which means the row is complex and - // the user needs to provide the data for it. - const auto modelIndex = index(0, columnIndex); - const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(modelIndex); - const QVariant cellData = columnRoleGetter.call(args).toVariant(); - - // We don't know the property name since it's provided through the function. - // roleData.name = ??? - roleData.isStringRole = false; - roleData.type = cellData.type(); - roleData.typeName = QString::fromLatin1(cellData.typeName()); - } else { - // Invalid role. - qmlWarning(this) << "TableModelColumn role for column at index " - << columnIndex << " must be either a string or a function; actual type is: " - << columnRoleGetter.toString(); - } - - return roleData; -} - -void QQmlTableModel::fetchColumnMetadata() -{ - qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:"; - - static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames(); - - // Since we support different data structures at the row level, we require that there - // is a TableModelColumn for each column. - // Collect and cache metadata for each column. This makes data lookup faster. - for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { - QQmlTableModelColumn *column = mColumns.at(columnIndex); - qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":"; - - ColumnMetadata metaData; - const auto builtInRoleKeys = supportedRoleNames.keys(); - for (const int builtInRoleKey : builtInRoleKeys) { - const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey); - ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex); - if (roleData.type == QVariant::Invalid) { - // This built-in role was not specified in this column. - continue; - } - - qCDebug(lcTableModel).nospace() << " - added metadata for built-in role " - << builtInRoleName << " at column index " << columnIndex - << ": name=" << roleData.name << " typeName=" << roleData.typeName - << " type=" << roleData.type; - - // This column now supports this specific built-in role. - metaData.roles.insert(builtInRoleName, roleData); - // Add it if it doesn't already exist. - mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1(); - } - mColumnMetadata.insert(columnIndex, metaData); - } -} - -/*! - \qmlmethod TableModel::appendRow(object row) - - Adds a new row to the end of the model, with the - values (cells) in \a row. - - \code - model.appendRow({ - checkable: true, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - \sa insertRow(), setRow(), removeRow() -*/ -void QQmlTableModel::appendRow(const QVariant &row) -{ - if (!validateNewRow("appendRow()", row, -1, AppendOperation)) - return; - - doInsert(mRowCount, row); -} - -/*! - \qmlmethod TableModel::clear() - - Removes all rows from the model. - - \sa removeRow() -*/ -void QQmlTableModel::clear() -{ - QQmlEngine *engine = qmlEngine(this); - Q_ASSERT(engine); - setRows(QVariant::fromValue(engine->newArray())); -} - -/*! - \qmlmethod object TableModel::getRow(int rowIndex) - - Returns the row at \a rowIndex in the model. - - Note that this equivalent to accessing the row directly - through the \l rows property: - - \code - Component.onCompleted: { - // These two lines are equivalent. - console.log(model.getRow(0).display); - console.log(model.rows[0].fruitName); - } - \endcode - - \note the returned object cannot be used to modify the contents of the - model; use setRow() instead. - - \sa setRow(), appendRow(), insertRow(), removeRow(), moveRow() -*/ -QVariant QQmlTableModel::getRow(int rowIndex) -{ - if (!validateRowIndex("getRow()", "rowIndex", rowIndex)) - return QVariant(); - - return mRows.at(rowIndex); -} - -/*! - \qmlmethod TableModel::insertRow(int rowIndex, object row) - - Adds a new row to the list model at position \a rowIndex, with the - values (cells) in \a row. - - \code - model.insertRow(2, { - checkable: true, checked: false, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - The \a rowIndex must be to an existing item in the list, or one past - the end of the list (equivalent to \l appendRow()). - - \sa appendRow(), setRow(), removeRow(), rowCount -*/ -void QQmlTableModel::insertRow(int rowIndex, const QVariant &row) -{ - if (!validateNewRow("insertRow()", row, rowIndex)) - return; - - doInsert(rowIndex, row); -} - -void QQmlTableModel::doInsert(int rowIndex, const QVariant &row) -{ - beginInsertRows(QModelIndex(), rowIndex, rowIndex); - - // Adding rowAsVariant.toList() will add each invidual variant in the list, - // which is definitely not what we want. - const QVariant rowAsVariant = row.value<QJSValue>().toVariant(); - mRows.insert(rowIndex, rowAsVariant); - ++mRowCount; - - qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index " - << rowIndex << ":\n" << rowAsVariant.toMap(); - - // Gather metadata the first time a row is added. - if (mColumnMetadata.isEmpty()) - fetchColumnMetadata(); - - endInsertRows(); - emit rowCountChanged(); -} - -void QQmlTableModel::classBegin() -{ -} - -void QQmlTableModel::componentComplete() -{ - componentCompleted = true; - - mColumnCount = mColumns.size(); - if (mColumnCount > 0) - emit columnCountChanged(); - - doSetRows(mRows); -} - -/*! - \qmlmethod TableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) - - Moves \a rows from the index at \a fromRowIndex to the index at - \a toRowIndex. - - The from and to ranges must exist; for example, to move the first 3 items - to the end of the list: - - \code - model.moveRow(0, model.rowCount - 3, 3) - \endcode - - \sa appendRow(), insertRow(), removeRow(), rowCount -*/ -void QQmlTableModel::moveRow(int fromRowIndex, int toRowIndex, int rows) -{ - if (fromRowIndex == toRowIndex) { - qmlWarning(this) << "moveRow(): \"fromRowIndex\" cannot be equal to \"toRowIndex\""; - return; - } - - if (rows <= 0) { - qmlWarning(this) << "moveRow(): \"rows\" is less than or equal to 0"; - return; - } - - if (!validateRowIndex("moveRow()", "fromRowIndex", fromRowIndex)) - return; - - if (!validateRowIndex("moveRow()", "toRowIndex", toRowIndex)) - return; - - if (fromRowIndex + rows > mRowCount) { - qmlWarning(this) << "moveRow(): \"fromRowIndex\" (" << fromRowIndex - << ") + \"rows\" (" << rows << ") = " << (fromRowIndex + rows) - << ", which is greater than rowCount() of " << mRowCount; - return; - } - - if (toRowIndex + rows > mRowCount) { - qmlWarning(this) << "moveRow(): \"toRowIndex\" (" << toRowIndex - << ") + \"rows\" (" << rows << ") = " << (toRowIndex + rows) - << ", which is greater than rowCount() of " << mRowCount; - return; - } - - qCDebug(lcTableModel).nospace() << "moving " << rows - << " row(s) from index " << fromRowIndex - << " to index " << toRowIndex; - - // Based on the same call in QQmlListModel::moveRow(). - beginMoveRows(QModelIndex(), fromRowIndex, fromRowIndex + rows - 1, QModelIndex(), - toRowIndex > fromRowIndex ? toRowIndex + rows : toRowIndex); - - // Based on ListModel::moveRow(). - if (fromRowIndex > toRowIndex) { - // Only move forwards - flip if moving backwards. - const int from = fromRowIndex; - const int to = toRowIndex; - fromRowIndex = to; - toRowIndex = to + rows; - rows = from - to; - } - - QVector<QVariant> store; - store.reserve(rows); - for (int i = 0; i < (toRowIndex - fromRowIndex); ++i) - store.append(mRows.at(fromRowIndex + rows + i)); - for (int i = 0; i < rows; ++i) - store.append(mRows.at(fromRowIndex + i)); - for (int i = 0; i < store.size(); ++i) - mRows[fromRowIndex + i] = store[i]; - - qCDebug(lcTableModel).nospace() << "after moving, rows are:\n" << mRows; - - endMoveRows(); -} - -/*! - \qmlmethod TableModel::removeRow(int rowIndex, int rows = 1) - - Removes the row at \a rowIndex from the model. - - \sa clear(), rowCount -*/ -void QQmlTableModel::removeRow(int rowIndex, int rows) -{ - if (!validateRowIndex("removeRow()", "rowIndex", rowIndex)) - return; - - if (rows <= 0) { - qmlWarning(this) << "removeRow(): \"rows\" is less than or equal to zero"; - return; - } - - if (rowIndex + rows - 1 >= mRowCount) { - qmlWarning(this) << "removeRow(): \"rows\" " << rows - << " exceeds available rowCount() of " << mRowCount - << " when removing from \"rowIndex\" " << rowIndex; - return; - } - - beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1); - - auto firstIterator = mRows.begin() + rowIndex; - // The "last" argument to erase() is exclusive, so we go one past the last item. - auto lastIterator = firstIterator + rows; - mRows.erase(firstIterator, lastIterator); - mRowCount -= rows; - - endRemoveRows(); - emit rowCountChanged(); - - qCDebug(lcTableModel).nospace() << "removed " << rows - << " items from the model, starting at index " << rowIndex; -} - -/*! - \qmlmethod TableModel::setRow(int rowIndex, object row) - - Changes the row at \a rowIndex in the model with \a row. - - All columns/cells must be present in \c row, and in the correct order. - - \code - model.setRow(0, { - checkable: true, - amount: 1, - fruitType: "Pear", - fruitName: "Williams", - fruitPrice: 1.50, - }) - \endcode - - If \a rowIndex is equal to \c rowCount(), then a new row is appended to the - model. Otherwise, \a rowIndex must point to an existing row in the model. - - \sa appendRow(), insertRow(), rowCount -*/ -void QQmlTableModel::setRow(int rowIndex, const QVariant &row) -{ - if (!validateNewRow("setRow()", row, rowIndex)) - return; - - if (rowIndex != mRowCount) { - // Setting an existing row. - mRows[rowIndex] = row; - - // For now we just assume the whole row changed, as it's simpler. - const QModelIndex topLeftModelIndex(createIndex(rowIndex, 0)); - const QModelIndex bottomRightModelIndex(createIndex(rowIndex, mColumnCount - 1)); - emit dataChanged(topLeftModelIndex, bottomRightModelIndex); - } else { - // Appending a row. - doInsert(rowIndex, row); - } -} - -QQmlListProperty<QQmlTableModelColumn> QQmlTableModel::columns() -{ - return QQmlListProperty<QQmlTableModelColumn>(this, nullptr, - &QQmlTableModel::columns_append, - &QQmlTableModel::columns_count, - &QQmlTableModel::columns_at, - &QQmlTableModel::columns_clear); -} - -void QQmlTableModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property, - QQmlTableModelColumn *value) -{ - QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); - QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(value); - if (column) - model->mColumns.append(column); -} - -int QQmlTableModel::columns_count(QQmlListProperty<QQmlTableModelColumn> *property) -{ - const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); - return model->mColumns.count(); -} - -QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index) -{ - const QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); - return model->mColumns.at(index); -} - -void QQmlTableModel::columns_clear(QQmlListProperty<QQmlTableModelColumn> *property) -{ - QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object); - return model->mColumns.clear(); -} - -/*! - \qmlmethod QModelIndex TableModel::index(int row, int column) - - Returns a \l QModelIndex object referencing the given \a row and \a column, - which can be passed to the data() function to get the data from that cell, - or to setData() to edit the contents of that cell. - - \code - import QtQml 2.14 - import Qt.labs.qmlmodels 1.0 - - TableModel { - id: model - - TableModelColumn { display: "fruitType" } - TableModelColumn { display: "fruitPrice" } - - rows: [ - { fruitType: "Apple", fruitPrice: 1.50 }, - { fruitType: "Orange", fruitPrice: 2.50 } - ] - - Component.onCompleted: { - for (var r = 0; r < model.rowCount; ++r) { - console.log("An " + model.data(model.index(r, 0)).display + - " costs " + model.data(model.index(r, 1)).display.toFixed(2)) - } - } - } - \endcode - - \sa {QModelIndex and related Classes in QML}, data() -*/ -// Note: we don't document the parent argument, because you never need it, because -// cells in a TableModel don't have parents. But it is there because this function is an override. -QModelIndex QQmlTableModel::index(int row, int column, const QModelIndex &parent) const -{ - return row >= 0 && row < rowCount() && column >= 0 && column < columnCount() && !parent.isValid() - ? createIndex(row, column) - : QModelIndex(); -} - -/*! - \qmlproperty int TableModel::rowCount - \readonly - - This read-only property holds the number of rows in the model. - - This value changes whenever rows are added or removed from the model. -*/ -int QQmlTableModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return mRowCount; -} - -/*! - \qmlproperty int TableModel::columnCount - \readonly - - This read-only property holds the number of columns in the model. - - The number of columns is fixed for the lifetime of the model - after the \l rows property is set or \l appendRow() is called for the first - time. -*/ -int QQmlTableModel::columnCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return mColumnCount; -} - -/*! - \qmlmethod variant TableModel::data(QModelIndex index, string role) - - Returns the data from the table cell at the given \a index belonging to the - given \a role. - - \sa index() -*/ -QVariant QQmlTableModel::data(const QModelIndex &index, const QString &role) const -{ - const int iRole = mRoleNames.key(role.toUtf8(), -1); - if (iRole >= 0) - return data(index, iRole); - return QVariant(); -} - -QVariant QQmlTableModel::data(const QModelIndex &index, int role) const -{ - const int row = index.row(); - if (row < 0 || row >= rowCount()) - return QVariant(); - - const int column = index.column(); - if (column < 0 || column >= columnCount()) - return QVariant(); - - const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); - const QString roleName = QString::fromUtf8(mRoleNames.value(role)); - if (!columnMetadata.roles.contains(roleName)) { - qmlWarning(this) << "setData(): no role named " << roleName - << " at column index " << column << ". The available roles for that column are: " - << columnMetadata.roles.keys(); - return QVariant(); - } - - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - if (roleData.isStringRole) { - // We know the data structure, so we can get the data for the user. - const QVariantMap rowData = mRows.at(row).toMap(); - const QString propertyName = columnMetadata.roles.value(roleName).name; - const QVariant value = rowData.value(propertyName); - return value; - } - - // We don't know the data structure, so the user has to modify their data themselves. - // First, find the getter for this column and role. - QJSValue getter = mColumns.at(column)->getterAtRole(roleName); - - // Then, call it and return what it returned. - const auto args = QJSValueList() << qmlEngine(this)->toScriptValue(index); - return getter.call(args).toVariant(); -} - -/*! - \qmlmethod bool TableModel::setData(QModelIndex index, string role, variant value) - - Inserts or updates the data field named by \a role in the table cell at the - given \a index with \a value. Returns true if sucessful, false if not. - - \sa index() -*/ -bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value) -{ - const int intRole = mRoleNames.key(role.toUtf8(), -1); - if (intRole >= 0) - return setData(index, value, intRole); - return false; -} - -bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - const int row = index.row(); - if (row < 0 || row >= rowCount()) - return false; - - const int column = index.column(); - if (column < 0 || column >= columnCount()) - return false; - - const QString roleName = QString::fromUtf8(mRoleNames.value(role)); - - qCDebug(lcTableModel).nospace() << "setData() called with index " - << index << ", value " << value << " and role " << roleName; - - // Verify that the role exists for this column. - const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column()); - if (!columnMetadata.roles.contains(roleName)) { - qmlWarning(this) << "setData(): no role named \"" << roleName - << "\" at column index " << column << ". The available roles for that column are: " - << columnMetadata.roles.keys(); - return false; - } - - // Verify that the type of the value is what we expect. - // If the value set is not of the expected type, we can try to convert it automatically. - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - QVariant effectiveValue = value; - if (value.type() != roleData.type) { - if (!value.canConvert(int(roleData.type))) { - qmlWarning(this).nospace() << "setData(): the value " << value - << " set at row " << row << " column " << column << " with role " << roleName - << " cannot be converted to " << roleData.typeName; - return false; - } - - if (!effectiveValue.convert(int(roleData.type))) { - qmlWarning(this).nospace() << "setData(): failed converting value " << value - << " set at row " << row << " column " << column << " with role " << roleName - << " to " << roleData.typeName; - return false; - } - } - - if (roleData.isStringRole) { - // We know the data structure, so we can set it for the user. - QVariantMap modifiedRow = mRows.at(row).toMap(); - modifiedRow[roleData.name] = value; - - mRows[row] = modifiedRow; - } else { - // We don't know the data structure, so the user has to modify their data themselves. - auto engine = qmlEngine(this); - auto args = QJSValueList() - // arg 0: modelIndex. - << engine->toScriptValue(index) - // arg 1: cellData. - << engine->toScriptValue(value); - // Do the actual setting. - QJSValue setter = mColumns.at(column)->setterAtRole(roleName); - setter.call(args); - - /* - The chain of events so far: - - - User did e.g.: model.edit = textInput.text - - setData() is called - - setData() calls the setter - (remember that we need to emit the dataChanged() signal, - which is why the user can't just set the data directly in the delegate) - - Now the user's setter function has modified *their* copy of the - data, but *our* copy of the data is old. Imagine the getters and setters looked like this: - - display: function(modelIndex) { return rows[modelIndex.row][1].amount } - setDisplay: function(modelIndex, cellData) { rows[modelIndex.row][1].amount = cellData } - - We don't know the structure of the user's data, so we can't just do - what we do above for the isStringRole case: - - modifiedRow[column][roleName] = value - - This means that, besides getting the implicit row count when rows is initially set, - our copy of the data is unused when it comes to complex columns. - - Another point to note is that we can't pass rowData in to the getter as a convenience, - because we would be passing in *our* copy of the row, which is not up-to-date. - Since the user already has access to the data, it's not a big deal for them to do: - - display: function(modelIndex) { return rows[modelIndex.row][1].amount } - - instead of: - - display: function(modelIndex, rowData) { return rowData[1].amount } - */ - } - - QVector<int> rolesChanged; - rolesChanged.append(role); - emit dataChanged(index, index, rolesChanged); - - return true; -} - -QHash<int, QByteArray> QQmlTableModel::roleNames() const -{ - return mRoleNames; -} - -QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata() -{ -} - -QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata( - bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) : - isStringRole(isStringRole), - name(name), - type(type), - typeName(typeName) -{ -} - -bool QQmlTableModel::ColumnRoleMetadata::isValid() const -{ - return !name.isEmpty(); -} - -bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const -{ - if (!row.canConvert<QJSValue>()) { - qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," - << " but got " << row.typeName() << " instead:\n" << row; - return false; - } - - const QJSValue rowAsJSValue = row.value<QJSValue>(); - if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) { - qmlWarning(this) << functionName << ": expected \"row\" argument " - << "to be an object or array, but got:\n" << rowAsJSValue.toString(); - return false; - } - - return true; -} - -bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row, - int rowIndex, NewRowOperationFlag operation) const -{ - if (mColumnMetadata.isEmpty()) { - // There is no column metadata, so we have nothing to validate the row against. - // Rows have to be added before we can gather metadata from them, so just this - // once we'll return true to allow the rows to be added. - return true; - } - - // Don't require each row to be a QJSValue when setting all rows, - // as they won't be; they'll be QVariantMap. - if (operation != SetRowsOperation && !validateRowType(functionName, row)) - return false; - - if (operation == OtherOperation) { - // Inserting/setting. - if (rowIndex < 0) { - qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative"; - return false; - } - - if (rowIndex > mRowCount) { - qmlWarning(this) << functionName << ": \"rowIndex\" " << rowIndex - << " is greater than rowCount() of " << mRowCount; - return false; - } - } - - const QVariant rowAsVariant = operation == SetRowsOperation - ? row : row.value<QJSValue>().toVariant(); - if (rowAsVariant.type() != QVariant::Map) { - qmlWarning(this) << functionName << ": row manipulation functions " - << "do not support complex rows (row index: " << rowIndex << ")"; - return false; - } - - const QVariantMap rowAsMap = rowAsVariant.toMap(); - const int columnCount = rowAsMap.size(); - if (columnCount < mColumnCount) { - qmlWarning(this) << functionName << ": expected " << mColumnCount - << " columns, but only got " << columnCount; - return false; - } - - // We can't validate complex structures, but we can make sure that - // each simple string-based role in each column is correct. - for (int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) { - QQmlTableModelColumn *column = mColumns.at(columnIndex); - const QHash<QString, QJSValue> getters = column->getters(); - const auto roleNames = getters.keys(); - const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex); - for (const QString &roleName : roleNames) { - const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName); - if (!roleData.isStringRole) - continue; - - if (!rowAsMap.contains(roleData.name)) { - qmlWarning(this).quote() << functionName << ": expected a property named " - << roleData.name << " in row at index " << rowIndex << ", but couldn't find one"; - return false; - } - - const QVariant rolePropertyValue = rowAsMap.value(roleData.name); - if (rolePropertyValue.type() != roleData.type) { - qmlWarning(this).quote() << functionName << ": expected the property named " - << roleData.name << " to be of type " << roleData.typeName - << ", but got " << QString::fromLatin1(rolePropertyValue.typeName()) << " instead"; - return false; - } - } - } - - return true; -} - -bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const -{ - if (rowIndex < 0) { - qmlWarning(this) << functionName << ": \"" << argumentName << "\" cannot be negative"; - return false; - } - - if (rowIndex >= mRowCount) { - qmlWarning(this) << functionName << ": \"" << argumentName - << "\" " << rowIndex << " is greater than or equal to rowCount() of " << mRowCount; - return false; - } - - return true; -} - -QT_END_NAMESPACE |