diff options
-rw-r--r-- | src/qml/qml/qqmlproperty.cpp | 172 | ||||
-rw-r--r-- | src/qml/qml/qqmlproperty_p.h | 14 | ||||
-rw-r--r-- | src/quick/util/qquickpropertychanges.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp | 111 |
4 files changed, 251 insertions, 49 deletions
diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 707b43cfd4..7dff37e3a6 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -58,6 +58,7 @@ #include <private/qv4functionobject_p.h> #include <private/qv4qobjectwrapper_p.h> #include <private/qqmlbuiltinfunctions_p.h> +#include <private/qqmlirbuilder_p.h> #include <QStringList> #include <QVector> @@ -234,14 +235,15 @@ QQmlProperty::QQmlProperty(QObject *obj, const QString &name, QQmlEngine *engine } QQmlProperty QQmlPropertyPrivate::create(QObject *target, const QString &propertyName, - const QQmlRefPointer<QQmlContextData> &context) + const QQmlRefPointer<QQmlContextData> &context, + QQmlPropertyPrivate::InitFlags flags) { QQmlProperty result; auto d = new QQmlPropertyPrivate; result.d = d; d->context = context; d->engine = context ? context->engine() : nullptr; - d->initProperty(target, propertyName); + d->initProperty(target, propertyName, flags); if (!result.isValid()) { d->object = nullptr; d->context = nullptr; @@ -265,10 +267,10 @@ QQmlRefPointer<QQmlContextData> QQmlPropertyPrivate::effectiveContext() const return nullptr; } -void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) +// ### Qt7: Do not accept the "onFoo" syntax for signals anymore, and change the flags accordingly. +void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name, + QQmlPropertyPrivate::InitFlags flags) { - if (!obj) return; - QQmlRefPointer<QQmlTypeNameCache> typeNameCache = context ? context->imports() : nullptr; QObject *currentObject = obj; @@ -319,12 +321,30 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) } QQmlPropertyData local; - QQmlPropertyData *property = - QQmlPropertyCache::property(engine, currentObject, pathName, context, &local); + QQmlPropertyData *property = currentObject + ? QQmlPropertyCache::property(engine, currentObject, pathName, context, &local) + : nullptr; + + if (!property) { + // Not a property; Might be an ID + if (!(flags & InitFlag::AllowId)) + return; + + for (auto idContext = context; idContext; idContext = idContext->parent()) { + const int objectId = idContext->propertyIndex(pathName.toString()); + if (objectId != -1 && objectId < idContext->numIdValues()) { + currentObject = context->idValue(objectId); + break; + } + } + + if (!currentObject) + return; - if (!property) return; // Not a property - if (property->isFunction()) + continue; + } else if (property->isFunction()) { return; // Not an object property + } if (ii == (path.count() - 2) && QQmlMetaType::isValueType(property->propType())) { // We're now at a value type property @@ -362,12 +382,41 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) } terminal = path.last(); + } else if (!currentObject) { + return; } - if (terminal.size() >= 3 && terminal.at(0) == u'o' && terminal.at(1) == u'n' - && (terminal.at(2).isUpper() || terminal.at(2) == u'_')) { + auto findSignalInMetaObject = [&](const QByteArray &signalName) { + const QMetaMethod method = findSignalByName(currentObject->metaObject(), signalName); + if (!method.isValid()) + return false; + + object = currentObject; + core.load(method); + return true; + }; + + QQmlData *ddata = QQmlData::get(currentObject, false); + auto findChangeSignal = [&](QStringView signalName) { + const QString changed = QStringLiteral("Changed"); + if (signalName.endsWith(changed)) { + const QStringView propName = signalName.first(signalName.length() - changed.length()); + QQmlPropertyData *d = ddata->propertyCache->property(propName, currentObject, context); + while (d && d->isFunction()) + d = ddata->propertyCache->overrideData(d); + + if (d && d->notifyIndex() != -1) { + object = currentObject; + core = *ddata->propertyCache->signal(d->notifyIndex()); + return true; + } + } + return false; + }; - QString signalName = terminal.mid(2).toString(); + const QString terminalString = terminal.toString(); + if (QmlIR::IRBuilder::isSignalPropertyName(terminalString)) { + QString signalName = terminalString.mid(2); int firstNon_; int length = signalName.length(); for (firstNon_ = 0; firstNon_ < length; ++firstNon_) @@ -375,13 +424,14 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) break; signalName[firstNon_] = signalName.at(firstNon_).toLower(); - // XXX - this code treats methods as signals - - QQmlData *ddata = QQmlData::get(currentObject, false); if (ddata && ddata->propertyCache) { - // Try method - QQmlPropertyData *d = ddata->propertyCache->property(signalName, currentObject, context); + QQmlPropertyData *d = ddata->propertyCache->property( + signalName, currentObject, context); + + // ### Qt7: This code treats methods as signals. It should use d->isSignal(). + // That would be a change in behavior, though. Right now you can construct a + // QQmlProperty from such a thing. while (d && !d->isFunction()) d = ddata->propertyCache->overrideData(d); @@ -391,39 +441,53 @@ void QQmlPropertyPrivate::initProperty(QObject *obj, const QString &name) return; } - // Try property - if (signalName.endsWith(QLatin1String("Changed"))) { - const QStringView propName = QStringView{signalName}.mid(0, signalName.length() - 7); - QQmlPropertyData *d = ddata->propertyCache->property(propName, currentObject, context); - while (d && d->isFunction()) - d = ddata->propertyCache->overrideData(d); + if (findChangeSignal(signalName)) + return; + } else if (findSignalInMetaObject(signalName.toUtf8())) { + return; + } + } - if (d && d->notifyIndex() != -1) { - object = currentObject; - core = *ddata->propertyCache->signal(d->notifyIndex()); - return; - } - } + if (ddata && ddata->propertyCache) { + QQmlPropertyData *property = ddata->propertyCache->property( + terminal, currentObject, context); - } else { - QMetaMethod method = findSignalByName(currentObject->metaObject(), - signalName.toLatin1()); - if (method.isValid()) { + // Technically, we might find an override that is not a function. + while (property && !property->isSignal()) { + if (!property->isFunction()) { object = currentObject; - core.load(method); + core = *property; + nameCache = terminalString; return; } + property = ddata->propertyCache->overrideData(property); } - } - // Property - QQmlPropertyData local; - QQmlPropertyData *property = - QQmlPropertyCache::property(engine, currentObject, terminal, context, &local); - if (property && !property->isFunction()) { - object = currentObject; - core = *property; - nameCache = terminal.toString(); + if (!(flags & InitFlag::AllowSignal)) + return; + + if (property) { + Q_ASSERT(property->isSignal()); + object = currentObject; + core = *property; + return; + } + + // At last: Try the change signal. + findChangeSignal(terminal); + } else { + // We might still find the property in the metaobject, even without property cache. + const QByteArray propertyName = terminal.toUtf8(); + const QMetaProperty prop = findPropertyByName(currentObject->metaObject(), propertyName); + + if (prop.isValid()) { + object = currentObject; + core.load(prop); + return; + } + + if (flags & InitFlag::AllowSignal) + findSignalInMetaObject(terminal.toUtf8()); } } @@ -709,7 +773,6 @@ QString QQmlProperty::name() const if (!d) return QString(); if (d->nameCache.isEmpty()) { - // ### if (!d->object) { } else if (d->isValueType()) { const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(d->core.propType()); @@ -718,8 +781,15 @@ QString QQmlProperty::name() const const char *vtName = valueTypeMetaObject->property(d->valueTypeData.coreIndex()).name(); d->nameCache = d->core.name(d->object) + QLatin1Char('.') + QString::fromUtf8(vtName); } else if (type() & SignalProperty) { - QString name = QLatin1String("on") + d->core.name(d->object); - name[2] = name.at(2).toUpper(); + // ### Qt7: Return the original signal name here. Do not prepend "on" + QString name = QStringLiteral("on") + d->core.name(d->object); + for (int i = 2, end = name.length(); i != end; ++i) { + const QChar c = name.at(i); + if (c != u'_') { + name[i] = c.toUpper(); + break; + } + } d->nameCache = name; } else { d->nameCache = d->core.name(d->object); @@ -1767,6 +1837,16 @@ QMetaMethod QQmlPropertyPrivate::findSignalByName(const QMetaObject *mo, const Q return QMetaMethod(); } +/*! + Return the property corresponding to \a name +*/ +QMetaProperty QQmlPropertyPrivate::findPropertyByName(const QMetaObject *mo, const QByteArray &name) +{ + Q_ASSERT(mo); + const int i = mo->indexOfProperty(name); + return i < 0 ? QMetaProperty() : mo->property(i); +} + /*! \internal If \a indexInSignalRange is true, \a index is treated as a signal index (see QObjectPrivate::signalIndex()), otherwise it is treated as a diff --git a/src/qml/qml/qqmlproperty_p.h b/src/qml/qml/qqmlproperty_p.h index 8800d9949a..700a299369 100644 --- a/src/qml/qml/qqmlproperty_p.h +++ b/src/qml/qml/qqmlproperty_p.h @@ -73,6 +73,13 @@ class QQmlBoundSignalExpression; class Q_QML_PRIVATE_EXPORT QQmlPropertyPrivate : public QQmlRefCount { public: + enum class InitFlag { + None = 0x0, + AllowId = 0x1, + AllowSignal = 0x2 + }; + Q_DECLARE_FLAGS(InitFlags, InitFlag); + QQmlRefPointer<QQmlContextData> context; QPointer<QQmlEngine> engine; QPointer<QObject> object; @@ -94,7 +101,7 @@ public: QQmlRefPointer<QQmlContextData> effectiveContext() const; - void initProperty(QObject *obj, const QString &name); + void initProperty(QObject *obj, const QString &name, InitFlags flags = InitFlag::None); void initDefault(QObject *obj); bool isValueType() const; @@ -159,6 +166,7 @@ public: static bool write(const QQmlProperty &that, const QVariant &, QQmlPropertyData::WriteFlags); static QQmlPropertyIndex propertyIndex(const QQmlProperty &that); static QMetaMethod findSignalByName(const QMetaObject *mo, const QByteArray &); + static QMetaProperty findPropertyByName(const QMetaObject *mo, const QByteArray &); static bool connect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, int type = 0, int *types = nullptr); @@ -169,11 +177,13 @@ public: const QVariant &value, const QQmlRefPointer<QQmlContextData> &ctxt); static QQmlProperty create( QObject *target, const QString &propertyName, - const QQmlRefPointer<QQmlContextData> &context); + const QQmlRefPointer<QQmlContextData> &context, + QQmlPropertyPrivate::InitFlags flags); }; Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlPropertyPrivate::BindingFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlPropertyPrivate::InitFlags); QT_END_NAMESPACE diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp index b7e02f730d..639ff88cc1 100644 --- a/src/quick/util/qquickpropertychanges.cpp +++ b/src/quick/util/qquickpropertychanges.cpp @@ -417,7 +417,8 @@ QQuickPropertyChangesPrivate::property(const QString &property) Q_Q(QQuickPropertyChanges); QQmlData *ddata = QQmlData::get(q); QQmlProperty prop = QQmlPropertyPrivate::create( - object, property, ddata ? ddata->outerContext : QQmlRefPointer<QQmlContextData>()); + object, property, ddata ? ddata->outerContext : QQmlRefPointer<QQmlContextData>(), + QQmlPropertyPrivate::InitFlag::None); if (!prop.isValid()) { qmlWarning(q) << QQuickPropertyChanges::tr("Cannot assign to non-existent property \"%1\"").arg(property); return QQmlProperty(); diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 9cf622c2d7..7012153c20 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -203,6 +203,11 @@ private slots: void dontRemoveQPropertyBinding(); void compatResolveUrls(); + + void initFlags_data(); + void initFlags(); + + void constructFromPlainMetaObject(); private: QQmlEngine engine; }; @@ -2391,6 +2396,112 @@ void tst_qqmlproperty::compatResolveUrls() #endif } +void tst_qqmlproperty::initFlags_data() +{ + QTest::addColumn<bool>("passObject"); + QTest::addColumn<QString>("name"); + QTest::addColumn<QQmlPropertyPrivate::InitFlags>("flags"); + + const QString names[] = { + QStringLiteral("foo"), + QStringLiteral("self.foo"), + QStringLiteral("onFoo"), + QStringLiteral("self.onFoo"), + QStringLiteral("bar"), + QStringLiteral("self.bar"), + QStringLiteral("abar"), + QStringLiteral("self.abar"), + }; + + const QQmlPropertyPrivate::InitFlags flagSets[] = { + QQmlPropertyPrivate::InitFlag::None, + QQmlPropertyPrivate::InitFlag::AllowId, + QQmlPropertyPrivate::InitFlag::AllowSignal, + QQmlPropertyPrivate::InitFlag::AllowId | QQmlPropertyPrivate::InitFlag::AllowSignal, + }; + + for (int i = 0; i < 2; ++i) { + const bool passObject = (i != 0); + for (const QString &name : names) { + for (const QQmlPropertyPrivate::InitFlags flagSet : flagSets) { + const QString rowName = QStringLiteral("%1,%2,%3") + .arg(passObject).arg(name).arg(flagSet.toInt()); + QTest::addRow("%s", qPrintable(rowName)) << passObject << name << flagSet; + } + } + } +} + +void tst_qqmlproperty::initFlags() +{ + QFETCH(bool, passObject); + QFETCH(QString, name); + QFETCH(QQmlPropertyPrivate::InitFlags, flags); + + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(R"( + import QtQml + QtObject { + id: self + signal foo() + property int bar: 12 + property alias abar: self.bar + } + )", QUrl()); + QVERIFY(c.isReady()); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QQmlRefPointer<QQmlContextData> context = QQmlContextData::get(qmlContext(o.data())); + + const QQmlProperty property = QQmlPropertyPrivate::create( + passObject ? o.data() : nullptr, name, context, flags); + + const bool usesId = name.startsWith(QStringLiteral("self.")); + const bool hasSignal = name.endsWith(QStringLiteral("foo")); + if (!passObject && !usesId) { + QVERIFY(!property.isValid()); + } else if (usesId && !(flags & QQmlPropertyPrivate::InitFlag::AllowId)) { + QVERIFY(!property.isValid()); + } else if (hasSignal && !(flags & QQmlPropertyPrivate::InitFlag::AllowSignal)) { + QVERIFY(!property.isValid()); + } else { + QVERIFY(property.isValid()); + if (name.endsWith(QStringLiteral("bar"))) { + QVERIFY(property.isProperty()); + QCOMPARE(property.name(), usesId ? name.mid(strlen("self.")) : name); + QCOMPARE(property.propertyMetaType(), QMetaType::fromType<int>()); + } else { + QVERIFY(property.isSignalProperty()); + QCOMPARE(property.name(), QStringLiteral("onFoo")); + QVERIFY(!property.propertyMetaType().isValid()); + } + } + +} + +void tst_qqmlproperty::constructFromPlainMetaObject() +{ + QScopedPointer<PropertyObject> obj(new PropertyObject); + + QQmlData *data = QQmlData::get(obj.data()); + QVERIFY(data == nullptr); + + QQmlProperty prop(obj.data(), "rectProperty"); + QVERIFY(prop.isValid()); + QVERIFY(prop.isProperty()); + QCOMPARE(prop.propertyMetaType(), QMetaType::fromType<QRect>()); + + QQmlProperty sig(obj.data(), "onOddlyNamedNotifySignal"); + QVERIFY(sig.isValid()); + QVERIFY(sig.isSignalProperty()); + QVERIFY(!sig.propertyMetaType().isValid()); + + data = QQmlData::get(obj.data()); + QVERIFY(data == nullptr); +} + QTEST_MAIN(tst_qqmlproperty) #include "tst_qqmlproperty.moc" |