diff options
Diffstat (limited to 'src/qtmetaobjectpublisher.cpp')
-rw-r--r-- | src/qtmetaobjectpublisher.cpp | 629 |
1 files changed, 574 insertions, 55 deletions
diff --git a/src/qtmetaobjectpublisher.cpp b/src/qtmetaobjectpublisher.cpp index 511f514..9ca0d93 100644 --- a/src/qtmetaobjectpublisher.cpp +++ b/src/qtmetaobjectpublisher.cpp @@ -43,24 +43,433 @@ ****************************************************************************/ #include "qtmetaobjectpublisher.h" +#include "qwebchannel.h" +#include "variantargument.h" +#include "signalhandler.h" #include <QStringList> #include <QMetaObject> -#include <QMetaProperty> +#include <QJsonObject> +#include <QJsonArray> +#include <QBasicTimer> #include <QDebug> +#include <QPointer> +#include <QEvent> -static const QString KEY_SIGNALS = QStringLiteral("signals"); -static const QString KEY_METHODS = QStringLiteral("methods"); -static const QString KEY_PROPERTIES = QStringLiteral("properties"); -static const QString KEY_ENUMS = QStringLiteral("enums"); +namespace { +const QString KEY_SIGNALS = QStringLiteral("signals"); +const QString KEY_METHODS = QStringLiteral("methods"); +const QString KEY_PROPERTIES = QStringLiteral("properties"); +const QString KEY_ENUMS = QStringLiteral("enums"); +const QString KEY_QOBJECT = QStringLiteral("__QObject*__"); +const QString KEY_ID = QStringLiteral("id"); +const QString KEY_DATA = QStringLiteral("data"); +const QString KEY_OBJECT = QStringLiteral("object"); +const QString KEY_DESTROYED = QStringLiteral("destroyed"); +const QString KEY_SIGNAL = QStringLiteral("signal"); +const QString KEY_TYPE = QStringLiteral("type"); +const QString KEY_MESSAGE = QStringLiteral("message"); +const QString KEY_METHOD = QStringLiteral("method"); +const QString KEY_ARGS = QStringLiteral("args"); +const QString KEY_PROPERTY = QStringLiteral("property"); +const QString KEY_VALUE = QStringLiteral("value"); -static const QString KEY_QOBJECT = QStringLiteral("__QObject*__"); -static const QString KEY_ID = QStringLiteral("id"); -static const QString KEY_DATA = QStringLiteral("data"); +const QString TYPE_SIGNAL = QStringLiteral("Qt.signal"); +const QString TYPE_PROPERTY_UPDATE = QStringLiteral("Qt.propertyUpdate"); +const QString TYPE_INIT = QStringLiteral("Qt.init"); +const QString TYPE_IDLE = QStringLiteral("Qt.idle"); +const QString TYPE_DEBUG = QStringLiteral("Qt.debug"); +const QString TYPE_INVOKE_METHOD = QStringLiteral("Qt.invokeMethod"); +const QString TYPE_CONNECT_TO_SIGNAL = QStringLiteral("Qt.connectToSignal"); +const QString TYPE_DISCONNECT_FROM_SIGNAL = QStringLiteral("Qt.disconnectFromSignal"); +const QString TYPE_SET_PROPERTY = QStringLiteral("Qt.setProperty"); -QtMetaObjectPublisher::QtMetaObjectPublisher(QQuickItem *parent) - : QQuickItem(parent) +QString objectId(const QObject *object) { + return QString::number(quintptr(object), 16); +} + +const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMethod("destroyed(QObject*)"); + +/// TODO: what is the proper value here? +const int PROPERTY_UPDATE_INTERVAL = 50; +} + +struct QtMetaObjectPublisherPrivate +{ + QtMetaObjectPublisherPrivate(QtMetaObjectPublisher *q) + : q(q) + , signalHandler(this) + , clientIsIdle(false) + , blockUpdates(false) + , pendingInit(false) + , propertyUpdatesInitialized(false) + { + } + + /** + * Set the client to idle or busy, based on the value of @p isIdle. + * + * When the value changed, start/stop the property update timer accordingly. + */ + void setClientIsIdle(bool isIdle); + + /** + * Initialize clients by sending them the class information of the registered objects. + * + * Furthermore, if that was not done already, connect to their property notify signals. + */ + void initializeClients(); + + /** + * Go through all properties of the given object and connect to their notify signal. + * + * When receiving a notify signal, it will store the information in pendingPropertyUpdates which + * gets send via a Qt.propertyUpdate message to the server when the grouping timer timeouts. + */ + void initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo); + + /** + * Send the clients the new property values since the last time this function was invoked. + * + * This is a grouped batch of all properties for which their notify signal was emitted. + * The list of signals as well as the arguments they contained, are also transmitted to + * the remote clients. + * + * @sa timer, initializePropertyUpdates + */ + void sendPendingPropertyUpdates(); + + /** + * Invoke the method of index @p methodIndex on @p object with the arguments @p args. + * + * The return value of the method invocation is then transmitted to the calling client + * via a webchannel response to the message identified by @p id. + */ + bool invokeMethod(QObject *const object, const int methodIndex, const QJsonArray &args, const QJsonValue &id); + + /** + * Callback of the signalHandler which forwards the signal invocation to the webchannel clients. + */ + void signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments); + + /** + * Callback for registered or wrapped objects which erases all data related to @p object. + * + * @sa signalEmitted + */ + void objectDestroyed(const QObject *object); + + /** + * Given a QVariant containing a QObject*, wrap the object and register for property updates + * return the objects class information. + * + * All other input types are returned as-is. + * + * TODO: support wrapping of initially-registered objects + */ + QVariant wrapResult(const QVariant &result); + + /** + * Invoke delete later on @p object. + */ + void deleteWrappedObject(QObject *object) const; + + QtMetaObjectPublisher *q; + QPointer<QWebChannel> webChannel; + SignalHandler<QtMetaObjectPublisherPrivate> signalHandler; + + // true when the client is idle, false otherwise + bool clientIsIdle; + + // true when no property updates should be sent, false otherwise + bool blockUpdates; + + // true when at least one client needs to be initialized, + // i.e. when a Qt.init came in which was not handled yet. + bool pendingInit; + + // true when at least one client was initialized and thus + // the property updates have been initialized and the + // object info map set. + bool propertyUpdatesInitialized; + + // Map of registered objects indexed by their id. + QHash<QString, QObject *> registeredObjects; + + // Map the registered objects to their id. + QHash<const QObject *, QString> registeredObjectIds; + + // Map of object names to maps of signal indices to a set of all their properties. + // The last value is a set as a signal can be the notify signal of multiple properties. + typedef QHash<int, QSet<QString> > SignalToPropertyNameMap; + QHash<const QObject *, SignalToPropertyNameMap> signalToPropertyMap; + + // Objects that changed their properties and are waiting for idle client. + // map of object name to map of signal index to arguments + typedef QHash<int, QVariantList> SignalToArgumentsMap; + typedef QHash<const QObject *, SignalToArgumentsMap> PendingPropertyUpdates; + PendingPropertyUpdates pendingPropertyUpdates; + + // Maps wrapped object to class info + QHash<const QObject *, QVariantMap> wrappedObjects; + + // 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 + // prevent message flooding. + QBasicTimer timer; +}; + +void QtMetaObjectPublisherPrivate::setClientIsIdle(bool isIdle) +{ + if (clientIsIdle == isIdle) { + return; + } + clientIsIdle = isIdle; + if (!isIdle && timer.isActive()) { + timer.stop(); + } else if (isIdle && !timer.isActive()) { + timer.start(PROPERTY_UPDATE_INTERVAL, q); + } +} + +void QtMetaObjectPublisherPrivate::initializeClients() +{ + QJsonObject objectInfos; + { + const QHash<QString, QObject *>::const_iterator end = registeredObjects.constEnd(); + for (QHash<QString, QObject *>::const_iterator it = registeredObjects.constBegin(); it != end; ++it) { + const QVariantMap &info = q->classInfoForObject(it.value()); + if (!propertyUpdatesInitialized) { + initializePropertyUpdates(it.value(), info); + } + objectInfos[it.key()] = QJsonObject::fromVariantMap(info); + } + } + webChannel->sendMessage(TYPE_INIT, objectInfos); + propertyUpdatesInitialized = true; + pendingInit = false; +} + +void QtMetaObjectPublisherPrivate::initializePropertyUpdates(const QObject *const object, const QVariantMap &objectInfo) +{ + foreach (const QVariant &propertyInfoVar, objectInfo[KEY_PROPERTIES].toList()) { + const QVariantList &propertyInfo = propertyInfoVar.toList(); + if (propertyInfo.size() < 2) { + qWarning() << "Invalid property info encountered:" << propertyInfoVar; + continue; + } + const QString &propertyName = propertyInfo.at(0).toString(); + const QVariantList &signalData = propertyInfo.at(1).toList(); + + if (signalData.isEmpty()) { + // Property without NOTIFY signal + continue; + } + + const int signalIndex = signalData.at(1).toInt(); + + QSet<QString> &connectedProperties = signalToPropertyMap[object][signalIndex]; + + // Only connect for a property update once + if (connectedProperties.isEmpty()) { + signalHandler.connectTo(object, signalIndex); + } + + connectedProperties.insert(propertyName); + } + + // also always connect to destroyed signal + signalHandler.connectTo(object, s_destroyedSignalIndex); +} + +void QtMetaObjectPublisherPrivate::sendPendingPropertyUpdates() +{ + if (blockUpdates || !clientIsIdle || pendingPropertyUpdates.isEmpty()) { + return; + } + + QJsonArray data; + + // convert pending property updates to JSON data + const PendingPropertyUpdates::const_iterator end = pendingPropertyUpdates.constEnd(); + for (PendingPropertyUpdates::const_iterator it = pendingPropertyUpdates.constBegin(); it != end; ++it) { + const QObject *object = it.key(); + const QMetaObject *const metaObject = object->metaObject(); + const SignalToPropertyNameMap &objectsSignalToPropertyMap = signalToPropertyMap.value(object); + // maps property name to current property value + QJsonObject properties; + // maps signal index to list of arguments of the last emit + QJsonObject sigs; + const SignalToArgumentsMap::const_iterator sigEnd = it.value().constEnd(); + for (SignalToArgumentsMap::const_iterator sigIt = it.value().constBegin(); sigIt != sigEnd; ++sigIt) { + // TODO: use property indices + foreach (const QString &propertyName, objectsSignalToPropertyMap.value(sigIt.key())) { + int propertyIndex = metaObject->indexOfProperty(qPrintable(propertyName)); + if (propertyIndex == -1) { + qWarning("Unknown property %d encountered", propertyIndex); + continue; + } + const QMetaProperty &property = metaObject->property(propertyIndex); + properties[QString::fromLatin1(property.name())] = QJsonValue::fromVariant(property.read(object)); + } + // TODO: can we get rid of the int <-> string conversions here? + sigs[QString::number(sigIt.key())] = QJsonArray::fromVariantList(sigIt.value()); + } + QJsonObject obj; + obj[KEY_OBJECT] = registeredObjectIds.value(object); + obj[KEY_SIGNALS] = sigs; + obj[KEY_PROPERTIES] = properties; + data.push_back(obj); + } + + pendingPropertyUpdates.clear(); + webChannel->sendMessage(TYPE_PROPERTY_UPDATE, data); + setClientIsIdle(false); +} + +bool QtMetaObjectPublisherPrivate::invokeMethod(QObject *const object, const int methodIndex, + const QJsonArray &args, const QJsonValue &id) +{ + const QMetaMethod &method = object->metaObject()->method(methodIndex); + + if (method.name() == QByteArrayLiteral("deleteLater")) { + // invoke `deleteLater` on wrapped QObject indirectly + deleteWrappedObject(object); + return true; + } else if (!method.isValid()) { + qWarning() << "Cannot invoke unknown method of index" << methodIndex << "on object" << object << '.'; + return false; + } else if (method.access() != QMetaMethod::Public) { + qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.'; + return false; + } else if (method.methodType() != QMetaMethod::Method && method.methodType() != QMetaMethod::Slot) { + qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.'; + return false; + } else if (args.size() > 10) { + qWarning() << "Cannot invoke method" << method.name() << "on object" << object << "with more than 10 arguments, as that is not supported by QMetaMethod::invoke."; + return false; + } else if (args.size() > method.parameterCount()) { + qWarning() << "Ignoring additional arguments while invoking method" << method.name() << "on object" << object << ':' + << args.size() << "arguments given, but method only takes" << method.parameterCount() << '.'; + } + + // construct converter objects of QVariant to QGenericArgument + VariantArgument arguments[10]; + for (int i = 0; i < qMin(args.size(), method.parameterCount()); ++i) { + arguments[i].setValue(args.at(i).toVariant(), method.parameterType(i)); + } + + // construct QGenericReturnArgument + QVariant returnValue; + if (method.returnType() != qMetaTypeId<QVariant>() && method.returnType() != qMetaTypeId<void>()) { + // Only init variant with return type if its not a variant itself, which would + // lead to nested variants which is not what we want. + // Also, skip void-return types for obvious reasons (and to prevent a runtime warning inside Qt). + returnValue = QVariant(method.returnType(), 0); + } + QGenericReturnArgument returnArgument(method.typeName(), returnValue.data()); + + // now we can call the method + method.invoke(object, returnArgument, + arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], + arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]); + + // and send the return value to the client + webChannel->respond(id, QJsonValue::fromVariant(wrapResult(returnValue))); + + return true; +} + +void QtMetaObjectPublisherPrivate::signalEmitted(const QObject *object, const int signalIndex, const QVariantList &arguments) +{ + if (!webChannel) { + return; + } + if (!signalToPropertyMap.value(object).contains(signalIndex)) { + QJsonObject data; + const QString &objectName = registeredObjectIds.value(object); + Q_ASSERT(!objectName.isEmpty()); + data[KEY_OBJECT] = objectName; + data[KEY_SIGNAL] = signalIndex; + if (!arguments.isEmpty()) { + // TODO: wrap (new) objects on the fly + data[KEY_ARGS] = QJsonArray::fromVariantList(arguments); + } + webChannel->sendMessage(TYPE_SIGNAL, data); + + if (signalIndex == s_destroyedSignalIndex) { + objectDestroyed(object); + } + } else { + pendingPropertyUpdates[object][signalIndex] = arguments; + if (clientIsIdle && !blockUpdates && !timer.isActive()) { + timer.start(PROPERTY_UPDATE_INTERVAL, q); + } + } +} + +void QtMetaObjectPublisherPrivate::objectDestroyed(const QObject *object) +{ + const QString &id = registeredObjectIds.take(object); + Q_ASSERT(!id.isEmpty()); + bool removed = registeredObjects.remove(id); + Q_ASSERT(removed); + Q_UNUSED(removed); + + signalToPropertyMap.remove(object); + pendingPropertyUpdates.remove(object); + wrappedObjects.remove(object); +} + +QVariant QtMetaObjectPublisherPrivate::wrapResult(const QVariant &result) +{ + if (QObject *object = result.value<QObject *>()) { + QVariantMap &objectInfo = wrappedObjects[object]; + if (!objectInfo.isEmpty()) { + // already registered, use cached information + Q_ASSERT(registeredObjectIds.contains(object)); + return objectInfo; + } // else the object is not yet wrapped, do it now + + const QString &id = objectId(object); + Q_ASSERT(!registeredObjects.contains(id)); + Q_ASSERT(!registeredObjectIds.contains(object)); + + objectInfo[KEY_QOBJECT] = true; + objectInfo[KEY_ID] = id; + objectInfo[KEY_DATA] = q->classInfoForObject(object); + + registeredObjectIds[object] = id; + registeredObjects[id] = object; + wrappedObjects.insert(object, objectInfo); + + initializePropertyUpdates(object, objectInfo); + return objectInfo; + } + + // no need to wrap this + return result; +} + +void QtMetaObjectPublisherPrivate::deleteWrappedObject(QObject *object) const +{ + if (!wrappedObjects.contains(object)) { + qWarning() << "Not deleting non-wrapped object" << object; + return; + } + object->deleteLater(); +} + +QtMetaObjectPublisher::QtMetaObjectPublisher(QObject *parent) + : QObject(parent) + , d(new QtMetaObjectPublisherPrivate(this)) +{ +} + +QtMetaObjectPublisher::~QtMetaObjectPublisher() +{ + } QVariantMap QtMetaObjectPublisher::classInfoForObjects(const QVariantMap &objectMap) const @@ -68,7 +477,7 @@ QVariantMap QtMetaObjectPublisher::classInfoForObjects(const QVariantMap &object QVariantMap ret; QMap<QString, QVariant>::const_iterator it = objectMap.constBegin(); while (it != objectMap.constEnd()) { - QObject* object = it.value().value<QObject*>(); + QObject *object = it.value().value<QObject *>(); if (object) { const QVariantMap &info = classInfoForObject(object); if (!info.isEmpty()) { @@ -90,15 +499,15 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const QVariantList qtSignals, qtMethods; QVariantList qtProperties; QVariantMap qtEnums; - const QMetaObject* metaObject = object->metaObject(); + const QMetaObject *metaObject = object->metaObject(); QSet<int> notifySignals; - QSet<QString> properties; + QSet<QString> identifiers; for (int i = 0; i < metaObject->propertyCount(); ++i) { const QMetaProperty &prop = metaObject->property(i); QVariantList propertyInfo; const QString &propertyName = QString::fromLatin1(prop.name()); propertyInfo.append(propertyName); - properties << propertyName; + identifiers << propertyName; if (prop.hasNotifySignal()) { notifySignals << prop.notifySignalIndex(); const int numParams = prop.notifySignal().parameterCount(); @@ -112,9 +521,9 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const if (notifySignal.length() == changedSuffix.length() + propertyName.length() && notifySignal.endsWith(changedSuffix) && notifySignal.startsWith(prop.name())) { - propertyInfo.append(1); + propertyInfo.append(QVariant::fromValue(QVariantList() << 1 << prop.notifySignalIndex())); } else { - propertyInfo.append(QString::fromLatin1(notifySignal)); + propertyInfo.append(QVariant::fromValue(QVariantList() << QString::fromLatin1(notifySignal) << prop.notifySignalIndex())); } } else { if (!prop.isConstant()) { @@ -122,7 +531,7 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const "value updates in HTML will be broken!", prop.name(), object->metaObject()->className()); } - propertyInfo.append(0); + propertyInfo.append(QVariant::fromValue(QVariantList())); } propertyInfo.append(prop.read(object)); qtProperties.append(QVariant::fromValue(propertyInfo)); @@ -132,18 +541,22 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const continue; } const QMetaMethod &method = metaObject->method(i); - //NOTE: This will not work for overloaded methods/signals. //NOTE: this must be a string, otherwise it will be converted to '{}' in QML const QString &name = QString::fromLatin1(method.name()); - if (properties.contains(name)) { - // optimize: Don't send the getter method, it gets overwritten by the - // property on the client side anyways. + // 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; } - if (method.methodType() == QMetaMethod::Signal) - qtSignals << name; - else if (method.access() == QMetaMethod::Public) - qtMethods << name; + identifiers << name; + // send data as array to client with format: [name, index] + const QVariant data = QVariant::fromValue(QVariantList() << name << 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); @@ -160,53 +573,159 @@ QVariantMap QtMetaObjectPublisher::classInfoForObject(QObject *object) const return data; } -static QString objectId(QObject *object) +void QtMetaObjectPublisher::registerObjects(const QVariantMap &objects) { - return QString::number(quintptr(object), 16); + 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(); + } } -QVariant QtMetaObjectPublisher::wrapObject(QObject *object) +bool QtMetaObjectPublisher::handleRequest(const QJsonObject &message) { - if (!object) - return QVariant(); - - const QString& id = objectId(object); + if (!message.contains(KEY_DATA)) { + return false; + } - const WrapMapCIt& p = m_wrappedObjects.constFind(id); - if (p != m_wrappedObjects.constEnd()) - return p.value().second; + const QJsonObject &payload = message.value(KEY_DATA).toObject(); + if (!payload.contains(KEY_TYPE)) { + return false; + } - QVariantMap objectInfo; - objectInfo[KEY_QOBJECT] = true; - objectInfo[KEY_ID] = id; - objectInfo[KEY_DATA] = classInfoForObject(object); + const QString &type = payload.value(KEY_TYPE).toString(); + if (type == TYPE_IDLE) { + d->setClientIsIdle(true); + return true; + } else if (type == TYPE_INIT) { + if (!d->blockUpdates) { + d->initializeClients(); + } else { + d->pendingInit = true; + } + return true; + } else if (type == TYPE_DEBUG) { + static QTextStream out(stdout); + out << "DEBUG: " << payload.value(KEY_MESSAGE).toString() << endl; + return true; + } else if (payload.contains(KEY_OBJECT)) { + const QString &objectName = payload.value(KEY_OBJECT).toString(); + QObject *object = d->registeredObjects.value(objectName); + if (!object) { + qWarning() << "Unknown object encountered" << objectName; + return false; + } - m_wrappedObjects.insert(id, WrapInfo(object, objectInfo)); - connect(object, SIGNAL(destroyed(QObject*)), SLOT(wrappedObjectDestroyed(QObject*))); + if (type == TYPE_INVOKE_METHOD) { + return d->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)); + return true; + } else if (type == TYPE_DISCONNECT_FROM_SIGNAL) { + d->signalHandler.disconnectFrom(object, payload.value(KEY_SIGNAL).toInt(-1)); + return true; + } else if (type == TYPE_SET_PROPERTY) { + // TODO: use property indices + const QString &propertyName = payload.value(KEY_PROPERTY).toString(); + const int propertyIdx = object->metaObject()->indexOfProperty(qPrintable(propertyName)); + if (propertyIdx == -1) { + qWarning() << "Cannot set unknown property" << propertyName << "of object" << objectName; + return false; + } + object->metaObject()->property(propertyIdx).write(object, payload.value(KEY_VALUE).toVariant()); + return true; + } + } + return false; +} - return objectInfo; +QWebChannel *QtMetaObjectPublisher::webChannel() const +{ + return d->webChannel; } -QObject *QtMetaObjectPublisher::unwrapObject(const QString& id) const +void QtMetaObjectPublisher::setWebChannel(QWebChannel *webChannel) { - const WrapMapCIt& p = m_wrappedObjects.constFind(id); - if (p != m_wrappedObjects.constEnd()) - return p.value().first; - return 0; + if (d->webChannel == webChannel) { + return; + } + + d->webChannel = webChannel; + + emit webChannelChanged(webChannel); } -void QtMetaObjectPublisher::wrappedObjectDestroyed(QObject* object) +bool QtMetaObjectPublisher::blockUpdates() const { - const QString& id = objectId(object); - m_wrappedObjects.remove(id); - emit wrappedObjectDestroyed(id); + return d->blockUpdates; } -void QtMetaObjectPublisher::deleteWrappedObject(QObject* object) const +void QtMetaObjectPublisher::setBlockUpdates(bool block) { - if (!m_wrappedObjects.contains(objectId(object))) { - qWarning() << "Not deleting non-wrapped object" << object; + if (d->blockUpdates == block) { return; } - object->deleteLater(); + d->blockUpdates = block; + + if (!d->blockUpdates) { + if (d->pendingInit) { + d->initializeClients(); + } else { + d->sendPendingPropertyUpdates(); + } + } else if (d->timer.isActive()) { + d->timer.stop(); + } + + emit blockUpdatesChanged(block); +} + +void QtMetaObjectPublisher::bench_ensureUpdatesInitialized() +{ + if (!d->propertyUpdatesInitialized) { + d->initializeClients(); + } +} + +void QtMetaObjectPublisher::bench_sendPendingPropertyUpdates() +{ + d->clientIsIdle = true; + d->sendPendingPropertyUpdates(); +} + +void QtMetaObjectPublisher::bench_initializeClients() +{ + d->propertyUpdatesInitialized = false; + d->signalToPropertyMap.clear(); + d->signalHandler.clear(); + d->initializeClients(); +} + +void QtMetaObjectPublisher::bench_registerObjects(const QVariantMap &objects) +{ + d->propertyUpdatesInitialized = false; + registerObjects(objects); +} + +bool QtMetaObjectPublisher::test_clientIsIdle() const +{ + return d->clientIsIdle; +} + +void QtMetaObjectPublisher::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == d->timer.timerId()) { + d->sendPendingPropertyUpdates(); + } else { + QObject::timerEvent(event); + } } |