From d481f2ff518df1e44103d1850e7d52bd69260c34 Mon Sep 17 00:00:00 2001 From: Michael Brasser Date: Wed, 31 Aug 2011 12:36:32 +1000 Subject: [PATCH] Allow reference to signals using 'on' handler syntax. This will allow APIs like the following: trigger: mouseArea.onClicked However, signal handlers will not be callable from QML: mouseArea.onClicked() //throws exception Change-Id: I2ef5cb4e1f3ed4814ef590962391e1b14e3f0c43 Reviewed-on: http://codereview.qt.nokia.com/3683 Reviewed-by: Qt Sanity Bot Reviewed-by: Aaron Kennedy --- src/declarative/qml/ftw/qmetaobjectbuilder.cpp | 10 ++- src/declarative/qml/qdeclarativepropertycache.cpp | 33 +++++++++++ src/declarative/qml/qdeclarativepropertycache_p.h | 5 +- src/declarative/qml/v8/qv8engine_p.h | 2 +- src/declarative/qml/v8/qv8qobjectwrapper.cpp | 57 +++++++++++++++--- src/declarative/qml/v8/qv8qobjectwrapper_p.h | 2 + .../qdeclarativeecmascript/data/signalHandlers.qml | 60 ++++++++++++++++++++ .../tst_qdeclarativeecmascript.cpp | 31 ++++++++++ 8 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 tests/auto/declarative/qdeclarativeecmascript/data/signalHandlers.qml diff --git a/src/declarative/qml/ftw/qmetaobjectbuilder.cpp b/src/declarative/qml/ftw/qmetaobjectbuilder.cpp index f5e8369..e52b9e1 100644 --- a/src/declarative/qml/ftw/qmetaobjectbuilder.cpp +++ b/src/declarative/qml/ftw/qmetaobjectbuilder.cpp @@ -155,6 +155,7 @@ struct QMetaObjectPrivate int enumeratorCount, enumeratorData; int constructorCount, constructorData; int flags; + int signalCount; }; static inline const QMetaObjectPrivate *priv(const uint* data) @@ -1206,7 +1207,7 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, QMetaObjectPrivate *pmeta = reinterpret_cast(buf + size); int pmetaSize = size; - dataIndex = 13; // Number of fields in the QMetaObjectPrivate. + dataIndex = 14; // Number of fields in the QMetaObjectPrivate. for (index = 0; index < d->properties.size(); ++index) { if (d->properties[index].notifySignal != -1) { hasNotifySignals = true; @@ -1214,9 +1215,10 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, } } if (buf) { - pmeta->revision = 3; + pmeta->revision = 4; pmeta->flags = d->flags; pmeta->className = 0; // Class name is always the first string. + //pmeta->signalCount is handled in the "output method loop" as an optimization. pmeta->classInfoCount = d->classInfoNames.size(); pmeta->classInfoData = dataIndex; @@ -1274,7 +1276,7 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, } // Reset the current data position to just past the QMetaObjectPrivate. - dataIndex = 13; + dataIndex = 14; // Add the class name to the string table. int offset = 0; @@ -1312,6 +1314,8 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf, data[dataIndex + 2] = ret; data[dataIndex + 3] = tag; data[dataIndex + 4] = attrs; + if (method->methodType() == QMetaMethod::Signal) + pmeta->signalCount++; } dataIndex += 5; } diff --git a/src/declarative/qml/qdeclarativepropertycache.cpp b/src/declarative/qml/qdeclarativepropertycache.cpp index 406e43f..05232d9 100644 --- a/src/declarative/qml/qdeclarativepropertycache.cpp +++ b/src/declarative/qml/qdeclarativepropertycache.cpp @@ -313,10 +313,14 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb allowedRevisionCache.append(0); int methodCount = metaObject->methodCount(); + Q_ASSERT(QMetaObjectPrivate::get(metaObject)->revision >= 4); + int signalCount = QMetaObjectPrivate::get(metaObject)->signalCount; // 3 to block the destroyed signal and the deleteLater() slot int methodOffset = qMax(3, metaObject->methodOffset()); methodIndexCache.resize(methodCount - methodIndexCacheStart); + signalHandlerIndexCache.resize(signalCount); + int signalHandlerIndex = 0; for (int ii = methodOffset; ii < methodCount; ++ii) { QMetaMethod m = metaObject->method(ii); if (m.access() == QMetaMethod::Private) @@ -329,6 +333,7 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb while (*cptr != '(') { Q_ASSERT(*cptr != 0); utf8 |= *cptr & 0x80; ++cptr; } Data *data = &methodIndexCache[ii - methodIndexCacheStart]; + Data *sigdata = 0; data->lazyLoad(m); @@ -342,6 +347,12 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb data->metaObjectOffset = allowedRevisionCache.count() - 1; + if (data->isSignal()) { + sigdata = &signalHandlerIndexCache[signalHandlerIndex]; + *sigdata = *data; + sigdata->flags |= Data::IsSignalHandler; + } + Data *old = 0; if (utf8) { @@ -349,11 +360,33 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb if (Data **it = stringCache.value(methodName)) old = *it; stringCache.insert(methodName, data); + + if (data->isSignal()) { + QHashedString on(QStringLiteral("on") % methodName.at(0).toUpper() % methodName.midRef(1)); + stringCache.insert(on, sigdata); + ++signalHandlerIndex; + } } else { QHashedCStringRef methodName(signature, cptr - signature); if (Data **it = stringCache.value(methodName)) old = *it; stringCache.insert(methodName, data); + + if (data->isSignal()) { + int length = methodName.length(); + + char str[length + 3]; + str[0] = 'o'; + str[1] = 'n'; + str[2] = toupper(signature[0]); + if (length > 1) + memcpy(&str[3], &signature[1], length - 1); + str[length + 2] = '\0'; + + QHashedString on(QString::fromLatin1(str)); + stringCache.insert(on, sigdata); + ++signalHandlerIndex; + } } if (old) { diff --git a/src/declarative/qml/qdeclarativepropertycache_p.h b/src/declarative/qml/qdeclarativepropertycache_p.h index 8a1da19..33565c4 100644 --- a/src/declarative/qml/qdeclarativepropertycache_p.h +++ b/src/declarative/qml/qdeclarativepropertycache_p.h @@ -104,9 +104,10 @@ public: IsSignal = 0x00008000, // Function is a signal IsVMESignal = 0x00010000, // Signal was added by QML IsV8Function = 0x00020000, // Function takes QDeclarativeV8Function* args + IsSignalHandler = 0x00040000, // Function is a signal handler // Internal QDeclarativePropertyCache flags - NotFullyResolved = 0x00040000 // True if the type data is to be lazily resolved + NotFullyResolved = 0x00080000 // True if the type data is to be lazily resolved }; Q_DECLARE_FLAGS(Flags, Flag) @@ -133,6 +134,7 @@ public: bool isSignal() const { return flags & IsSignal; } bool isVMESignal() const { return flags & IsVMESignal; } bool isV8Function() const { return flags & IsV8Function; } + bool isSignalHandler() const { return flags & IsSignalHandler; } union { int propType; // When !NotFullyResolved @@ -221,6 +223,7 @@ private: IndexCache propertyIndexCache; IndexCache methodIndexCache; + IndexCache signalHandlerIndexCache; StringCache stringCache; AllowedRevisionCache allowedRevisionCache; v8::Persistent constructor; diff --git a/src/declarative/qml/v8/qv8engine_p.h b/src/declarative/qml/v8/qv8engine_p.h index bd5f360..ab4da42 100644 --- a/src/declarative/qml/v8/qv8engine_p.h +++ b/src/declarative/qml/v8/qv8engine_p.h @@ -135,7 +135,7 @@ public: QV8ObjectResource(QV8Engine *engine) : engine(engine) { Q_ASSERT(engine); } enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType, ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType, - ListModelType, Context2DType, ParticleDataType }; + ListModelType, Context2DType, ParticleDataType, SignalHandlerType }; virtual ResourceType resourceType() const = 0; QV8Engine *engine; diff --git a/src/declarative/qml/v8/qv8qobjectwrapper.cpp b/src/declarative/qml/v8/qv8qobjectwrapper.cpp index 52e1064..bf8dcea 100644 --- a/src/declarative/qml/v8/qv8qobjectwrapper.cpp +++ b/src/declarative/qml/v8/qv8qobjectwrapper.cpp @@ -109,6 +109,16 @@ public: QV8QObjectWrapper *wrapper; }; +class QV8SignalHandlerResource : public QV8ObjectResource +{ + V8_RESOURCE_TYPE(SignalHandlerType) +public: + QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index); + + QDeclarativeGuard object; + int index; +}; + namespace { struct MetaCallArgument { inline MetaCallArgument(); @@ -152,6 +162,11 @@ QV8QObjectResource::QV8QObjectResource(QV8Engine *engine, QObject *object) { } +QV8SignalHandlerResource::QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index) +: QV8ObjectResource(engine), object(object), index(index) +{ +} + static QAtomicInt objectIdCounter(1); QV8QObjectWrapper::QV8QObjectWrapper() @@ -177,6 +192,7 @@ void QV8QObjectWrapper::destroy() qPersistentDispose(m_hiddenObject); qPersistentDispose(m_destroySymbol); qPersistentDispose(m_toStringSymbol); + qPersistentDispose(m_signalHandlerConstructor); qPersistentDispose(m_methodConstructor); qPersistentDispose(m_constructor); } @@ -278,10 +294,21 @@ void QV8QObjectWrapper::init(QV8Engine *engine) m_methodConstructor = qPersistentNew(createFn); } + v8::Local connect = V8FUNCTION(Connect, engine); + v8::Local disconnect = V8FUNCTION(Disconnect, engine); + + { + v8::Local ft = v8::FunctionTemplate::New(); + ft->InstanceTemplate()->SetHasExternalResource(true); + ft->PrototypeTemplate()->Set(v8::String::New("connect"), connect, v8::DontEnum); + ft->PrototypeTemplate()->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum); + m_signalHandlerConstructor = qPersistentNew(ft->GetFunction()); + } + { v8::Local prototype = engine->global()->Get(v8::String::New("Function"))->ToObject()->Get(v8::String::New("prototype"))->ToObject(); - prototype->Set(v8::String::New("connect"), V8FUNCTION(Connect, engine), v8::DontEnum); - prototype->Set(v8::String::New("disconnect"), V8FUNCTION(Disconnect, engine), v8::DontEnum); + prototype->Set(v8::String::New("connect"), connect, v8::DontEnum); + prototype->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum); } } @@ -461,6 +488,11 @@ v8::Handle QV8QObjectWrapper::GetProperty(QV8Engine *engine, QObject return ((QDeclarativeVMEMetaObject *)(object->metaObject()))->vmeMethod(result->coreIndex); } else if (result->isV8Function()) { return MethodClosure::createWithGlobal(engine, object, objectHandle, result->coreIndex); + } else if (result->isSignalHandler()) { + v8::Local handler = engine->qobjectWrapper()->m_signalHandlerConstructor->NewInstance(); + QV8SignalHandlerResource *r = new QV8SignalHandlerResource(engine, object, result->coreIndex); + handler->SetExternalResource(r); + return handler; } else { return MethodClosure::create(engine, object, objectHandle, result->coreIndex); } @@ -998,6 +1030,17 @@ v8::Handle QV8QObjectWrapper::newQObject(QObject *object) } } +QPair QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle object) +{ + if (object->IsFunction()) + return ExtractQtMethod(engine, v8::Handle::Cast(object)); + + if (QV8SignalHandlerResource *resource = v8_resource_cast(object)) + return qMakePair(resource->object.data(), resource->index); + + return qMakePair((QObject *)0, -1); +} + QPair QV8QObjectWrapper::ExtractQtMethod(QV8Engine *engine, v8::Handle function) { v8::ScriptOrigin origin = function->GetScriptOrigin(); @@ -1166,10 +1209,7 @@ v8::Handle QV8QObjectWrapper::Connect(const v8::Arguments &args) QV8Engine *engine = V8ENGINE(); - if (!args.This()->IsFunction()) - V8THROW_ERROR("Function.prototype.connect: this object is not a signal"); - - QPair signalInfo = ExtractQtMethod(engine, v8::Handle::Cast(args.This())); + QPair signalInfo = ExtractQtSignal(engine, args.This()); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; @@ -1228,10 +1268,7 @@ v8::Handle QV8QObjectWrapper::Disconnect(const v8::Arguments &args) QV8Engine *engine = V8ENGINE(); - if (!args.This()->IsFunction()) - V8THROW_ERROR("Function.prototype.disconnect: this object is not a signal"); - - QPair signalInfo = ExtractQtMethod(engine, v8::Handle::Cast(args.This())); + QPair signalInfo = ExtractQtSignal(engine, args.This()); QObject *signalObject = signalInfo.first; int signalIndex = signalInfo.second; diff --git a/src/declarative/qml/v8/qv8qobjectwrapper_p.h b/src/declarative/qml/v8/qv8qobjectwrapper_p.h index d0a489b..be118a9 100644 --- a/src/declarative/qml/v8/qv8qobjectwrapper_p.h +++ b/src/declarative/qml/v8/qv8qobjectwrapper_p.h @@ -111,11 +111,13 @@ private: static v8::Handle Disconnect(const v8::Arguments &args); static v8::Handle Invoke(const v8::Arguments &args); static QPair ExtractQtMethod(QV8Engine *, v8::Handle); + static QPair ExtractQtSignal(QV8Engine *, v8::Handle); QV8Engine *m_engine; quint32 m_id; v8::Persistent m_constructor; v8::Persistent m_methodConstructor; + v8::Persistent m_signalHandlerConstructor; v8::Persistent m_toStringSymbol; v8::Persistent m_destroySymbol; QHashedV8String m_toStringString; diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/signalHandlers.qml b/tests/auto/declarative/qdeclarativeecmascript/data/signalHandlers.qml new file mode 100644 index 0000000..975be1b --- /dev/null +++ b/tests/auto/declarative/qdeclarativeecmascript/data/signalHandlers.qml @@ -0,0 +1,60 @@ +import Qt.test 1.0 +import QtQuick 2.0 + +QtObject { + id: root + + property int count: 0 + signal testSignal + onTestSignal: count++ + + property int funcCount: 0 + function testFunction() { + funcCount++; + } + + //should increment count + function testSignalCall() { + testSignal() + } + + //should NOT increment count, and should throw an exception + property string errorString + function testSignalHandlerCall() { + try { + onTestSignal() + } catch (error) { + errorString = error.toString(); + } + } + + //should increment funcCount once + function testSignalConnection() { + testSignal.connect(testFunction) + testSignal(); + testSignal.disconnect(testFunction) + testSignal(); + } + + //should increment funcCount once + function testSignalHandlerConnection() { + onTestSignal.connect(testFunction) + testSignal(); + onTestSignal.disconnect(testFunction) + testSignal(); + } + + //should be defined + property bool definedResult: false + function testSignalDefined() { + if (testSignal !== undefined) + definedResult = true; + } + + //should be defined + property bool definedHandlerResult: false + function testSignalHandlerDefined() { + if (onTestSignal !== undefined) + definedHandlerResult = true; + } +} diff --git a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp index f14db0a..ba1aaca 100644 --- a/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp +++ b/tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp @@ -188,6 +188,7 @@ private slots: void realToInt(); void dynamicString(); void include(); + void signalHandlers(); void callQtInvokables(); void invokableObjectArg(); @@ -3597,6 +3598,36 @@ void tst_qdeclarativeecmascript::include() } } +void tst_qdeclarativeecmascript::signalHandlers() +{ + QDeclarativeComponent component(&engine, TEST_FILE("signalHandlers.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QVERIFY(o->property("count").toInt() == 0); + QMetaObject::invokeMethod(o, "testSignalCall"); + QCOMPARE(o->property("count").toInt(), 1); + + QMetaObject::invokeMethod(o, "testSignalHandlerCall"); + QCOMPARE(o->property("count").toInt(), 1); + QCOMPARE(o->property("errorString").toString(), QLatin1String("TypeError: Property 'onTestSignal' of object [object Object] is not a function")); + + QVERIFY(o->property("funcCount").toInt() == 0); + QMetaObject::invokeMethod(o, "testSignalConnection"); + QCOMPARE(o->property("funcCount").toInt(), 1); + + QMetaObject::invokeMethod(o, "testSignalHandlerConnection"); + QCOMPARE(o->property("funcCount").toInt(), 2); + + QMetaObject::invokeMethod(o, "testSignalDefined"); + QCOMPARE(o->property("definedResult").toBool(), true); + + QMetaObject::invokeMethod(o, "testSignalHandlerDefined"); + QCOMPARE(o->property("definedHandlerResult").toBool(), true); + + delete o; +} + void tst_qdeclarativeecmascript::qtbug_10696() { QDeclarativeComponent component(&engine, TEST_FILE("qtbug_10696.qml")); -- 1.7.2.5