1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtTest 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 QSIGNALSPY_H
41#define QSIGNALSPY_H
42
43#include <QtCore/qbytearray.h>
44#include <QtCore/qlist.h>
45#include <QtCore/qobject.h>
46#include <QtCore/qmetaobject.h>
47#include <QtCore/qvariant.h>
48#include <QtCore/qvector.h>
49#include <QtTest/qtesteventloop.h>
50
51QT_BEGIN_NAMESPACE
52
53
54class QVariant;
55
56class QSignalSpy: public QObject, public QList<QList<QVariant> >
57{
58public:
59 explicit QSignalSpy(const QObject *obj, const char *aSignal)
60 : m_waiting(false)
61 {
62 static const int memberOffset = QObject::staticMetaObject.methodCount();
63 if (!obj) {
64 qWarning("QSignalSpy: Cannot spy on a null object");
65 return;
66 }
67
68 if (!aSignal) {
69 qWarning("QSignalSpy: Null signal name is not valid");
70 return;
71 }
72
73 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
74 qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
75 return;
76 }
77
78 const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
79 const QMetaObject * const mo = obj->metaObject();
80 const int sigIndex = mo->indexOfMethod(ba.constData());
81 if (sigIndex < 0) {
82 qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
83 return;
84 }
85
86 if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
87 Qt::DirectConnection, nullptr)) {
88 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
89 return;
90 }
91 sig = ba;
92 initArgs(mo->method(sigIndex), obj);
93 }
94
95#ifdef Q_CLANG_QDOC
96 template <typename PointerToMemberFunction>
97 QSignalSpy(const QObject *object, PointerToMemberFunction signal);
98#else
99 template <typename Func>
100 QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
101 : m_waiting(false)
102 {
103 static const int memberOffset = QObject::staticMetaObject.methodCount();
104 if (!obj) {
105 qWarning("QSignalSpy: Cannot spy on a null object");
106 return;
107 }
108
109 if (!signal0) {
110 qWarning("QSignalSpy: Null signal name is not valid");
111 return;
112 }
113
114 const QMetaObject * const mo = obj->metaObject();
115 const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
116 const int sigIndex = signalMetaMethod.methodIndex();
117 if (!signalMetaMethod.isValid() ||
118 signalMetaMethod.methodType() != QMetaMethod::Signal) {
119 qWarning("QSignalSpy: Not a valid signal: '%s'",
120 signalMetaMethod.methodSignature().constData());
121 return;
122 }
123
124 if (!QMetaObject::connect(obj, sigIndex, this, memberOffset,
125 Qt::DirectConnection, nullptr)) {
126 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
127 return;
128 }
129 sig = signalMetaMethod.methodSignature();
130 initArgs(mo->method(sigIndex), obj);
131 }
132#endif // Q_CLANG_QDOC
133
134 inline bool isValid() const { return !sig.isEmpty(); }
135 inline QByteArray signal() const { return sig; }
136
137 bool wait(int timeout = 5000)
138 {
139 Q_ASSERT(!m_waiting);
140 const int origCount = count();
141 m_waiting = true;
142 m_loop.enterLoopMSecs(timeout);
143 m_waiting = false;
144 return count() > origCount;
145 }
146
147 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
148 {
149 methodId = QObject::qt_metacall(call, methodId, a);
150 if (methodId < 0)
151 return methodId;
152
153 if (call == QMetaObject::InvokeMetaMethod) {
154 if (methodId == 0) {
155 appendArgs(a);
156 }
157 --methodId;
158 }
159 return methodId;
160 }
161
162private:
163 void initArgs(const QMetaMethod &member, const QObject *obj)
164 {
165 args.reserve(member.parameterCount());
166 for (int i = 0; i < member.parameterCount(); ++i) {
167 int tp = member.parameterType(i);
168 if (tp == QMetaType::UnknownType && obj) {
169 void *argv[] = { &tp, &i };
170 QMetaObject::metacall(const_cast<QObject*>(obj),
171 QMetaObject::RegisterMethodArgumentMetaType,
172 member.methodIndex(), argv);
173 if (tp == -1)
174 tp = QMetaType::UnknownType;
175 }
176 if (tp == QMetaType::UnknownType) {
177 qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
178 " use qRegisterMetaType to register it.",
179 member.parameterNames().at(i).constData(),
180 member.parameterTypes().at(i).constData(),
181 member.name().constData());
182 }
183 args << tp;
184 }
185 }
186
187 void appendArgs(void **a)
188 {
189 QList<QVariant> list;
190 list.reserve(args.count());
191 for (int i = 0; i < args.count(); ++i) {
192 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
193 if (type == QMetaType::QVariant)
194 list << *reinterpret_cast<QVariant *>(a[i + 1]);
195 else
196 list << QVariant(type, a[i + 1]);
197 }
198 append(list);
199
200 if (m_waiting)
201 m_loop.exitLoop();
202 }
203
204 // the full, normalized signal name
205 QByteArray sig;
206 // holds the QMetaType types for the argument list of the signal
207 QVector<int> args;
208
209 QTestEventLoop m_loop;
210 bool m_waiting;
211};
212
213QT_END_NAMESPACE
214
215#endif
216