Allow reference to signals using 'on' handler syntax.
authorMichael Brasser <michael.brasser@nokia.com>
Wed, 31 Aug 2011 02:36:32 +0000 (12:36 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 1 Sep 2011 04:58:23 +0000 (06:58 +0200)
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 <qt_sanity_bot@ovi.com>
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>

src/declarative/qml/ftw/qmetaobjectbuilder.cpp
src/declarative/qml/qdeclarativepropertycache.cpp
src/declarative/qml/qdeclarativepropertycache_p.h
src/declarative/qml/v8/qv8engine_p.h
src/declarative/qml/v8/qv8qobjectwrapper.cpp
src/declarative/qml/v8/qv8qobjectwrapper_p.h
tests/auto/declarative/qdeclarativeecmascript/data/signalHandlers.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp

index f5e8369..e52b9e1 100644 (file)
@@ -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<QMetaObjectPrivate *>(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;
     }
index 406e43f..05232d9 100644 (file)
@@ -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) {
index 8a1da19..33565c4 100644 (file)
@@ -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<v8::Function> constructor;
index bd5f360..ab4da42 100644 (file)
@@ -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;
index 52e1064..bf8dcea 100644 (file)
@@ -109,6 +109,16 @@ public:
     QV8QObjectWrapper *wrapper;
 };
 
+class QV8SignalHandlerResource : public QV8ObjectResource
+{
+    V8_RESOURCE_TYPE(SignalHandlerType)
+public:
+    QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index);
+
+    QDeclarativeGuard<QObject> 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<v8::Function>(createFn);
     }
 
+    v8::Local<v8::Function> connect = V8FUNCTION(Connect, engine);
+    v8::Local<v8::Function> disconnect = V8FUNCTION(Disconnect, engine);
+
+    {
+    v8::Local<v8::FunctionTemplate> 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<v8::Function>(ft->GetFunction());
+    }
+
     {
     v8::Local<v8::Object> 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<v8::Value> 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<v8::Object> 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<v8::Value> QV8QObjectWrapper::newQObject(QObject *object)
     }
 }
 
+QPair<QObject *, int> QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle<v8::Object> object)
+{
+    if (object->IsFunction())
+        return ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(object));
+
+    if (QV8SignalHandlerResource *resource = v8_resource_cast<QV8SignalHandlerResource>(object))
+        return qMakePair(resource->object.data(), resource->index);
+
+    return qMakePair((QObject *)0, -1);
+}
+
 QPair<QObject *, int> QV8QObjectWrapper::ExtractQtMethod(QV8Engine *engine, v8::Handle<v8::Function> function)
 {
     v8::ScriptOrigin origin = function->GetScriptOrigin();
@@ -1166,10 +1209,7 @@ v8::Handle<v8::Value> 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<QObject *, int> signalInfo = ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(args.This()));
+    QPair<QObject *, int> signalInfo = ExtractQtSignal(engine, args.This());
     QObject *signalObject = signalInfo.first;
     int signalIndex = signalInfo.second;
 
@@ -1228,10 +1268,7 @@ v8::Handle<v8::Value> 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<QObject *, int> signalInfo = ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(args.This()));
+    QPair<QObject *, int> signalInfo = ExtractQtSignal(engine, args.This());
     QObject *signalObject = signalInfo.first;
     int signalIndex = signalInfo.second;
 
index d0a489b..be118a9 100644 (file)
@@ -111,11 +111,13 @@ private:
     static v8::Handle<v8::Value> Disconnect(const v8::Arguments &args);
     static v8::Handle<v8::Value> Invoke(const v8::Arguments &args);
     static QPair<QObject *, int> ExtractQtMethod(QV8Engine *, v8::Handle<v8::Function>);
+    static QPair<QObject *, int> ExtractQtSignal(QV8Engine *, v8::Handle<v8::Object>);
 
     QV8Engine *m_engine;
     quint32 m_id;
     v8::Persistent<v8::Function> m_constructor;
     v8::Persistent<v8::Function> m_methodConstructor;
+    v8::Persistent<v8::Function> m_signalHandlerConstructor;
     v8::Persistent<v8::String> m_toStringSymbol;
     v8::Persistent<v8::String> 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 (file)
index 0000000..975be1b
--- /dev/null
@@ -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;
+    }
+}
index f14db0a..ba1aaca 100644 (file)
@@ -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"));