From 3195b44e1c9678584c05ed823aab2eb32518d868 Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 8 Sep 2022 14:41:42 +0200 Subject: [PATCH] Allow more options for creating value types from JS objects We allow value types to be created 1. by calling Q_INVOKABLE constructors 2. by setting their values from properties of a JS object Both have to be opted into by setting a class info. If opted into, these options override the existing methods. When a a type can be created by setting its properties, that implies you can also initialize it using an invokable constructor. However, when given a JS object, the properties method is used. We keep this internal and undocumented for now. As the last try (the create(QJSValue) methods and QJSValue ctors) was not that stellar, let's first wait a bit and see if we're getting it right this time around. Fixes: QTBUG-106480 Change-Id: I767230924afcba032d501846cc3263dad57b7bf0 Reviewed-by: Volker Hilsheimer --- src/qml/doc/src/qmlfunctions.qdoc | 95 +++++ src/qml/jsruntime/qv4engine.cpp | 43 ++- src/qml/jsruntime/qv4sequenceobject.cpp | 20 +- src/qml/qml/qqml.cpp | 16 +- src/qml/qml/qqml.h | 42 ++- src/qml/qml/qqmlbuiltinfunctions.cpp | 31 +- src/qml/qml/qqmlglobal.cpp | 325 +++++++++++++++++- src/qml/qml/qqmlglobal_p.h | 7 +- src/qml/qml/qqmlmetatype.cpp | 23 +- src/qml/qml/qqmlmetatype_p.h | 20 ++ src/qml/qml/qqmlobjectcreator.cpp | 38 +- src/qml/qml/qqmlprivate.h | 8 +- src/qml/qml/qqmlproperty.cpp | 110 ++++-- src/qml/qml/qqmlpropertyvalidator.cpp | 8 +- src/qml/qml/qqmlstringconverters.cpp | 12 +- src/qml/qml/qqmltype.cpp | 14 + src/qml/qml/qqmltype_p.h | 3 + src/qml/qml/qqmltype_p_p.h | 2 + src/qml/qml/qqmlvaluetype_p.h | 7 + src/qmlintegration/qqmlintegration.h | 11 +- src/quick/items/qquickitemsmodule_p.h | 1 + src/quick/util/qquickvaluetypes.cpp | 5 + src/quick/util/qquickvaluetypes_p.h | 9 + .../data/structuredValueTypes.qml | 38 ++ .../qml/qqmlvaluetypeproviders/testtypes.cpp | 2 + .../qml/qqmlvaluetypeproviders/testtypes.h | 81 +++++ .../tst_qqmlvaluetypeproviders.cpp | 90 +++++ 27 files changed, 941 insertions(+), 120 deletions(-) create mode 100644 tests/auto/qml/qqmlvaluetypeproviders/data/structuredValueTypes.qml diff --git a/src/qml/doc/src/qmlfunctions.qdoc b/src/qml/doc/src/qmlfunctions.qdoc index 89b52131d9..fbc11c515b 100644 --- a/src/qml/doc/src/qmlfunctions.qdoc +++ b/src/qml/doc/src/qmlfunctions.qdoc @@ -1302,3 +1302,98 @@ \sa QML_ELEMENT, QML_NAMED_ELEMENT, QML_SINGLETON, qmlRegisterType(), qmlRegisterSingletonType() */ + +/*! + \macro QML_VALUE_TYPE(name) + \relates QQmlEngine + + Declares the enclosing type or namespace to be available in QML, using \a name + as the name. The type has to be a value type and the name has to be lower case. + + \code + class MyValueType + { + Q_GADGET + QML_VALUE_TYPE(myValueType) + + // ... + }; + \endcode + + \sa {Choosing the Correct Integration Method Between C++ and QML}, QML_NAMED_ELEMENT +*/ + +/*! + \macro QML_CONSTRUCTIBLE_VALUE + \internal + \relates QQmlEngine + + Marks the surrounding value type as constructible. That is, any \l Q_INVOKABLE + constructors of the type that take exactly one argument can be used when + assigning a JavaScript value to a property of this type. + + You can declare a constructible value type as follows: + + \code + class MyValueType + { + Q_GADGET + QML_VALUE_TYPE(myValueType) + QML_CONSTRUCTIBLE_VALUE + Q_INVOKABLE MyValueType(double d); + + // ... + }; + \endcode + + With the above type, the following QML code will produce a \c MyValueType + value using the given constructor and assign it to the property. + + \qml + QtObject { + property myValueType v: 5.4 + } + \endqml + + \sa QML_VALUE_TYPE +*/ + +/*! + \macro QML_STRUCTURED_VALUE + \internal + \relates QQmlEngine + + Marks the surrounding value type as structured. Structured value types can + and will preferably be constructed property-by-property from a JavaScript + object. A structured value type, however is always \l QML_CONSTRUCTIBLE_VALUE, + too. This means, you can still provide \l Q_INVOKABLE constructors in order to + handle construction from primitive types. + + You can declare a structured value type as follows: + + \code + class MyValueType + { + Q_GADGET + QML_VALUE_TYPE(myValueType) + QML_STRUCTURED_VALUE + Q_PROPERTY(double d READ d WRITE setD) + Q_PROPERTY(string e READ e WRITE setE) + + // ... + }; + \endcode + + Then you can populate a property of this type as follows: + + \qml + QtObject { + property myValueType v: ({d: 4.4, e: "a string"}) + } + \endqml + + The extra parentheses are necessary to disambiguate the JavaScript object + from what might be interpreted as a JavaScript code block. + + \sa QML_VALUE_TYPE QML_CONSTRUCTIBLE_VALUE +*/ diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 43b3c6fa29..c177883214 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -1580,7 +1580,13 @@ static QVariant toVariant( QV4::ScopedValue arrayValue(scope); for (qint64 i = 0; i < length; ++i) { arrayValue = a->get(i); - QVariant asVariant; + QVariant asVariant(valueMetaType); + if (QQmlValueTypeProvider::createValueType( + arrayValue, valueMetaType, asVariant.data())) { + retnAsIterable.metaContainer().addValue(retn.data(), asVariant.constData()); + continue; + } + if (QMetaType::canConvert(QMetaType::fromType(), valueMetaType)) { // before attempting a conversion from the concrete types, // check if there exists a conversion from QJSValue -> out type @@ -1600,10 +1606,12 @@ static QVariant toVariant( auto originalType = asVariant.metaType(); bool couldConvert = asVariant.convert(valueMetaType); if (!couldConvert) { - qWarning() << QLatin1String("Could not convert array value at position %1 from %2 to %3") - .arg(QString::number(i), - QString::fromUtf8(originalType.name()), - QString::fromUtf8(valueMetaType.name())); + qWarning().noquote() + << QLatin1String("Could not convert array value " + "at position %1 from %2 to %3") + .arg(QString::number(i), + QString::fromUtf8(originalType.name()), + QString::fromUtf8(valueMetaType.name())); // create default constructed value asVariant = QVariant(valueMetaType, nullptr); } @@ -1662,6 +1670,12 @@ static QVariant toVariant( return re->toQRegularExpression(); #endif + if (metaType.isValid() && !(metaType.flags() & QMetaType::PointerToQObject)) { + QVariant result(metaType); + if (QQmlValueTypeProvider::createValueType(value, metaType, result.data())) + return result; + } + if (createJSValueForObjects) return QVariant::fromValue(QJSValuePrivate::fromReturnedValue(o->asReturnedValue())); @@ -2504,8 +2518,17 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi } break; } +#if QT_CONFIG(qml_locale) + case QMetaType::QLocale: { + if (const QV4::QQmlLocaleData *l = value.as()) { + *reinterpret_cast(data) = *l->d()->locale; + return true; + } + break; + } +#endif default: - ; + break; } if (metaType.flags() & QMetaType::IsEnumeration) { @@ -2579,14 +2602,8 @@ bool ExecutionEngine::metaTypeFromJS(const Value &value, QMetaType metaType, voi QJSValuePrivate::setValue(reinterpret_cast(data), value.asReturnedValue()); return true; } else if (!isPointer) { - QVariant val; - if (QQmlValueTypeProvider::createValueType( - metaType, QJSValuePrivate::fromReturnedValue(value.asReturnedValue()), val)) { - Q_ASSERT(val.metaType() == metaType); - metaType.destruct(data); - metaType.construct(data, val.constData()); + if (QQmlValueTypeProvider::createValueType(value, metaType, data)) return true; - } } if (const QV4::Sequence *sequence = value.as()) { diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index cb36ece6a4..261c55af30 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -648,12 +648,20 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin meta->addValueAtEnd(result.data(), &variant); } else { const QMetaType originalType = variant.metaType(); - if (originalType != valueMetaType && !variant.convert(valueMetaType)) { - qWarning() << QLatin1String( - "Could not convert array value at position %1 from %2 to %3") - .arg(QString::number(i), QString::fromUtf8(originalType.name()), - QString::fromUtf8(valueMetaType.name())); - variant = QVariant(valueMetaType); + if (originalType != valueMetaType) { + QVariant converted(valueMetaType); + if (QQmlValueTypeProvider::createValueType( + variant, valueMetaType, converted.data())) { + variant = converted; + } else if (!variant.convert(valueMetaType)) { + qWarning().noquote() + << QLatin1String("Could not convert array value " + "at position %1 from %2 to %3") + .arg(QString::number(i), + QString::fromUtf8(originalType.name()), + QString::fromUtf8(valueMetaType.name())); + variant = converted; + } } meta->addValueAtEnd(result.data(), variant.constData()); } diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 81416b6a0d..4b7c21e3ac 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -166,7 +166,8 @@ int qmlRegisterUncreatableMetaObject(const QMetaObject &staticMetaObject, nullptr, QTypeRevision::zero(), - -1 + -1, + QQmlPrivate::ValueTypeCreationMethod::None }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -464,11 +465,19 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) && boolClassInfo(type.classInfoMetaObject, "QML.Creatable", true); QString noCreateReason; + ValueTypeCreationMethod creationMethod = ValueTypeCreationMethod::None; if (!creatable) { - noCreateReason = QString::fromUtf8(classInfo(type.classInfoMetaObject, "QML.UncreatableReason")); + noCreateReason = QString::fromUtf8( + classInfo(type.classInfoMetaObject, "QML.UncreatableReason")); if (noCreateReason.isEmpty()) noCreateReason = QLatin1String("Type cannot be created in QML."); + } else if (!(type.typeId.flags() & QMetaType::PointerToQObject)) { + const char *method = classInfo(type.classInfoMetaObject, "QML.CreationMethod"); + if (qstrcmp(method, "structured") == 0) + creationMethod = ValueTypeCreationMethod::Structured; + else if (qstrcmp(method, "construct") == 0) + creationMethod = ValueTypeCreationMethod::Construct; } RegisterType typeRevision = { @@ -493,7 +502,8 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) type.extensionMetaObject, nullptr, QTypeRevision(), - type.structVersion > 0 ? type.finalizerCast : -1 + type.structVersion > 0 ? type.finalizerCast : -1, + creationMethod }; QQmlPrivate::RegisterSequentialContainer sequenceRevision = { diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index e45dd1bfdc..504e64cbeb 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -78,7 +78,8 @@ int qmlRegisterAnonymousType(const char *uri, int versionMajor) nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -115,7 +116,8 @@ int qmlRegisterAnonymousType(const char *uri, int versionMajor) nullptr, QTypeRevision::fromMinorVersion(metaObjectRevisionMinor), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -160,7 +162,8 @@ int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMin nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -193,7 +196,8 @@ int qmlRegisterUncreatableType(const char *uri, int versionMajor, int versionMin nullptr, QTypeRevision::fromMinorVersion(metaObjectRevision), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -233,7 +237,8 @@ int qmlRegisterExtendedUncreatableType(const char *uri, int versionMajor, int ve nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -273,7 +278,8 @@ int qmlRegisterExtendedUncreatableType(const char *uri, int versionMajor, int ve nullptr, QTypeRevision::fromMinorVersion(metaObjectRevision), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -306,7 +312,8 @@ int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const c nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -337,7 +344,8 @@ int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const c nullptr, QTypeRevision::fromMinorVersion(metaObjectRevision), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -368,7 +376,8 @@ int qmlRegisterRevision(const char *uri, int versionMajor, int versionMinor) nullptr, QTypeRevision::fromMinorVersion(metaObjectRevision), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -401,7 +410,8 @@ int qmlRegisterExtendedType(const char *uri, int versionMajor) nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -440,7 +450,8 @@ int qmlRegisterExtendedType(const char *uri, int versionMajor, int versionMinor, nullptr, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -490,7 +501,8 @@ int qmlRegisterCustomType(const char *uri, int versionMajor, int versionMinor, parser, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -522,7 +534,8 @@ int qmlRegisterCustomType(const char *uri, int versionMajor, int versionMinor, parser, QTypeRevision::fromMinorVersion(metaObjectRevision), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); @@ -561,7 +574,8 @@ int qmlRegisterCustomExtendedType(const char *uri, int versionMajor, int version parser, QTypeRevision::zero(), - QQmlPrivate::StaticCastSelector::cast() + QQmlPrivate::StaticCastSelector::cast(), + QQmlPrivate::ValueTypeCreationMethod::None, }; return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type); diff --git a/src/qml/qml/qqmlbuiltinfunctions.cpp b/src/qml/qml/qqmlbuiltinfunctions.cpp index 286849cb18..d244dae6ad 100644 --- a/src/qml/qml/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/qqmlbuiltinfunctions.cpp @@ -457,11 +457,9 @@ QVariant QtObject::font(const QJSValue &fontSpecifier) const } { - QVariant v; - if (QQmlValueTypeProvider::createValueType( - QMetaType(QMetaType::QFont), fontSpecifier, v)) { + QVariant v((QMetaType(QMetaType::QFont))); + if (QQmlValueTypeProvider::constructFromJSValue(fontSpecifier, v.metaType(), v.data())) return v; - } } v4Engine()->throwError(QStringLiteral("Qt.font(): Invalid argument: " @@ -489,14 +487,14 @@ void addParameters(QJSEngine *e, QJSValue &result, int i, T parameter, Others... } template -static QVariant createValueType(QJSEngine *e, QMetaType type, T... parameters) +static QVariant constructFromJSValue(QJSEngine *e, QMetaType type, T... parameters) { if (!e) return QVariant(); QJSValue params = e->newArray(sizeof...(parameters)); addParameters(e, params, 0, parameters...); - QVariant variant; - QQmlValueTypeProvider::createValueType(type, params, variant); + QVariant variant(type); + QQmlValueTypeProvider::constructFromJSValue(params, type, variant.data()); return variant; } @@ -507,7 +505,7 @@ static QVariant createValueType(QJSEngine *e, QMetaType type, T... parameters) */ QVariant QtObject::vector2d(double x, double y) const { - return createValueType(jsEngine(), QMetaType(QMetaType::QVector2D), x, y); + return constructFromJSValue(jsEngine(), QMetaType(QMetaType::QVector2D), x, y); } /*! @@ -517,7 +515,7 @@ QVariant QtObject::vector2d(double x, double y) const */ QVariant QtObject::vector3d(double x, double y, double z) const { - return createValueType(jsEngine(), QMetaType(QMetaType::QVector3D), x, y, z); + return constructFromJSValue(jsEngine(), QMetaType(QMetaType::QVector3D), x, y, z); } /*! @@ -527,7 +525,7 @@ QVariant QtObject::vector3d(double x, double y, double z) const */ QVariant QtObject::vector4d(double x, double y, double z, double w) const { - return createValueType(jsEngine(), QMetaType(QMetaType::QVector4D), x, y, z, w); + return constructFromJSValue(jsEngine(), QMetaType(QMetaType::QVector4D), x, y, z, w); } /*! @@ -537,7 +535,7 @@ QVariant QtObject::vector4d(double x, double y, double z, double w) const */ QVariant QtObject::quaternion(double scalar, double x, double y, double z) const { - return createValueType(jsEngine(), QMetaType(QMetaType::QQuaternion), scalar, x, y, z); + return constructFromJSValue(jsEngine(), QMetaType(QMetaType::QQuaternion), scalar, x, y, z); } /*! @@ -563,16 +561,17 @@ QVariant QtObject::quaternion(double scalar, double x, double y, double z) const */ QVariant QtObject::matrix4x4() const { - QVariant variant; - QQmlValueTypeProvider::createValueType(QMetaType(QMetaType::QMatrix4x4), QJSValue(), variant); + QVariant variant((QMetaType(QMetaType::QMatrix4x4))); + QQmlValueTypeProvider::constructFromJSValue( + QJSValue(), variant.metaType(), variant.data()); return variant; } QVariant QtObject::matrix4x4(const QJSValue &value) const { if (value.isObject()) { - QVariant v; - if (QQmlValueTypeProvider::createValueType(QMetaType(QMetaType::QMatrix4x4), value, v)) + QVariant v((QMetaType(QMetaType::QMatrix4x4))); + if (QQmlValueTypeProvider::constructFromJSValue(value, v.metaType(), v.data())) return v; } @@ -586,7 +585,7 @@ QVariant QtObject::matrix4x4(double m11, double m12, double m13, double m14, double m31, double m32, double m33, double m34, double m41, double m42, double m43, double m44) const { - return createValueType(jsEngine(), QMetaType(QMetaType::QMatrix4x4), + return constructFromJSValue(jsEngine(), QMetaType(QMetaType::QMatrix4x4), m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44); } diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp index 48244f3259..74eeaee761 100644 --- a/src/qml/qml/qqmlglobal.cpp +++ b/src/qml/qml/qqmlglobal.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -12,13 +13,237 @@ QT_BEGIN_NAMESPACE -bool QQmlValueTypeProvider::createValueType(QMetaType metaType, const QJSValue &s, QVariant &data) +// Pre-filter the metatype before poking QQmlMetaType::qmlType() and locking its mutex. +static bool isConstructibleMetaType(const QMetaType metaType) { - const QQmlType qmlType = QQmlMetaType::qmlType(metaType); - if (auto valueTypeFunction = qmlType.createValueTypeFunction()) { + switch (metaType.id()) { + // The builtins are not constructible this way. + case QMetaType::Void: + case QMetaType::Nullptr: + case QMetaType::QVariant: + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::Float: + case QMetaType::Double: + case QMetaType::Long: + case QMetaType::ULong: + case QMetaType::Short: + case QMetaType::UShort: + case QMetaType::Char: + case QMetaType::SChar: + case QMetaType::UChar: + case QMetaType::QChar: + case QMetaType::QString: + case QMetaType::Bool: + case QMetaType::QDateTime: + case QMetaType::QDate: + case QMetaType::QTime: + case QMetaType::QUrl: + case QMetaType::QRegularExpression: + case QMetaType::QByteArray: + case QMetaType::QLocale: + return false; + default: + break; + } + + // QJSValue is also builtin + if (metaType == QMetaType::fromType()) + return false; + + // We also don't want to construct pointers of any kind, or lists, or enums. + if (metaType.flags() & + (QMetaType::PointerToQObject + | QMetaType::IsEnumeration + | QMetaType::SharedPointerToQObject + | QMetaType::WeakPointerToQObject + | QMetaType::TrackingPointerToQObject + | QMetaType::IsUnsignedEnumeration + | QMetaType::PointerToGadget + | QMetaType::IsPointer + | QMetaType::IsQmlList)) { + return false; + } + + return true; +} + +static void callConstructor( + const QMetaObject *mo, int i, void *parameter, QMetaType metaType, void *data) +{ + // Unfortunately CreateInstance unconditionally creates the instance on the heap. + void *gadget = nullptr; + void *p[] = { &gadget, parameter }; + mo->static_metacall(QMetaObject::CreateInstance, i, p); + Q_ASSERT(gadget); + metaType.destruct(data); + metaType.construct(data, gadget); + metaType.destroy(gadget); +} + +static bool fromMatchingType( + const QMetaObject *mo, const QV4::Value &s, const QMetaType metaType, void *data) +{ + for (int i = 0, end = mo->constructorCount(); i < end; ++i) { + const QMetaMethod ctor = mo->constructor(i); + if (ctor.parameterCount() != 1) + continue; + + const QMetaType parameterType = ctor.parameterMetaType(0); + QVariant parameter = QV4::ExecutionEngine::toVariant(s, parameterType); + if (parameter.metaType() == parameterType) { + callConstructor(mo, i, parameter.data(), metaType, data); + return true; + } + + QVariant converted(parameterType); + if (QQmlValueTypeProvider::createValueType(s, parameterType, converted.data())) { + callConstructor(mo, i, converted.data(), metaType, data); + return true; + } + + if (QMetaType::convert(parameter.metaType(), parameter.constData(), + parameterType, converted.data())) { + callConstructor(mo, i, converted.data(), metaType, data); + return true; + } + } + + return false; +} + +static bool fromMatchingType( + const QMetaObject *mo, QVariant s, const QMetaType metaType, void *data) +{ + const QMetaType sourceMetaType = s.metaType(); + if (sourceMetaType == QMetaType::fromType()) { + QJSValue val = s.value(); + return fromMatchingType( + mo, QV4::Value(QJSValuePrivate::asReturnedValue(&val)), metaType, data); + } + + for (int i = 0, end = mo->constructorCount(); i < end; ++i) { + const QMetaMethod ctor = mo->constructor(i); + if (ctor.parameterCount() != 1) + continue; + + const QMetaType parameterType = ctor.parameterMetaType(0); + if (sourceMetaType == parameterType) { + callConstructor(mo, i, s.data(), metaType, data); + return true; + } + + QVariant parameter(parameterType); + if (QQmlValueTypeProvider::createValueType(s, parameterType, parameter.data())) { + callConstructor(mo, i, parameter.data(), metaType, data); + return true; + } + + // At this point, s should be a builtin type. For builtin types + // the QMetaType converters are good enough. + if (QMetaType::convert(sourceMetaType, s.constData(), parameterType, parameter.data())) { + callConstructor(mo, i, s.data(), metaType, data); + return true; + } + } + + return false; +} + +static bool fromString( + const QMetaObject *mo, QString s, const QMetaType metaType, void *data) +{ + for (int i = 0, end = mo->constructorCount(); i < end; ++i) { + const QMetaMethod ctor = mo->constructor(i); + if (ctor.parameterCount() != 1) + continue; + + if (ctor.parameterMetaType(0) == QMetaType::fromType()) { + callConstructor(mo, i, &s, metaType, data); + return true; + } + } + + + return false; +} + +static bool byProperties( + const QMetaObject *mo, const QV4::Value &s, void *data) +{ + if (!s.isObject()) + return false; + + if (!mo) + return false; + + const QV4::Object *o = static_cast(&s); + QV4::Scope scope(o->engine()); + QV4::ScopedObject object(scope, o); + + for (int i = 0; i < mo->propertyCount(); ++i) { + const QMetaProperty metaProperty = mo->property(i); + const QString propertyName = QString::fromUtf8(metaProperty.name()); + + QV4::ScopedString v4PropName(scope, scope.engine->newString(propertyName)); + QV4::ScopedValue v4PropValue(scope, object->get(v4PropName)); + + // We assume that data is freshly constructed. + // There is no point in reset()'ing properties of a freshly created object. + if (v4PropValue->isUndefined()) + continue; + + const QMetaType propertyType = metaProperty.metaType(); + QVariant property = QV4::ExecutionEngine::toVariant(v4PropValue, propertyType); + if (property.metaType() == propertyType) { + metaProperty.writeOnGadget(data, property); + continue; + } + + QVariant converted(propertyType); + if (QQmlValueTypeProvider::createValueType(v4PropValue, propertyType, converted.data())) { + metaProperty.writeOnGadget(data, converted); + continue; + } + + if (QMetaType::convert(property.metaType(), property.constData(), + propertyType, converted.data())) { + metaProperty.writeOnGadget(data, converted); + continue; + } + + qWarning().noquote() + << QLatin1String("Could not convert %1 to %2 for property %3") + .arg(v4PropValue->toQStringNoThrow(), QString::fromUtf8(propertyType.name()), + propertyName); + } + return true; +} + +static bool byProperties( + const QMetaObject *mo, const QVariant &s, void *data) +{ + if (!mo) + return false; + + if (s.metaType() == QMetaType::fromType()) { + QJSValue val = s.value(); + return byProperties(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&val)), data); + } + + return false; +} + +static bool fromJSValue( + const QQmlType &type, const QJSValue &s, QMetaType metaType, void *data) +{ + if (const auto valueTypeFunction = type.createValueTypeFunction()) { QVariant result = valueTypeFunction(s); if (result.metaType() == metaType) { - data = std::move(result); + metaType.destruct(data); + metaType.construct(data, result.constData()); return true; } } @@ -26,6 +251,98 @@ bool QQmlValueTypeProvider::createValueType(QMetaType metaType, const QJSValue & return false; } +bool QQmlValueTypeProvider::constructFromJSValue( + const QJSValue &s, QMetaType metaType, void *data) +{ + return isConstructibleMetaType(metaType) + && fromJSValue(QQmlMetaType::qmlType(metaType), s, metaType, data); +} + +bool QQmlValueTypeProvider::createValueType( + const QString &s, QMetaType metaType, void *data) +{ + if (!isConstructibleMetaType(metaType)) + return false; + const QQmlType type = QQmlMetaType::qmlType(metaType); + const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type); + if (mo && type.canConstructValueType()) { + if (fromString(mo, s, metaType, data)) + return true; + } + + return fromJSValue(type, s, metaType, data); +} + +bool QQmlValueTypeProvider::createValueType( + const QJSValue &s, QMetaType metaType, void *data) +{ + if (!isConstructibleMetaType(metaType)) + return false; + const QQmlType type = QQmlMetaType::qmlType(metaType); + if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) { + if (type.canPopulateValueType() + && byProperties(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&s)), data)) { + return true; + } + + if (type.canConstructValueType() + && fromMatchingType(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&s)), + metaType, data)) { + return true; + } + } + + return constructFromJSValue(s, metaType, data); +} + +bool QQmlValueTypeProvider::createValueType( + const QV4::Value &s, QMetaType metaType, void *data) +{ + if (!isConstructibleMetaType(metaType)) + return false; + const QQmlType type = QQmlMetaType::qmlType(metaType); + if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) { + if (type.canPopulateValueType() && byProperties(mo, s, data)) + return true; + if (type.canConstructValueType()) { + if (fromMatchingType(mo, s, metaType, data)) + return true; + qWarning().noquote() + << "Could not find any constructor for value type" + << mo->className() << "to call with value" << s.toQStringNoThrow(); + } + } + + return constructFromJSValue( + QJSValuePrivate::fromReturnedValue(s.asReturnedValue()), metaType, data); + +} + +/*! + * \internal + * This should only be called with either builtin types or wrapped QJSValues as source. + */ +bool QQmlValueTypeProvider::createValueType( + const QVariant &s, QMetaType metaType, void *data) +{ + if (!isConstructibleMetaType(metaType)) + return false; + const QQmlType type = QQmlMetaType::qmlType(metaType); + if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) { + if (type.canPopulateValueType() && byProperties(mo, s, data)) + return true; + if (type.canConstructValueType()) { + if (fromMatchingType(mo, s, metaType, data)) + return true; + qWarning().noquote() + << "Could not find any constructor for value type" + << mo->className() << "to call with value" << s; + } + } + + return false; +} + QQmlColorProvider::~QQmlColorProvider() {} QVariant QQmlColorProvider::colorFromString(const QString &, bool *ok) { if (ok) *ok = false; return QVariant(); } unsigned QQmlColorProvider::rgbaFromString(const QString &, bool *ok) { if (ok) *ok = false; return 0; } diff --git a/src/qml/qml/qqmlglobal_p.h b/src/qml/qml/qqmlglobal_p.h index 0d22cc2de7..f7a50e8350 100644 --- a/src/qml/qml/qqmlglobal_p.h +++ b/src/qml/qml/qqmlglobal_p.h @@ -185,7 +185,12 @@ inline void QQml_setParent_noEvent(QObject *object, QObject *parent) class QQmlValueTypeProvider { public: - static bool createValueType(QMetaType, const QJSValue &, QVariant &); + static bool constructFromJSValue(const QJSValue &, QMetaType, void *); + + static bool createValueType(const QString &, QMetaType, void *); + static bool createValueType(const QJSValue &, QMetaType, void *); + static bool createValueType(const QV4::Value &, QMetaType, void *); + static bool createValueType(const QVariant &, QMetaType, void *); }; class Q_QML_PRIVATE_EXPORT QQmlColorProvider diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index e3ff358c1d..b5cddd112b 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -152,6 +152,10 @@ static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &el d->extraData.cd->customParser = reinterpret_cast(type.customParser); d->extraData.cd->registerEnumClassesUnscoped = true; d->extraData.cd->registerEnumsFromRelatedTypes = true; + d->extraData.cd->constructValueType = type.has(QQmlPrivate::RegisterType::CreationMethod) + && type.creationMethod != QQmlPrivate::ValueTypeCreationMethod::None; + d->extraData.cd->populateValueType = type.has(QQmlPrivate::RegisterType::CreationMethod) + && type.creationMethod == QQmlPrivate::ValueTypeCreationMethod::Structured; if (type.extensionMetaObject) d->extraData.cd->extMetaObject = type.extensionMetaObject; @@ -1707,23 +1711,8 @@ const QMetaObject *QQmlMetaType::metaObjectForValueType(QMetaType metaType) // call QObject pointers value types. Explicitly registered types also override // the implicit use of gadgets. if (!(metaType.flags() & QMetaType::PointerToQObject)) { - const QQmlType qmlType = QQmlMetaType::qmlType(metaType); - - // Prefer the extension meta object, if any. - // Extensions allow registration of non-gadget value types. - if (const QMetaObject *extensionMetaObject = qmlType.extensionMetaObject()) { - // This may be a namespace even if the original metaType isn't. - // You can do such things with QML_FOREIGN declarations. - if (extensionMetaObject->metaType().flags() & QMetaType::IsGadget) - return extensionMetaObject; - } - - if (const QMetaObject *qmlTypeMetaObject = qmlType.metaObject()) { - // This may be a namespace even if the original metaType isn't. - // You can do such things with QML_FOREIGN declarations. - if (qmlTypeMetaObject->metaType().flags() & QMetaType::IsGadget) - return qmlTypeMetaObject; - } + if (const QMetaObject *mo = metaObjectForValueType(QQmlMetaType::qmlType(metaType))) + return mo; } // If it _is_ a gadget, we can just use it. diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 7b8ecdb162..c7a860eaca 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -240,6 +240,26 @@ public: static bool isValueType(QMetaType type); static QQmlValueType *valueType(QMetaType metaType); static const QMetaObject *metaObjectForValueType(QMetaType type); + static const QMetaObject *metaObjectForValueType(const QQmlType &qmlType) + { + // Prefer the extension meta object, if any. + // Extensions allow registration of non-gadget value types. + if (const QMetaObject *extensionMetaObject = qmlType.extensionMetaObject()) { + // This may be a namespace even if the original metaType isn't. + // You can do such things with QML_FOREIGN declarations. + if (extensionMetaObject->metaType().flags() & QMetaType::IsGadget) + return extensionMetaObject; + } + + if (const QMetaObject *qmlTypeMetaObject = qmlType.metaObject()) { + // This may be a namespace even if the original metaType isn't. + // You can do such things with QML_FOREIGN declarations. + if (qmlTypeMetaObject->metaType().flags() & QMetaType::IsGadget) + return qmlTypeMetaObject; + } + + return nullptr; + } static QQmlPropertyCache::ConstPtr findPropertyCacheInCompositeTypes(QMetaType t); static void registerInternalCompositeType(QV4::ExecutableCompilationUnit *compilationUnit); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 1bea403dda..b85a6c1220 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -425,9 +425,9 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const } break; case QMetaType::QColor: { - QVariant data; + QVariant data(propertyType); if (QQmlValueTypeProvider::createValueType( - propertyType, compilationUnit->bindingValueAsString(binding), data)) { + compilationUnit->bindingValueAsString(binding), propertyType, data.data())) { property->writeProperty(_qobject, data.data(), propertyWriteFlags); } } @@ -508,9 +508,10 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const case QMetaType::QVector3D: case QMetaType::QVector4D: case QMetaType::QQuaternion: { - QVariant result; + QVariant result(propertyType); bool ok = QQmlValueTypeProvider::createValueType( - propertyType, compilationUnit->bindingValueAsString(binding), result); + compilationUnit->bindingValueAsString(binding), + result.metaType(), result.data()); assertOrNull(ok); Q_UNUSED(ok); property->writeProperty(_qobject, result.data(), propertyWriteFlags); @@ -576,6 +577,35 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const } property->writeProperty(_qobject, &value, propertyWriteFlags); break; + } else { + QVariant source; + switch (binding->type()) { + case QV4::CompiledData::Binding::Type_Boolean: + source = binding->valueAsBoolean(); + break; + case QV4::CompiledData::Binding::Type_Number: { + const double n = compilationUnit->bindingValueAsNumber(binding); + if (double(int(n)) == n) + source = int(n); + else + source = n; + break; + } + case QV4::CompiledData::Binding::Type_Null: + source = QVariant::fromValue(nullptr); + break; + case QV4::CompiledData::Binding::Type_Invalid: + break; + default: + source = compilationUnit->bindingValueAsString(binding); + break; + } + + QVariant target(propertyType); + if (QQmlValueTypeProvider::createValueType(source, propertyType, target.data())) { + property->writeProperty(_qobject, target.data(), propertyWriteFlags); + break; + } } // string converters are not exposed, so ending up here indicates an error diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index c7df4b7e5b..92e8dc9e00 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -427,11 +427,14 @@ namespace QQmlPrivate enum AutoParentResult { Parented, IncompatibleObject, IncompatibleParent }; typedef AutoParentResult (*AutoParentFunction)(QObject *object, QObject *parent); + enum class ValueTypeCreationMethod { None, Construct, Structured }; + struct RegisterType { enum StructVersion: int { Base = 0, FinalizerCast = 1, - CurrentVersion = FinalizerCast, + CreationMethod = 2, + CurrentVersion = CreationMethod, }; bool has(StructVersion v) const { return structVersion >= int(v); } @@ -446,6 +449,7 @@ namespace QQmlPrivate void *userdata; QString noCreationReason; + // ### Qt7: Get rid of this. It can be covered by creationMethod below. QVariant (*createValueType)(const QJSValue &); const char *uri; @@ -467,6 +471,8 @@ namespace QQmlPrivate QTypeRevision revision; int finalizerCast; + + ValueTypeCreationMethod creationMethod; // If this is extended ensure "version" is bumped!!! }; diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 402c23bb28..9f5703a3c3 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1220,7 +1220,7 @@ bool QQmlPropertyPrivate::writeEnumProperty(const QMetaProperty &prop, int idx, return false; QVariant v = value; - if (prop.isEnumType()) { + if (prop.isEnumType() && v.metaType() != prop.metaType()) { QMetaEnum menum = prop.enumerator(); if (v.userType() == QMetaType::QString) { bool ok; @@ -1360,6 +1360,80 @@ private: QUntypedPropertyBinding untypedBinding; }; +struct ConvertAndAssignResult { + bool couldConvert = false; + bool couldWrite = false; + + operator bool() const { return couldConvert; } +}; + +static ConvertAndAssignResult tryConvertAndAssign( + QObject *object, const QQmlPropertyData &property, const QVariant &value, + QQmlPropertyData::WriteFlags flags, QMetaType propertyMetaType, QMetaType variantMetaType, + bool isUrl) { + + if (isUrl + || variantMetaType == QMetaType::fromType() + || propertyMetaType == QMetaType::fromType>() + || property.isQList()) { + return {false, false}; + } + + // common cases: + switch (propertyMetaType.id()) { + case QMetaType::Bool: + if (value.canConvert(propertyMetaType)) { + bool b = value.toBool(); + return {true, property.writeProperty(object, &b, flags)}; + } + return {false, false}; + case QMetaType::Int: { + bool ok = false; + int i = value.toInt(&ok); + return {ok, ok && property.writeProperty(object, &i, flags)}; + } + case QMetaType::UInt: { + bool ok = false; + uint u = value.toUInt(&ok); + return {ok, ok && property.writeProperty(object, &u, flags)}; + } + case QMetaType::Double: { + bool ok = false; + double d = value.toDouble(&ok); + return {ok, ok && property.writeProperty(object, &d, flags)}; + } + case QMetaType::Float: { + bool ok = false; + float f = value.toFloat(&ok); + return {ok, ok && property.writeProperty(object, &f, flags)}; + } + case QMetaType::QString: + if (value.canConvert(propertyMetaType)) { + QString s = value.toString(); + return {true, property.writeProperty(object, &s, flags)}; + } + return {false, false}; + case QMetaType::QVariantMap: + if (value.canConvert(propertyMetaType)) { + QVariantMap m = value.toMap(); + return {true, property.writeProperty(object, &m, flags)}; + } + return {false, false}; + default: { + break; + } + } + + QVariant converted(propertyMetaType); + if (QQmlValueTypeProvider::createValueType(value, propertyMetaType, converted.data()) + || QMetaType::convert(value.metaType(), value.constData(), + propertyMetaType, converted.data())) { + return {true, property.writeProperty(object, converted.data(), flags)}; + } + + return {false, false}; +}; + bool QQmlPropertyPrivate::write( QObject *object, const QQmlPropertyData &property, const QVariant &value, const QQmlRefPointer &context, QQmlPropertyData::WriteFlags flags) @@ -1419,37 +1493,9 @@ bool QQmlPropertyPrivate::write( } else { return false; } - } else if (value.canConvert(propertyMetaType) - && !isUrl && variantMetaType != QMetaType::fromType() - && propertyMetaType != QMetaType::fromType>() && !property.isQList()) { - // common cases: - switch (propertyMetaType.id()) { - case QMetaType::Bool: { - bool b = value.toBool(); - return property.writeProperty(object, &b, flags); - } - case QMetaType::Int: { - int i = value.toInt(); - return property.writeProperty(object, &i, flags); - } - case QMetaType::Double: { - double d = value.toDouble(); - return property.writeProperty(object, &d, flags); - } - case QMetaType::Float: { - float f = value.toFloat(); - return property.writeProperty(object, &f, flags); - } - case QMetaType::QString: { - QString s = value.toString(); - return property.writeProperty(object, &s, flags); - } - default: { // "fallback": - QVariant v = value; - v.convert(propertyMetaType); - return property.writeProperty(object, const_cast(v.constData()), flags); - } - } + } else if (ConvertAndAssignResult result = tryConvertAndAssign( + object, property, value, flags, propertyMetaType, variantMetaType, isUrl)) { + return result.couldWrite; } else if (propertyMetaType == QMetaType::fromType()) { return property.writeProperty(object, const_cast(&value), flags); } else if (isUrl) { diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index c16efc7031..c6783842dc 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -568,10 +568,10 @@ QQmlError QQmlPropertyValidator::validateLiteralBinding( default: return QString(); } }; - QVariant result; + QVariant result(property->propType()); if (!QQmlValueTypeProvider::createValueType( - property->propType(), - compilationUnit->bindingValueAsString(binding), result)) { + compilationUnit->bindingValueAsString(binding), + result.metaType(), result.data())) { return warnOrError(tr("Invalid property assignment: %1 expected") .arg(typeName())); } @@ -618,6 +618,8 @@ QQmlError QQmlPropertyValidator::validateLiteralBinding( } else if (property->isQObject() && bindingType == QV4::CompiledData::Binding::Type_Null) { break; + } else if (QQmlMetaType::qmlType(property->propType()).canConstructValueType()) { + break; } return warnOrError(tr("Invalid property assignment: unsupported type \"%1\"").arg(QString::fromLatin1(property->propType().name()))); diff --git a/src/qml/qml/qqmlstringconverters.cpp b/src/qml/qml/qqmlstringconverters.cpp index ccaa6e7aa3..c1625b6667 100644 --- a/src/qml/qml/qqmlstringconverters.cpp +++ b/src/qml/qml/qqmlstringconverters.cpp @@ -40,11 +40,15 @@ QVariant QQmlStringConverters::variantFromString(const QString &s, QMetaType pre case QMetaType::QRect: return QVariant::fromValue(rectFFromString(s, ok).toRect()); default: { - QVariant ret; - bool success = QQmlValueTypeProvider::createValueType(preferredType, QJSValue(s), ret); + QVariant ret(preferredType); + if (QQmlValueTypeProvider::createValueType(s, preferredType, ret.data())) { + if (ok) + *ok = true; + return ret; + } if (ok) - *ok = success; - return ret; + *ok = false; + return QVariant(); } } } diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index 7e614380f5..dd2fd62a52 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -527,6 +527,20 @@ QQmlType::CreateValueTypeFunc QQmlType::createValueTypeFunction() const return d->extraData.cd->createValueTypeFunc; } +bool QQmlType::canConstructValueType() const +{ + if (!d || d->regType != CppType) + return false; + return d->extraData.cd->constructValueType; +} + +bool QQmlType::canPopulateValueType() const +{ + if (!d || d->regType != CppType) + return false; + return d->extraData.cd->populateValueType; +} + QQmlType::CreateFunc QQmlType::createFunction() const { if (!d || d->regType != CppType) diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index 9ae6fdea55..c2c85c9c2e 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -71,6 +71,9 @@ public: typedef QVariant (*CreateValueTypeFunc)(const QJSValue &); CreateValueTypeFunc createValueTypeFunction() const; + bool canConstructValueType() const; + bool canPopulateValueType() const; + QObject *create() const; QObject *create(void **, size_t) const; QObject *createWithQQmlData() const; diff --git a/src/qml/qml/qqmltype_p_p.h b/src/qml/qml/qqmltype_p_p.h index cf1d9fbf47..d2b282955f 100644 --- a/src/qml/qml/qqmltype_p_p.h +++ b/src/qml/qml/qqmltype_p_p.h @@ -92,6 +92,8 @@ public: int finalizerCast; bool registerEnumClassesUnscoped; bool registerEnumsFromRelatedTypes; + bool constructValueType; + bool populateValueType; }; struct QQmlSingletonTypeData diff --git a/src/qml/qml/qqmlvaluetype_p.h b/src/qml/qml/qqmlvaluetype_p.h index 89a35495ad..a3a4d1eddc 100644 --- a/src/qml/qml/qqmlvaluetype_p.h +++ b/src/qml/qml/qqmlvaluetype_p.h @@ -100,6 +100,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlPointFValueType QML_FOREIGN(QPointF) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlPointFValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -119,6 +120,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlPointValueType QML_FOREIGN(QPoint) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlPointValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -138,6 +140,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlSizeFValueType QML_FOREIGN(QSizeF) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlSizeFValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -157,6 +160,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlSizeValueType QML_FOREIGN(QSize) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlSizeValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -182,6 +186,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlRectFValueType QML_FOREIGN(QRectF) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlRectFValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -217,6 +222,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlRectValueType QML_FOREIGN(QRect) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlRectValueType) + QML_STRUCTURED_VALUE public: Q_INVOKABLE QString toString() const; @@ -282,6 +288,7 @@ struct Q_QML_PRIVATE_EXPORT QQmlEasingValueType QML_FOREIGN(QEasingCurve) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQmlEasingValueType) + QML_STRUCTURED_VALUE Q_PROPERTY(QQmlEasingEnums::Type type READ type WRITE setType FINAL) Q_PROPERTY(qreal amplitude READ amplitude WRITE setAmplitude FINAL) diff --git a/src/qmlintegration/qqmlintegration.h b/src/qmlintegration/qqmlintegration.h index 67830cff4c..6c1ba86112 100644 --- a/src/qmlintegration/qqmlintegration.h +++ b/src/qmlintegration/qqmlintegration.h @@ -48,8 +48,15 @@ QT_END_NAMESPACE Q_CLASSINFO("QML.UncreatableReason", REASON) #define QML_VALUE_TYPE(NAME) \ - Q_CLASSINFO("QML.Element", #NAME) \ - QML_UNCREATABLE("Value types cannot be created.") + Q_CLASSINFO("QML.Element", #NAME) + +#define QML_CONSTRUCTIBLE_VALUE \ + Q_CLASSINFO("QML.Creatable", "true") \ + Q_CLASSINFO("QML.CreationMethod", "construct") + +#define QML_STRUCTURED_VALUE \ + Q_CLASSINFO("QML.Creatable", "true") \ + Q_CLASSINFO("QML.CreationMethod", "structured") #define QML_SINGLETON \ Q_CLASSINFO("QML.Singleton", "true") \ diff --git a/src/quick/items/qquickitemsmodule_p.h b/src/quick/items/qquickitemsmodule_p.h index 2602101f68..1c85b7e2d4 100644 --- a/src/quick/items/qquickitemsmodule_p.h +++ b/src/quick/items/qquickitemsmodule_p.h @@ -52,6 +52,7 @@ struct QPointingDeviceUniqueIdForeign QML_FOREIGN(QPointingDeviceUniqueId) QML_VALUE_TYPE(pointingDeviceUniqueId) QML_ADDED_IN_VERSION(2, 9) + QML_UNCREATABLE("pointingDeviceUniqueId cannot be created in QML.") }; #if !QT_CONFIG(quick_animatedimage) diff --git a/src/quick/util/qquickvaluetypes.cpp b/src/quick/util/qquickvaluetypes.cpp index c32a840673..723ba0f24c 100644 --- a/src/quick/util/qquickvaluetypes.cpp +++ b/src/quick/util/qquickvaluetypes.cpp @@ -10,6 +10,11 @@ QT_BEGIN_NAMESPACE +QQuickColorValueType::QQuickColorValueType(const QString &string) + : v(QColor::fromString(string)) +{ +} + QVariant QQuickColorValueType::create(const QJSValue ¶ms) { return params.isString() ? QColor::fromString(params.toString()) : QVariant(); diff --git a/src/quick/util/qquickvaluetypes_p.h b/src/quick/util/qquickvaluetypes_p.h index 01c9316792..653c488e34 100644 --- a/src/quick/util/qquickvaluetypes_p.h +++ b/src/quick/util/qquickvaluetypes_p.h @@ -49,10 +49,12 @@ class Q_QUICK_PRIVATE_EXPORT QQuickColorValueType QML_FOREIGN(QColor) QML_VALUE_TYPE(color) QML_EXTENDED(QQuickColorValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); + Q_INVOKABLE QQuickColorValueType(const QString &string); Q_INVOKABLE QString toString() const; Q_INVOKABLE QVariant alpha(qreal value) const; @@ -93,6 +95,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector2DValueType QML_FOREIGN(QVector2D) QML_VALUE_TYPE(vector2d) QML_EXTENDED(QQuickVector2DValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); @@ -128,6 +131,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector3DValueType QML_FOREIGN(QVector3D) QML_VALUE_TYPE(vector3d) QML_EXTENDED(QQuickVector3DValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); @@ -168,6 +172,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickVector4DValueType QML_FOREIGN(QVector4D) QML_VALUE_TYPE(vector4d) QML_EXTENDED(QQuickVector4DValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); @@ -209,6 +214,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickQuaternionValueType QML_FOREIGN(QQuaternion) QML_VALUE_TYPE(quaternion) QML_EXTENDED(QQuickQuaternionValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); @@ -267,6 +273,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickMatrix4x4ValueType QML_FOREIGN(QMatrix4x4) QML_VALUE_TYPE(matrix4x4) QML_EXTENDED(QQuickMatrix4x4ValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); @@ -390,6 +397,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickFontValueType QML_FOREIGN(QFont) QML_ADDED_IN_VERSION(2, 0) QML_EXTENDED(QQuickFontValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue &value); @@ -494,6 +502,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickColorSpaceValueType QML_FOREIGN(QColorSpace) QML_ADDED_IN_VERSION(2, 15) QML_EXTENDED(QQuickColorSpaceValueType) + QML_STRUCTURED_VALUE public: static QVariant create(const QJSValue ¶ms); diff --git a/tests/auto/qml/qqmlvaluetypeproviders/data/structuredValueTypes.qml b/tests/auto/qml/qqmlvaluetypeproviders/data/structuredValueTypes.qml new file mode 100644 index 0000000000..6cfdb6db78 --- /dev/null +++ b/tests/auto/qml/qqmlvaluetypeproviders/data/structuredValueTypes.qml @@ -0,0 +1,38 @@ +import QtQml +import Test + +MyTypeObject { + property point p: ({x: 7, y: 77, notthere: 2}) + property size s: ({width: 7, height: "77"}) + property rect r: ({x: 5, y: 55, width: 7, height: 77}) + + property point p2 + property size s2 + property rect r2 + + property constructible c1: 5 + property constructible c2: ({foo: 7}) + property constructible c3 + property constructible c4 + + property list ps: [{x: 1, y: 2}, {x: 3, y: 4}, {x: 55, y: Qt.locale()}] + property list ss: [{width: 5, height: 6}, {width: 7, height: 8}, {height: 99}] + property list cs: [1, 2, 3, 4, 5, {}] + + property structured b1: ({i: 10, c: 14, p: {x: 1, y: 44} }) + property structured b2 + property list bb: [{i : 21}, {c: 22}, {p: {x: 199, y: 222}}] + + constructible: 47 + structured: ({i: 11, c: 12, p: {x: 7, y: 8}}) + + Component.onCompleted: { + p2 = {x: 4, y: 5}; + s2 = {width: 7, height: 8}; + r2 = {x: 9, y: 10, width: 11, height: 12}; + c3 = 99; + b2 = {i: 11, c: 15, p: {x: 4} } + + c4 = {foo: 11}; + } +} diff --git a/tests/auto/qml/qqmlvaluetypeproviders/testtypes.cpp b/tests/auto/qml/qqmlvaluetypeproviders/testtypes.cpp index 1192043933..b46eebc2e7 100644 --- a/tests/auto/qml/qqmlvaluetypeproviders/testtypes.cpp +++ b/tests/auto/qml/qqmlvaluetypeproviders/testtypes.cpp @@ -5,4 +5,6 @@ void registerTypes() { qmlRegisterType("Test", 1, 0, "MyTypeObject"); + qmlRegisterTypesAndRevisions("Test", 1); + qmlRegisterTypesAndRevisions("Test", 1); } diff --git a/tests/auto/qml/qqmlvaluetypeproviders/testtypes.h b/tests/auto/qml/qqmlvaluetypeproviders/testtypes.h index d72da3421c..da3ab55a49 100644 --- a/tests/auto/qml/qqmlvaluetypeproviders/testtypes.h +++ b/tests/auto/qml/qqmlvaluetypeproviders/testtypes.h @@ -19,6 +19,61 @@ #include #include +struct ConstructibleValueType +{ + Q_GADGET + Q_PROPERTY(int foo MEMBER m_foo CONSTANT) + + QML_VALUE_TYPE(constructible) + QML_CONSTRUCTIBLE_VALUE + +public: + ConstructibleValueType() = default; + Q_INVOKABLE ConstructibleValueType(int foo) : m_foo(foo) {} + + int foo() const { return m_foo; } + +private: + friend bool operator==(const ConstructibleValueType &a, const ConstructibleValueType &b) + { + return a.m_foo == b.m_foo; + } + + int m_foo = 0; +}; + +struct StructuredValueType +{ + Q_GADGET + Q_PROPERTY(int i READ i WRITE setI) + Q_PROPERTY(ConstructibleValueType c READ c WRITE setC) + Q_PROPERTY(QPointF p READ p WRITE setP) + + QML_VALUE_TYPE(structured) + QML_STRUCTURED_VALUE + +public: + int i() const { return m_i; } + void setI(int newI) { m_i = newI; } + + const ConstructibleValueType &c() const { return m_c; } + void setC(const ConstructibleValueType &newC) { m_c = newC; } + + QPointF p() const { return m_p; } + void setP(QPointF newP) { m_p = newP; } + +private: + + friend bool operator==(const StructuredValueType &a, const StructuredValueType &b) + { + return a.m_i == b.m_i && a.m_c == b.m_c && a.m_p == b.m_p; + } + + int m_i = 0; + ConstructibleValueType m_c; + QPointF m_p; +}; + class MyTypeObject : public QObject { Q_OBJECT @@ -41,6 +96,8 @@ class MyTypeObject : public QObject Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY changed) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed) Q_PROPERTY(QVariant variant READ variant NOTIFY changed) + Q_PROPERTY(ConstructibleValueType constructible READ constructible WRITE setConstructible NOTIFY constructibleChanged) + Q_PROPERTY(StructuredValueType structured READ structured WRITE setStructured NOTIFY structuredChanged) public: MyTypeObject() : @@ -144,12 +201,36 @@ public: void emitRunScript() { emit runScript(); } + const ConstructibleValueType &constructible() const { return m_constructible; } + void setConstructible(const ConstructibleValueType &newConstructible) + { + if (m_constructible == newConstructible) + return; + m_constructible = newConstructible; + emit constructibleChanged(); + } + + const StructuredValueType &structured() const { return m_structured; } + void setStructured(const StructuredValueType &newStructured) + { + if (m_structured == newStructured) + return; + m_structured = newStructured; + emit structuredChanged(); + } + signals: void changed(); void runScript(); + void constructibleChanged(); + void structuredChanged(); + public slots: QSize method() { return QSize(13, 14); } +private: + ConstructibleValueType m_constructible; + StructuredValueType m_structured; }; void registerTypes(); diff --git a/tests/auto/qml/qqmlvaluetypeproviders/tst_qqmlvaluetypeproviders.cpp b/tests/auto/qml/qqmlvaluetypeproviders/tst_qqmlvaluetypeproviders.cpp index 8a60b646db..6aa5b64646 100644 --- a/tests/auto/qml/qqmlvaluetypeproviders/tst_qqmlvaluetypeproviders.cpp +++ b/tests/auto/qml/qqmlvaluetypeproviders/tst_qqmlvaluetypeproviders.cpp @@ -38,6 +38,7 @@ private slots: void invokableFunctions(); void userType(); void changedSignal(); + void structured(); }; void tst_qqmlvaluetypeproviders::initTestCase() @@ -245,6 +246,95 @@ void tst_qqmlvaluetypeproviders::changedSignal() QVERIFY(object->property("success").toBool()); } +void tst_qqmlvaluetypeproviders::structured() +{ + QQmlEngine e; + const QUrl url = testFileUrl("structuredValueTypes.qml"); + QQmlComponent component(&e, url); + QVERIFY2(!component.isError(), qPrintable(component.errorString())); + + const char *warnings[] = { + "Could not find any constructor for value type ConstructibleValueType to call" + " with value [object Object]", + "Could not find any constructor for value type ConstructibleValueType to call" + " with value QVariant(QJSValue, )", + "Could not convert [object Object] to double for property y", + "Could not find any constructor for value type ConstructibleValueType to call" + " with value [object Object]", + "Could not find any constructor for value type ConstructibleValueType to call" + " with value QVariant(QVariantMap, QMap())", + "Could not convert array value at position 5 from QVariantMap to ConstructibleValueType", + "Could not find any constructor for value type ConstructibleValueType to call" + " with value [object Object]", + "Could not find any constructor for value type ConstructibleValueType to call" + " with value QVariant(QJSValue, )" + }; + + for (const auto warning : warnings) + QTest::ignoreMessage(QtWarningMsg, warning); + + QTest::ignoreMessage(QtWarningMsg, qPrintable( + url.toString() + QStringLiteral(":36: Error: Cannot assign QJSValue " + "to ConstructibleValueType"))); + + QTest::ignoreMessage(QtWarningMsg, qPrintable( + url.toString() + QStringLiteral(":14:5: Unable to assign QJSValue " + "to ConstructibleValueType"))); + + QScopedPointer o(component.create()); + QVERIFY2(!o.isNull(), qPrintable(component.errorString())); + + QCOMPARE(o->property("p").value(), QPointF(7, 77)); + QCOMPARE(o->property("s").value(), QSizeF(7, 77)); + QCOMPARE(o->property("r").value(), QRectF(5, 55, 7, 77)); + + QCOMPARE(o->property("p2").value(), QPointF(4, 5)); + QCOMPARE(o->property("s2").value(), QSizeF(7, 8)); + QCOMPARE(o->property("r2").value(), QRectF(9, 10, 11, 12)); + + QCOMPARE(o->property("c1").value(), ConstructibleValueType(5)); + QCOMPARE(o->property("c2").value(), ConstructibleValueType(0)); + QCOMPARE(o->property("c3").value(), ConstructibleValueType(99)); + QCOMPARE(o->property("c4").value(), ConstructibleValueType(0)); + + QCOMPARE(o->property("ps").value>(), + QList({QPointF(1, 2), QPointF(3, 4), QPointF(55, 0)})); + QCOMPARE(o->property("ss").value>(), + QList({QSizeF(5, 6), QSizeF(7, 8), QSizeF(-1, 99)})); + QCOMPARE(o->property("cs").value>(), + QList({1, 2, 3, 4, 5, 0})); + + StructuredValueType b1; + b1.setI(10); + b1.setC(14); + b1.setP(QPointF(1, 44)); + QCOMPARE(o->property("b1").value(), b1); + + StructuredValueType b2; + b2.setI(11); + b2.setC(15); + b2.setP(QPointF(4, 0)); + QCOMPARE(o->property("b2").value(), b2); + + + QList bb(3); + bb[0].setI(21); + bb[1].setC(22); + bb[2].setP(QPointF(199, 222)); + QCOMPARE(o->property("bb").value>(), bb); + + MyTypeObject *t = qobject_cast(o.data()); + QVERIFY(t); + + QCOMPARE(t->constructible(), ConstructibleValueType(47)); + + StructuredValueType structured; + structured.setI(11); + structured.setC(12); + structured.setP(QPointF(7, 8)); + QCOMPARE(t->structured(), structured); +} + QTEST_MAIN(tst_qqmlvaluetypeproviders) #include "tst_qqmlvaluetypeproviders.moc"