aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/types
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@qt.io>2019-03-07 10:21:53 +0100
committerMitch Curtis <mitch.curtis@qt.io>2019-04-08 07:42:08 +0000
commit7a303424f2095c53889f8102f115ec38013ef8d9 (patch)
tree883debcf88b5a9ce3da8acef37b86fc865f79294 /src/qml/types
parent74313fd30a79e6f26734127157870c4491331501 (diff)
Add TableModelColumn
This allows us to support simple object rows by default, which we expect to be the most common use case for TableModel. Complex rows are supported, but with a limited subset of functionality. Things that could be improved: - Would be nice if we could get arbitrary/dynamic properties like ListModel has, without the complex code that comes with it. That way we could get rid of all of the role properties and users could have their own custom roles. The limitation of only having built-in roles becomes too restrictive very quickly. Change-Id: Icbdb6b39665851c55c69c0b79e0aa523c5d46dfe Reviewed-by: Venugopal Shivashankar <Venugopal.Shivashankar@qt.io> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Diffstat (limited to 'src/qml/types')
-rw-r--r--src/qml/types/qqmlmodelsmodule.cpp2
-rw-r--r--src/qml/types/qqmltablemodel.cpp731
-rw-r--r--src/qml/types/qqmltablemodel_p.h56
-rw-r--r--src/qml/types/qqmltablemodelcolumn.cpp200
-rw-r--r--src/qml/types/qqmltablemodelcolumn_p.h224
-rw-r--r--src/qml/types/types.pri6
6 files changed, 884 insertions, 335 deletions
diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp
index b7b9c9ee1c..119ede256f 100644
--- a/src/qml/types/qqmlmodelsmodule.cpp
+++ b/src/qml/types/qqmlmodelsmodule.cpp
@@ -48,6 +48,7 @@
#endif
#include <private/qqmlobjectmodel_p.h>
#include <private/qqmltablemodel_p.h>
+#include <private/qqmltablemodelcolumn_p.h>
QT_BEGIN_NAMESPACE
@@ -77,6 +78,7 @@ void QQmlModelsModule::defineLabsModule()
qmlRegisterType<QQmlDelegateChooser>(uri, 1, 0, "DelegateChooser");
qmlRegisterType<QQmlDelegateChoice>(uri, 1, 0, "DelegateChoice");
qmlRegisterType<QQmlTableModel>(uri, 1, 0, "TableModel");
+ qmlRegisterType<QQmlTableModelColumn>(uri, 1, 0, "TableModelColumn");
}
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltablemodel.cpp b/src/qml/types/qqmltablemodel.cpp
index 6068155f5a..4a96e7a46b 100644
--- a/src/qml/types/qqmltablemodel.cpp
+++ b/src/qml/types/qqmltablemodel.cpp
@@ -47,27 +47,26 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcTableModel, "qt.qml.tablemodel")
-static const QString lengthPropertyName = QStringLiteral("length");
-static const QString displayRoleName = QStringLiteral("display");
-
/*!
\qmltype TableModel
\instantiates QQmlTableModel
\inqmlmodule Qt.labs.qmlmodels
\brief Encapsulates a simple table model.
- \since 5.12
-
- The TableModel type stores JavaScript objects as data for a table model
- that can be used with \l TableView.
+ \since 5.14
- The following snippet shows the simplest use case for TableModel:
+ 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 data is set with either the \l rows property or by
- calling \l appendRow(). Once the first row has been added to the table, the
- columns and roles are established and will be fixed for the lifetime of the
- model.
+ 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
@@ -87,14 +86,65 @@ static const QString displayRoleName = QStringLiteral("display");
data that is set, it will be automatically converted via
\l {QVariant::canConvert()}{QVariant}.
- For convenience, TableModel provides the \c display role if it is not
- explicitly specified in any column. When a column only has one role
- declared, that role will be used used as the display role. However, when
- there is more than one role in a column, which role will be used is
- undefined. This is because JavaScript does not guarantee that properties
- within an object can be accessed according to the order in which they were
- declared. This is why \c checkable may be used as the display role for the
- first column even though \c checked is declared before it, for example.
+ \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
@@ -112,13 +162,12 @@ static const QString displayRoleName = QStringLiteral("display");
\l [QtQuickControls2]{TextField}, and so that delegate is declared
last as a fallback.
- \sa QAbstractTableModel, TableView
+ \sa TableModelColumn, TableView, QAbstractTableModel
*/
QQmlTableModel::QQmlTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
- mRoleNames = QAbstractTableModel::roleNames();
}
QQmlTableModel::~QQmlTableModel()
@@ -153,31 +202,34 @@ void QQmlTableModel::setRows(const QVariant &rows)
return;
}
- QVariant firstRowAsVariant;
- QVariantList firstRow;
- if (!rowsAsVariantList.isEmpty()) {
- // There are rows to validate. If they're not valid,
- // we'll return early without changing anything.
- firstRowAsVariant = rowsAsVariantList.first();
- firstRow = firstRowAsVariant.toList();
+ if (!componentCompleted) {
+ // Store the rows until we can call doSetRows() after component completion.
+ mRows = rowsAsVariantList;
+ return;
+ }
- if (firstRowAsVariant.type() != QVariant::List) {
- qmlWarning(this) << "setRows(): each row in \"rows\" must be an array of objects";
- return;
- }
+ doSetRows(rowsAsVariantList);
+}
- if (mColumnCount > 0) {
- qCDebug(lcTableModel) << "validating" << rowsAsVariantList.size()
- << "rows against existing metadata";
-
- // This is not the first time the rows have been set; validate the new columns.
- for (int i = 0; i < rowsAsVariantList.size(); ++i) {
- // validateNewRow() expects a QVariant wrapping a QJSValue, so to
- // simplify the code, just create one here.
- const QVariant row = QVariant::fromValue(rowsAsJSValue.property(i));
- if (!validateNewRow("setRows()", row, i))
- return;
- }
+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;
}
}
@@ -191,59 +243,9 @@ void QQmlTableModel::setRows(const QVariant &rows)
mRows = rowsAsVariantList;
mRowCount = mRows.size();
- const bool isFirstTimeSet = mColumnCount == 0;
- if (isFirstTimeSet && mRowCount > 0) {
- // This is the first time the rows have been set, so establish
- // the column count and gather column metadata.
- mColumnCount = firstRow.size();
- qCDebug(lcTableModel) << "gathering metadata for" << mColumnCount << "columns from first row:";
-
- // Go through each property of each cell in the first row
- // and make a role name from it.
- int userRoleKey = Qt::UserRole;
- for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) {
- // We need it as a QVariantMap because we need to get
- // the name of the property, which we can't do with QJSValue's API.
- const QVariantMap column = firstRow.at(columnIndex).toMap();
- const QStringList columnPropertyNames = column.keys();
- ColumnProperties properties;
- int propertyInfoIndex = 0;
-
- qCDebug(lcTableModel).nospace() << "- column " << columnIndex << ":";
-
- for (const QString &roleName : columnPropertyNames) {
- // QML/JS supports utf8.
- const QByteArray roleNameUtf8 = roleName.toUtf8();
- if (!mRoleNames.values().contains(roleNameUtf8)) {
- // We don't already have this role name, so it's a user role.
- mRoleNames[userRoleKey] = roleName.toUtf8().constData();
- qCDebug(lcTableModel) << " - added new user role" << roleName << "with key" << userRoleKey;
- ++userRoleKey;
- } else {
- qCDebug(lcTableModel) << " - found existing role" << roleName;
- }
-
- if (properties.explicitDisplayRoleIndex == -1 && roleName == displayRoleName) {
- // The user explicitly declared a "display" role,
- // so now we don't need to make it the first role in the column for them.
- properties.explicitDisplayRoleIndex = propertyInfoIndex;
- }
-
- // Keep track of the type of property so we can use it to validate new rows later on.
- const QVariant roleValue = column.value(roleName);
- const auto propertyInfo = ColumnPropertyInfo(roleName, roleValue.type(),
- QString::fromLatin1(roleValue.typeName()));
- properties.infoForProperties.append(propertyInfo);
-
- qCDebug(lcTableModel) << " - column property" << propertyInfo.name
- << "has type" << propertyInfo.typeName;
-
- ++propertyInfoIndex;
- }
-
- mColumnProperties.append(properties);
- }
- }
+ // Gather metadata the first time rows is set.
+ if (firstTimeValidRowsHaveBeenSet && !mRows.isEmpty())
+ fetchColumnMetadata();
endResetModel();
@@ -255,6 +257,94 @@ void QQmlTableModel::setRows(const QVariant &rows)
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)
@@ -262,13 +352,13 @@ void QQmlTableModel::setRows(const QVariant &rows)
values (cells) in \a row.
\code
- model.appendRow([
- { checkable: true, checked: false },
- { amount: 1 },
- { fruitType: "Pear" },
- { fruitName: "Williams" },
- { fruitPrice: 1.50 },
- ])
+ model.appendRow({
+ checkable: true,
+ amount: 1,
+ fruitType: "Pear",
+ fruitName: "Williams",
+ fruitPrice: 1.50,
+ })
\endcode
\sa insertRow(), setRow(), removeRow()
@@ -306,7 +396,7 @@ void QQmlTableModel::clear()
\code
Component.onCompleted: {
// These two lines are equivalent.
- console.log(model.getRow(0).fruitName);
+ console.log(model.getRow(0).display);
console.log(model.rows[0].fruitName);
}
\endcode
@@ -331,13 +421,13 @@ QVariant QQmlTableModel::getRow(int rowIndex)
values (cells) in \a row.
\code
- model.insertRow(2, [
- { checkable: true, checked: false },
- { amount: 1 },
- { fruitType: "Pear" },
- { fruitName: "Williams" },
- { fruitPrice: 1.50 },
- ])
+ 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
@@ -363,13 +453,32 @@ void QQmlTableModel::doInsert(int rowIndex, const QVariant &row)
mRows.insert(rowIndex, rowAsVariant);
++mRowCount;
- qCDebug(lcTableModel).nospace() << "inserted the following row to the model at index"
- << rowIndex << ":\n" << rowAsVariant.toList();
+ 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)
@@ -496,13 +605,13 @@ void QQmlTableModel::removeRow(int rowIndex, int rows)
All columns/cells must be present in \c row, and in the correct order.
\code
- model.setRow(0, [
- { checkable: true, checked: false },
- { amount: 1 },
- { fruitType: "Pear" },
- { fruitName: "Williams" },
- { fruitPrice: 1.50 },
- ])
+ 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
@@ -529,36 +638,40 @@ void QQmlTableModel::setRow(int rowIndex, const QVariant &row)
}
}
-/*!
- \qmlproperty var TableModel::roleDataProvider
-
- This property can hold a function that will map roles to values.
-
- When assigned, it will be called each time data() is called, to enable
- extracting arbitrary values, converting the data in arbitrary ways, or even
- doing calculations. It takes 3 arguments: \c index (\l QModelIndex),
- \c role (string), and \c cellData (object), which is the complete data that
- is stored in the given cell. (If the cell contains a JS object with
- multiple named values, the entire object will be given in \c cellData.)
- The function that you define must return the value to be used; for example
- a typical delegate will display the value returned for the \c display role,
- so you can check whether that is the role and return data in a form that is
- suitable for the delegate to show:
-
- \snippet qml/tablemodel/roleDataProvider.qml 0
-*/
-QJSValue QQmlTableModel::roleDataProvider() const
+QQmlListProperty<QQmlTableModelColumn> QQmlTableModel::columns()
{
- return mRoleDataProvider;
+ return QQmlListProperty<QQmlTableModelColumn>(this, nullptr,
+ &QQmlTableModel::columns_append,
+ &QQmlTableModel::columns_count,
+ &QQmlTableModel::columns_at,
+ &QQmlTableModel::columns_clear);
}
-void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider)
+void QQmlTableModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property,
+ QQmlTableModelColumn *value)
{
- if (roleDataProvider.strictlyEquals(mRoleDataProvider))
- return;
+ QQmlTableModel *model = static_cast<QQmlTableModel*>(property->object);
+ QQmlTableModelColumn *column = qobject_cast<QQmlTableModelColumn*>(value);
+ if (column)
+ model->mColumns.append(column);
+}
- mRoleDataProvider = roleDataProvider;
- emit roleDataProviderChanged();
+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();
}
/*!
@@ -574,14 +687,19 @@ void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider)
TableModel {
id: model
+
+ TableModelColumn { display: "fruitType" }
+ TableModelColumn { display: "fruitPrice" }
+
rows: [
- [{ fruitType: "Apple" }, { fruitPrice: 1.50 }],
- [{ fruitType: "Orange" }, { fruitPrice: 2.50 }]
+ { 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)).fruitType +
- " costs " + model.data(model.index(r, 1)).fruitPrice.toFixed(2))
+ console.log("An " + model.data(model.index(r, 0)).display +
+ " costs " + model.data(model.index(r, 1)).display.toFixed(2))
}
}
}
@@ -658,27 +776,31 @@ QVariant QQmlTableModel::data(const QModelIndex &index, int role) const
if (column < 0 || column >= columnCount())
return QVariant();
- if (!mRoleNames.contains(role))
+ 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 QVariantList rowData = mRows.at(row).toList();
-
- if (mRoleDataProvider.isCallable()) {
- auto engine = qmlEngine(this);
- const auto args = QJSValueList() <<
- engine->toScriptValue(index) <<
- QString::fromUtf8(mRoleNames.value(role)) <<
- engine->toScriptValue(rowData.at(column));
- return const_cast<QQmlTableModel*>(this)->mRoleDataProvider.call(args).toVariant();
+ 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;
}
- // TODO: should we also allow this code to be executed if roleDataProvider doesn't
- // handle the role/column, so that it only has to handle the case where there is
- // more than one role in a column?
- const QVariantMap columnData = rowData.at(column).toMap();
- const QString propertyName = columnPropertyNameFromRole(column, role);
- const QVariant value = columnData.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();
}
/*!
@@ -691,9 +813,9 @@ QVariant QQmlTableModel::data(const QModelIndex &index, int role) const
*/
bool QQmlTableModel::setData(const QModelIndex &index, const QString &role, const QVariant &value)
{
- const int iRole = mRoleNames.key(role.toUtf8(), -1);
- if (iRole >= 0)
- return setData(index, value, iRole);
+ const int intRole = mRoleNames.key(role.toUtf8(), -1);
+ if (intRole >= 0)
+ return setData(index, value, intRole);
return false;
}
@@ -707,56 +829,92 @@ bool QQmlTableModel::setData(const QModelIndex &index, const QVariant &value, in
if (column < 0 || column >= columnCount())
return false;
- if (!mRoleNames.contains(role))
- return false;
-
- const QVariantList rowData = mRows.at(row).toList();
- const QString propertyName = columnPropertyNameFromRole(column, role);
+ const QString roleName = QString::fromUtf8(mRoleNames.value(role));
qCDebug(lcTableModel).nospace() << "setData() called with index "
- << index << ", value " << value << " and role " << propertyName;
+ << index << ", value " << value << " and role " << roleName;
// Verify that the role exists for this column.
- const ColumnPropertyInfo propertyInfo = findColumnPropertyInfo(column, propertyName);
- if (!propertyInfo.isValid()) {
- QString message;
- QDebug stream(&message);
- stream.nospace() << "setData(): no role named " << propertyName
- << " at column index " << column << ". The available roles for that column are:\n";
-
- const QVector<ColumnPropertyInfo> availableProperties = mColumnProperties.at(column).infoForProperties;
- for (auto propertyInfo : availableProperties)
- stream << " - " << propertyInfo.name << " (" << qPrintable(propertyInfo.typeName) << ")";
-
- qmlWarning(this) << message;
+ 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() != propertyInfo.type) {
- if (!value.canConvert(int(propertyInfo.type))) {
+ 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 " << propertyName
- << " cannot be converted to " << propertyInfo.typeName;
+ << " set at row " << row << " column " << column << " with role " << roleName
+ << " cannot be converted to " << roleData.typeName;
return false;
}
- if (!effectiveValue.convert(int(propertyInfo.type))) {
+ if (!effectiveValue.convert(int(roleData.type))) {
qmlWarning(this).nospace() << "setData(): failed converting value " << value
- << " set at row " << row << " column " << column << " with role " << propertyName
- << " to " << propertyInfo.typeName;
+ << " set at row " << row << " column " << column << " with role " << roleName
+ << " to " << roleData.typeName;
return false;
}
}
- QVariantMap modifiedColumn = rowData.at(column).toMap();
- modifiedColumn[propertyName] = value;
+ 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:
- QVariantList modifiedRow = rowData;
- modifiedRow[column] = modifiedColumn;
- mRows[row] = modifiedRow;
+ - 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);
@@ -770,35 +928,36 @@ QHash<int, QByteArray> QQmlTableModel::roleNames() const
return mRoleNames;
}
-QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo()
+QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata()
{
}
-QQmlTableModel::ColumnPropertyInfo::ColumnPropertyInfo(
- const QString &name, QVariant::Type type, const QString &typeName) :
+QQmlTableModel::ColumnRoleMetadata::ColumnRoleMetadata(
+ bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName) :
+ isStringRole(isStringRole),
name(name),
type(type),
typeName(typeName)
{
}
-bool QQmlTableModel::ColumnPropertyInfo::isValid() const
+bool QQmlTableModel::ColumnRoleMetadata::isValid() const
{
return !name.isEmpty();
}
bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &row) const
{
- if (row.userType() != qMetaTypeId<QJSValue>()) {
- qmlWarning(this) << functionName << ": expected \"row\" argument to be an array,"
- << " but got " << row.typeName() << " instead";
+ if (!row.canConvert<QJSValue>()) {
+ qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue,"
+ << " but got " << row.typeName() << " instead:\n" << row;
return false;
}
- const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
- if (rowAsVariant.type() != QVariant::List) {
- qmlWarning(this) << functionName << ": expected \"row\" argument to be an array,"
- << " but got " << row.typeName() << " instead";
+ 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;
}
@@ -806,12 +965,21 @@ bool QQmlTableModel::validateRowType(const char *functionName, const QVariant &r
}
bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &row,
- int rowIndex, NewRowOperationFlag appendFlag) const
+ int rowIndex, NewRowOperationFlag operation) const
{
- if (!validateRowType(functionName, row))
+ 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 (appendFlag == OtherOperation) {
+ if (operation == OtherOperation) {
// Inserting/setting.
if (rowIndex < 0) {
qmlWarning(this) << functionName << ": \"rowIndex\" cannot be negative";
@@ -825,29 +993,48 @@ bool QQmlTableModel::validateNewRow(const char *functionName, const QVariant &ro
}
}
- const QVariant rowAsVariant = row.value<QJSValue>().toVariant();
- const QVariantList rowAsList = rowAsVariant.toList();
+ 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 int columnCount = rowAsList.size();
- if (columnCount != mColumnCount) {
+ const QVariantMap rowAsMap = rowAsVariant.toMap();
+ const int columnCount = rowAsMap.size();
+ if (columnCount < mColumnCount) {
qmlWarning(this) << functionName << ": expected " << mColumnCount
- << " columns, but got " << columnCount;
+ << " columns, but only got " << columnCount;
return false;
}
- // Verify that the row's columns and their roles match the name and type of existing data.
- // This iterates across the columns in the row. For example:
- // [
- // { checkable: true, checked: false }, // columnIndex == 0
- // { amount: 1 }, // columnIndex == 1
- // { fruitType: "Orange" }, // etc.
- // { fruitName: "Navel" },
- // { fruitPrice: 2.50 }
- // ],
- for (int columnIndex = 0; columnIndex < mColumnCount; ++columnIndex) {
- const QVariantMap column = rowAsList.at(columnIndex).toMap();
- if (!validateColumnPropertyTypes(functionName, column, columnIndex))
- 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;
@@ -869,82 +1056,4 @@ bool QQmlTableModel::validateRowIndex(const char *functionName, const char *argu
return true;
}
-bool QQmlTableModel::validateColumnPropertyTypes(const char *functionName,
- const QVariantMap &column, int columnIndex) const
-{
- // Actual
- const QVariantList columnProperties = column.values();
- const QStringList propertyNames = column.keys();
- // Expected
- const QVector<ColumnPropertyInfo> properties = mColumnProperties.at(columnIndex).infoForProperties;
-
- // This iterates across the properties in the column. For example:
- // 0 1 2
- // { foo: "A", bar: 1, baz: true },
- for (int propertyIndex = 0; propertyIndex < properties.size(); ++propertyIndex) {
- const QString propertyName = propertyNames.at(propertyIndex);
- const QVariant propertyValue = columnProperties.at(propertyIndex);
- const ColumnPropertyInfo expectedPropertyFormat = properties.at(propertyIndex);
-
- if (!validateColumnPropertyType(functionName, propertyName,
- propertyValue, expectedPropertyFormat, columnIndex)) {
- return false;
- }
- }
-
- return true;
-}
-
-bool QQmlTableModel::validateColumnPropertyType(const char *functionName, const QString &propertyName,
- const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const
-{
- if (propertyName != expectedPropertyFormat.name) {
- qmlWarning(this) << functionName
- << ": expected property named " << expectedPropertyFormat.name
- << " at column index " << columnIndex
- << ", but got " << propertyName << " instead";
- return false;
- }
-
- if (propertyValue.type() != expectedPropertyFormat.type) {
- qmlWarning(this) << functionName
- << ": expected property with type " << expectedPropertyFormat.typeName
- << " at column index " << columnIndex
- << ", but got " << propertyValue.typeName() << " instead";
- return false;
- }
-
- return true;
-}
-
-QQmlTableModel::ColumnPropertyInfo QQmlTableModel::findColumnPropertyInfo(
- int columnIndex, const QString &columnPropertyName) const
-{
- // TODO: check if a hash with its string-based lookup is faster,
- // keeping in mind that we may be doing index-based lookups too.
- const QVector<ColumnPropertyInfo> properties = mColumnProperties.at(columnIndex).infoForProperties;
- for (int i = 0; i < properties.size(); ++i) {
- const ColumnPropertyInfo &info = properties.at(i);
- if (info.name == columnPropertyName)
- return info;
- }
-
- return ColumnPropertyInfo();
-}
-
-QString QQmlTableModel::columnPropertyNameFromRole(int columnIndex, int role) const
-{
- QString propertyName;
- if (role == Qt::DisplayRole && mColumnProperties.at(columnIndex).explicitDisplayRoleIndex == -1) {
- // The user is getting or setting data for the display role,
- // but didn't specify any role with the name "display" in this column.
- // So, we give them the implicit display role, aka the first property we find.
- propertyName = mColumnProperties.at(columnIndex).infoForProperties.first().name;
- } else {
- // QML/JS supports utf8.
- propertyName = QString::fromUtf8(mRoleNames.value(role));
- }
- return propertyName;
-}
-
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltablemodel_p.h b/src/qml/types/qqmltablemodel_p.h
index 33b2189fcd..a1bb97e7d4 100644
--- a/src/qml/types/qqmltablemodel_p.h
+++ b/src/qml/types/qqmltablemodel_p.h
@@ -55,17 +55,21 @@
#include <QtCore/QAbstractTableModel>
#include <QtQml/qqml.h>
#include <QtQml/private/qtqmlglobal_p.h>
+#include <QtQml/private/qqmltablemodelcolumn_p.h>
#include <QtQml/QJSValue>
+#include <QtQml/QQmlListProperty>
QT_BEGIN_NAMESPACE
-class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel
+class Q_QML_PRIVATE_EXPORT QQmlTableModel : public QAbstractTableModel, public QQmlParserStatus
{
Q_OBJECT
Q_PROPERTY(int columnCount READ columnCount NOTIFY columnCountChanged FINAL)
Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged FINAL)
Q_PROPERTY(QVariant rows READ rows WRITE setRows NOTIFY rowsChanged FINAL)
- Q_PROPERTY(QJSValue roleDataProvider READ roleDataProvider WRITE setRoleDataProvider NOTIFY roleDataProviderChanged)
+ Q_PROPERTY(QQmlListProperty<QQmlTableModelColumn> columns READ columns CONSTANT FINAL)
+ Q_INTERFACES(QQmlParserStatus)
+ Q_CLASSINFO("DefaultProperty", "columns")
public:
QQmlTableModel(QObject *parent = nullptr);
@@ -82,8 +86,12 @@ public:
Q_INVOKABLE void removeRow(int rowIndex, int rows = 1);
Q_INVOKABLE void setRow(int rowIndex, const QVariant &row);
- QJSValue roleDataProvider() const;
- void setRoleDataProvider(QJSValue roleDataProvider);
+ QQmlListProperty<QQmlTableModelColumn> columns();
+
+ static void columns_append(QQmlListProperty<QQmlTableModelColumn> *property, QQmlTableModelColumn *value);
+ static int columns_count(QQmlListProperty<QQmlTableModelColumn> *property);
+ static QQmlTableModelColumn *columns_at(QQmlListProperty<QQmlTableModelColumn> *property, int index);
+ static void columns_clear(QQmlListProperty<QQmlTableModelColumn> *property);
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -98,57 +106,61 @@ Q_SIGNALS:
void columnCountChanged();
void rowCountChanged();
void rowsChanged();
- void roleDataProviderChanged();
private:
- class ColumnPropertyInfo
+ class ColumnRoleMetadata
{
public:
- ColumnPropertyInfo();
- ColumnPropertyInfo(const QString &name, QVariant::Type type, const QString &typeName);
+ ColumnRoleMetadata();
+ ColumnRoleMetadata(bool isStringRole, const QString &name, QVariant::Type type, const QString &typeName);
bool isValid() const;
+ // If this is false, it's a function role.
+ bool isStringRole = false;
QString name;
QVariant::Type type = QVariant::Invalid;
QString typeName;
};
- struct ColumnProperties
+ struct ColumnMetadata
{
- QVector<ColumnPropertyInfo> infoForProperties;
- // If there was a display role found in this column, it'll be stored here.
- // The index is into infoForProperties.
- int explicitDisplayRoleIndex = -1;
+ // Key = role name that will be made visible to the delegate
+ // Value = metadata about that role, including actual name in the model data, type, etc.
+ QHash<QString, ColumnRoleMetadata> roles;
};
enum NewRowOperationFlag {
OtherOperation, // insert(), set(), etc.
+ SetRowsOperation,
AppendOperation
};
+ void doSetRows(const QVariantList &rowsAsVariantList);
+ ColumnRoleMetadata fetchColumnRoleData(const QString &roleNameKey,
+ QQmlTableModelColumn *tableModelColumn, int columnIndex) const;
+ void fetchColumnMetadata();
+
bool validateRowType(const char *functionName, const QVariant &row) const;
bool validateNewRow(const char *functionName, const QVariant &row,
- int rowIndex, NewRowOperationFlag appendFlag = OtherOperation) const;
+ int rowIndex, NewRowOperationFlag operation = OtherOperation) const;
bool validateRowIndex(const char *functionName, const char *argumentName, int rowIndex) const;
- bool validateColumnPropertyTypes(const char *functionName, const QVariantMap &column, int columnIndex) const;
- bool validateColumnPropertyType(const char *functionName, const QString &propertyName,
- const QVariant &propertyValue, const ColumnPropertyInfo &expectedPropertyFormat, int columnIndex) const;
-
- ColumnPropertyInfo findColumnPropertyInfo(int columnIndex, const QString &columnPropertyNameFromRole) const;
- QString columnPropertyNameFromRole(int columnIndex, int role) const;
void doInsert(int rowIndex, const QVariant &row);
+ void classBegin() override;
+ void componentComplete() override;
+
+ bool componentCompleted = false;
QVariantList mRows;
+ QList<QQmlTableModelColumn *> mColumns;
int mRowCount = 0;
int mColumnCount = 0;
// Each entry contains information about the properties of the column at that index.
- QVector<ColumnProperties> mColumnProperties;
+ QVector<ColumnMetadata> mColumnMetadata;
// key = property index (0 to number of properties across all columns)
// value = role name
QHash<int, QByteArray> mRoleNames;
- QJSValue mRoleDataProvider;
};
QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltablemodelcolumn.cpp b/src/qml/types/qqmltablemodelcolumn.cpp
new file mode 100644
index 0000000000..93da0642de
--- /dev/null
+++ b/src/qml/types/qqmltablemodelcolumn.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** 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 "qqmltablemodelcolumn_p.h"
+
+#include <QtQml/qqmlinfo.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \qmltype TableModelColumn
+ \instantiates QQmlTableModelColumn
+ \inqmlmodule Qt.labs.qmlmodels
+ \brief Represents a column in a model.
+ \since 5.14
+
+ \section1 Supported Roles
+
+ TableModelColumn supports all of \l {Qt::ItemDataRole}{Qt's roles},
+ with the exception of \c Qt::InitialSortOrderRole.
+
+ \sa TableModel, TableView
+*/
+
+static const QString displayRoleName = QStringLiteral("display");
+static const QString decorationRoleName = QStringLiteral("decoration");
+static const QString editRoleName = QStringLiteral("edit");
+static const QString toolTipRoleName = QStringLiteral("toolTip");
+static const QString statusTipRoleName = QStringLiteral("statusTip");
+static const QString whatsThisRoleName = QStringLiteral("whatsThis");
+
+static const QString fontRoleName = QStringLiteral("font");
+static const QString textAlignmentRoleName = QStringLiteral("textAlignment");
+static const QString backgroundRoleName = QStringLiteral("background");
+static const QString foregroundRoleName = QStringLiteral("foreground");
+static const QString checkStateRoleName = QStringLiteral("checkState");
+
+static const QString accessibleTextRoleName = QStringLiteral("accessibleText");
+static const QString accessibleDescriptionRoleName = QStringLiteral("accessibleDescription");
+
+static const QString sizeHintRoleName = QStringLiteral("sizeHint");
+
+
+QQmlTableModelColumn::QQmlTableModelColumn(QObject *parent)
+ : QObject(parent)
+{
+}
+
+QQmlTableModelColumn::~QQmlTableModelColumn()
+{
+}
+
+#define DEFINE_ROLE_PROPERTIES(getterGetterName, getterSetterName, getterSignal, setterGetterName, setterSetterName, setterSignal, roleName) \
+QJSValue QQmlTableModelColumn::getterGetterName() const \
+{ \
+ return mGetters.value(roleName); \
+} \
+\
+void QQmlTableModelColumn::getterSetterName(const QJSValue &stringOrFunction) \
+{ \
+ if (!stringOrFunction.isString() && !stringOrFunction.isCallable()) { \
+ qmlWarning(this).quote() << "getter for " << roleName << " must be a function"; \
+ return; \
+ } \
+ if (stringOrFunction.strictlyEquals(decoration())) \
+ return; \
+\
+ mGetters[roleName] = stringOrFunction; \
+ emit decorationChanged(); \
+} \
+\
+QJSValue QQmlTableModelColumn::setterGetterName() const \
+{ \
+ return mSetters.value(roleName); \
+} \
+\
+void QQmlTableModelColumn::setterSetterName(const QJSValue &function) \
+{ \
+ if (!function.isCallable()) { \
+ qmlWarning(this).quote() << "setter for " << roleName << " must be a function"; \
+ return; \
+ } \
+\
+ if (function.strictlyEquals(getSetDisplay())) \
+ return; \
+\
+ mSetters[roleName] = function; \
+ emit setDisplayChanged(); \
+}
+
+DEFINE_ROLE_PROPERTIES(display, setDisplay, displayChanged,
+ getSetDisplay, setSetDisplay, setDisplayChanged, displayRoleName)
+DEFINE_ROLE_PROPERTIES(decoration, setDecoration, decorationChanged,
+ getSetDecoration, setSetDecoration, setDecorationChanged, decorationRoleName)
+DEFINE_ROLE_PROPERTIES(edit, setEdit, editChanged,
+ getSetEdit, setSetEdit, setEditChanged, editRoleName)
+DEFINE_ROLE_PROPERTIES(toolTip, setToolTip, toolTipChanged,
+ getSetToolTip, setSetToolTip, setToolTipChanged, toolTipRoleName)
+DEFINE_ROLE_PROPERTIES(statusTip, setStatusTip, statusTipChanged,
+ getSetStatusTip, setSetStatusTip, setStatusTipChanged, statusTipRoleName)
+DEFINE_ROLE_PROPERTIES(whatsThis, setWhatsThis, whatsThisChanged,
+ getSetWhatsThis, setSetWhatsThis, setWhatsThisChanged, whatsThisRoleName)
+
+DEFINE_ROLE_PROPERTIES(font, setFont, fontChanged,
+ getSetFont, setSetFont, setFontChanged, fontRoleName)
+DEFINE_ROLE_PROPERTIES(textAlignment, setTextAlignment, textAlignmentChanged,
+ getSetTextAlignment, setSetTextAlignment, setTextAlignmentChanged, textAlignmentRoleName)
+DEFINE_ROLE_PROPERTIES(background, setBackground, backgroundChanged,
+ getSetBackground, setSetBackground, setBackgroundChanged, backgroundRoleName)
+DEFINE_ROLE_PROPERTIES(foreground, setForeground, foregroundChanged,
+ getSetForeground, setSetForeground, setForegroundChanged, foregroundRoleName)
+DEFINE_ROLE_PROPERTIES(checkState, setCheckState, checkStateChanged,
+ getSetCheckState, setSetCheckState, setCheckStateChanged, checkStateRoleName)
+
+DEFINE_ROLE_PROPERTIES(accessibleText, setAccessibleText, accessibleTextChanged,
+ getSetAccessibleText, setSetAccessibleText, setAccessibleTextChanged, accessibleTextRoleName)
+DEFINE_ROLE_PROPERTIES(accessibleDescription, setAccessibleDescription, accessibleDescriptionChanged,
+ getSetAccessibleDescription, setSetAccessibleDescription, setAccessibleDescriptionChanged, accessibleDescriptionRoleName)
+
+DEFINE_ROLE_PROPERTIES(sizeHint, setSizeHint, sizeHintChanged,
+ getSetSizeHint, setSetSizeHint, setSizeHintChanged, sizeHintRoleName)
+
+QJSValue QQmlTableModelColumn::getterAtRole(const QString &roleName)
+{
+ auto it = mGetters.find(roleName);
+ if (it == mGetters.end())
+ return QJSValue();
+ return *it;
+}
+
+QJSValue QQmlTableModelColumn::setterAtRole(const QString &roleName)
+{
+ auto it = mSetters.find(roleName);
+ if (it == mSetters.end())
+ return QJSValue();
+ return *it;
+}
+
+const QHash<QString, QJSValue> QQmlTableModelColumn::getters() const
+{
+ return mGetters;
+}
+
+const QHash<int, QString> QQmlTableModelColumn::supportedRoleNames()
+{
+ QHash<int, QString> names;
+ names[Qt::DisplayRole] = QLatin1String("display");
+ names[Qt::DecorationRole] = QLatin1String("decoration");
+ names[Qt::EditRole] = QLatin1String("edit");
+ names[Qt::ToolTipRole] = QLatin1String("toolTip");
+ names[Qt::StatusTipRole] = QLatin1String("statusTip");
+ names[Qt::WhatsThisRole] = QLatin1String("whatsThis");
+ names[Qt::FontRole] = QLatin1String("font");
+ names[Qt::TextAlignmentRole] = QLatin1String("textAlignment");
+ names[Qt::BackgroundRole] = QLatin1String("background");
+ names[Qt::ForegroundRole] = QLatin1String("foreground");
+ names[Qt::CheckStateRole] = QLatin1String("checkState");
+ names[Qt::AccessibleTextRole] = QLatin1String("accessibleText");
+ names[Qt::AccessibleDescriptionRole] = QLatin1String("accessibleDescription");
+ names[Qt::SizeHintRole] = QLatin1String("sizeHint");
+ return names;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/types/qqmltablemodelcolumn_p.h b/src/qml/types/qqmltablemodelcolumn_p.h
new file mode 100644
index 0000000000..41c02482c0
--- /dev/null
+++ b/src/qml/types/qqmltablemodelcolumn_p.h
@@ -0,0 +1,224 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef QQMLTABLEMODELCOLUMN_P_H
+#define QQMLTABLEMODELCOLUMN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QObject>
+#include <QtQml/qqml.h>
+#include <QtQml/private/qtqmlglobal_p.h>
+#include <QtQml/qjsvalue.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_QML_AUTOTEST_EXPORT QQmlTableModelColumn : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QJSValue display READ display WRITE setDisplay NOTIFY displayChanged FINAL)
+ Q_PROPERTY(QJSValue setDisplay READ getSetDisplay WRITE setSetDisplay NOTIFY setDisplayChanged)
+ Q_PROPERTY(QJSValue decoration READ decoration WRITE setDecoration NOTIFY decorationChanged FINAL)
+ Q_PROPERTY(QJSValue setDecoration READ getSetDecoration WRITE setSetDecoration NOTIFY setDecorationChanged FINAL)
+ Q_PROPERTY(QJSValue edit READ edit WRITE setEdit NOTIFY editChanged FINAL)
+ Q_PROPERTY(QJSValue setEdit READ getSetEdit WRITE setSetEdit NOTIFY setEditChanged FINAL)
+ Q_PROPERTY(QJSValue toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged FINAL)
+ Q_PROPERTY(QJSValue setToolTip READ getSetToolTip WRITE setSetToolTip NOTIFY setToolTipChanged FINAL)
+ Q_PROPERTY(QJSValue statusTip READ statusTip WRITE setStatusTip NOTIFY statusTipChanged FINAL)
+ Q_PROPERTY(QJSValue setStatusTip READ getSetStatusTip WRITE setSetStatusTip NOTIFY setStatusTipChanged FINAL)
+ Q_PROPERTY(QJSValue whatsThis READ whatsThis WRITE setWhatsThis NOTIFY whatsThisChanged FINAL)
+ Q_PROPERTY(QJSValue setWhatsThis READ getSetWhatsThis WRITE setSetWhatsThis NOTIFY setWhatsThisChanged FINAL)
+
+ Q_PROPERTY(QJSValue font READ font WRITE setFont NOTIFY fontChanged FINAL)
+ Q_PROPERTY(QJSValue setFont READ getSetFont WRITE setSetFont NOTIFY setFontChanged FINAL)
+ Q_PROPERTY(QJSValue textAlignment READ textAlignment WRITE setTextAlignment NOTIFY textAlignmentChanged FINAL)
+ Q_PROPERTY(QJSValue setTextAlignment READ getSetTextAlignment WRITE setSetTextAlignment NOTIFY setTextAlignmentChanged FINAL)
+ Q_PROPERTY(QJSValue background READ background WRITE setBackground NOTIFY backgroundChanged FINAL)
+ Q_PROPERTY(QJSValue setBackground READ getSetBackground WRITE setSetBackground NOTIFY setBackgroundChanged FINAL)
+ Q_PROPERTY(QJSValue foreground READ foreground WRITE setForeground NOTIFY foregroundChanged FINAL)
+ Q_PROPERTY(QJSValue setForeground READ getSetForeground WRITE setSetForeground NOTIFY setForegroundChanged FINAL)
+ Q_PROPERTY(QJSValue checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL)
+ Q_PROPERTY(QJSValue setCheckState READ getSetCheckState WRITE setSetCheckState NOTIFY setCheckStateChanged FINAL)
+
+ Q_PROPERTY(QJSValue accessibleText READ accessibleText WRITE setAccessibleText NOTIFY accessibleTextChanged FINAL)
+ Q_PROPERTY(QJSValue setAccessibleText READ getSetAccessibleText WRITE setSetAccessibleText NOTIFY setAccessibleTextChanged FINAL)
+ Q_PROPERTY(QJSValue accessibleDescription READ accessibleDescription
+ WRITE setAccessibleDescription NOTIFY accessibleDescriptionChanged FINAL)
+ Q_PROPERTY(QJSValue setAccessibleDescription READ getSetAccessibleDescription
+ WRITE setSetAccessibleDescription NOTIFY setAccessibleDescriptionChanged FINAL)
+
+ Q_PROPERTY(QJSValue sizeHint READ sizeHint WRITE setSizeHint NOTIFY sizeHintChanged FINAL)
+ Q_PROPERTY(QJSValue setSizeHint READ getSetSizeHint WRITE setSetSizeHint NOTIFY setSizeHintChanged FINAL)
+
+public:
+ QQmlTableModelColumn(QObject *parent = nullptr);
+ ~QQmlTableModelColumn() override;
+
+ QJSValue display() const;
+ void setDisplay(const QJSValue &stringOrFunction);
+ QJSValue getSetDisplay() const;
+ void setSetDisplay(const QJSValue &function);
+
+ QJSValue decoration() const;
+ void setDecoration(const QJSValue &stringOrFunction);
+ QJSValue getSetDecoration() const;
+ void setSetDecoration(const QJSValue &function);
+
+ QJSValue edit() const;
+ void setEdit(const QJSValue &stringOrFunction);
+ QJSValue getSetEdit() const;
+ void setSetEdit(const QJSValue &function);
+
+ QJSValue toolTip() const;
+ void setToolTip(const QJSValue &stringOrFunction);
+ QJSValue getSetToolTip() const;
+ void setSetToolTip(const QJSValue &function);
+
+ QJSValue statusTip() const;
+ void setStatusTip(const QJSValue &stringOrFunction);
+ QJSValue getSetStatusTip() const;
+ void setSetStatusTip(const QJSValue &function);
+
+ QJSValue whatsThis() const;
+ void setWhatsThis(const QJSValue &stringOrFunction);
+ QJSValue getSetWhatsThis() const;
+ void setSetWhatsThis(const QJSValue &function);
+
+ QJSValue font() const;
+ void setFont(const QJSValue &stringOrFunction);
+ QJSValue getSetFont() const;
+ void setSetFont(const QJSValue &function);
+
+ QJSValue textAlignment() const;
+ void setTextAlignment(const QJSValue &stringOrFunction);
+ QJSValue getSetTextAlignment() const;
+ void setSetTextAlignment(const QJSValue &function);
+
+ QJSValue background() const;
+ void setBackground(const QJSValue &stringOrFunction);
+ QJSValue getSetBackground() const;
+ void setSetBackground(const QJSValue &function);
+
+ QJSValue foreground() const;
+ void setForeground(const QJSValue &stringOrFunction);
+ QJSValue getSetForeground() const;
+ void setSetForeground(const QJSValue &function);
+
+ QJSValue checkState() const;
+ void setCheckState(const QJSValue &stringOrFunction);
+ QJSValue getSetCheckState() const;
+ void setSetCheckState(const QJSValue &function);
+
+ QJSValue accessibleText() const;
+ void setAccessibleText(const QJSValue &stringOrFunction);
+ QJSValue getSetAccessibleText() const;
+ void setSetAccessibleText(const QJSValue &function);
+
+ QJSValue accessibleDescription() const;
+ void setAccessibleDescription(const QJSValue &stringOrFunction);
+ QJSValue getSetAccessibleDescription() const;
+ void setSetAccessibleDescription(const QJSValue &function);
+
+ QJSValue sizeHint() const;
+ void setSizeHint(const QJSValue &stringOrFunction);
+ QJSValue getSetSizeHint() const;
+ void setSetSizeHint(const QJSValue &function);
+
+ QJSValue getterAtRole(const QString &roleName);
+ QJSValue setterAtRole(const QString &roleName);
+
+ const QHash<QString, QJSValue> getters() const;
+
+ static const QHash<int, QString> supportedRoleNames();
+
+Q_SIGNALS:
+ void indexChanged();
+ void displayChanged();
+ void setDisplayChanged();
+ void decorationChanged();
+ void setDecorationChanged();
+ void editChanged();
+ void setEditChanged();
+ void toolTipChanged();
+ void setToolTipChanged();
+ void statusTipChanged();
+ void setStatusTipChanged();
+ void whatsThisChanged();
+ void setWhatsThisChanged();
+
+ void fontChanged();
+ void setFontChanged();
+ void textAlignmentChanged();
+ void setTextAlignmentChanged();
+ void backgroundChanged();
+ void setBackgroundChanged();
+ void foregroundChanged();
+ void setForegroundChanged();
+ void checkStateChanged();
+ void setCheckStateChanged();
+
+ void accessibleTextChanged();
+ void setAccessibleTextChanged();
+ void accessibleDescriptionChanged();
+ void setAccessibleDescriptionChanged();
+ void sizeHintChanged();
+ void setSizeHintChanged();
+
+private:
+ int mIndex = -1;
+
+ // We store these in hashes because QQuickTableModel needs string-based lookup in certain situations.
+ QHash<QString, QJSValue> mGetters;
+ QHash<QString, QJSValue> mSetters;
+};
+
+QT_END_NAMESPACE
+
+QML_DECLARE_TYPE(QQmlTableModelColumn)
+
+#endif // QQMLTABLEMODELCOLUMN_P_H
diff --git a/src/qml/types/types.pri b/src/qml/types/types.pri
index 1765beb09e..5a56208dc4 100644
--- a/src/qml/types/types.pri
+++ b/src/qml/types/types.pri
@@ -7,7 +7,8 @@ SOURCES += \
$$PWD/qquickpackage.cpp \
$$PWD/qqmlinstantiator.cpp \
$$PWD/qqmltableinstancemodel.cpp \
- $$PWD/qqmltablemodel.cpp
+ $$PWD/qqmltablemodel.cpp \
+ $$PWD/qqmltablemodelcolumn.cpp
HEADERS += \
$$PWD/qqmlbind_p.h \
@@ -19,7 +20,8 @@ HEADERS += \
$$PWD/qqmlinstantiator_p.h \
$$PWD/qqmlinstantiator_p_p.h \
$$PWD/qqmltableinstancemodel_p.h \
- $$PWD/qqmltablemodel_p.h
+ $$PWD/qqmltablemodel_p.h \
+ $$PWD/qqmltablemodelcolumn_p.h
qtConfig(qml-worker-script) {
SOURCES += \