diff options
Diffstat (limited to 'src/webchannel/qmetaobjectpublisher.cpp')
-rw-r--r-- | src/webchannel/qmetaobjectpublisher.cpp | 344 |
1 files changed, 142 insertions, 202 deletions
diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index 27c7d70..d3659bd 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -40,7 +40,6 @@ ** ****************************************************************************/ -#include "qmetaobjectpublisher.h" #include "qmetaobjectpublisher_p.h" #include "qwebchannel.h" @@ -87,8 +86,9 @@ QString objectId(const QObject *object) const int PROPERTY_UPDATE_INTERVAL = 50; } -QMetaObjectPublisherPrivate::QMetaObjectPublisherPrivate(QMetaObjectPublisher *q) - : q(q) +QMetaObjectPublisher::QMetaObjectPublisher(QWebChannel *webChannel) + : QObject(webChannel) + , webChannel(webChannel) , signalHandler(this) , clientIsIdle(false) , blockUpdates(false) @@ -97,7 +97,112 @@ QMetaObjectPublisherPrivate::QMetaObjectPublisherPrivate(QMetaObjectPublisher *q { } -void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) +QMetaObjectPublisher::~QMetaObjectPublisher() +{ + +} + +void QMetaObjectPublisher::registerObject(const QString &id, QObject *object) +{ + if (propertyUpdatesInitialized) { + qWarning("Registered new object after initialization. This does not work!"); + return; + } + registeredObjects[id] = object; + registeredObjectIds[object] = id; +} + +QJsonObject QMetaObjectPublisher::classInfoForObject(const QObject *object) const +{ + QJsonObject data; + if (!object) { + qWarning("null object given to MetaObjectPublisher - bad API usage?"); + return data; + } + + QJsonArray qtSignals; + QJsonArray qtMethods; + QJsonArray qtProperties; + QJsonObject qtEnums; + + const QMetaObject *metaObject = object->metaObject(); + QSet<int> notifySignals; + QSet<QString> identifiers; + for (int i = 0; i < metaObject->propertyCount(); ++i) { + const QMetaProperty &prop = metaObject->property(i); + QJsonArray propertyInfo; + const QString &propertyName = QString::fromLatin1(prop.name()); + propertyInfo.append(i); + propertyInfo.append(propertyName); + identifiers << propertyName; + QJsonArray signalInfo; + if (prop.hasNotifySignal()) { + notifySignals << prop.notifySignalIndex(); + const int numParams = prop.notifySignal().parameterCount(); + if (numParams > 1) { + qWarning("Notify signal for property '%s' has %d parameters, expected zero or one.", + prop.name(), numParams); + } + // optimize: compress the common propertyChanged notification names, just send a 1 + const QByteArray ¬ifySignal = prop.notifySignal().name(); + static const QByteArray changedSuffix = QByteArrayLiteral("Changed"); + if (notifySignal.length() == changedSuffix.length() + propertyName.length() && + notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name())) + { + signalInfo.append(1); + } else { + signalInfo.append(QString::fromLatin1(notifySignal)); + } + signalInfo.append(prop.notifySignalIndex()); + } else if (!prop.isConstant()) { + qWarning("Property '%s'' of object '%s' has no notify signal and is not constant, " + "value updates in HTML will be broken!", + prop.name(), object->metaObject()->className()); + } + propertyInfo.append(signalInfo); + propertyInfo.append(QJsonValue::fromVariant(prop.read(object))); + qtProperties.append(propertyInfo); + } + for (int i = 0; i < metaObject->methodCount(); ++i) { + if (notifySignals.contains(i)) { + continue; + } + const QMetaMethod &method = metaObject->method(i); + //NOTE: this must be a string, otherwise it will be converted to '{}' in QML + const QString &name = QString::fromLatin1(method.name()); + // optimize: skip overloaded methods/signals or property getters, on the JS side we can only + // call one of them anyways + // TODO: basic support for overloaded signals, methods + if (identifiers.contains(name)) { + continue; + } + identifiers << name; + // send data as array to client with format: [name, index] + QJsonArray data; + data.append(name); + data.append(i); + if (method.methodType() == QMetaMethod::Signal) { + qtSignals.append(data); + } else if (method.access() == QMetaMethod::Public) { + qtMethods.append(data); + } + } + for (int i = 0; i < metaObject->enumeratorCount(); ++i) { + QMetaEnum enumerator = metaObject->enumerator(i); + QJsonObject values; + for (int k = 0; k < enumerator.keyCount(); ++k) { + values[QString::fromLatin1(enumerator.key(k))] = enumerator.value(k); + } + qtEnums[QString::fromLatin1(enumerator.name())] = values; + } + data[KEY_SIGNALS] = qtSignals; + data[KEY_METHODS] = qtMethods; + data[KEY_PROPERTIES] = qtProperties; + data[KEY_ENUMS] = qtEnums; + return data; +} + +void QMetaObjectPublisher::setClientIsIdle(bool isIdle) { if (clientIsIdle == isIdle) { return; @@ -106,11 +211,11 @@ void QMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) if (!isIdle && timer.isActive()) { timer.stop(); } else if (isIdle && !timer.isActive()) { - timer.start(PROPERTY_UPDATE_INTERVAL, q); + timer.start(PROPERTY_UPDATE_INTERVAL, this); } } -void QMetaObjectPublisherPrivate::initializeClients() +void QMetaObjectPublisher::initializeClients() { if (!webChannel) { return; @@ -120,7 +225,7 @@ void QMetaObjectPublisherPrivate::initializeClients() { const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd(); for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) { - const QJsonObject &info = q->classInfoForObject(it.value()); + const QJsonObject &info = classInfoForObject(it.value()); if (!propertyUpdatesInitialized) { initializePropertyUpdates(it.value(), info); } @@ -132,7 +237,7 @@ void QMetaObjectPublisherPrivate::initializeClients() pendingInit = false; } -void QMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) +void QMetaObjectPublisher::initializePropertyUpdates(const QObject *const object, const QJsonObject &objectInfo) { foreach (const QJsonValue &propertyInfoVar, objectInfo[KEY_PROPERTIES].toArray()) { const QJsonArray &propertyInfo = propertyInfoVar.toArray(); @@ -164,7 +269,7 @@ void QMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const signalHandler.connectTo(object, s_destroyedSignalIndex); } -void QMetaObjectPublisherPrivate::sendPendingPropertyUpdates() +void QMetaObjectPublisher::sendPendingPropertyUpdates() { if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) { return; @@ -204,7 +309,7 @@ void QMetaObjectPublisherPrivate::sendPendingPropertyUpdates() setClientIsIdle(false); } -bool QMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int methodIndex, +bool QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id) { const QMetaMethod &method = object->metaObject()->method(methodIndex); @@ -257,7 +362,7 @@ bool QMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int return true; } -void QMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) +void QMetaObjectPublisher::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) { if (!webChannel) { return; @@ -280,12 +385,12 @@ void QMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int } else { pendingPropertyUpdates[object][signalIndex] = arguments; if (clientIsIdle && !blockUpdates && !timer.isActive()) { - timer.start(PROPERTY_UPDATE_INTERVAL, q); + timer.start(PROPERTY_UPDATE_INTERVAL, this); } } } -void QMetaObjectPublisherPrivate::objectDestroyed(const QObject *object) +void QMetaObjectPublisher::objectDestroyed(const QObject *object) { const QString &id = registeredObjectIds.take(object); Q_ASSERT(!id.isEmpty()); @@ -298,7 +403,7 @@ void QMetaObjectPublisherPrivate::objectDestroyed(const QObject *object) wrappedObjects.remove(object); } -QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) +QJsonValue QMetaObjectPublisher::wrapResult(const QVariant &result) { if (QObject *object = result.value<QObject *>()) { QJsonObject &objectInfo = wrappedObjects[object]; @@ -314,7 +419,7 @@ QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) objectInfo[KEY_QOBJECT] = true; objectInfo[KEY_ID] = id; - objectInfo[KEY_DATA] = q->classInfoForObject(object); + objectInfo[KEY_DATA] = classInfoForObject(object); registeredObjectIds[object] = id; registeredObjects[id] = object; @@ -328,7 +433,7 @@ QJsonValue QMetaObjectPublisherPrivate::wrapResult(const QVariant &result) return QJsonValue::fromVariant(result); } -void QMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const +void QMetaObjectPublisher::deleteWrappedObject(QObject *object) const { if (!wrappedObjects.contains(object)) { qWarning() << "Not deleting non-wrapped object" << object; @@ -337,142 +442,6 @@ void QMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const object->deleteLater(); } -QMetaObjectPublisher::QMetaObjectPublisher(QObject *parent) - : QObject(parent) - , d(new QMetaObjectPublisherPrivate(this)) -{ -} - -QMetaObjectPublisher::~QMetaObjectPublisher() -{ - -} - -QJsonObject QMetaObjectPublisher::classInfoForObjects(const QVariantMap &objectMap) const -{ - QJsonObject ret; - QMap<QString, QVariant>::const_iterator it = objectMap.constBegin(); - while (it != objectMap.constEnd()) { - QObject *object = it.value().value<QObject *>(); - if (object) { - const QJsonObject &info = classInfoForObject(object); - if (!info.isEmpty()) { - ret[it.key()] = info; - } - } - ++it; - } - return ret; -} - -QJsonObject QMetaObjectPublisher::classInfoForObject(QObject *object) const -{ - QJsonObject data; - if (!object) { - qWarning("null object given to MetaObjectPublisher - bad API usage?"); - return data; - } - - QJsonArray qtSignals; - QJsonArray qtMethods; - QJsonArray qtProperties; - QJsonObject qtEnums; - - const QMetaObject *metaObject = object->metaObject(); - QSet<int> notifySignals; - QSet<QString> identifiers; - for (int i = 0; i < metaObject->propertyCount(); ++i) { - const QMetaProperty &prop = metaObject->property(i); - QJsonArray propertyInfo; - const QString &propertyName = QString::fromLatin1(prop.name()); - propertyInfo.append(i); - propertyInfo.append(propertyName); - identifiers << propertyName; - QJsonArray signalInfo; - if (prop.hasNotifySignal()) { - notifySignals << prop.notifySignalIndex(); - const int numParams = prop.notifySignal().parameterCount(); - if (numParams > 1) { - qWarning("Notify signal for property '%s' has %d parameters, expected zero or one.", - prop.name(), numParams); - } - // optimize: compress the common propertyChanged notification names, just send a 1 - const QByteArray ¬ifySignal = prop.notifySignal().name(); - static const QByteArray changedSuffix = QByteArrayLiteral("Changed"); - if (notifySignal.length() == changedSuffix.length() + propertyName.length() && - notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name())) - { - signalInfo.append(1); - } else { - signalInfo.append(QString::fromLatin1(notifySignal)); - } - signalInfo.append(prop.notifySignalIndex()); - } else if (!prop.isConstant()) { - qWarning("Property '%s'' of object '%s' has no notify signal and is not constant, " - "value updates in HTML will be broken!", - prop.name(), object->metaObject()->className()); - } - propertyInfo.append(signalInfo); - propertyInfo.append(QJsonValue::fromVariant(prop.read(object))); - qtProperties.append(propertyInfo); - } - for (int i = 0; i < metaObject->methodCount(); ++i) { - if (notifySignals.contains(i)) { - continue; - } - const QMetaMethod &method = metaObject->method(i); - //NOTE: this must be a string, otherwise it will be converted to '{}' in QML - const QString &name = QString::fromLatin1(method.name()); - // optimize: skip overloaded methods/signals or property getters, on the JS side we can only - // call one of them anyways - // TODO: basic support for overloaded signals, methods - if (identifiers.contains(name)) { - continue; - } - identifiers << name; - // send data as array to client with format: [name, index] - QJsonArray data; - data.append(name); - data.append(i); - if (method.methodType() == QMetaMethod::Signal) { - qtSignals.append(data); - } else if (method.access() == QMetaMethod::Public) { - qtMethods.append(data); - } - } - for (int i = 0; i < metaObject->enumeratorCount(); ++i) { - QMetaEnum enumerator = metaObject->enumerator(i); - QJsonObject values; - for (int k = 0; k < enumerator.keyCount(); ++k) { - values[QString::fromLatin1(enumerator.key(k))] = enumerator.value(k); - } - qtEnums[QString::fromLatin1(enumerator.name())] = values; - } - data[KEY_SIGNALS] = qtSignals; - data[KEY_METHODS] = qtMethods; - data[KEY_PROPERTIES] = qtProperties; - data[KEY_ENUMS] = qtEnums; - return data; -} - -void QMetaObjectPublisher::registerObjects(const QVariantMap &objects) -{ - if (d->propertyUpdatesInitialized) { - qWarning("Registered new object after initialization. This does not work!"); - return; - } - const QMap<QString, QVariant>::const_iterator end = objects.end(); - for (QMap<QString, QVariant>::const_iterator it = objects.begin(); it != end; ++it) { - QObject *object = it.value().value<QObject *>(); - if (!object) { - qWarning("Invalid QObject given to register under name %s", qPrintable(it.key())); - continue; - } - d->registeredObjects[it.key()] = object; - d->registeredObjectIds[object] = it.key(); - } -} - bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) { if (!message.contains(KEY_DATA)) { @@ -486,13 +455,13 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) const QString &type = payload.value(KEY_TYPE).toString(); if (type == TYPE_IDLE) { - d->setClientIsIdle(true); + setClientIsIdle(true); return true; } else if (type == TYPE_INIT) { - if (!d->blockUpdates) { - d->initializeClients(); + if (!blockUpdates) { + initializeClients(); } else { - d->pendingInit = true; + pendingInit = true; } return true; } else if (type == TYPE_DEBUG) { @@ -501,19 +470,19 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) return true; } else if (payload.contains(KEY_OBJECT)) { const QString &objectName = payload.value(KEY_OBJECT).toString(); - QObject *object = d->registeredObjects.value(objectName); + QObject *object = registeredObjects.value(objectName); if (!object) { qWarning() << "Unknown object encountered" << objectName; return false; } if (type == TYPE_INVOKE_METHOD) { - return d->invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID)); + return invokeMethod(object, payload.value(KEY_METHOD).toInt(-1), payload.value(KEY_ARGS).toArray(), message.value(KEY_ID)); } else if (type == TYPE_CONNECT_TO_SIGNAL) { - d->signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1)); + signalHandler.connectTo(object, payload.value(KEY_SIGNAL).toInt(-1)); return true; } else if (type == TYPE_DISCONNECT_FROM_SIGNAL) { - d->signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1)); + signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1)); return true; } else if (type == TYPE_SET_PROPERTY) { const int propertyIdx = payload.value(KEY_PROPERTY).toInt(-1); @@ -534,65 +503,36 @@ bool QMetaObjectPublisher::handleRequest(const QJsonObject &message) void QMetaObjectPublisher::handleRawMessage(const QString &message) { - QJsonParseError error; - const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error) { - qWarning() << "Could not parse raw input message as JSON: " << error.errorString() << "Message was: " << message; - } else if (doc.isObject() && !handleRequest(doc.object())) { - qWarning() << "Could not handle raw message as meta object request: " << message; - } -} - -QWebChannel *QMetaObjectPublisher::webChannel() const -{ - return d->webChannel; -} - -void QMetaObjectPublisher::setWebChannel(QWebChannel *webChannel) -{ - if (d->webChannel == webChannel) { - return; + const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8()); + if (doc.isObject()) { + handleRequest(doc.object()); } - - d->webChannel = webChannel; - - emit webChannelChanged(webChannel); -} - -bool QMetaObjectPublisher::blockUpdates() const -{ - return d->blockUpdates; } void QMetaObjectPublisher::setBlockUpdates(bool block) { - if (d->blockUpdates == block) { + if (blockUpdates == block) { return; } - d->blockUpdates = block; + blockUpdates = block; - if (!d->blockUpdates) { - if (d->pendingInit) { - d->initializeClients(); + if (!blockUpdates) { + if (pendingInit) { + initializeClients(); } else { - d->sendPendingPropertyUpdates(); + sendPendingPropertyUpdates(); } - } else if (d->timer.isActive()) { - d->timer.stop(); + } else if (timer.isActive()) { + timer.stop(); } emit blockUpdatesChanged(block); } -bool QMetaObjectPublisher::test_clientIsIdle() const -{ - return d->clientIsIdle; -} - void QMetaObjectPublisher::timerEvent(QTimerEvent *event) { - if (event->timerId() == d->timer.timerId()) { - d->sendPendingPropertyUpdates(); + if (event->timerId() == timer.timerId()) { + sendPendingPropertyUpdates(); } else { QObject::timerEvent(event); } |