blob: 27afadb4de71651075ce84f4bbe719a4152bf7fc [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
** Contact: https://www.qt.io/licensing/
**
** 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 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 Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef SIGNALHANDLER_H
#define SIGNALHANDLER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QObject>
#include <QHash>
#include <QVector>
#include <QMetaMethod>
#include <QDebug>
QT_BEGIN_NAMESPACE
static const int s_destroyedSignalIndex = QObject::staticMetaObject.indexOfMethod("destroyed(QObject*)");
/**
* 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);
/**
* 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) override;
/**
* Reset all connections, useful for benchmarks.
*/
void clear();
/**
* Fully remove and disconnect an object from handler
*/
void remove(const QObject *object);
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);
void setupSignalArgumentTypes(const QMetaObject *metaObject, const QMetaMethod &signal);
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;
};
template<class Receiver>
SignalHandler<Receiver>::SignalHandler(Receiver *receiver, QObject *parent)
: QObject(parent)
, m_receiver(receiver)
{
// we must know the arguments of a destroyed signal for the global static meta object of QObject
// otherwise, we might end up with missing m_signalArgumentTypes information in dispatch
setupSignalArgumentTypes(&QObject::staticMetaObject, QObject::staticMetaObject.method(s_destroyedSignalIndex));
}
/**
* 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.
*/
inline 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
static const int memberOffset = QObject::staticMetaObject.methodCount();
QMetaObject::Connection connection = QMetaObject::connect(object, signal.methodIndex(), this, memberOffset + signalIndex, Qt::AutoConnection, 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;
setupSignalArgumentTypes(metaObject, signal);
}
template<class Receiver>
void SignalHandler<Receiver>::setupSignalArgumentTypes(const QMetaObject *metaObject, const QMetaMethod &signal)
{
if (m_signalArgumentTypes.value(metaObject).contains(signal.methodIndex())) {
return;
}
// 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) {
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) {
const QObject *object = sender();
Q_ASSERT(object);
Q_ASSERT(senderSignalIndex() == methodId);
Q_ASSERT(m_connectionsCounter.contains(object));
Q_ASSERT(m_connectionsCounter.value(object).contains(methodId));
dispatch(object, methodId, args);
return -1;
}
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();
const SignalArgumentHash keep = m_signalArgumentTypes.take(&QObject::staticMetaObject);
m_signalArgumentTypes.clear();
m_signalArgumentTypes[&QObject::staticMetaObject] = keep;
}
template<class Receiver>
void SignalHandler<Receiver>::remove(const QObject *object)
{
Q_ASSERT(m_connectionsCounter.contains(object));
const SignalConnectionHash &connections = m_connectionsCounter.value(object);
foreach (const ConnectionPair &connection, connections) {
QObject::disconnect(connection.first);
}
m_connectionsCounter.remove(object);
}
QT_END_NAMESPACE
#endif // SIGNALHANDLER_H