From d9ee9fff1b9291395ba7ab75d7e46ddf4a9ff3a4 Mon Sep 17 00:00:00 2001 From: Brett Stottlemyer Date: Tue, 24 Apr 2018 20:26:22 -0400 Subject: Allow nodes to proxy (or convert) replicas This would support, for example, using the "local" (or "qnx") backend on a target, while still supporting debug via proxying the same objects over a single tcp connection. It should also, in theory, support converting to other marshalling formats (maybe CBOR?). Change-Id: Ib3c536d0c99b7a722c1c6ed145c8b5b567453f76 Reviewed-by: Michael Brasser --- src/remoteobjects/qconnectionfactories_p.h | 10 + .../qremoteobjectabstractitemmodeladapter_p.h | 2 - src/remoteobjects/qremoteobjectnode.cpp | 309 +++++++++++++++++++-- src/remoteobjects/qremoteobjectnode.h | 7 + src/remoteobjects/qremoteobjectnode_p.h | 28 ++ src/remoteobjects/qremoteobjectpacket.cpp | 2 - src/remoteobjects/qremoteobjectreplica.cpp | 1 - src/remoteobjects/qremoteobjectsource.cpp | 18 +- src/remoteobjects/qremoteobjectsource.h | 11 +- tests/auto/auto.pro | 1 + tests/auto/modelview/tst_modelview.cpp | 88 +----- tests/auto/proxy/engine.rep | 8 + tests/auto/proxy/proxy.pro | 11 + tests/auto/proxy/subclass.rep | 13 + tests/auto/proxy/tst_proxy.cpp | 302 ++++++++++++++++++++ tests/auto/shared/model_utilities.h | 121 ++++++++ tools/repc/repcodegenerator.cpp | 10 +- 17 files changed, 800 insertions(+), 142 deletions(-) create mode 100644 tests/auto/proxy/engine.rep create mode 100644 tests/auto/proxy/proxy.pro create mode 100644 tests/auto/proxy/subclass.rep create mode 100644 tests/auto/proxy/tst_proxy.cpp create mode 100644 tests/auto/shared/model_utilities.h diff --git a/src/remoteobjects/qconnectionfactories_p.h b/src/remoteobjects/qconnectionfactories_p.h index b71d006..27f69de 100644 --- a/src/remoteobjects/qconnectionfactories_p.h +++ b/src/remoteobjects/qconnectionfactories_p.h @@ -190,6 +190,11 @@ public: }; } + bool isValid(const QUrl &url) + { + return m_creatorFuncs.contains(url.scheme()); + } + private: friend class QtROFactoryLoader; QtROServerFactory(); @@ -224,6 +229,11 @@ public: }; } + bool isValid(const QUrl &url) + { + return m_creatorFuncs.contains(url.scheme()); + } + private: friend class QtROFactoryLoader; QtROClientFactory(); diff --git a/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h b/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h index fddf9b6..4f1dbb0 100644 --- a/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h +++ b/src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h @@ -191,7 +191,6 @@ struct QAbstractItemAdapterSourceAPI : public SourceApiMap { switch (index) { case 0: return m_properties[1]; - case 1: return m_properties[2]; } return -1; } @@ -199,7 +198,6 @@ struct QAbstractItemAdapterSourceAPI : public SourceApiMap { switch (index) { case 0: return 0; - case 1: return 1; } return -1; } diff --git a/src/remoteobjects/qremoteobjectnode.cpp b/src/remoteobjects/qremoteobjectnode.cpp index 39c5ea7..3d668ad 100644 --- a/src/remoteobjects/qremoteobjectnode.cpp +++ b/src/remoteobjects/qremoteobjectnode.cpp @@ -162,6 +162,172 @@ void QRemoteObjectNode::setHeartbeatInterval(int interval) emit heartbeatIntervalChanged(interval); } +/*! + \since 5.11 + \brief Forward Remote Objects from another network + + The proxy functionality is useful when you want to share \l Source objects + over multiple networks. For instance, if you have an embedded target using + target-only connections (like local) and you want to make some of those + same objects available externally. + + As a concrete example, say you have a set of processes talking to each + other on your target hardware using a registry, with the \l Registry at + "local:registry" and separate processes using a node at "local:MyHost" that + holds \l Source objects. If you wanted to access these objects, but over + tcp, you could create a new proxyNode like so: + +\code + // myInternalHost is a node only visible on the device... + QRemoteObjectHost myInternalHost("local:MyHost"); + myInternalHost.enableRemoting(&someObject); + + // Regular host node, listening on port 12123, so visible to other + // devices + QRemoteObjectHost proxyNode("tcp://localhost:12123"); + + // Enable proxying objects from nodes on the local machine's internal + // QtRO bus + proxyNode.proxy("local:registry"); +\endcode + + And from another device you create another node: + +\code + // NB: localhost resolves to a different ip address than proxyNode + QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234"); + + // Connect to the target's proxyNode directly, or use a tcp registry... + nodeOnRemoteDevice.connectToNode("tcp://:12123"); + + // Because of the proxy, we can get the object over tcp/ip port 12123, + // even though we can't connect directly to "local:MyHost" + SomeObject *so = nodeOnRemoteDevice.acquire(); +\endcode + + This would (internally) create a node in proxyNode, which (again + internally/automatically) connects to the provided registry (given by the + \a registryUrl parameter, "local:registry" in this example). Whenever + local:registry emits the \l remoteObjectAdded signal, the \l + QRemoteObjectSourceLocation is passed to the \a filter given to the proxy + call. If this method returns true (the default filter simply returns true + without any filtering), the object is acquired() from the internal node and + enableRemoting() (once the replica is initialized) is called on proxyNode. + + If a \a hostUrl is provided (which is required to enable reverseProxy, but + not needed otherwise), the internal node will be a \l QRemoteObjectHostNode + configured with the provided address. If no \a hostUrl is provided, the + internal node will be a QRemoteObjectNode (not HostNode). + + \sa reverseProxy() +*/ +bool QRemoteObjectHostBase::proxy(const QUrl ®istryUrl, const QUrl &hostUrl, RemoteObjectNameFilter filter) +{ + Q_D(QRemoteObjectHostBase); + if (!registryUrl.isValid() || !QtROClientFactory::instance()->isValid(registryUrl)) { + qROWarning(this) << "Can't proxy to registryUrl (invalid url or schema)" << registryUrl; + return false; + } + + if (!hostUrl.isEmpty() && !QtROClientFactory::instance()->isValid(hostUrl)) { + qROWarning(this) << "Can't proxy using hostUrl (invalid schema)" << hostUrl; + return false; + } + + if (d->proxyInfo) { + qROWarning(this) << "Proxying from multiple objects is currently not supported."; + return false; + } + + QRemoteObjectNode *node; + if (hostUrl.isEmpty()) { + node = new QRemoteObjectNode(registryUrl); + } else { + node = new QRemoteObjectHost(hostUrl, registryUrl); + } + d->proxyInfo = new ProxyInfo(node, this, filter); + return true; +} + +/*! + \since 5.11 + \brief Forward Remote Objects to another network + + The reverseProxy() function allows the \l proxy() functionality to be + extended, in effect mirroring the proxy functionality in the "reverse" + direction. These are distinct, because node communication is not symmetric, + one side calls enableRemoting() with a \l Source object, the other side + calls acquire() to get a \l Replica. Using \l proxy() allows you to + "observe" objects on a target device remotely via acquire, but it does not + allow off-target \l Source objects to be acquired from the device's local:* + network. That is where \l reverseProxy() comes in. If a proxyNode is + created like so: + +\code + // myInternalHost is a node only visible on the device... + QRemoteObjectHost myInternalHost("local:MyHost"); + + // Regular host node, listening on port 12123, so visible to other + // devices + QRemoteObjectHost proxyNode("tcp://localhost:12123"); + + // Enable proxying objects from nodes on the local machine's internal + // QtRO bus. Note the hostUrl parameter is now needed. + proxyNode.proxy("local:registry", "local:fromProxy"); + proxyNode.reverseProxy(); +\endcode + + And from another device you create another node: + +\code + // NB: localhost resolves to a different ip address than proxyNode + QRemoteObjectHost nodeOnRemoteDevice("tcp://localhost:23234"); + + // Connect to the target's proxyNode directly, or use a tcp registry... + nodeOnRemoteDevice.connectToNode("tcp://:12123"); + + // Because of the reverseProxy, we can expose objects on this device + // and they will make their way to proxyNode... + nodeOnRemoteDevice.enableRemoting(&otherObject); +\endcode + +\code + // Acquire() can now see the objects on other devices through proxyNode, + // due to the reverseProxy call. + OtherObject *oo = myInternalHost.acquire(); +\endcode + + While the \l proxy() functionality allows \l Source objects on another + network to be acquired(), reverseProxy() allows \l Source objects to be + "pushed" to an otherwise inaccessible network. + + Note: \l proxy() needs to be called before \l reverseProxy(), and a \a + hostUrl needs to be provided to \l proxy for \l reverseProxy() to work. The + \l reverseProxy() method allows a separate \a filter to be applied. This + reverseProxy specific filter will receive notifications of new \l Source + objects on proxyNode and acquire them on the internal node if they pass the + reverseFilter. + + \sa proxy() +*/ +bool QRemoteObjectHostBase::reverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter) +{ + Q_D(QRemoteObjectHostBase); + + if (!d->proxyInfo) { + qROWarning(this) << "proxy() needs to be called before setting up reverse proxy."; + return false; + } + + QRemoteObjectHost *host = qobject_cast(d->proxyInfo->proxyNode); + if (!host) { + qROWarning(this) << "proxy() needs called with host-url to enable reverse proxy."; + return false; + } + + return d->proxyInfo->setReverseProxy(filter); +} + /*! \internal The replica needs to have a default constructor to be able to create a replica from QML. In order for it to be properly @@ -618,30 +784,6 @@ void QRemoteObjectNodePrivate::handleReplicaConnection(const QByteArray &sourceS rep->setState(QRemoteObjectReplica::SignatureMismatch); return; } - if (!rep->m_metaObject) { - rep->setConnection(connection); - return; - } - for (int i = rep->m_metaObject->propertyOffset(); i < rep->m_metaObject->propertyCount(); ++i) { - const QMetaProperty property = rep->m_metaObject->property(i); - if (!QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) - continue; - - const auto type = property.typeName(); - const size_t len = strlen(type); - if (len > 8 && strncmp(&type[len-8], "Replica*", 8) == 0) { - auto propertyMeta = QMetaType::metaObjectForType(property.userType()); - QString name; - if (propertyMeta->inherits(&QAbstractItemModel::staticMetaObject)) - name = MODEL().arg(QString::fromLatin1(property.name())); - else - name = CLASS().arg(QString::fromLatin1(property.name())); - if (replicas.contains(name)) - handleReplicaConnection(name); - else - qROPrivWarning() << "Child type" << name << "not available. Objects:" << replicas.keys(); - } - } rep->setConnection(connection); } @@ -1337,6 +1479,8 @@ QVariant QRemoteObjectNodePrivate::handlePointerToQObjectProperty(QConnectedRepl retval = rep->getProperty(index); //Use existing value so changed signal isn't emitted QSharedPointer childRep = qSharedPointerCast(replicas.value(childInfo.name).toStrongRef()); + if (childRep->connectionToSource.isNull()) + childRep->connectionToSource = rep->connectionToSource; if (childRep->needsDynamicInitialization()) { if (childInfo.classDefinition.isEmpty()) childRep->setDynamicMetaObject(dynamicTypeManager.metaObjectForType(childInfo.typeName)); @@ -1344,9 +1488,12 @@ QVariant QRemoteObjectNodePrivate::handlePointerToQObjectProperty(QConnectedRepl QDataStream in(childInfo.classDefinition); childRep->setDynamicMetaObject(dynamicTypeManager.addDynamicType(in)); } + handlePointerToQObjectProperties(childRep.data(), childInfo.parameters); + childRep->setDynamicProperties(childInfo.parameters); + } else { + handlePointerToQObjectProperties(childRep.data(), childInfo.parameters); + childRep->initialize(childInfo.parameters); } - handlePointerToQObjectProperties(childRep.data(), childInfo.parameters); - childRep->setDynamicProperties(childInfo.parameters); } return retval; @@ -1641,6 +1788,116 @@ QRemoteObjectRegistryHostPrivate::QRemoteObjectRegistryHostPrivate() , registrySource(nullptr) { } +ProxyInfo::ProxyInfo(QRemoteObjectNode *node, QRemoteObjectHostBase *parent, + QRemoteObjectHostBase::RemoteObjectNameFilter filter) + : QObject(parent) + , proxyNode(node) + , parentNode(parent) + , proxyFilter(filter) +{ + const auto registry = node->registry(); + proxyNode->setObjectName(QString::fromLatin1("_ProxyNode")); + + connect(registry, &QRemoteObjectRegistry::remoteObjectAdded, this, + [this](const QRemoteObjectSourceLocation &entry) + { + this->proxyObject(entry, ProxyDirection::Forward); + }); + connect(registry, &QRemoteObjectRegistry::remoteObjectRemoved, this, + &ProxyInfo::unproxyObject); + connect(registry, &QRemoteObjectRegistry::initialized, this, [registry, this]() { + QRemoteObjectSourceLocations locations = registry->sourceLocations(); + QRemoteObjectSourceLocations::const_iterator i = locations.constBegin(); + while (i != locations.constEnd()) { + proxyObject(QRemoteObjectSourceLocation(i.key(), i.value())); + ++i; + } + }); +} + +ProxyInfo::~ProxyInfo() { + for (ProxyReplicaInfo* info : proxiedReplicas) + delete info; +} + +bool ProxyInfo::setReverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter) +{ + const auto registry = proxyNode->registry(); + this->reverseFilter = filter; + + connect(registry, &QRemoteObjectRegistry::remoteObjectAdded, this, + [this](const QRemoteObjectSourceLocation &entry) + { + this->proxyObject(entry, ProxyDirection::Reverse); + }); + connect(registry, &QRemoteObjectRegistry::remoteObjectRemoved, this, + &ProxyInfo::unproxyObject); + connect(registry, &QRemoteObjectRegistry::initialized, this, [registry, this]() { + QRemoteObjectSourceLocations locations = registry->sourceLocations(); + QRemoteObjectSourceLocations::const_iterator i = locations.constBegin(); + while (i != locations.constEnd()) { + proxyObject(QRemoteObjectSourceLocation(i.key(), i.value()), ProxyDirection::Reverse); + ++i; + } + }); + + return true; +} + +void ProxyInfo::proxyObject(const QRemoteObjectSourceLocation &entry, ProxyDirection direction) +{ + const QString name = entry.first; + const QString typeName = entry.second.typeName; + + if (direction == ProxyDirection::Forward) { + if (!proxyFilter(name, typeName)) + return; + + qCDebug(QT_REMOTEOBJECT) << "Starting proxy for" << name << "from" << entry.second.hostUrl; + + QRemoteObjectDynamicReplica *rep = proxyNode->acquireDynamic(name); + Q_ASSERT(!proxiedReplicas.contains(name)); + proxiedReplicas.insert(name, new ProxyReplicaInfo{rep, direction}); + connect(rep, &QRemoteObjectDynamicReplica::initialized, this, + [rep, name, this]() { this->parentNode->enableRemoting(rep, name); }); + } else { + if (!reverseFilter(name, typeName)) + return; + + qCDebug(QT_REMOTEOBJECT) << "Starting reverse proxy for" << name << "from" << entry.second.hostUrl; + + QRemoteObjectDynamicReplica *rep = this->parentNode->acquireDynamic(name); + Q_ASSERT(!proxiedReplicas.contains(name)); + proxiedReplicas.insert(name, new ProxyReplicaInfo{rep, direction}); + connect(rep, &QRemoteObjectDynamicReplica::initialized, this, + [rep, name, this]() + { + QRemoteObjectHostBase *host = qobject_cast(this->proxyNode); + Q_ASSERT(host); + host->enableRemoting(rep, name); + }); + } + +} + +void ProxyInfo::unproxyObject(const QRemoteObjectSourceLocation &entry) +{ + const QString name = entry.first; + + if (proxiedReplicas.contains(name)) { + qCDebug(QT_REMOTEOBJECT) << "Stopping proxy for" << name; + auto const info = proxiedReplicas.take(name); + if (info->direction == ProxyDirection::Forward) + this->parentNode->disableRemoting(info->replica); + else { + QRemoteObjectHostBase *host = qobject_cast(this->proxyNode); + Q_ASSERT(host); + host->disableRemoting(info->replica); + } + delete info; + } +} + QT_END_NAMESPACE #include "moc_qremoteobjectnode.cpp" diff --git a/src/remoteobjects/qremoteobjectnode.h b/src/remoteobjects/qremoteobjectnode.h index 0987621..c9309c4 100644 --- a/src/remoteobjects/qremoteobjectnode.h +++ b/src/remoteobjects/qremoteobjectnode.h @@ -46,6 +46,8 @@ #include #include +#include + QT_BEGIN_NAMESPACE class QRemoteObjectReplica; @@ -175,6 +177,11 @@ public: bool enableRemoting(QAbstractItemModel *model, const QString &name, const QVector roles, QItemSelectionModel *selectionModel = nullptr); bool disableRemoting(QObject *remoteObject); + typedef std::function RemoteObjectNameFilter; + bool proxy(const QUrl ®istryUrl, const QUrl &hostUrl={}, + RemoteObjectNameFilter filter=[](const QString &, const QString &) {return true; }); + bool reverseProxy(RemoteObjectNameFilter filter=[](const QString &, const QString &) {return true; }); + protected: virtual QUrl hostUrl() const; virtual bool setHostUrl(const QUrl &hostAddress); diff --git a/src/remoteobjects/qremoteobjectnode_p.h b/src/remoteobjects/qremoteobjectnode_p.h index 032d02f..69cb434 100644 --- a/src/remoteobjects/qremoteobjectnode_p.h +++ b/src/remoteobjects/qremoteobjectnode_p.h @@ -96,6 +96,33 @@ private: QHash dynamicTypes; }; +struct ProxyReplicaInfo; +class ProxyInfo : public QObject +{ + Q_OBJECT +public: + ProxyInfo(QRemoteObjectNode *node, QRemoteObjectHostBase *parent, QRemoteObjectHostBase::RemoteObjectNameFilter filter); + ~ProxyInfo() override; + enum class ProxyDirection { Forward, Reverse }; + + bool setReverseProxy(QRemoteObjectHostBase::RemoteObjectNameFilter filter); + void proxyObject(const QRemoteObjectSourceLocation &entry, ProxyDirection direction = ProxyDirection::Forward); + void unproxyObject(const QRemoteObjectSourceLocation &entry); + + QRemoteObjectNode *proxyNode; + QRemoteObjectHostBase *parentNode; + QRemoteObjectHostBase::RemoteObjectNameFilter proxyFilter; + QRemoteObjectHostBase::RemoteObjectNameFilter reverseFilter; + QHash proxiedReplicas; +}; + +struct ProxyReplicaInfo +{ + QRemoteObjectDynamicReplica* replica; + ProxyInfo::ProxyDirection direction; + ~ProxyReplicaInfo() { delete replica; } +}; + class QRemoteObjectNodePrivate : public QObjectPrivate { public: @@ -168,6 +195,7 @@ public: public: QRemoteObjectSourceIo *remoteObjectIo; + ProxyInfo *proxyInfo = nullptr; Q_DECLARE_PUBLIC(QRemoteObjectHostBase); }; diff --git a/src/remoteobjects/qremoteobjectpacket.cpp b/src/remoteobjects/qremoteobjectpacket.cpp index 73ef5a0..193d543 100644 --- a/src/remoteobjects/qremoteobjectpacket.cpp +++ b/src/remoteobjects/qremoteobjectpacket.cpp @@ -43,8 +43,6 @@ #include "qremoteobjectsource.h" #include "qremoteobjectsource_p.h" -#include "private/qmetaobjectbuilder_p.h" - QT_BEGIN_NAMESPACE using namespace QtRemoteObjects; diff --git a/src/remoteobjects/qremoteobjectreplica.cpp b/src/remoteobjects/qremoteobjectreplica.cpp index 3ec539e..b09c40d 100644 --- a/src/remoteobjects/qremoteobjectreplica.cpp +++ b/src/remoteobjects/qremoteobjectreplica.cpp @@ -46,7 +46,6 @@ #include "qremoteobjectpendingcall_p.h" #include "qconnectionfactories_p.h" #include "qremoteobjectsource_p.h" -#include "private/qmetaobjectbuilder_p.h" #include #include diff --git a/src/remoteobjects/qremoteobjectsource.cpp b/src/remoteobjects/qremoteobjectsource.cpp index 37a8044..11179bb 100644 --- a/src/remoteobjects/qremoteobjectsource.cpp +++ b/src/remoteobjects/qremoteobjectsource.cpp @@ -130,6 +130,8 @@ QRemoteObjectSourceBase::QRemoteObjectSourceBase(QObject *obj, Private *d, const roles.clear(); const auto knownRoles = model->roleNames(); for (auto role : modelInfo.roles.split('|')) { + if (role.isEmpty()) + continue; const int roleIndex = knownRoles.key(role, -1); if (roleIndex == -1) { qCWarning(QT_REMOTEOBJECT) << "Invalid role" << role << "for model" << model->metaObject()->className(); @@ -143,7 +145,7 @@ QRemoteObjectSourceBase::QRemoteObjectSourceBase(QObject *obj, Private *d, const } } else { const auto classApi = api->m_subclasses.at(subclassIndex++); - m_children.insert(i, new QRemoteObjectSource(child, d, classApi.api, nullptr)); + m_children.insert(i, new QRemoteObjectSource(child, d, classApi, nullptr)); } } } @@ -172,23 +174,21 @@ QRemoteObjectSourceBase::~QRemoteObjectSourceBase() QRemoteObjectSource::~QRemoteObjectSource() { - auto end = m_children.cend(); - for (auto it = m_children.cbegin(); it != end; ++it) { + for (auto it : m_children) { // We used QPointers for m_children because we don't control the lifetime of child QObjects // Since the this/source QObject's parent is the referenced QObject, it could have already // been deleted - delete it.value(); + delete it; } } QRemoteObjectRootSource::~QRemoteObjectRootSource() { - auto end = m_children.cend(); - for (auto it = m_children.cbegin(); it != end; ++it) { + for (auto it : m_children) { // We used QPointers for m_children because we don't control the lifetime of child QObjects // Since the this/source QObject's parent is the referenced QObject, it could have already // been deleted - delete it.value(); + delete it; } d->m_sourceIo->unregisterSource(this); Q_FOREACH (ServerIoDevice *io, d->m_listeners) { @@ -280,7 +280,7 @@ void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call, qCDebug(QT_REMOTEOBJECT) << "# Listeners" << d->m_listeners.length(); qCDebug(QT_REMOTEOBJECT) << "Invoke args:" << m_object << call << index << marshalArgs(index, a); - serializeInvokePacket(d->m_packet, m_api->name(), call, index, *marshalArgs(index, a), -1, propertyIndex); + serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex); d->m_packet.baseAddress = 0; Q_FOREACH (ServerIoDevice *io, d->m_listeners) @@ -367,7 +367,7 @@ DynamicApiMap::DynamicApiMap(QObject *object, const QMetaObject *metaObject, con if (typeName.isNull()) typeName = QString::fromLatin1(propertyMeta->className()); - m_subclasses << SubclassInfo{child, QString::fromLatin1(property.name()), new DynamicApiMap(child, meta, QString::fromLatin1(property.name()), typeName)}; + m_subclasses << new DynamicApiMap(child, meta, QString::fromLatin1(property.name()), typeName); } } m_properties << i; diff --git a/src/remoteobjects/qremoteobjectsource.h b/src/remoteobjects/qremoteobjectsource.h index 73fc1ae..1ce5310 100644 --- a/src/remoteobjects/qremoteobjectsource.h +++ b/src/remoteobjects/qremoteobjectsource.h @@ -131,15 +131,6 @@ struct ModelInfo QByteArray roles; }; -class SourceApiMap; -struct SubclassInfo -{ - SubclassInfo(QObject *_ptr = nullptr, QString _name = QString(), SourceApiMap *_api = nullptr) : ptr(_ptr), name(_name), api(_api) {} - QObject *ptr; - QString name; - SourceApiMap *api; -}; - class SourceApiMap { protected: @@ -174,7 +165,7 @@ public: virtual bool isAdapterMethod(int) const { return false; } virtual bool isAdapterProperty(int) const { return false; } QVector m_models; - QVector m_subclasses; + QVector m_subclasses; }; QT_END_NAMESPACE diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index a1bc76a..c5f7abc 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -13,6 +13,7 @@ SUBDIRS += \ modelreplica \ modelview \ pods \ + proxy \ repc \ repcodegenerator \ repparser \ diff --git a/tests/auto/modelview/tst_modelview.cpp b/tests/auto/modelview/tst_modelview.cpp index 2dfea2e..1512f10 100644 --- a/tests/auto/modelview/tst_modelview.cpp +++ b/tests/auto/modelview/tst_modelview.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "modeltest.h" +#include "../shared/model_utilities.h" #include #include @@ -71,19 +72,6 @@ bool waitForSignal(QVector *storage, QSignalSpy *spy) return storage->isEmpty() && spy->size() == storageSize; } -inline bool compareIndices(const QModelIndex &lhs, const QModelIndex &rhs) -{ - QModelIndex left = lhs; - QModelIndex right = rhs; - while (left.row() == right.row() && left.column() == right.column() && left.isValid() && right.isValid()) { - left = left.parent(); - right = right.parent(); - } - if (left.isValid() || right.isValid()) - return false; - return true; -} - QList createInsertionChildren(int num, const QString& name, const QColor &background) { QList children; @@ -135,80 +123,6 @@ struct InsertedRow int m_end; }; -struct WaitForDataChanged -{ - struct IndexPair - { - QModelIndex topLeft; - QModelIndex bottomRight; - }; - - WaitForDataChanged(const QVector &pending, QSignalSpy *spy) : m_pending(pending), m_spy(spy){} - bool wait() - { - Q_ASSERT(m_spy); - const int maxRuns = std::min(m_pending.size(), 100); - int runs = 0; - bool cancel = false; - while (!cancel) { - const int numSignals = m_spy->size(); - for (int i = 0; i < numSignals; ++i) { - const QList &signal = m_spy->at(i); - IndexPair pair = extractPair(signal); - checkAndRemoveRange(pair.topLeft, pair.bottomRight); - cancel = m_pending.isEmpty(); - } - if (!cancel) - m_spy->wait(50); - ++runs; - if (runs >= maxRuns) - cancel = true; - } - return runs < maxRuns; - } - - static IndexPair extractPair(const QList &signal) - { - IndexPair pair; - if (signal.size() != 3) - return pair; - const bool matchingTypes = signal[0].type() == QVariant::nameToType("QModelIndex") - && signal[1].type() == QVariant::nameToType("QModelIndex") - && signal[2].type() == QVariant::nameToType("QVector"); - if (!matchingTypes) - return pair; - const QModelIndex topLeft = signal[0].value(); - const QModelIndex bottomRight = signal[1].value(); - pair.topLeft = topLeft; - pair.bottomRight = bottomRight; - return pair; - } - - void checkAndRemoveRange(const QModelIndex &topLeft, const QModelIndex &bottomRight) - { - QVERIFY(topLeft.parent() == bottomRight.parent()); - QVector toRemove; - for (int i = 0; i < m_pending.size(); ++i) { - const QModelIndex &pending = m_pending.at(i); - if (pending.isValid() && compareIndices(pending.parent(), topLeft.parent())) { - const bool fitLeft = topLeft.column() <= pending.column(); - const bool fitRight = bottomRight.column() >= pending.column(); - const bool fitTop = topLeft.row() <= pending.row(); - const bool fitBottom = bottomRight.row() >= pending.row(); - if (fitLeft && fitRight && fitTop && fitBottom) - toRemove.append(pending); - } - } - foreach (const QModelIndex &index, toRemove) { - const int ind = m_pending.indexOf(index); - m_pending.remove(ind); - } - } - - QVector m_pending; - QSignalSpy *m_spy; -}; - QTextStream cout(stdout, QIODevice::WriteOnly); //Keep in case we need detailed debugging in the future... diff --git a/tests/auto/proxy/engine.rep b/tests/auto/proxy/engine.rep new file mode 100644 index 0000000..797943d --- /dev/null +++ b/tests/auto/proxy/engine.rep @@ -0,0 +1,8 @@ +class Engine +{ + ENUM EngineType {Null, Gas, Electric, Hybrid}; + PROP(int cylinders = 4) + PROP(bool started) + PROP(int rpm) + PROP(EngineType type) +} diff --git a/tests/auto/proxy/proxy.pro b/tests/auto/proxy/proxy.pro new file mode 100644 index 0000000..c4e0a07 --- /dev/null +++ b/tests/auto/proxy/proxy.pro @@ -0,0 +1,11 @@ +CONFIG += testcase +TARGET = tst_proxy +QT += testlib remoteobjects +QT -= gui + +SOURCES += tst_proxy.cpp + +REPC_MERGED += engine.rep +REPC_MERGED += subclass.rep + +contains(QT_CONFIG, c++11): CONFIG += c++11 diff --git a/tests/auto/proxy/subclass.rep b/tests/auto/proxy/subclass.rep new file mode 100644 index 0000000..5a82059 --- /dev/null +++ b/tests/auto/proxy/subclass.rep @@ -0,0 +1,13 @@ +POD MyPOD(int i, float f, QString s) + +class SubClass +{ + PROP(MyPOD myPOD) +} + +class ParentClass +{ + CLASS subClass(SubClass) + MODEL tracks(display) +} + diff --git a/tests/auto/proxy/tst_proxy.cpp b/tests/auto/proxy/tst_proxy.cpp new file mode 100644 index 0000000..931c43b --- /dev/null +++ b/tests/auto/proxy/tst_proxy.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "rep_engine_merged.h" +#include "rep_subclass_merged.h" +#include "../shared/model_utilities.h" + +#include +#include +#include + +const QUrl localHostUrl = QUrl(QLatin1String("local:testHost")); +const QUrl tcpHostUrl = QUrl(QLatin1String("tcp://127.0.0.1:9989")); +const QUrl registryUrl = QUrl(QLatin1String("local:testRegistry")); + +#define SET_NODE_NAME(obj) (obj).setName(QLatin1String(#obj)) + +class ProxyTest : public QObject +{ + Q_OBJECT + + void cleanup() + { + // wait for delivery of RemoveObject events to the source + QTest::qWait(200); + } +private Q_SLOTS: + + void testProxy_data(); + void testProxy(); + // The following should fail to compile, verifying the SourceAPI templates work + // for subclasses + /* + void testSubclass() + { + QRemoteObjectHost host(localHostUrl); + + struct invalidchild { + MyPOD myPOD() { return MyPOD(12, 13.f, QStringLiteral("Yay!")); } + }; + struct badparent { + invalidchild *subClass() { return new invalidchild; } + } parent; + host.enableRemoting(&parent); + } + */ +}; + +void ProxyTest::testProxy_data() +{ + QTest::addColumn("sourceApi"); + QTest::addColumn("useProxy"); + QTest::addColumn("dynamic"); + + QTest::newRow("dynamicApi, no proxy") << false << false << false; + QTest::newRow("sourceApi, no proxy") << true << false << false; + QTest::newRow("dynamicApi, with proxy") << false << true << false; + QTest::newRow("sourceApi, with proxy") << true << true << false; + QTest::newRow("dynamicApi, no proxy, dynamicRep") << false << false << true; + QTest::newRow("sourceApi, no proxy, dynamicRep") << true << false << true; + QTest::newRow("dynamicApi, with proxy, dynamicRep") << false << true << true; + QTest::newRow("sourceApi, with proxy, dynamicRep") << true << true << true; +} + +void ProxyTest::testProxy() +{ + QFETCH(bool, sourceApi); + QFETCH(bool, useProxy); + QFETCH(bool, dynamic); + + //Setup Local Registry + QRemoteObjectRegistryHost registry(registryUrl); + SET_NODE_NAME(registry); + //Setup Local Host + QRemoteObjectHost host(localHostUrl); + SET_NODE_NAME(host); + host.setRegistryUrl(registryUrl); + EngineSimpleSource engine; + engine.setRpm(1234); + engine.setType(EngineSimpleSource::Gas); + if (sourceApi) + host.enableRemoting(&engine); + else + host.enableRemoting(&engine); + + QRemoteObjectHost proxyNode; + SET_NODE_NAME(proxyNode); + if (useProxy) { + proxyNode.setHostUrl(tcpHostUrl); + proxyNode.proxy(registryUrl); + } + + //Setup Local Replica + QRemoteObjectNode client; + SET_NODE_NAME(client); + if (useProxy) + client.connectToNode(tcpHostUrl); + else + client.setRegistryUrl(registryUrl); + + QScopedPointer replica; + if (!dynamic) { + //QLoggingCategory::setFilterRules("qt.remoteobjects*=true"); + replica.reset(client.acquire()); + QVERIFY(replica->waitForSource(1000)); + + EngineReplica *rep = qobject_cast(replica.data()); + + //Compare Replica to Source + QCOMPARE(rep->rpm(), engine.rpm()); + QCOMPARE((EngineReplica::EngineType)rep->type(), EngineReplica::Gas); + + //Change Replica and make sure change propagates to source + QSignalSpy sourceSpy(&engine, SIGNAL(rpmChanged(int))); + QSignalSpy replicaSpy(rep, SIGNAL(rpmChanged(int))); + rep->pushRpm(42); + sourceSpy.wait(); + QCOMPARE(sourceSpy.count(), 1); + QCOMPARE(engine.rpm(), 42); + + // ... and the change makes it back to the replica + replicaSpy.wait(); + QCOMPARE(replicaSpy.count(), 1); + QCOMPARE(rep->rpm(), 42); + } else { + replica.reset(client.acquireDynamic(QStringLiteral("Engine"))); + QVERIFY(replica->waitForSource(1000)); + + //Compare Replica to Source + const QMetaObject *metaObject = replica->metaObject(); + const int rpmIndex = metaObject->indexOfProperty("rpm"); + Q_ASSERT(rpmIndex != -1); + const QMetaProperty rpmMeta = metaObject->property(rpmIndex); + QCOMPARE(rpmMeta.read(replica.data()).value(), engine.rpm()); + const int typeIndex = metaObject->indexOfProperty("type"); + Q_ASSERT(typeIndex != -1); + const QMetaProperty typeMeta = metaObject->property(typeIndex); + QCOMPARE(typeMeta.read(replica.data()).value(), EngineReplica::Gas); + + //Change Replica and make sure change propagates to source + QSignalSpy sourceSpy(&engine, SIGNAL(rpmChanged(int))); + QSignalSpy replicaSpy(replica.data(), QByteArray(QByteArrayLiteral("2")+rpmMeta.notifySignal().methodSignature().constData())); + + const int rpmPushIndex = metaObject->indexOfMethod("pushRpm(int)"); + Q_ASSERT(rpmPushIndex != -1); + QMetaMethod pushMethod = metaObject->method(rpmPushIndex); + Q_ASSERT(pushMethod.isValid()); + QVERIFY(pushMethod.invoke(replica.data(), Q_ARG(int, 42))); + + sourceSpy.wait(); + QCOMPARE(sourceSpy.count(), 1); + QCOMPARE(engine.rpm(), 42); + + // ... and the change makes it back to the replica + replicaSpy.wait(); + QCOMPARE(replicaSpy.count(), 1); + QCOMPARE(rpmMeta.read(replica.data()).value(), engine.rpm()); + } + + // Make sure disabling the Source cascades the state change + bool res = host.disableRemoting(&engine); + Q_ASSERT(res); + QSignalSpy stateSpy(replica.data(), SIGNAL(stateChanged(QRemoteObjectReplica::State,QRemoteObjectReplica::State))); + stateSpy.wait(); + QCOMPARE(stateSpy.count(), 1); + QCOMPARE(replica->state(), QRemoteObjectReplica::Suspect); + + // Now test subclass Source + SubClassSimpleSource subclass; + const MyPOD initialValue(42, 3.14, QStringLiteral("SubClass")); + subclass.setMyPOD(initialValue); + QStringListModel model; + model.setStringList(QStringList() << "Track1" << "Track2" << "Track3"); + ParentClassSimpleSource parent(&subclass, &model); + QCOMPARE(subclass.myPOD(), initialValue); + if (sourceApi) + host.enableRemoting(&parent); + else + host.enableRemoting(&parent); + if (!dynamic) { + replica.reset(client.acquire()); + ParentClassReplica *rep = qobject_cast(replica.data()); + QSignalSpy tracksSpy(rep->tracks(), &QAbstractItemModelReplica::initialized); + QVERIFY(replica->waitForSource(1000)); + //QLoggingCategory::setFilterRules("qt.remoteobjects*=false"); + + //Compare Replica to Source + QVERIFY(rep->subClass() != nullptr); + QCOMPARE(rep->subClass()->myPOD(), parent.subClass()->myPOD()); + QVERIFY(rep->tracks() != nullptr); + QVERIFY(tracksSpy.count() || tracksSpy.wait()); + // Rep file only uses display role, but proxy doesn't forward that yet + if (!useProxy) + QCOMPARE(rep->tracks()->availableRoles(), QVector{Qt::DisplayRole}); + else { + QCOMPARE(QSet::fromList(rep->tracks()->availableRoles().toList()), + QSet::fromList(model.roleNames().keys())); + } + QSignalSpy dataSpy(rep->tracks(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector))); + QVector pending; + QCOMPARE(rep->tracks()->rowCount(), model.rowCount()); + for (int i = 0; i < rep->tracks()->rowCount(); i++) + { + // We haven't received any data yet + const auto index = rep->tracks()->index(i, 0); + QCOMPARE(rep->tracks()->data(index), QVariant()); + pending.append(index); + } + if (useProxy) { // A first batch of updates will be the empty proxy values + WaitForDataChanged w(pending, &dataSpy); + QVERIFY(w.wait()); + } + WaitForDataChanged w(pending, &dataSpy); + QVERIFY(w.wait()); + for (int i = 0; i < rep->tracks()->rowCount(); i++) + { + QCOMPARE(rep->tracks()->data(rep->tracks()->index(i, 0)), model.data(model.index(i), Qt::DisplayRole)); + } + } else { + replica.reset(client.acquireDynamic(QStringLiteral("ParentClass"))); + QVERIFY(replica->waitForSource(1000)); + + const QMetaObject *metaObject = replica->metaObject(); + // Verify subClass pointer + const int subclassIndex = metaObject->indexOfProperty("subClass"); + QVERIFY(subclassIndex != -1); + const QMetaProperty subclassMeta = metaObject->property(subclassIndex); + QObject *subclassQObjectPtr = subclassMeta.read(replica.data()).value(); + QVERIFY(subclassQObjectPtr != nullptr); + QRemoteObjectDynamicReplica *subclassReplica = qobject_cast(subclassQObjectPtr); + QVERIFY(subclassReplica != nullptr); + // Verify tracks pointer + const int tracksIndex = metaObject->indexOfProperty("tracks"); + QVERIFY(tracksIndex != -1); + const QMetaProperty tracksMeta = metaObject->property(tracksIndex); + QObject *tracksQObjectPtr = tracksMeta.read(replica.data()).value(); + QVERIFY(tracksQObjectPtr != nullptr); + QAbstractItemModelReplica *tracksReplica = qobject_cast(tracksQObjectPtr); + QVERIFY(tracksReplica != nullptr); + + // Verify subClass data + const int podIndex = subclassReplica->metaObject()->indexOfProperty("myPOD"); + QVERIFY(podIndex != -1); + const QMetaProperty podMeta = subclassReplica->metaObject()->property(podIndex); + MyPOD pod = podMeta.read(subclassReplica).value(); + QCOMPARE(pod, parent.subClass()->myPOD()); + + // Verify tracks data + // Rep file only uses display role, but proxy doesn't forward that yet + if (!useProxy) + QCOMPARE(tracksReplica->availableRoles(), QVector{Qt::DisplayRole}); + else { + QCOMPARE(QSet::fromList(tracksReplica->availableRoles().toList()), + QSet::fromList(model.roleNames().keys())); + } + QTRY_COMPARE(tracksReplica->isInitialized(), true); + /* Fixed in next commit (already in review) + QCOMPARE(tracksReplica->rowCount(), model.rowCount()); + for (int i = 0; i < tracksReplica->rowCount(); i++) + { + // We haven't received any data yet + QCOMPARE(tracksReplica->data(tracksReplica->index(i, 0)), QVariant()); + } + // Wait for data to be fetch and confirm + QTest::qWait(100); + for (int i = 0; i < tracksReplica->rowCount(); i++) + { + QCOMPARE(tracksReplica->data(tracksReplica->index(i, 0)), model.data(model.index(i), Qt::DisplayRole)); + } + */ + } + replica.reset(); +} + +QTEST_MAIN(ProxyTest) + +#include "tst_proxy.moc" diff --git a/tests/auto/shared/model_utilities.h b/tests/auto/shared/model_utilities.h new file mode 100644 index 0000000..0d77499 --- /dev/null +++ b/tests/auto/shared/model_utilities.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Ford Motor Company +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtRemoteObjects module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +namespace { + +inline bool compareIndices(const QModelIndex &lhs, const QModelIndex &rhs) +{ + QModelIndex left = lhs; + QModelIndex right = rhs; + while (left.row() == right.row() && left.column() == right.column() && left.isValid() && right.isValid()) { + left = left.parent(); + right = right.parent(); + } + if (left.isValid() || right.isValid()) + return false; + return true; +} + +struct WaitForDataChanged +{ + struct IndexPair + { + QModelIndex topLeft; + QModelIndex bottomRight; + }; + + WaitForDataChanged(const QVector &pending, QSignalSpy *spy) : m_pending(pending), m_spy(spy){} + bool wait() + { + Q_ASSERT(m_spy); + const int maxRuns = std::min(m_pending.size(), 100); + int runs = 0; + bool cancel = false; + while (!cancel) { + const int numSignals = m_spy->size(); + for (int i = 0; i < numSignals; ++i) { + const QList &signal = m_spy->takeFirst(); + IndexPair pair = extractPair(signal); + checkAndRemoveRange(pair.topLeft, pair.bottomRight); + cancel = m_pending.isEmpty(); + } + if (!cancel) + m_spy->wait(50); + ++runs; + if (runs >= maxRuns) + cancel = true; + } + return runs < maxRuns; + } + + static IndexPair extractPair(const QList &signal) + { + IndexPair pair; + if (signal.size() != 3) + return pair; + const bool matchingTypes = signal[0].type() == QVariant::nameToType("QModelIndex") + && signal[1].type() == QVariant::nameToType("QModelIndex") + && signal[2].type() == QVariant::nameToType("QVector"); + if (!matchingTypes) + return pair; + const QModelIndex topLeft = signal[0].value(); + const QModelIndex bottomRight = signal[1].value(); + pair.topLeft = topLeft; + pair.bottomRight = bottomRight; + return pair; + } + + void checkAndRemoveRange(const QModelIndex &topLeft, const QModelIndex &bottomRight) + { + QVERIFY(topLeft.parent() == bottomRight.parent()); + QVector toRemove; + for (int i = 0; i < m_pending.size(); ++i) { + const QModelIndex &pending = m_pending.at(i); + if (pending.isValid() && compareIndices(pending.parent(), topLeft.parent())) { + const bool fitLeft = topLeft.column() <= pending.column(); + const bool fitRight = bottomRight.column() >= pending.column(); + const bool fitTop = topLeft.row() <= pending.row(); + const bool fitBottom = bottomRight.row() >= pending.row(); + if (fitLeft && fitRight && fitTop && fitBottom) + toRemove.append(pending); + } + } + foreach (const QModelIndex &index, toRemove) { + const int ind = m_pending.indexOf(index); + m_pending.remove(ind); + } + } + + QVector m_pending; + QSignalSpy *m_spy; +}; + +} // namespace diff --git a/tools/repc/repcodegenerator.cpp b/tools/repc/repcodegenerator.cpp index fabc733..40cfdac 100644 --- a/tools/repc/repcodegenerator.cpp +++ b/tools/repc/repcodegenerator.cpp @@ -386,7 +386,7 @@ QString RepCodeGenerator::typeForMode(const ASTProperty &property, RepCodeGenera void RepCodeGenerator::generateSimpleSetter(QTextStream &out, const ASTProperty &property, bool generateOverride) { - out << " virtual void set" << cap(property.name) << "(" << property.type << " " << property.name << ")"; + out << " virtual void set" << cap(property.name) << "(" << typeForMode(property, SIMPLE_SOURCE) << " " << property.name << ")"; if (generateOverride) out << " override"; out << endl; @@ -821,7 +821,7 @@ void RepCodeGenerator::generateClass(Mode mode, QTextStream &out, const ASTClass Q_FOREACH (const ASTProperty &property, astClass.properties) { if (property.modifier == ASTProperty::ReadWrite || property.modifier == ASTProperty::ReadPush) - out << " virtual void set" << cap(property.name) << "(" << property.type << " " << property.name << ") = 0;" << endl; + out << " virtual void set" << cap(property.name) << "(" << typeForMode(property, mode) << " " << property.name << ") = 0;" << endl; } } else { Q_FOREACH (const ASTProperty &property, astClass.properties) @@ -1004,7 +1004,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl out << QString::fromLatin1(" m_signals[%1] = QtPrivate::qtro_signal_index(&ObjectType::%2Changed, " "static_cast(0),m_signalArgCount+%4,&m_signalArgTypes[%4]);") .arg(QString::number(i+1), onChangeProperties.at(i).name, - fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), onChangeProperties.at(i).type), + fullyQualifiedTypeName(astClass, QStringLiteral("typename ObjectType"), typeForMode(onChangeProperties.at(i), SOURCE)), QString::number(i)) << endl; QVector signalsList = transformEnumParams(astClass, astClass.signalsList, QStringLiteral("typename ObjectType")); @@ -1056,7 +1056,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl } for (int i : astClass.subClassPropertyIndices) { const ASTProperty &child = astClass.properties.at(i); - out << QString::fromLatin1(" m_subclasses << SubclassInfo{object->%1(), QStringLiteral(\"%1\"), new %2SourceAPI<%1_type_t>(object->%1(), QStringLiteral(\"%1\"))};") + out << QString::fromLatin1(" m_subclasses << new %2SourceAPI<%1_type_t>(object->%1(), QStringLiteral(\"%1\"));") .arg(child.name, child.type) << endl; } out << QStringLiteral(" }") << endl; @@ -1159,7 +1159,7 @@ void RepCodeGenerator::generateSourceAPI(QTextStream &out, const ASTClass &astCl out << QStringLiteral(" switch (index) {") << endl; for (int i = 0; i < changedCount; ++i) out << QString::fromLatin1(" case %1: return QByteArrayLiteral(\"%2Changed(%3)\");") - .arg(QString::number(i), onChangeProperties.at(i).name, onChangeProperties.at(i).type) << endl; + .arg(QString::number(i), onChangeProperties.at(i).name, typeForMode(onChangeProperties.at(i), SOURCE)) << endl; for (int i = 0; i < signalCount; ++i) { const ASTFunction &sig = astClass.signalsList.at(i); -- cgit v1.2.3