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 <volker.hilsheimer@qt.io>
This commit is contained in:
Ulf Hermann 2022-09-08 14:41:42 +02:00 committed by Volker Hilsheimer
parent b101be9be6
commit 3195b44e1c
27 changed files with 941 additions and 120 deletions

View File

@ -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
*/

View File

@ -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<QJSValue>(), 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<QQmlLocaleData>()) {
*reinterpret_cast<QLocale *>(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<QJSValue*>(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<Sequence>()) {

View File

@ -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());
}

View File

@ -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 = {

View File

@ -78,7 +78,8 @@ int qmlRegisterAnonymousType(const char *uri, int versionMajor)
nullptr,
QTypeRevision::zero(),
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T, QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T, QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::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<T,QQmlFinalizerHook>::cast()
QQmlPrivate::StaticCastSelector<T,QQmlFinalizerHook>::cast(),
QQmlPrivate::ValueTypeCreationMethod::None,
};
return QQmlPrivate::qmlregister(QQmlPrivate::TypeRegistration, &type);

View File

@ -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<typename ...T>
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);
}

View File

@ -3,6 +3,7 @@
#include <private/qqmlglobal_p.h>
#include <QtQml/private/qqmlmetatype_p.h>
#include <QtQml/private/qjsvalue_p.h>
#include <QtQml/qqmlengine.h>
#include <QtCore/qvariant.h>
@ -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<QJSValue>())
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>()) {
QJSValue val = s.value<QJSValue>();
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<QString>()) {
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<const QV4::Object *>(&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>()) {
QJSValue val = s.value<QJSValue>();
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; }

View File

@ -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

View File

@ -152,6 +152,10 @@ static QQmlTypePrivate *createQQmlType(QQmlMetaTypeData *data, const QString &el
d->extraData.cd->customParser = reinterpret_cast<QQmlCustomParser *>(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.

View File

@ -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);

View File

@ -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<std::nullptr_t>(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

View File

@ -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!!!
};

View File

@ -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<QString>()
|| propertyMetaType == QMetaType::fromType<QList<QUrl>>()
|| 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<QQmlContextData> &context, QQmlPropertyData::WriteFlags flags)
@ -1419,37 +1493,9 @@ bool QQmlPropertyPrivate::write(
} else {
return false;
}
} else if (value.canConvert(propertyMetaType)
&& !isUrl && variantMetaType != QMetaType::fromType<QString>()
&& propertyMetaType != QMetaType::fromType<QList<QUrl>>() && !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<void *>(v.constData()), flags);
}
}
} else if (ConvertAndAssignResult result = tryConvertAndAssign(
object, property, value, flags, propertyMetaType, variantMetaType, isUrl)) {
return result.couldWrite;
} else if (propertyMetaType == QMetaType::fromType<QVariant>()) {
return property.writeProperty(object, const_cast<QVariant *>(&value), flags);
} else if (isUrl) {

View File

@ -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())));

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -92,6 +92,8 @@ public:
int finalizerCast;
bool registerEnumClassesUnscoped;
bool registerEnumsFromRelatedTypes;
bool constructValueType;
bool populateValueType;
};
struct QQmlSingletonTypeData

View File

@ -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)

View File

@ -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") \

View File

@ -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)

View File

@ -10,6 +10,11 @@
QT_BEGIN_NAMESPACE
QQuickColorValueType::QQuickColorValueType(const QString &string)
: v(QColor::fromString(string))
{
}
QVariant QQuickColorValueType::create(const QJSValue &params)
{
return params.isString() ? QColor::fromString(params.toString()) : QVariant();

View File

@ -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 &params);
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 &params);
@ -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 &params);
@ -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 &params);
@ -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 &params);
@ -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 &params);
@ -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 &params);

View File

@ -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<point> ps: [{x: 1, y: 2}, {x: 3, y: 4}, {x: 55, y: Qt.locale()}]
property list<size> ss: [{width: 5, height: 6}, {width: 7, height: 8}, {height: 99}]
property list<constructible> cs: [1, 2, 3, 4, 5, {}]
property structured b1: ({i: 10, c: 14, p: {x: 1, y: 44} })
property structured b2
property list<structured> 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};
}
}

View File

@ -5,4 +5,6 @@
void registerTypes()
{
qmlRegisterType<MyTypeObject>("Test", 1, 0, "MyTypeObject");
qmlRegisterTypesAndRevisions<ConstructibleValueType>("Test", 1);
qmlRegisterTypesAndRevisions<StructuredValueType>("Test", 1);
}

View File

@ -19,6 +19,61 @@
#include <QColor>
#include <qqml.h>
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();

View File

@ -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<QObject> o(component.create());
QVERIFY2(!o.isNull(), qPrintable(component.errorString()));
QCOMPARE(o->property("p").value<QPointF>(), QPointF(7, 77));
QCOMPARE(o->property("s").value<QSizeF>(), QSizeF(7, 77));
QCOMPARE(o->property("r").value<QRectF>(), QRectF(5, 55, 7, 77));
QCOMPARE(o->property("p2").value<QPointF>(), QPointF(4, 5));
QCOMPARE(o->property("s2").value<QSizeF>(), QSizeF(7, 8));
QCOMPARE(o->property("r2").value<QRectF>(), QRectF(9, 10, 11, 12));
QCOMPARE(o->property("c1").value<ConstructibleValueType>(), ConstructibleValueType(5));
QCOMPARE(o->property("c2").value<ConstructibleValueType>(), ConstructibleValueType(0));
QCOMPARE(o->property("c3").value<ConstructibleValueType>(), ConstructibleValueType(99));
QCOMPARE(o->property("c4").value<ConstructibleValueType>(), ConstructibleValueType(0));
QCOMPARE(o->property("ps").value<QList<QPointF>>(),
QList<QPointF>({QPointF(1, 2), QPointF(3, 4), QPointF(55, 0)}));
QCOMPARE(o->property("ss").value<QList<QSizeF>>(),
QList<QSizeF>({QSizeF(5, 6), QSizeF(7, 8), QSizeF(-1, 99)}));
QCOMPARE(o->property("cs").value<QList<ConstructibleValueType>>(),
QList<ConstructibleValueType>({1, 2, 3, 4, 5, 0}));
StructuredValueType b1;
b1.setI(10);
b1.setC(14);
b1.setP(QPointF(1, 44));
QCOMPARE(o->property("b1").value<StructuredValueType>(), b1);
StructuredValueType b2;
b2.setI(11);
b2.setC(15);
b2.setP(QPointF(4, 0));
QCOMPARE(o->property("b2").value<StructuredValueType>(), b2);
QList<StructuredValueType> bb(3);
bb[0].setI(21);
bb[1].setC(22);
bb[2].setP(QPointF(199, 222));
QCOMPARE(o->property("bb").value<QList<StructuredValueType>>(), bb);
MyTypeObject *t = qobject_cast<MyTypeObject *>(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"