1/****************************************************************************
2**
3** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWebChannel module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#ifndef SIGNALHANDLER_H
41#define SIGNALHANDLER_H
42
43//
44// W A R N I N G
45// -------------
46//
47// This file is not part of the Qt API. It exists purely as an
48// implementation detail. This header file may change from version to
49// version without notice, or even be removed.
50//
51// We mean it.
52//
53
54#include <QObject>
55#include <QHash>
56#include <QVector>
57#include <QMetaMethod>
58#include <QDebug>
59
60QT_BEGIN_NAMESPACE
61
62static const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMethod(method: "destroyed(QObject*)");
63
64/**
65 * The signal handler is similar to QSignalSpy, but geared towards the usecase of the web channel.
66 *
67 * It allows connecting to any number of signals of arbitrary objects and forwards the signal
68 * invocations to the Receiver by calling its signalEmitted function, which takes the object,
69 * signal index and a QVariantList of arguments.
70 */
71template<class Receiver>
72class SignalHandler : public QObject
73{
74public:
75 SignalHandler(Receiver *receiver, QObject *parent = 0);
76
77 /**
78 * Connect to a signal of @p object identified by @p signalIndex.
79 *
80 * If the handler is already connected to the signal, an internal counter is increased,
81 * i.e. the handler never connects multiple times to the same signal.
82 */
83 void connectTo(const QObject *object, const int signalIndex);
84
85 /**
86 * Decrease the connection counter for the connection to the given signal.
87 *
88 * When the counter drops to zero, the connection is disconnected.
89 */
90 void disconnectFrom(const QObject *object, const int signalIndex);
91
92 /**
93 * @internal
94 *
95 * Custom implementation of qt_metacall which calls dispatch() for connected signals.
96 */
97 int qt_metacall(QMetaObject::Call call, int methodId, void **args) override;
98
99 /**
100 * Reset all connections, useful for benchmarks.
101 */
102 void clear();
103
104 /**
105 * Fully remove and disconnect an object from handler
106 */
107 void remove(const QObject *object);
108
109private:
110 /**
111 * Exctract the arguments of a signal call and pass them to the receiver.
112 *
113 * The @p argumentData is converted to a QVariantList and then passed to the receiver's
114 * signalEmitted method.
115 */
116 void dispatch(const QObject *object, const int signalIdx, void **argumentData);
117
118 void setupSignalArgumentTypes(const QMetaObject *metaObject, const QMetaMethod &signal);
119
120 Receiver *m_receiver;
121
122 // maps meta object -> signalIndex -> list of arguments
123 // NOTE: This data is "leaked" on disconnect until deletion of the handler, is this a problem?
124 typedef QVector<int> ArgumentTypeList;
125 typedef QHash<int, ArgumentTypeList> SignalArgumentHash;
126 QHash<const QMetaObject *, SignalArgumentHash > m_signalArgumentTypes;
127
128 /*
129 * Tracks how many connections are active to object signals.
130 *
131 * Maps object -> signalIndex -> pair of connection and number of connections
132 *
133 * Note that the handler is connected to the signal only once, whereas clients
134 * may have connected multiple times.
135 *
136 * TODO: Move more of this logic to the HTML client side, esp. the connection counting.
137 */
138 typedef QPair<QMetaObject::Connection, int> ConnectionPair;
139 typedef QHash<int, ConnectionPair> SignalConnectionHash;
140 typedef QHash<const QObject*, SignalConnectionHash> ConnectionHash;
141 ConnectionHash m_connectionsCounter;
142};
143
144template<class Receiver>
145SignalHandler<Receiver>::SignalHandler(Receiver *receiver, QObject *parent)
146 : QObject(parent)
147 , m_receiver(receiver)
148{
149 // we must know the arguments of a destroyed signal for the global static meta object of QObject
150 // otherwise, we might end up with missing m_signalArgumentTypes information in dispatch
151 setupSignalArgumentTypes(metaObject: &QObject::staticMetaObject, signal: QObject::staticMetaObject.method(index: s_destroyedSignalIndex));
152}
153
154/**
155 * Find and return the signal of index @p signalIndex in the meta object of @p object and return it.
156 *
157 * The return value is also verified to ensure it is a signal.
158 */
159inline QMetaMethod findSignal(const QMetaObject *metaObject, const int signalIndex)
160{
161 QMetaMethod signal = metaObject->method(index: signalIndex);
162 if (!signal.isValid()) {
163 qWarning(msg: "Cannot find signal with index %d of object %s", signalIndex, metaObject->className());
164 return QMetaMethod();
165 }
166 Q_ASSERT(signal.methodType() == QMetaMethod::Signal);
167 return signal;
168}
169
170template<class Receiver>
171void SignalHandler<Receiver>::connectTo(const QObject *object, const int signalIndex)
172{
173 const QMetaObject *metaObject = object->metaObject();
174 const QMetaMethod &signal = findSignal(metaObject, signalIndex);
175 if (!signal.isValid()) {
176 return;
177 }
178
179 ConnectionPair &connectionCounter = m_connectionsCounter[object][signalIndex];
180 if (connectionCounter.first) {
181 // increase connection counter if already connected
182 ++connectionCounter.second;
183 return;
184 } // otherwise not yet connected, do so now
185
186 static const int memberOffset = QObject::staticMetaObject.methodCount();
187 QMetaObject::Connection connection = QMetaObject::connect(sender: object, signal_index: signal.methodIndex(), receiver: this, method_index: memberOffset + signalIndex, type: Qt::AutoConnection, types: 0);
188 if (!connection) {
189 qWarning() << "SignalHandler: QMetaObject::connect returned false. Unable to connect to" << object << signal.name() << signal.methodSignature();
190 return;
191 }
192 connectionCounter.first = connection;
193 connectionCounter.second = 1;
194
195 setupSignalArgumentTypes(metaObject, signal);
196}
197
198template<class Receiver>
199void SignalHandler<Receiver>::setupSignalArgumentTypes(const QMetaObject *metaObject, const QMetaMethod &signal)
200{
201 if (m_signalArgumentTypes.value(key: metaObject).contains(key: signal.methodIndex())) {
202 return;
203 }
204 // find the type ids of the signal parameters, see also QSignalSpy::initArgs
205 QVector<int> args;
206 args.reserve(size: signal.parameterCount());
207 for (int i = 0; i < signal.parameterCount(); ++i) {
208 int tp = signal.parameterType(index: i);
209 if (tp == QMetaType::UnknownType) {
210 qWarning(msg: "Don't know how to handle '%s', use qRegisterMetaType to register it.",
211 signal.parameterNames().at(i).constData());
212 }
213 args << tp;
214 }
215
216 m_signalArgumentTypes[metaObject][signal.methodIndex()] = args;
217}
218
219template<class Receiver>
220void SignalHandler<Receiver>::dispatch(const QObject *object, const int signalIdx, void **argumentData)
221{
222 Q_ASSERT(m_signalArgumentTypes.contains(object->metaObject()));
223 const QHash<int, QVector<int> > &objectSignalArgumentTypes = m_signalArgumentTypes.value(key: object->metaObject());
224 QHash<int, QVector<int> >::const_iterator signalIt = objectSignalArgumentTypes.constFind(key: signalIdx);
225 if (signalIt == objectSignalArgumentTypes.constEnd()) {
226 // not connected to this signal, skip
227 return;
228 }
229 const QVector<int> &argumentTypes = *signalIt;
230 QVariantList arguments;
231 arguments.reserve(alloc: argumentTypes.count());
232 // TODO: basic overload resolution based on number of arguments?
233 for (int i = 0; i < argumentTypes.count(); ++i) {
234 const QMetaType::Type type = static_cast<QMetaType::Type>(argumentTypes.at(i));
235 QVariant arg;
236 if (type == QMetaType::QVariant) {
237 arg = *reinterpret_cast<QVariant *>(argumentData[i + 1]);
238 } else {
239 arg = QVariant(type, argumentData[i + 1]);
240 }
241 arguments.append(t: arg);
242 }
243 m_receiver->signalEmitted(object, signalIdx, arguments);
244}
245
246template<class Receiver>
247void SignalHandler<Receiver>::disconnectFrom(const QObject *object, const int signalIndex)
248{
249 Q_ASSERT(m_connectionsCounter.value(object).contains(signalIndex));
250 ConnectionPair &connection = m_connectionsCounter[object][signalIndex];
251 --connection.second;
252 if (!connection.second || !connection.first) {
253 QObject::disconnect(connection.first);
254 m_connectionsCounter[object].remove(key: signalIndex);
255 if (m_connectionsCounter[object].isEmpty()) {
256 m_connectionsCounter.remove(key: object);
257 }
258 }
259}
260
261template<class Receiver>
262int SignalHandler<Receiver>::qt_metacall(QMetaObject::Call call, int methodId, void **args)
263{
264 methodId = QObject::qt_metacall(call, methodId, args);
265 if (methodId < 0)
266 return methodId;
267
268 if (call == QMetaObject::InvokeMetaMethod) {
269 const QObject *object = sender();
270 Q_ASSERT(object);
271 Q_ASSERT(senderSignalIndex() == methodId);
272 Q_ASSERT(m_connectionsCounter.contains(object));
273 Q_ASSERT(m_connectionsCounter.value(object).contains(methodId));
274
275 dispatch(object, signalIdx: methodId, argumentData: args);
276
277 return -1;
278 }
279 return methodId;
280}
281
282template<class Receiver>
283void SignalHandler<Receiver>::clear()
284{
285 foreach (const SignalConnectionHash &connections, m_connectionsCounter) {
286 foreach (const ConnectionPair &connection, connections) {
287 QObject::disconnect(connection.first);
288 }
289 }
290 m_connectionsCounter.clear();
291 const SignalArgumentHash keep = m_signalArgumentTypes.take(akey: &QObject::staticMetaObject);
292 m_signalArgumentTypes.clear();
293 m_signalArgumentTypes[&QObject::staticMetaObject] = keep;
294}
295
296template<class Receiver>
297void SignalHandler<Receiver>::remove(const QObject *object)
298{
299 Q_ASSERT(m_connectionsCounter.contains(object));
300 const SignalConnectionHash &connections = m_connectionsCounter.value(key: object);
301 foreach (const ConnectionPair &connection, connections) {
302 QObject::disconnect(connection.first);
303 }
304 m_connectionsCounter.remove(key: object);
305}
306
307QT_END_NAMESPACE
308
309#endif // SIGNALHANDLER_H
310

source code of qtwebchannel/src/webchannel/signalhandler_p.h