summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/qtobject/main.cpp1
-rw-r--r--examples/qtobject/qml/qtobject/index.html107
-rw-r--r--examples/qtobject/qml/qtobject/main.qml27
-rw-r--r--examples/qtobject/testobject.cpp13
-rw-r--r--examples/qtobject/testobject.h10
-rw-r--r--src/MetaObjectPublisher.qml59
-rw-r--r--src/qobject.js26
-rw-r--r--src/qtmetaobjectpublisher.cpp55
-rw-r--r--src/qtmetaobjectpublisher.h26
-rw-r--r--src/src.pri10
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