From db292d1fe70a0cfaf315a72d099441cf3969e284 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 21 Dec 2018 16:00:48 +0100 Subject: Enable lookups in QML The main feature that needs to be implemented in order to enable lookups in QML files is to respect that the QObject wrapper has its own storage layer (meta-object properties). Lookups need to be able to index those when the base is a QObject. This is done by caching the property data and guarding the validity by comparing property cache pointers. The same lookup logic is also implemented for value type wrappers. OVerall there's more that can be done with lookups in meta-objects, for constant properties for example. For "global" lookups we have a safeguard in place that generates a LoadName instruction for property access that should end up in the qml context wrapper. So no changes are needed here at first, but the lookup in the QML context can be optimized in the future. The way of storing the property cache in the lookup itself trades ugliness on destruction against the creation of less internal classes. Another option would be to store the property cache in the internal class and let QObjectWrapper always transition via the property cache. Task-number: QTBUG-69898 Change-Id: I9c378c071acc6d7d4a34a2a76616f9594119d515 Reviewed-by: Ulf Hermann --- src/qml/types/qqmllistmodel.cpp | 7 +++++++ src/qml/types/qqmllistmodel_p_p.h | 2 ++ 2 files changed, 9 insertions(+) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index debf14df97..006825cc93 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -1613,6 +1614,12 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va return that->engine()->fromVariant(value); } +ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) +{ + lookup->getter = Lookup::getterFallback; + return lookup->getter(lookup, engine, *object); +} + struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator { int roleNameIndex = 0; diff --git a/src/qml/types/qqmllistmodel_p_p.h b/src/qml/types/qqmllistmodel_p_p.h index ff52ee049f..2876c71de6 100644 --- a/src/qml/types/qqmllistmodel_p_p.h +++ b/src/qml/types/qqmllistmodel_p_p.h @@ -181,6 +181,8 @@ struct ModelObject : public QObjectWrapper protected: static bool virtualPut(Managed *m, PropertyKey id, const Value& value, Value *receiver); static ReturnedValue virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty); + static ReturnedValue virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup); + static ReturnedValue lookupGetter(Lookup *l, ExecutionEngine *engine, const Value &object); static OwnPropertyKeyIterator *virtualOwnPropertyKeys(const Object *m, Value *target); }; -- cgit v1.2.3 From 5cfccf30898aed5ca96c0f8779b0f8a1117118b7 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 3 Jan 2019 15:44:34 +0100 Subject: Remove dead compile time QML context/scope property and id object code After enabling lookups in QML files, we can remove all the code that tries to deal with (type) compile time detection of access to id objects and properties of the scope/context object. This also allows removing quite a bit of run-time code paths and even byte code instructions. Task-number: QTBUG-69898 Change-Id: I7b26d7983393594a3ef56466d3e633f1822b76f4 Reviewed-by: Ulf Hermann --- src/qml/types/qqmllistmodel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 006825cc93..69b7876cf6 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -1605,8 +1605,7 @@ ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Va if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) { QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine); if (ep && ep->propertyCapture) - ep->propertyCapture->captureProperty(that->object(), -1, role->index, - QQmlPropertyCapture::OnlyOnce, false); + ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false); } const int elementIndex = that->d()->elementIndex(); -- cgit v1.2.3 From 4e5ff56a18dc7bb13680a8fbe30ba5aae2dfa206 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Mon, 4 Mar 2019 12:18:52 +0100 Subject: QQmlInstanceModel: add variantValue() This is necessary in order to add valueRole API to Qt Quick Controls 2's ComboBox. Make stringValue() non-virtual and have it just call variantValue().toString(). Task-number: QTBUG-73491 Change-Id: Ic4ecf8370eb0bb8045a967bd589e54dfc1ac5263 Reviewed-by: Richard Moe Gustavsen --- src/qml/types/qqmldelegatemodel.cpp | 18 +++++++++--------- src/qml/types/qqmldelegatemodel_p.h | 2 +- src/qml/types/qqmldelegatemodel_p_p.h | 4 ++-- src/qml/types/qqmlobjectmodel.cpp | 4 ++-- src/qml/types/qqmlobjectmodel_p.h | 5 +++-- src/qml/types/qqmltableinstancemodel_p.h | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 572f58339f..0b95c7c3e7 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -1127,7 +1127,7 @@ QQmlIncubator::Status QQmlDelegateModel::incubationStatus(int index) return QQmlIncubator::Ready; } -QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index, const QString &name) +QVariant QQmlDelegateModelPrivate::variantValue(QQmlListCompositor::Group group, int index, const QString &name) { Compositor::iterator it = m_compositor.find(group, index); if (QQmlAdaptorModel *model = it.list()) { @@ -1139,20 +1139,20 @@ QString QQmlDelegateModelPrivate::stringValue(Compositor::Group group, int index while (dot > 0) { QObject *obj = qvariant_cast(value); if (!obj) - return QString(); - int from = dot+1; + return QVariant(); + const int from = dot + 1; dot = name.indexOf(QLatin1Char('.'), from); value = obj->property(name.midRef(from, dot - from).toUtf8()); } - return value.toString(); + return value; } - return QString(); + return QVariant(); } -QString QQmlDelegateModel::stringValue(int index, const QString &name) +QVariant QQmlDelegateModel::variantValue(int index, const QString &role) { Q_D(QQmlDelegateModel); - return d->stringValue(d->m_compositorGroup, index, name); + return d->variantValue(d->m_compositorGroup, index, role); } int QQmlDelegateModel::indexOf(QObject *item, QObject *) const @@ -3338,9 +3338,9 @@ QQmlInstanceModel::ReleaseFlags QQmlPartsModel::release(QObject *item) return flags; } -QString QQmlPartsModel::stringValue(int index, const QString &role) +QVariant QQmlPartsModel::variantValue(int index, const QString &role) { - return QQmlDelegateModelPrivate::get(m_model)->stringValue(m_compositorGroup, index, role); + return QQmlDelegateModelPrivate::get(m_model)->variantValue(m_compositorGroup, index, role); } void QQmlPartsModel::setWatchedRoles(const QList &roles) diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h index 0ad8939732..fbf3614a7c 100644 --- a/src/qml/types/qqmldelegatemodel_p.h +++ b/src/qml/types/qqmldelegatemodel_p.h @@ -114,7 +114,7 @@ public: QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *object) override; void cancel(int index) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList &roles) override; QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmldelegatemodel_p_p.h b/src/qml/types/qqmldelegatemodel_p_p.h index 0028849828..7f10bbf370 100644 --- a/src/qml/types/qqmldelegatemodel_p_p.h +++ b/src/qml/types/qqmldelegatemodel_p_p.h @@ -276,7 +276,7 @@ public: void requestMoreIfNecessary(); QObject *object(Compositor::Group group, int index, QQmlIncubator::IncubationMode incubationMode); QQmlDelegateModel::ReleaseFlags release(QObject *object); - QString stringValue(Compositor::Group group, int index, const QString &name); + QVariant variantValue(Compositor::Group group, int index, const QString &name); void emitCreatedPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitInitPackage(QQDMIncubationTask *incubationTask, QQuickPackage *package); void emitCreatedItem(QQDMIncubationTask *incubationTask, QObject *item) { @@ -378,7 +378,7 @@ public: bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *item) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; QList watchedRoles() const { return m_watchedRoles; } void setWatchedRoles(const QList &roles) override; QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmlobjectmodel.cpp b/src/qml/types/qqmlobjectmodel.cpp index 2f4d427430..b6330b4295 100644 --- a/src/qml/types/qqmlobjectmodel.cpp +++ b/src/qml/types/qqmlobjectmodel.cpp @@ -273,12 +273,12 @@ QQmlInstanceModel::ReleaseFlags QQmlObjectModel::release(QObject *item) return nullptr; } -QString QQmlObjectModel::stringValue(int index, const QString &name) +QVariant QQmlObjectModel::variantValue(int index, const QString &role) { Q_D(QQmlObjectModel); if (index < 0 || index >= d->children.count()) return QString(); - return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(name).toString(); + return QQmlEngine::contextForObject(d->children.at(index).item)->contextProperty(role); } QQmlIncubator::Status QQmlObjectModel::incubationStatus(int) diff --git a/src/qml/types/qqmlobjectmodel_p.h b/src/qml/types/qqmlobjectmodel_p.h index 4ac4f1c65b..1284ba1780 100644 --- a/src/qml/types/qqmlobjectmodel_p.h +++ b/src/qml/types/qqmlobjectmodel_p.h @@ -79,7 +79,8 @@ public: virtual QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) = 0; virtual ReleaseFlags release(QObject *object) = 0; virtual void cancel(int) {} - virtual QString stringValue(int, const QString &) = 0; + QString stringValue(int index, const QString &role) { return variantValue(index, role).toString(); } + virtual QVariant variantValue(int, const QString &) = 0; virtual void setWatchedRoles(const QList &roles) = 0; virtual QQmlIncubator::Status incubationStatus(int index) = 0; @@ -119,7 +120,7 @@ public: bool isValid() const override; QObject *object(int index, QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::AsynchronousIfNested) override; ReleaseFlags release(QObject *object) override; - QString stringValue(int index, const QString &role) override; + QVariant variantValue(int index, const QString &role) override; void setWatchedRoles(const QList &) override {} QQmlIncubator::Status incubationStatus(int index) override; diff --git a/src/qml/types/qqmltableinstancemodel_p.h b/src/qml/types/qqmltableinstancemodel_p.h index 3dd5c4e4ce..39ec66d136 100644 --- a/src/qml/types/qqmltableinstancemodel_p.h +++ b/src/qml/types/qqmltableinstancemodel_p.h @@ -122,7 +122,7 @@ public: QQmlIncubator::Status incubationStatus(int index) override; - QString stringValue(int, const QString &) override { Q_UNREACHABLE(); return QString(); } + QVariant variantValue(int, const QString &) override { Q_UNREACHABLE(); return QVariant(); } void setWatchedRoles(const QList &) override { Q_UNREACHABLE(); } int indexOf(QObject *, QObject *) const override { Q_UNREACHABLE(); return 0; } -- cgit v1.2.3 From 03314e4c8337895f89f456273526d62cf317c3c5 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:10:32 +0200 Subject: QQuickWorkerScript: remove unused includes Change-Id: Ib231bb59347bc47edd2e339da4c197daf9bc24e3 Reviewed-by: Simon Hausmann --- src/qml/types/qquickworkerscript.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/qml/types') diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp index edb112276c..aaa89846aa 100644 --- a/src/qml/types/qquickworkerscript.cpp +++ b/src/qml/types/qquickworkerscript.cpp @@ -39,10 +39,6 @@ #include "qtqmlglobal_p.h" #include "qquickworkerscript_p.h" -#if QT_CONFIG(qml_list_model) -#include "qqmllistmodel_p.h" -#include "qqmllistmodelworkeragent_p.h" -#endif #include #include -- cgit v1.2.3 From 7a303424f2095c53889f8102f115ec38013ef8d9 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Thu, 7 Mar 2019 10:21:53 +0100 Subject: 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 Reviewed-by: Richard Moe Gustavsen --- src/qml/types/qqmlmodelsmodule.cpp | 2 + src/qml/types/qqmltablemodel.cpp | 731 +++++++++++++++++++-------------- src/qml/types/qqmltablemodel_p.h | 56 ++- src/qml/types/qqmltablemodelcolumn.cpp | 200 +++++++++ src/qml/types/qqmltablemodelcolumn_p.h | 224 ++++++++++ src/qml/types/types.pri | 6 +- 6 files changed, 884 insertions(+), 335 deletions(-) create mode 100644 src/qml/types/qqmltablemodelcolumn.cpp create mode 100644 src/qml/types/qqmltablemodelcolumn_p.h (limited to 'src/qml/types') 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 #include +#include QT_BEGIN_NAMESPACE @@ -77,6 +78,7 @@ void QQmlModelsModule::defineLabsModule() qmlRegisterType(uri, 1, 0, "DelegateChooser"); qmlRegisterType(uri, 1, 0, "DelegateChoice"); qmlRegisterType(uri, 1, 0, "TableModel"); + qmlRegisterType(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 QQmlTableModel::columns() { - return mRoleDataProvider; + return QQmlListProperty(this, nullptr, + &QQmlTableModel::columns_append, + &QQmlTableModel::columns_count, + &QQmlTableModel::columns_at, + &QQmlTableModel::columns_clear); } -void QQmlTableModel::setRoleDataProvider(QJSValue roleDataProvider) +void QQmlTableModel::columns_append(QQmlListProperty *property, + QQmlTableModelColumn *value) { - if (roleDataProvider.strictlyEquals(mRoleDataProvider)) - return; + QQmlTableModel *model = static_cast(property->object); + QQmlTableModelColumn *column = qobject_cast(value); + if (column) + model->mColumns.append(column); +} - mRoleDataProvider = roleDataProvider; - emit roleDataProviderChanged(); +int QQmlTableModel::columns_count(QQmlListProperty *property) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.count(); +} + +QQmlTableModelColumn *QQmlTableModel::columns_at(QQmlListProperty *property, int index) +{ + const QQmlTableModel *model = static_cast(property->object); + return model->mColumns.at(index); +} + +void QQmlTableModel::columns_clear(QQmlListProperty *property) +{ + QQmlTableModel *model = static_cast(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(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 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 rolesChanged; rolesChanged.append(role); @@ -770,35 +928,36 @@ QHash 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()) { - qmlWarning(this) << functionName << ": expected \"row\" argument to be an array," - << " but got " << row.typeName() << " instead"; + if (!row.canConvert()) { + qmlWarning(this) << functionName << ": expected \"row\" argument to be a QJSValue," + << " but got " << row.typeName() << " instead:\n" << row; return false; } - const QVariant rowAsVariant = row.value().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(); + 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().toVariant(); - const QVariantList rowAsList = rowAsVariant.toList(); + const QVariant rowAsVariant = operation == SetRowsOperation + ? row : row.value().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 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 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 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 #include #include +#include #include +#include 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 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 columns(); + + static void columns_append(QQmlListProperty *property, QQmlTableModelColumn *value); + static int columns_count(QQmlListProperty *property); + static QQmlTableModelColumn *columns_at(QQmlListProperty *property, int index); + static void columns_clear(QQmlListProperty *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 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 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 mColumns; int mRowCount = 0; int mColumnCount = 0; // Each entry contains information about the properties of the column at that index. - QVector mColumnProperties; + QVector mColumnMetadata; // key = property index (0 to number of properties across all columns) // value = role name QHash 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 + +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 QQmlTableModelColumn::getters() const +{ + return mGetters; +} + +const QHash QQmlTableModelColumn::supportedRoleNames() +{ + QHash 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 +#include +#include +#include + +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 getters() const; + + static const QHash 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 mGetters; + QHash 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 += \ -- cgit v1.2.3 From 4b6832828e04a9806aaf345da5f456817dd3970b Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Mon, 8 Apr 2019 15:39:45 +0200 Subject: Don't use delegate model types on -no-feature-delegate-model Fixes: QTBUG-74884 Change-Id: I7a675f6ef41937cef0f8e67960486c5b022d735c Reviewed-by: Simon Hausmann --- src/qml/types/qqmlmodelsmodule.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index 30915d96fd..bb72771cd9 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -72,9 +72,11 @@ void QQmlModelsModule::defineLabsModule() { const char uri[] = "Qt.labs.qmlmodels"; +#if QT_CONFIG(qml_delegate_model) qmlRegisterUncreatableType(uri, 1, 0, "AbstractDelegateComponent", QQmlAbstractDelegateComponent::tr("Cannot create instance of abstract class AbstractDelegateComponent.")); qmlRegisterType(uri, 1, 0, "DelegateChooser"); qmlRegisterType(uri, 1, 0, "DelegateChoice"); +#endif } QT_END_NAMESPACE -- cgit v1.2.3 From 65299ce292180a845eb2d76756faeeaa1456d8f0 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 4 Apr 2019 16:00:36 +0200 Subject: Remove QQmlV4Handle This is just an alias for QV4::ReturnedValue. We can as well use the latter. Change-Id: Ibd2c038a3ca726b39a8f0f05e02922adb9fccbdb Reviewed-by: Simon Hausmann --- src/qml/types/qqmldelegatemodel.cpp | 20 +++++++++----------- src/qml/types/qqmldelegatemodel_p.h | 5 ++--- src/qml/types/qqmllistmodel.cpp | 8 ++++---- src/qml/types/qqmllistmodel_p.h | 4 ++-- src/qml/types/qqmllistmodelworkeragent.cpp | 4 ++-- src/qml/types/qqmllistmodelworkeragent_p.h | 4 ++-- src/qml/types/qquickworkerscript.cpp | 8 +++----- src/qml/types/qquickworkerscript_p.h | 3 +-- 8 files changed, 25 insertions(+), 31 deletions(-) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmldelegatemodel.cpp b/src/qml/types/qqmldelegatemodel.cpp index 0b95c7c3e7..0e57119368 100644 --- a/src/qml/types/qqmldelegatemodel.cpp +++ b/src/qml/types/qqmldelegatemodel.cpp @@ -2419,17 +2419,15 @@ void QQmlDelegateModelGroupPrivate::setModel(QQmlDelegateModel *m, Compositor::G bool QQmlDelegateModelGroupPrivate::isChangedConnected() { Q_Q(QQmlDelegateModelGroup); - IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QQmlV4Handle &,const QQmlV4Handle &)); + IS_SIGNAL_CONNECTED(q, QQmlDelegateModelGroup, changed, (const QJSValue &,const QJSValue &)); } void QQmlDelegateModelGroupPrivate::emitChanges(QV4::ExecutionEngine *v4) { Q_Q(QQmlDelegateModelGroup); if (isChangedConnected() && !changeSet.isEmpty()) { - QV4::Scope scope(v4); - QV4::ScopedValue removed(scope, engineData(scope.engine)->array(v4, changeSet.removes())); - QV4::ScopedValue inserted(scope, engineData(scope.engine)->array(v4, changeSet.inserts())); - emit q->changed(QQmlV4Handle(removed), QQmlV4Handle(inserted)); + emit q->changed(QJSValue(v4, engineData(v4)->array(v4, changeSet.removes())), + QJSValue(v4, engineData(v4)->array(v4, changeSet.inserts()))); } if (changeSet.difference() != 0) emit q->countChanged(); @@ -2607,18 +2605,18 @@ void QQmlDelegateModelGroup::setDefaultInclude(bool include) \endlist */ -QQmlV4Handle QQmlDelegateModelGroup::get(int index) +QJSValue QQmlDelegateModelGroup::get(int index) { Q_D(QQmlDelegateModelGroup); if (!d->model) - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); QQmlDelegateModelPrivate *model = QQmlDelegateModelPrivate::get(d->model); if (!model->m_context || !model->m_context->isValid()) { - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); } else if (index < 0 || index >= model->m_compositor.count(d->group)) { qmlWarning(this) << tr("get: index out of range"); - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); } Compositor::iterator it = model->m_compositor.find(d->group, index); @@ -2630,7 +2628,7 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index) cacheItem = model->m_adaptorModel.createItem( model->m_cacheMetaType, it.modelIndex()); if (!cacheItem) - return QQmlV4Handle(QV4::Encode::undefined()); + return QJSValue(); cacheItem->groups = it->flags; model->m_cache.insert(it.cacheIndex, cacheItem); @@ -2646,7 +2644,7 @@ QQmlV4Handle QQmlDelegateModelGroup::get(int index) o->setPrototypeOf(p); ++cacheItem->scriptRef; - return QQmlV4Handle(o); + return QJSValue(v4, o->asReturnedValue()); } bool QQmlDelegateModelGroupPrivate::parseIndex(const QV4::Value &value, int *index, Compositor::Group *group) const diff --git a/src/qml/types/qqmldelegatemodel_p.h b/src/qml/types/qqmldelegatemodel_p.h index fbf3614a7c..2684162514 100644 --- a/src/qml/types/qqmldelegatemodel_p.h +++ b/src/qml/types/qqmldelegatemodel_p.h @@ -59,7 +59,6 @@ #include #include -#include #include QT_REQUIRE_CONFIG(qml_delegate_model); @@ -179,7 +178,7 @@ public: bool defaultInclude() const; void setDefaultInclude(bool include); - Q_INVOKABLE QQmlV4Handle get(int index); + Q_INVOKABLE QJSValue get(int index); public Q_SLOTS: void insert(QQmlV4Function *); @@ -195,7 +194,7 @@ Q_SIGNALS: void countChanged(); void nameChanged(); void defaultIncludeChanged(); - void changed(const QQmlV4Handle &removed, const QQmlV4Handle &inserted); + void changed(const QJSValue &removed, const QJSValue &inserted); private: Q_DECLARE_PRIVATE(QQmlDelegateModelGroup) }; diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp index 565e60b3c1..5b5bcd8464 100644 --- a/src/qml/types/qqmllistmodel.cpp +++ b/src/qml/types/qqmllistmodel.cpp @@ -2545,7 +2545,7 @@ void QQmlListModel::append(QQmlV4Function *args) \sa append() */ -QQmlV4Handle QQmlListModel::get(int index) const +QJSValue QQmlListModel::get(int index) const { QV4::Scope scope(engine()); QV4::ScopedValue result(scope, QV4::Value::undefinedValue()); @@ -2568,7 +2568,7 @@ QQmlV4Handle QQmlListModel::get(int index) const } } - return QQmlV4Handle(result); + return QJSValue(engine(), result->asReturnedValue()); } /*! @@ -2587,10 +2587,10 @@ QQmlV4Handle QQmlListModel::get(int index) const \sa append() */ -void QQmlListModel::set(int index, const QQmlV4Handle &handle) +void QQmlListModel::set(int index, const QJSValue &value) { QV4::Scope scope(engine()); - QV4::ScopedObject object(scope, handle); + QV4::ScopedObject object(scope, QJSValuePrivate::getValue(&value)); if (!object) { qmlWarning(this) << tr("set: value is not an object"); diff --git a/src/qml/types/qqmllistmodel_p.h b/src/qml/types/qqmllistmodel_p.h index 95b797c898..471e33aa5a 100644 --- a/src/qml/types/qqmllistmodel_p.h +++ b/src/qml/types/qqmllistmodel_p.h @@ -100,8 +100,8 @@ public: Q_INVOKABLE void remove(QQmlV4Function *args); Q_INVOKABLE void append(QQmlV4Function *args); Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QQmlV4Handle get(int index) const; - Q_INVOKABLE void set(int index, const QQmlV4Handle &); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); Q_INVOKABLE void move(int from, int to, int count); Q_INVOKABLE void sync(); diff --git a/src/qml/types/qqmllistmodelworkeragent.cpp b/src/qml/types/qqmllistmodelworkeragent.cpp index fe3eaa3198..f7cb08dcf4 100644 --- a/src/qml/types/qqmllistmodelworkeragent.cpp +++ b/src/qml/types/qqmllistmodelworkeragent.cpp @@ -114,12 +114,12 @@ void QQmlListModelWorkerAgent::insert(QQmlV4Function *args) m_copy->insert(args); } -QQmlV4Handle QQmlListModelWorkerAgent::get(int index) const +QJSValue QQmlListModelWorkerAgent::get(int index) const { return m_copy->get(index); } -void QQmlListModelWorkerAgent::set(int index, const QQmlV4Handle &value) +void QQmlListModelWorkerAgent::set(int index, const QJSValue &value) { m_copy->set(index, value); } diff --git a/src/qml/types/qqmllistmodelworkeragent_p.h b/src/qml/types/qqmllistmodelworkeragent_p.h index ae2d4b11e0..69d1785618 100644 --- a/src/qml/types/qqmllistmodelworkeragent_p.h +++ b/src/qml/types/qqmllistmodelworkeragent_p.h @@ -85,8 +85,8 @@ public: Q_INVOKABLE void remove(QQmlV4Function *args); Q_INVOKABLE void append(QQmlV4Function *args); Q_INVOKABLE void insert(QQmlV4Function *args); - Q_INVOKABLE QQmlV4Handle get(int index) const; - Q_INVOKABLE void set(int index, const QQmlV4Handle &); + Q_INVOKABLE QJSValue get(int index) const; + Q_INVOKABLE void set(int index, const QJSValue &value); Q_INVOKABLE void setProperty(int index, const QString& property, const QVariant& value); Q_INVOKABLE void move(int from, int to, int count); Q_INVOKABLE void sync(); diff --git a/src/qml/types/qquickworkerscript.cpp b/src/qml/types/qquickworkerscript.cpp index aaa89846aa..c081c9e7fc 100644 --- a/src/qml/types/qquickworkerscript.cpp +++ b/src/qml/types/qquickworkerscript.cpp @@ -647,12 +647,10 @@ void QQuickWorkerScript::componentComplete() bool QQuickWorkerScript::event(QEvent *event) { if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) { - QQmlEngine *engine = qmlEngine(this); - if (engine) { + if (QQmlEngine *engine = qmlEngine(this)) { + QV4::ExecutionEngine *v4 = engine->handle(); WorkerDataEvent *workerEvent = static_cast(event); - QV4::Scope scope(engine->handle()); - QV4::ScopedValue value(scope, QV4::Serialize::deserialize(workerEvent->data(), scope.engine)); - emit message(QQmlV4Handle(value)); + emit message(QJSValue(v4, QV4::Serialize::deserialize(workerEvent->data(), v4))); } return true; } else if (event->type() == (QEvent::Type)WorkerErrorEvent::WorkerError) { diff --git a/src/qml/types/qquickworkerscript_p.h b/src/qml/types/qquickworkerscript_p.h index 1a8d2ab076..87cf2e9754 100644 --- a/src/qml/types/qquickworkerscript_p.h +++ b/src/qml/types/qquickworkerscript_p.h @@ -83,7 +83,6 @@ private: }; class QQmlV4Function; -class QQmlV4Handle; class Q_AUTOTEST_EXPORT QQuickWorkerScript : public QObject, public QQmlParserStatus { Q_OBJECT @@ -102,7 +101,7 @@ public Q_SLOTS: Q_SIGNALS: void sourceChanged(); - void message(const QQmlV4Handle &messageObject); + void message(const QJSValue &messageObject); protected: void classBegin() override; -- cgit v1.2.3 From 43573c8df170c5662b350703a06537c457c2d2fd Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 3 Apr 2019 15:54:27 +0200 Subject: Clean up type registration The QML-specific types need to be registered only for "QtQml". Make sure we don't accidentally add new QML-specific types to QtQuick. We also don't want the base types to be registered under any random URI and version. Formally qmlRegisterBaseTypes() is a public function, but it _really_ should not be called by anyone. Finally, split the types into ones that should belong to QtQml.Models and ones that belong to QtQml proper. Add a plugin that handles QtQml itself rather than using the QQmlEngine ctor for this. [ChangeLog] The accidentally exported function qmlRegisterBaseTypes() was removed. Change-Id: I8bf9f8515e18b016750c721fe694d4cda076780b Reviewed-by: Erik Verbruggen --- src/qml/types/qqmlmodelsmodule.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/qml/types/qqmlmodelsmodule_p.h | 5 +++++ 2 files changed, 42 insertions(+) (limited to 'src/qml/types') diff --git a/src/qml/types/qqmlmodelsmodule.cpp b/src/qml/types/qqmlmodelsmodule.cpp index 5423054934..840b435d18 100644 --- a/src/qml/types/qqmlmodelsmodule.cpp +++ b/src/qml/types/qqmlmodelsmodule.cpp @@ -49,9 +49,42 @@ #include #include #include +#include +#include QT_BEGIN_NAMESPACE +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + +void QQmlModelsModule::registerQmlTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + qmlRegisterType("QtQml", 2, 1, "Instantiator"); // Only available in >= 2.1 + qmlRegisterType(); +} + +void QQmlModelsModule::registerQuickTypes() +{ + // Don't add anything here. These are only for backwards compatibility. + + const char uri[] = "QtQuick"; + + qmlRegisterType(uri, 2, 1, "Instantiator"); + qmlRegisterType(); +#if QT_CONFIG(qml_list_model) + qmlRegisterType(uri, 2, 0, "ListElement"); + qmlRegisterCustomType(uri, 2, 0, "ListModel", new QQmlListModelParser); +#endif + qmlRegisterType(uri, 2, 0, "Package"); +#if QT_CONFIG(qml_delegate_model) + qmlRegisterType(uri, 2, 0, "VisualDataModel"); + qmlRegisterType(uri, 2, 0, "VisualDataGroup"); +#endif + qmlRegisterType(uri, 2, 0, "VisualItemModel"); +} + +#endif // QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + void QQmlModelsModule::defineModule() { const char uri[] = "QtQml.Models"; @@ -68,6 +101,10 @@ void QQmlModelsModule::defineModule() qmlRegisterType(uri, 2, 3, "ObjectModel"); qmlRegisterType(uri, 2, 2, "ItemSelectionModel"); + + qmlRegisterType(uri, 2, 14, "Package"); + qmlRegisterType(uri, 2, 14, "Instantiator"); + qmlRegisterType(); } void QQmlModelsModule::defineLabsModule() diff --git a/src/qml/types/qqmlmodelsmodule_p.h b/src/qml/types/qqmlmodelsmodule_p.h index 939ecc1500..2bb04f1e11 100644 --- a/src/qml/types/qqmlmodelsmodule_p.h +++ b/src/qml/types/qqmlmodelsmodule_p.h @@ -58,6 +58,11 @@ QT_BEGIN_NAMESPACE class Q_QML_PRIVATE_EXPORT QQmlModelsModule { public: +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + static void registerQmlTypes(); + static void registerQuickTypes(); +#endif + static void defineModule(); static void defineLabsModule(); }; -- cgit v1.2.3