| 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 | |