1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QSIGNALSPY_H |
5 | #define QSIGNALSPY_H |
6 | |
7 | #include <QtCore/qbytearray.h> |
8 | #include <QtCore/qlist.h> |
9 | #include <QtCore/qobject.h> |
10 | #include <QtCore/qmetaobject.h> |
11 | #include <QtTest/qtesteventloop.h> |
12 | #include <QtCore/qvariant.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | |
17 | class QVariant; |
18 | |
19 | class QSignalSpy: public QObject, public QList<QList<QVariant> > |
20 | { |
21 | public: |
22 | explicit QSignalSpy(const QObject *obj, const char *aSignal) |
23 | : m_waiting(false) |
24 | { |
25 | if (!isObjectValid(object: obj)) |
26 | return; |
27 | |
28 | if (!aSignal) { |
29 | qWarning(msg: "QSignalSpy: Null signal name is not valid" ); |
30 | return; |
31 | } |
32 | |
33 | if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) { |
34 | qWarning(msg: "QSignalSpy: Not a valid signal, use the SIGNAL macro" ); |
35 | return; |
36 | } |
37 | |
38 | const QByteArray ba = QMetaObject::normalizedSignature(method: aSignal + 1); |
39 | const QMetaObject * const mo = obj->metaObject(); |
40 | const int sigIndex = mo->indexOfMethod(method: ba.constData()); |
41 | if (sigIndex < 0) { |
42 | qWarning(msg: "QSignalSpy: No such signal: '%s'" , ba.constData()); |
43 | return; |
44 | } |
45 | |
46 | if (!connectToSignal(sender: obj, sigIndex)) |
47 | return; |
48 | |
49 | sig = ba; |
50 | initArgs(member: mo->method(index: sigIndex), obj); |
51 | } |
52 | |
53 | #ifdef Q_QDOC |
54 | template <typename PointerToMemberFunction> |
55 | QSignalSpy(const QObject *object, PointerToMemberFunction signal); |
56 | #else |
57 | template <typename Func> |
58 | QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0) |
59 | : m_waiting(false) |
60 | { |
61 | if (!isObjectValid(object: obj)) |
62 | return; |
63 | |
64 | if (!signal0) { |
65 | qWarning(msg: "QSignalSpy: Null signal name is not valid" ); |
66 | return; |
67 | } |
68 | |
69 | const QMetaObject * const mo = obj->metaObject(); |
70 | const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0); |
71 | const int sigIndex = signalMetaMethod.methodIndex(); |
72 | |
73 | if (!isSignalMetaMethodValid(signal: signalMetaMethod)) |
74 | return; |
75 | |
76 | if (!connectToSignal(sender: obj, sigIndex)) |
77 | return; |
78 | |
79 | sig = signalMetaMethod.methodSignature(); |
80 | initArgs(member: mo->method(index: sigIndex), obj); |
81 | } |
82 | #endif // Q_QDOC |
83 | |
84 | QSignalSpy(const QObject *obj, const QMetaMethod &signal) |
85 | : m_waiting(false) |
86 | { |
87 | if (isObjectValid(object: obj) && isSignalMetaMethodValid(signal) && |
88 | connectToSignal(sender: obj, sigIndex: signal.methodIndex())) { |
89 | sig = signal.methodSignature(); |
90 | initArgs(member: signal, obj); |
91 | } |
92 | } |
93 | |
94 | inline bool isValid() const { return !sig.isEmpty(); } |
95 | inline QByteArray signal() const { return sig; } |
96 | |
97 | bool wait(int timeout) |
98 | { return wait(timeout: std::chrono::milliseconds{timeout}); } |
99 | |
100 | bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{5}) |
101 | { |
102 | Q_ASSERT(!m_waiting); |
103 | const qsizetype origCount = size(); |
104 | m_waiting = true; |
105 | m_loop.enterLoop(msecs: timeout); |
106 | m_waiting = false; |
107 | return size() > origCount; |
108 | } |
109 | |
110 | int qt_metacall(QMetaObject::Call call, int methodId, void **a) override |
111 | { |
112 | methodId = QObject::qt_metacall(call, methodId, a); |
113 | if (methodId < 0) |
114 | return methodId; |
115 | |
116 | if (call == QMetaObject::InvokeMetaMethod) { |
117 | if (methodId == 0) { |
118 | appendArgs(a); |
119 | } |
120 | --methodId; |
121 | } |
122 | return methodId; |
123 | } |
124 | |
125 | private: |
126 | bool connectToSignal(const QObject *sender, int sigIndex) |
127 | { |
128 | static const int memberOffset = QObject::staticMetaObject.methodCount(); |
129 | const bool connected = QMetaObject::connect( |
130 | sender, signal_index: sigIndex, receiver: this, method_index: memberOffset, type: Qt::DirectConnection, types: nullptr); |
131 | |
132 | if (!connected) |
133 | qWarning(msg: "QSignalSpy: QMetaObject::connect returned false. Unable to connect." ); |
134 | |
135 | return connected; |
136 | } |
137 | |
138 | static bool isSignalMetaMethodValid(const QMetaMethod &signal) |
139 | { |
140 | const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal; |
141 | |
142 | if (!valid) |
143 | qWarning(msg: "QSignalSpy: Not a valid signal: '%s'" , signal.methodSignature().constData()); |
144 | |
145 | return valid; |
146 | } |
147 | |
148 | static bool isObjectValid(const QObject *object) |
149 | { |
150 | const bool valid = !!object; |
151 | |
152 | if (!valid) |
153 | qWarning(msg: "QSignalSpy: Cannot spy on a null object" ); |
154 | |
155 | return valid; |
156 | } |
157 | |
158 | void initArgs(const QMetaMethod &member, const QObject *obj) |
159 | { |
160 | args.reserve(size: member.parameterCount()); |
161 | for (int i = 0; i < member.parameterCount(); ++i) { |
162 | QMetaType tp = member.parameterMetaType(index: i); |
163 | if (!tp.isValid() && obj) { |
164 | void *argv[] = { &tp, &i }; |
165 | QMetaObject::metacall(const_cast<QObject*>(obj), |
166 | QMetaObject::RegisterMethodArgumentMetaType, |
167 | member.methodIndex(), argv); |
168 | } |
169 | if (!tp.isValid()) { |
170 | qWarning(msg: "QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s'," |
171 | " use qRegisterMetaType to register it." , |
172 | member.parameterNames().at(i).constData(), |
173 | member.parameterTypes().at(i).constData(), |
174 | member.name().constData()); |
175 | } |
176 | args << tp.id(); |
177 | } |
178 | } |
179 | |
180 | void appendArgs(void **a) |
181 | { |
182 | QList<QVariant> list; |
183 | list.reserve(size: args.size()); |
184 | for (int i = 0; i < args.size(); ++i) { |
185 | const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i)); |
186 | if (type == QMetaType::QVariant) |
187 | list << *reinterpret_cast<QVariant *>(a[i + 1]); |
188 | else |
189 | list << QVariant(QMetaType(type), a[i + 1]); |
190 | } |
191 | append(t: list); |
192 | |
193 | if (m_waiting) |
194 | m_loop.exitLoop(); |
195 | } |
196 | |
197 | // the full, normalized signal name |
198 | QByteArray sig; |
199 | // holds the QMetaType types for the argument list of the signal |
200 | QList<int> args; |
201 | |
202 | QTestEventLoop m_loop; |
203 | bool m_waiting; |
204 | }; |
205 | |
206 | QT_END_NAMESPACE |
207 | |
208 | #endif |
209 | |