aboutsummaryrefslogtreecommitdiffstats
path: root/src/webchannel/signalhandler_p.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/webchannel/signalhandler_p.h')
-rw-r--r--src/webchannel/signalhandler_p.h278
1 files changed, 278 insertions, 0 deletions
diff --git a/src/webchannel/signalhandler_p.h b/src/webchannel/signalhandler_p.h
new file mode 100644
index 0000000..2613f92
--- /dev/null
+++ b/src/webchannel/signalhandler_p.h
@@ -0,0 +1,278 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Copyright (C) 2013 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
+** Contact: http://www.qt-project.org/legal
+*
+** This file is part of the QtWebChannel module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef SIGNALHANDLER_H
+#define SIGNALHANDLER_H
+
+#include <QObject>
+#include <QHash>
+#include <QVector>
+#include <QMetaMethod>
+#include <QDebug>
+
+/**
+ * The signal handler is similar to QSignalSpy, but geared towards the usecase of the web channel.
+ *
+ * It allows connecting to any number of signals of arbitrary objects and forwards the signal
+ * invocations to the Receiver by calling its signalEmitted function, which takes the object,
+ * signal index and a QVariantList of arguments.
+ */
+template<class Receiver>
+class SignalHandler : public QObject
+{
+public:
+ SignalHandler(Receiver *receiver, QObject *parent = 0)
+ : QObject(parent)
+ , m_receiver(receiver)
+ {
+ }
+
+ /**
+ * Connect to a signal of @p object identified by @p signalIndex.
+ *
+ * If the handler is already connected to the signal, an internal counter is increased,
+ * i.e. the handler never connects multiple times to the same signal.
+ */
+ void connectTo(const QObject *object, const int signalIndex);
+
+ /**
+ * Decrease the connection counter for the connection to the given signal.
+ *
+ * When the counter drops to zero, the connection is disconnected.
+ */
+ void disconnectFrom(const QObject *object, const int signalIndex);
+
+ /**
+ * @internal
+ *
+ * Custom implementation of qt_metacall which calls dispatch() for connected signals.
+ */
+ int qt_metacall(QMetaObject::Call call, int methodId, void **args) Q_DECL_OVERRIDE;
+
+ /**
+ * Reset all connections, useful for benchmarks.
+ */
+ void clear();
+
+private:
+ /**
+ * Exctract the arguments of a signal call and pass them to the receiver.
+ *
+ * The @p argumentData is converted to a QVariantList and then passed to the receiver's
+ * signalEmitted method.
+ */
+ void dispatch(const QObject *object, const int signalIdx, void **argumentData);
+
+ Receiver *m_receiver;
+
+ // maps meta object -> signalIndex -> list of arguments
+ // NOTE: This data is "leaked" on disconnect until deletion of the handler, is this a problem?
+ typedef QVector<int> ArgumentTypeList;
+ typedef QHash<int, ArgumentTypeList> SignalArgumentHash;
+ QHash<const QMetaObject *, SignalArgumentHash > m_signalArgumentTypes;
+
+ /*
+ * Tracks how many connections are active to object signals.
+ *
+ * Maps object -> signalIndex -> pair of connection and number of connections
+ *
+ * Note that the handler is connected to the signal only once, whereas clients
+ * may have connected multiple times.
+ *
+ * TODO: Move more of this logic to the HTML client side, esp. the connection counting.
+ */
+ typedef QPair<QMetaObject::Connection, int> ConnectionPair;
+ typedef QHash<int, ConnectionPair> SignalConnectionHash;
+ typedef QHash<const QObject*, SignalConnectionHash> ConnectionHash;
+ ConnectionHash m_connectionsCounter;
+};
+
+/**
+ * Find and return the signal of index @p signalIndex in the meta object of @p object and return it.
+ *
+ * The return value is also verified to ensure it is a signal.
+ */
+QMetaMethod findSignal(const QMetaObject *metaObject, const int signalIndex)
+{
+ QMetaMethod signal = metaObject->method(signalIndex);
+ if (!signal.isValid()) {
+ qWarning("Cannot find signal with index %d of object %s", signalIndex, metaObject->className());
+ return QMetaMethod();
+ }
+ Q_ASSERT(signal.methodType() == QMetaMethod::Signal);
+ return signal;
+}
+
+template<class Receiver>
+void SignalHandler<Receiver>::connectTo(const QObject *object, const int signalIndex)
+{
+ const QMetaObject *metaObject = object->metaObject();
+ const QMetaMethod &signal = findSignal(metaObject, signalIndex);
+ if (!signal.isValid()) {
+ return;
+ }
+
+ ConnectionPair &connectionCounter = m_connectionsCounter[object][signalIndex];
+ if (connectionCounter.first) {
+ // increase connection counter if already connected
+ ++connectionCounter.second;
+ return;
+ } // otherwise not yet connected, do so now
+
+ const int memberOffset = QObject::staticMetaObject.methodCount();
+ QMetaObject::Connection connection = QMetaObject::connect(object, signal.methodIndex(), this, memberOffset, Qt::DirectConnection, 0);
+ if (!connection) {
+ qWarning() << "SignalHandler: QMetaObject::connect returned false. Unable to connect to" << object << signal.name() << signal.methodSignature();
+ return;
+ }
+ connectionCounter.first = connection;
+ connectionCounter.second = 1;
+
+ if (!m_signalArgumentTypes.value(metaObject).contains(signal.methodIndex())) {
+ // find the type ids of the signal parameters, see also QSignalSpy::initArgs
+ QVector<int> args;
+ args.reserve(signal.parameterCount());
+ for (int i = 0; i < signal.parameterCount(); ++i) {
+ int tp = signal.parameterType(i);
+ if (tp == QMetaType::UnknownType && object) {
+ void *argv[] = { &tp, &i };
+ QMetaObject::metacall(const_cast<QObject *>(object),
+ QMetaObject::RegisterMethodArgumentMetaType,
+ signal.methodIndex(), argv);
+ if (tp == -1) {
+ tp = QMetaType::UnknownType;
+ }
+ }
+ if (tp == QMetaType::UnknownType) {
+ Q_ASSERT(tp != QMetaType::Void); // void parameter => metaobject is corrupt
+ qWarning("Don't know how to handle '%s', use qRegisterMetaType to register it.",
+ signal.parameterNames().at(i).constData());
+ }
+ args << tp;
+ }
+
+ m_signalArgumentTypes[metaObject][signal.methodIndex()] = args;
+ }
+}
+
+template<class Receiver>
+void SignalHandler<Receiver>::dispatch(const QObject *object, const int signalIdx, void **argumentData)
+{
+ Q_ASSERT(m_signalArgumentTypes.contains(object->metaObject()));
+ const QHash<int, QVector<int> > &objectSignalArgumentTypes = m_signalArgumentTypes.value(object->metaObject());
+ QHash<int, QVector<int> >::const_iterator signalIt = objectSignalArgumentTypes.constFind(signalIdx);
+ if (signalIt == objectSignalArgumentTypes.constEnd()) {
+ // not connected to this signal, skip
+ return;
+ }
+ const QVector<int> &argumentTypes = *signalIt;
+ QVariantList arguments;
+ arguments.reserve(argumentTypes.count());
+ // TODO: basic overload resolution based on number of arguments?
+ for (int i = 0; i < argumentTypes.count(); ++i) {
+ const QMetaType::Type type = static_cast<QMetaType::Type>(argumentTypes.at(i));
+ QVariant arg;
+ if (type == QMetaType::QVariant) {
+ arg = *reinterpret_cast<QVariant *>(argumentData[i + 1]);
+ } else {
+ arg = QVariant(type, argumentData[i + 1]);
+ }
+ arguments.append(arg);
+ }
+ m_receiver->signalEmitted(object, signalIdx, arguments);
+}
+
+template<class Receiver>
+void SignalHandler<Receiver>::disconnectFrom(const QObject *object, const int signalIndex)
+{
+ Q_ASSERT(m_connectionsCounter.value(object).contains(signalIndex));
+ ConnectionPair &connection = m_connectionsCounter[object][signalIndex];
+ --connection.second;
+ if (!connection.second || !connection.first) {
+ QObject::disconnect(connection.first);
+ m_connectionsCounter[object].remove(signalIndex);
+ if (m_connectionsCounter[object].isEmpty()) {
+ m_connectionsCounter.remove(object);
+ }
+ }
+}
+
+template<class Receiver>
+int SignalHandler<Receiver>::qt_metacall(QMetaObject::Call call, int methodId, void **args)
+{
+ methodId = QObject::qt_metacall(call, methodId, args);
+ if (methodId < 0)
+ return methodId;
+
+ if (call == QMetaObject::InvokeMetaMethod) {
+ if (methodId == 0) {
+ Q_ASSERT(sender());
+ Q_ASSERT(senderSignalIndex() != -1);
+ dispatch(sender(), senderSignalIndex(), args);
+ static const int destroyedIndex = metaObject()->indexOfSignal("destroyed(QObject*)");
+ if (senderSignalIndex() == destroyedIndex) {
+ ConnectionHash::iterator it = m_connectionsCounter.find(sender());
+ Q_ASSERT(it != m_connectionsCounter.end());
+ foreach (const ConnectionPair &connection, *it) {
+ QObject::disconnect(connection.first);
+ }
+ m_connectionsCounter.erase(it);
+ }
+ }
+ --methodId;
+ }
+ return methodId;
+}
+
+template<class Receiver>
+void SignalHandler<Receiver>::clear()
+{
+ foreach (const SignalConnectionHash &connections, m_connectionsCounter) {
+ foreach (const ConnectionPair &connection, connections) {
+ QObject::disconnect(connection.first);
+ }
+ }
+ m_connectionsCounter.clear();
+ m_signalArgumentTypes.clear();
+}
+
+#endif // SIGNALHANDLER_H