diff options
author | Milian Wolff <milian.wolff@kdab.com> | 2013-10-23 13:19:06 +0200 |
---|---|---|
committer | Pierre Rossi <pierre.rossi@gmail.com> | 2013-11-01 13:57:53 +0100 |
commit | 2b744932116e55e32e4740f302ec99bf4f51476e (patch) | |
tree | aefdc2c518061060ac887c3c6867953371d9d892 | |
parent | 3f8026932650a2dffb4e7ca82bdedc7b0e814e95 (diff) | |
download | qtwebchannel-2b744932116e55e32e4740f302ec99bf4f51476e.tar.gz |
Make it possible to wrap QObject's on the fly.
This is required for factory-like methods on the C++/QML side,
which we want to access from the HTML side as well.
Change-Id: I2852bbc9c8effb6d6f49b5be784241a6e2320823
Reviewed-by: Pierre Rossi <pierre.rossi@gmail.com>
-rw-r--r-- | examples/qtobject/main.cpp | 1 | ||||
-rw-r--r-- | examples/qtobject/qml/qtobject/index.html | 107 | ||||
-rw-r--r-- | examples/qtobject/qml/qtobject/main.qml | 27 | ||||
-rw-r--r-- | examples/qtobject/testobject.cpp | 13 | ||||
-rw-r--r-- | examples/qtobject/testobject.h | 10 | ||||
-rw-r--r-- | src/MetaObjectPublisher.qml | 59 | ||||
-rw-r--r-- | src/qobject.js | 26 | ||||
-rw-r--r-- | src/qtmetaobjectpublisher.cpp | 55 | ||||
-rw-r--r-- | src/qtmetaobjectpublisher.h | 26 | ||||
-rw-r--r-- | src/src.pri | 10 |
10 files changed, 275 insertions, 59 deletions
diff --git a/examples/qtobject/main.cpp b/examples/qtobject/main.cpp index b816f0e..e9fbc65 100644 --- a/examples/qtobject/main.cpp +++ b/examples/qtobject/main.cpp @@ -8,6 +8,7 @@ int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); + qmlRegisterType<TestObjectFactory>("Qt.labs", 1, 0, "TestObjectFactory"); qmlRegisterType<TestObject>("Qt.labs", 1, 0, "TestObject"); QtQuick2ApplicationViewer viewer; diff --git a/examples/qtobject/qml/qtobject/index.html b/examples/qtobject/qml/qtobject/index.html index 5e2b866..af2b940 100644 --- a/examples/qtobject/qml/qtobject/index.html +++ b/examples/qtobject/qml/qtobject/index.html @@ -3,51 +3,90 @@ <script type="text/javascript" src="qrc:///qwebchannel/webchannel.js"></script> <script type="text/javascript" src="qrc:///qwebchannel/qobject.js"></script> <script type="text/javascript"> - window.output = function(x) { + //BEGIN HELPER + function output(x) { document.querySelector("#out").innerHTML += x + "\n"; } + function createLink(label, onclick) { + var link = document.createElement("a"); + link.href = "#"; + link.onclick = onclick + link.appendChild(document.createTextNode(label)); + return link; + } + function addObject(object) { + object.timeout.connect(function() { output('timeout of object ' + object.objectName()); }); + object.sig1.connect(function(a, b, c) { + output('sig1 of object ' + object.objectName() + ": a = " + a + ", b = " + b + ", c = " + c); + }); + object.sig2.connect(function() { output('sig2 of object ' + object.objectName()); }); + object.prop1Changed.connect(function() { + // note: notify signal doesn't have the new value, so you must use direct access + output("prop1 of object " + object.objectName() + " changed, direct: " + object.prop1()); + }); + object.prop2Changed.connect(function(newVal) { + output("prop2 of object " + object.objectName() + " changed, new val: " + newVal + ", direct: " + object.prop2()); + }); + object.destroyed.connect(function() { + output("object destroyed " + object.objectName()); + }); + var container = document.getElementById("objects"); + var element = document.createElement("p"); + element.appendChild(document.createTextNode(object.objectName() + ":")); + element.appendChild(createLink("debugMe", function() { + object.debugMe('Debugging!', function(result) { output(result); }); + })); + element.appendChild(createLink("manyArgs", function() { + object.manyArgs(1, 0.5, 'asdf', function(result) { output(result); }); + })); + element.appendChild(createLink("get prop1", function() { + output(object.prop1()); + })); + element.appendChild(createLink("set prop1", function() { + object.prop1 = "Set prop1 on " + (new Date()); + })); + element.appendChild(createLink("get prop2", function() { + output(object.prop2()); + })); + element.appendChild(createLink("set prop2", function() { + object.prop2 = "Set prop2 on " + (new Date()); + })); + element.appendChild(createLink("start timer", function() { + object.startTimer(1000); + })); + element.appendChild(createLink("delete", function() { + object.deleteLater(); + })); + container.appendChild(element); + } + var createdObjects = 0; + function createObject() { + testObjectFactory.createObject("myObj" + (createdObjects++), function(createdObject) { + addObject(createdObject); + }); + } + + //END HELPER + //BEGIN SETUP var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]); new QWebChannel(baseUrl, function(channel) { setupQObjectWebChannel(channel, function() { - testObject1.sig1.connect(function(a, b, c) { output("1 sig1" + a + b + c); }); - testObject1.sig2.connect(function() { output("1 sig2"); }); - testObject2.sig1.connect(function(a, b, c) { output("2 sig1" + a + b + c); }); - testObject2.sig2.connect(function() { output("2 sig2"); }); - testObject3.sig1.connect(function(a, b, c) { output("3 sig1" + a + b + c); }); - testObject3.sig2.connect(function() { output("3 sig2"); }); + // do stuff with registered QObjects + addObject(initialTestObject); }); }); + //END SETUP </script> + <style type="text/css"> + #objects a { + margin: 0 10px; + } + </style> </head> <body> - <p>TestObject 1: - <a href="#" onclick="testObject1.debugMe('Debugging!', function(result) { output(result); })">method 1</a> - <a href="#" onclick="testObject1.manyArgs(1, 0.5, 'asdf', function(result) { output(result); })">method 2</a> - <a href="#" onclick="testObject1.prop1(function(value) { output(value); })">Get prop1</a> - <a href="#" onclick="testObject1.prop1 = 'Different property'; testObject1.prop1(function(value) { output(value); })">Set prop1</a> - <a href="#" onclick="testObject1.prop2(function(value) { output(value); })">Get prop2</a> - <a href="#" onclick="testObject1.prop2 = 'Different property'; testObject1.prop2(function(value) { output(value); })">Set prop2</a> - <a href="#" onclick="testObject1.timeout.connect(function() { output('timeout 1'); }); testObject1.startTimer(1000);">Timer</a> - </p> - <p>TestObject 2: - <a href="#" onclick="testObject2.debugMe('Debugging!', function(result) { output(result); })">method 1</a> - <a href="#" onclick="testObject2.manyArgs(1, 0.5, 'asdf', function(result) { output(result); })">method 2</a> - <a href="#" onclick="testObject2.prop1(function(value) { output(value); })">Get prop1</a> - <a href="#" onclick="testObject2.prop1 = 'Different property'; testObject2.prop1(function(value) { output(value); })">Set prop1</a> - <a href="#" onclick="testObject2.prop2(function(value) { output(value); })">Get prop2</a> - <a href="#" onclick="testObject2.prop2 = 'Different property'; testObject2.prop2(function(value) { output(value); })">Set prop2</a> - <a href="#" onclick="testObject2.timeout.connect(function() { output('timeout 2'); }); testObject2.startTimer(1000);">Timer</a> - </p> - <p>TestObject 3: - <a href="#" onclick="testObject3.debugMe('Debugging!', function(result) { output(result); })">method 1</a> - <a href="#" onclick="testObject3.manyArgs(1, 0.5, 'asdf', function(result) { output(result); })">method 2</a> - <a href="#" onclick="testObject3.prop1(function(value) { output(value); })">Get prop1</a> - <a href="#" onclick="testObject3.prop1 = 'Different property'; testObject3.prop1(function(value) { output(value); })">Set prop1</a> - <a href="#" onclick="testObject3.prop2(function(value) { output(value); })">Get prop2</a> - <a href="#" onclick="testObject3.prop2 = 'Different property'; testObject3.prop2(function(value) { output(value); })">Set prop2</a> - <a href="#" onclick="testObject3.timeout.connect(function() { output('timeout 3'); }); testObject3.startTimer(1000);">Timer</a> - </p> + <div id="objects"></div> <br/> + <a href="#" onclick="createObject()">Create New Object</a>. Note: Only created objects can be deleted, the initial object will stay.<br/> <textarea id="out" style="height:80%; width: 80%"></textarea> </body> </html> diff --git a/examples/qtobject/qml/qtobject/main.qml b/examples/qtobject/qml/qtobject/main.qml index d00c9ac..fabeb1d 100644 --- a/examples/qtobject/qml/qtobject/main.qml +++ b/examples/qtobject/qml/qtobject/main.qml @@ -47,28 +47,22 @@ import QtWebKit 3.0 import QtWebKit.experimental 1.0 Rectangle { - MetaObjectPublisher { - id: publisher - webChannel: webChannel - } - - TestObject { - id: testObject1 - objectName: "object1" + TestObjectFactory { + id: factory } - TestObject { - id: testObject2 - objectName: "object2" + id: testObject + objectName: "initialTestObject" } - TestObject { - id: testObject3 - objectName: "object3" + MetaObjectPublisher { + id: publisher + webChannel: webChannel } WebChannel { id: webChannel + onRawMessageReceived: { if (!publisher.handleRequest(rawMessage, webChannel)) { console.log("unhandled request: ", rawMessage); @@ -77,9 +71,8 @@ Rectangle { onInitialized: { publisher.registerObjects({ - "testObject1": testObject1, - "testObject2": testObject2, - "testObject3":testObject3 + "testObjectFactory": factory, + "initialTestObject": testObject }); } } diff --git a/examples/qtobject/testobject.cpp b/examples/qtobject/testobject.cpp index e18dbc0..9d47e72 100644 --- a/examples/qtobject/testobject.cpp +++ b/examples/qtobject/testobject.cpp @@ -34,3 +34,16 @@ QString TestObject::manyArgs(int a, float b, const QString& c) const qDebug() << a << b << c; return c; } + +TestObjectFactory::TestObjectFactory(QObject* parent) + : QObject(parent) +{ + +} + +TestObject* TestObjectFactory::createObject(const QString& name) +{ + TestObject* ret = new TestObject(this); + ret->setObjectName(name); + return ret; +} diff --git a/examples/qtobject/testobject.h b/examples/qtobject/testobject.h index f3f1c03..72e57bb 100644 --- a/examples/qtobject/testobject.h +++ b/examples/qtobject/testobject.h @@ -4,6 +4,7 @@ #include <QObject> #include <QtDebug> #include <QTimer> + class TestObject : public QObject { Q_OBJECT @@ -40,4 +41,13 @@ private: QTimer timer; }; +class TestObjectFactory : public QObject +{ + Q_OBJECT +public: + explicit TestObjectFactory(QObject* parent = 0); + + Q_INVOKABLE TestObject* createObject(const QString& name); +}; + #endif // TESTOBJECT_H diff --git a/src/MetaObjectPublisher.qml b/src/MetaObjectPublisher.qml index 2b97c02..226e0a3 100644 --- a/src/MetaObjectPublisher.qml +++ b/src/MetaObjectPublisher.qml @@ -78,6 +78,25 @@ MetaObjectPublisherImpl // object info map set. property bool propertyUpdatesInitialized: false + /** + * Wrap a result value if it's a Qt QObject + * + * @return object info for wrapped Qt Object, + * or the same value if no wrapping needed + * + */ + function wrapResult(result) + { + if (typeof(result) === "object" + && result["objectName"] !== undefined) + { + var ret = wrapObject(result); + initializePropertyUpdates(ret.id, ret.data, result, webChannel); + return ret; + } + return result; + } + function convertQMLArgsToJSArgs(qmlArgs) { // NOTE: QML arguments is a map not an array it seems... @@ -106,12 +125,26 @@ MetaObjectPublisherImpl } if (payload.object) { + var isWrapped = false; var object = registeredObjects[payload.object]; + if (!object) { + object = unwrapObject(payload.object); + if (object) + isWrapped = true; + else + return false + } if (payload.type === "Qt.invokeMethod") { var method = object[payload.method]; if (method !== undefined) { - webChannel.respond(message.id, method.apply(method, payload.args)); + webChannel.respond(message.id, + wrapResult(method.apply(method, payload.args))); + return true; + } + if (isWrapped && payload.method === "deleteLater") { + // invoke `deleteLater` on wrapped QObject indirectly + deleteWrappedObject(object); return true; } return false; @@ -135,6 +168,11 @@ MetaObjectPublisherImpl } return true; } + // connecting to `destroyed` signal of wrapped QObject + if (isWrapped && payload.signal === "destroyed") { + // is a no-op on this side + return true; + } return false; } if (payload.type === "Qt.setProperty") { @@ -248,6 +286,13 @@ MetaObjectPublisherImpl var data = []; for (var objectName in pendingPropertyUpdates) { var object = registeredObjects[objectName]; + if (!object) { + object = unwrapObject(objectName); + if (!object) { + console.error("Got property update for unknown object " + objectName); + continue; + } + } var signals = pendingPropertyUpdates[objectName]; var propertyMap = {}; for (var signalName in signals) { @@ -291,6 +336,18 @@ MetaObjectPublisherImpl } } + onWrappedObjectDestroyed: { // (const QString& id) + // act as if object had sent `destroyed` signal + webChannel.sendMessage("Qt.signal", { + object: id, + signal: "destroyed", + args: [] + }); + delete subscriberCountMap[id]; + delete pendingPropertyUpdates[id]; + delete signalToPropertyMap[id] + } + /** * Aggregate property updates since we get multiple Qt.idle message when we have multiple * clients. They all share the same QWebProcess though so we must take special care to diff --git a/src/qobject.js b/src/qobject.js index 012332e..5f55334 100644 --- a/src/qobject.js +++ b/src/qobject.js @@ -54,6 +54,26 @@ function QObject(name, data, webChannel) // ---------------------------------------------------------------------- + function unwrapQObject( response ) + { + if (!response["__QObject*__"] + || response["id"] === undefined + || response["data"] === undefined) { + return response; + } + var objectId = response.id; + if (webChannel.objectMap[objectId]) + return webChannel.objectMap[objectId]; + + var qObject = new QObject( objectId, response.data, webChannel ); + qObject.destroyed.connect(function() { + if (webChannel.objectMap[objectId] === qObject) { + delete webChannel.objectMap[objectId]; + } + }); + return qObject; + } + function addSignal(signal, isPropertyNotifySignal) { object[signal] = { @@ -126,7 +146,7 @@ function QObject(name, data, webChannel) webChannel.exec({"type": "Qt.invokeMethod", "object": object.__id__, "method": method, "args": args}, function(response) { if ( (response !== undefined) && callback ) { - (callback)(response); + (callback)(unwrapQObject(response)); } }); }; @@ -199,7 +219,7 @@ window.setupQObjectWebChannel = function(webChannel, doneCallback) webChannel.subscribe( "Qt.signal", function(payload) { - var object = webChannel.objectMap[payload.object]; + var object = window[payload.object] || webChannel.objectMap[payload.object]; if (object) { object.signalEmitted(payload.signal, payload.args); } else { @@ -213,7 +233,7 @@ window.setupQObjectWebChannel = function(webChannel, doneCallback) function(payload) { for (var i in payload) { var data = payload[i]; - var object = webChannel.objectMap[data.object]; + var object = window[data.object] || webChannel.objectMap[data.object]; if (object) { object.propertyUpdate(data.signals, data.propertyMap); } else { diff --git a/src/qtmetaobjectpublisher.cpp b/src/qtmetaobjectpublisher.cpp index 8e871e0..85ba62c 100644 --- a/src/qtmetaobjectpublisher.cpp +++ b/src/qtmetaobjectpublisher.cpp @@ -50,6 +50,10 @@ static const QString KEY_METHODS = QStringLiteral("methods"); static const QString KEY_PROPERTIES = QStringLiteral("properties"); static const QString KEY_ENUMS = QStringLiteral("enums"); +static const QString KEY_QOBJECT = QStringLiteral("__QObject*__"); +static const QString KEY_ID = QStringLiteral("id"); +static const QString KEY_DATA = QStringLiteral("data"); + QtMetaObjectPublisher::QtMetaObjectPublisher(QQuickItem *parent) : QQuickItem(parent) { @@ -142,3 +146,54 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const data[KEY_ENUMS] = qtEnums; return data; } + +static QString objectId(QObject *object) +{ + return QString::number(quintptr(object), 16); +} + +QVariant QtMetaObjectPublisher::wrapObject(QObject *object) +{ + if (!object) + return QVariant(); + + const QString& id = objectId(object); + + const WrapMapCIt& p = m_wrappedObjects.constFind(id); + if (p != m_wrappedObjects.constEnd()) + return p.value().second; + + QVariantMap objectInfo; + objectInfo[KEY_QOBJECT] = true; + objectInfo[KEY_ID] = id; + objectInfo[KEY_DATA] = classInfoForObject(object); + + m_wrappedObjects.insert(id, WrapInfo(object, objectInfo)); + connect(object, SIGNAL(destroyed(QObject*)), SLOT(wrappedObjectDestroyed(QObject*))); + + return objectInfo; +} + +QObject *QtMetaObjectPublisher::unwrapObject(const QString& id) const +{ + const WrapMapCIt& p = m_wrappedObjects.constFind(id); + if (p != m_wrappedObjects.constEnd()) + return p.value().first; + return 0; +} + +void QtMetaObjectPublisher::wrappedObjectDestroyed(QObject* object) +{ + const QString& id = objectId(object); + m_wrappedObjects.remove(id); + emit wrappedObjectDestroyed(id); +} + +void QtMetaObjectPublisher::deleteWrappedObject(QObject* object) const +{ + if (!m_wrappedObjects.contains(objectId(object))) { + qWarning() << "Not deleting non-wrapped object" << object; + return; + } + object->deleteLater(); +} diff --git a/src/qtmetaobjectpublisher.h b/src/qtmetaobjectpublisher.h index 0c19374..bd48678 100644 --- a/src/qtmetaobjectpublisher.h +++ b/src/qtmetaobjectpublisher.h @@ -46,8 +46,6 @@ #include <QVariantMap> #include <QQuickItem> -class QObjectWrapper; - // NOTE: QQuickItem inheritance required to enable QML item nesting (i.e. Timer in MetaObjectPublisher) class QtMetaObjectPublisher : public QQuickItem { @@ -57,6 +55,30 @@ public: Q_INVOKABLE QVariantMap classInfoForObjects(const QVariantMap &objects) const; Q_INVOKABLE QVariantMap classInfoForObject(QObject *object) const; + + /// wrap object and return class info + Q_INVOKABLE QVariant wrapObject(QObject *object); + /// Search object by id and return it, or null if it could not be found. + Q_INVOKABLE QObject *unwrapObject(const QString &id) const; + /// Invoke delete later on @p object, but only if it is a wrapped object. + Q_INVOKABLE void deleteWrappedObject(QObject *object) const; + +signals: + void wrappedObjectDestroyed(const QString& id); + +private slots: + void wrappedObjectDestroyed(QObject* object); + +private: + /// Pairs of QObject and generated object info + typedef QPair<QObject *, QVariantMap> WrapInfo; + /// Maps object id to wrap info + typedef QHash<QString, WrapInfo> WrapMap; + /// Const iterator for map + typedef WrapMap::const_iterator WrapMapCIt; + + /// Map of wrapped objects + WrapMap m_wrappedObjects; }; #endif // QTMETAOBJECTPUBLISHER_H diff --git a/src/src.pri b/src/src.pri index dc8670b..408b430 100644 --- a/src/src.pri +++ b/src/src.pri @@ -1,3 +1,9 @@ QT += network -SOURCES += $$PWD/qwebchannel.cpp $$PWD/qtmetaobjectpublisher.cpp $$PWD/qwebsocketserver.cpp -HEADERS += $$PWD/qwebchannel.h $$PWD/qtmetaobjectpublisher.h $$PWD/qwebsocketserver.h +SOURCES += \ + $$PWD/qwebchannel.cpp \ + $$PWD/qtmetaobjectpublisher.cpp \ + $$PWD/qwebsocketserver.cpp +HEADERS += \ + $$PWD/qwebchannel.h \ + $$PWD/qtmetaobjectpublisher.h \ + $$PWD/qwebsocketserver.h |