summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrett Stottlemyer <bstottle@ford.com>2018-04-24 20:26:22 -0400
committerMichael Brasser <michael.brasser@live.com>2018-04-25 13:44:53 +0000
commitd9ee9fff1b9291395ba7ab75d7e46ddf4a9ff3a4 (patch)
tree519eed3d0354da313070e9fcf7e1519118e8d7ee
parentb47f3b6386c7d984e11ca7b552e5aeab16c61225 (diff)
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 <michael.brasser@live.com>
-rw-r--r--src/remoteobjects/qconnectionfactories_p.h10
-rw-r--r--src/remoteobjects/qremoteobjectabstractitemmodeladapter_p.h2
-rw-r--r--src/remoteobjects/qremoteobjectnode.cpp309
-rw-r--r--src/remoteobjects/qremoteobjectnode.h7
-rw-r--r--src/remoteobjects/qremoteobjectnode_p.h28
-rw-r--r--src/remoteobjects/qremoteobjectpacket.cpp2
-rw-r--r--src/remoteobjects/qremoteobjectreplica.cpp1
-rw-r--r--src/remoteobjects/qremoteobjectsource.cpp18
-rw-r--r--src/remoteobjects/qremoteobjectsource.h11
-rw-r--r--tests/auto/auto.pro1
-rw-r--r--tests/auto/modelview/tst_modelview.cpp88
-rw-r--r--tests/auto/proxy/engine.rep8
-rw-r--r--tests/auto/proxy/proxy.pro11
-rw-r--r--tests/auto/proxy/subclass.rep13
-rw-r--r--tests/auto/proxy/tst_proxy.cpp302
-rw-r--r--tests/auto/shared/model_utilities.h121
-rw-r--r--tools/repc/repcodegenerator.cpp10
17 files changed, 800 insertions, 142 deletions
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
@@ -163,6 +163,172 @@ void QRemoteObjectNode::setHeartbeatInterval(int 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>(&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://<target device>: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<SomeObject>();
+\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 &registryUrl, 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://<target device>:12123");
+
+ // Because of the reverseProxy, we can expose objects on this device
+ // and they will make their way to proxyNode...
+ nodeOnRemoteDevice.enableRemoting<OtherObject>(&otherObject);
+\endcode
+
+\code
+ // Acquire() can now see the objects on other devices through proxyNode,
+ // due to the reverseProxy call.
+ OtherObject *oo = myInternalHost.acquire<OtherObject>();
+\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<QRemoteObjectHost *>(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
constructed, there needs to be a way to associate the replica with a
@@ -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<QConnectedReplicaImplementation> childRep = qSharedPointerCast<QConnectedReplicaImplementation>(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<QRemoteObjectHostBase *>(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<QRemoteObjectHostBase *>(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 <QtRemoteObjects/qremoteobjectregistry.h>
#include <QtRemoteObjects/qremoteobjectdynamicreplica.h>
+#include <functional>
+
QT_BEGIN_NAMESPACE
class QRemoteObjectReplica;
@@ -175,6 +177,11 @@ public:
bool enableRemoting(QAbstractItemModel *model, const QString &name, const QVector<int> roles, QItemSelectionModel *selectionModel = nullptr);
bool disableRemoting(QObject *remoteObject);
+ typedef std::function<bool(const QString &, const QString &)> RemoteObjectNameFilter;
+ bool proxy(const QUrl &registryUrl, 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<QString, QMetaObject*> 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<QString, ProxyReplicaInfo*> 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 <QCoreApplication>
#include <QDataStream>
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<ModelInfo> m_models;
- QVector<SubclassInfo> m_subclasses;
+ QVector<SourceApiMap *> 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 <QtTest/QtTest>
#include <QMetaType>
@@ -71,19 +72,6 @@ bool waitForSignal(QVector<Storage> *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<QStandardItem*> createInsertionChildren(int num, const QString& name, const QColor &background)
{
QList<QStandardItem*> children;
@@ -135,80 +123,6 @@ struct InsertedRow
int m_end;
};
-struct WaitForDataChanged
-{
- struct IndexPair
- {
- QModelIndex topLeft;
- QModelIndex bottomRight;
- };
-
- WaitForDataChanged(const QVector<QModelIndex> &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<QVariant> &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<QVariant> &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<int>");
- if (!matchingTypes)
- return pair;
- const QModelIndex topLeft = signal[0].value<QModelIndex>();
- const QModelIndex bottomRight = signal[1].value<QModelIndex>();
- pair.topLeft = topLeft;
- pair.bottomRight = bottomRight;
- return pair;
- }
-
- void checkAndRemoveRange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
- {
- QVERIFY(topLeft.parent() == bottomRight.parent());
- QVector<QModelIndex> 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<QModelIndex> 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 <QtTest/QtTest>
+#include <QRemoteObjectReplica>
+#include <QRemoteObjectNode>
+
+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<ParentClassSourceAPI>(&parent);
+ }
+ */
+};
+
+void ProxyTest::testProxy_data()
+{
+ QTest::addColumn<bool>("sourceApi");
+ QTest::addColumn<bool>("useProxy");
+ QTest::addColumn<bool>("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<EngineSourceAPI>(&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<QRemoteObjectReplica> replica;
+ if (!dynamic) {
+ //QLoggingCategory::setFilterRules("qt.remoteobjects*=true");
+ replica.reset(client.acquire<EngineReplica>());
+ QVERIFY(replica->waitForSource(1000));
+
+ EngineReplica *rep = qobject_cast<EngineReplica *>(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<int>(), 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::EngineType>(), 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<int>(), 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<ParentClassSourceAPI>(&parent);
+ else
+ host.enableRemoting(&parent);
+ if (!dynamic) {
+ replica.reset(client.acquire<ParentClassReplica>());
+ ParentClassReplica *rep = qobject_cast<ParentClassReplica *>(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<int>{Qt::DisplayRole});
+ else {
+ QCOMPARE(QSet<int>::fromList(rep->tracks()->availableRoles().toList()),
+ QSet<int>::fromList(model.roleNames().keys()));
+ }
+ QSignalSpy dataSpy(rep->tracks(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ QVector<QModelIndex> 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<QObject *>();
+ QVERIFY(subclassQObjectPtr != nullptr);
+ QRemoteObjectDynamicReplica *subclassReplica = qobject_cast<QRemoteObjectDynamicReplica *>(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<QObject *>();
+ QVERIFY(tracksQObjectPtr != nullptr);
+ QAbstractItemModelReplica *tracksReplica = qobject_cast<QAbstractItemModelReplica *>(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<MyPOD>();
+ 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<int>{Qt::DisplayRole});
+ else {
+ QCOMPARE(QSet<int>::fromList(tracksReplica->availableRoles().toList()),
+ QSet<int>::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 <QtTest/QtTest>
+#include <QModelIndex>
+
+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<QModelIndex> &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<QVariant> &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<QVariant> &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<int>");
+ if (!matchingTypes)
+ return pair;
+ const QModelIndex topLeft = signal[0].value<QModelIndex>();
+ const QModelIndex bottomRight = signal[1].value<QModelIndex>();
+ pair.topLeft = topLeft;
+ pair.bottomRight = bottomRight;
+ return pair;
+ }
+
+ void checkAndRemoveRange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+ {
+ QVERIFY(topLeft.parent() == bottomRight.parent());
+ QVector<QModelIndex> 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<QModelIndex> 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>(&ObjectType::%2Changed, "
"static_cast<void (QObject::*)(%3)>(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<ASTFunction> 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);