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 | |
60 | QT_BEGIN_NAMESPACE |
61 | |
62 | static 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 | */ |
71 | template<class Receiver> |
72 | class SignalHandler : public QObject |
73 | { |
74 | public: |
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 | |
109 | private: |
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 | |
144 | template<class Receiver> |
145 | SignalHandler<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 | */ |
159 | inline 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 | |
170 | template<class Receiver> |
171 | void 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 | |
198 | template<class Receiver> |
199 | void 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 | |
219 | template<class Receiver> |
220 | void 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 | |
246 | template<class Receiver> |
247 | void 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 | |
261 | template<class Receiver> |
262 | int 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 | |
282 | template<class Receiver> |
283 | void 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 | |
296 | template<class Receiver> |
297 | void 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 | |
307 | QT_END_NAMESPACE |
308 | |
309 | #endif // SIGNALHANDLER_H |
310 | |