File indexing completed on 2025-02-21 10:12:22
0001
0002
0003
0004 #ifndef QSIGNALSPY_H
0005 #define QSIGNALSPY_H
0006
0007 #include <QtCore/qbytearray.h>
0008 #include <QtCore/qlist.h>
0009 #include <QtCore/qobject.h>
0010 #include <QtCore/qmetaobject.h>
0011 #include <QtTest/qtesteventloop.h>
0012 #include <QtCore/qvariant.h>
0013 #include <QtCore/qmutex.h>
0014
0015 QT_BEGIN_NAMESPACE
0016
0017
0018 class QVariant;
0019
0020 class QSignalSpy: public QObject, public QList<QList<QVariant> >
0021 {
0022 public:
0023 explicit QSignalSpy(const QObject *obj, const char *aSignal)
0024 {
0025 if (!isObjectValid(obj))
0026 return;
0027
0028 if (!aSignal) {
0029 qWarning("QSignalSpy: Null signal name is not valid");
0030 return;
0031 }
0032
0033 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
0034 qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
0035 return;
0036 }
0037
0038 const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
0039 const QMetaObject * const mo = obj->metaObject();
0040 const int sigIndex = mo->indexOfMethod(ba.constData());
0041 if (sigIndex < 0) {
0042 qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
0043 return;
0044 }
0045
0046 initArgs(mo->method(sigIndex), obj);
0047 if (!connectToSignal(obj, sigIndex))
0048 return;
0049
0050 sig = ba;
0051 }
0052
0053 #ifdef Q_QDOC
0054 template <typename PointerToMemberFunction>
0055 QSignalSpy(const QObject *object, PointerToMemberFunction signal);
0056 #else
0057 template <typename Func>
0058 QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
0059 {
0060 if (!isObjectValid(obj))
0061 return;
0062
0063 if (!signal0) {
0064 qWarning("QSignalSpy: Null signal pointer is not valid");
0065 return;
0066 }
0067
0068 const QMetaObject * const mo = obj->metaObject();
0069 const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
0070 const int sigIndex = signalMetaMethod.methodIndex();
0071
0072 if (!isSignalMetaMethodValid(signalMetaMethod))
0073 return;
0074
0075 initArgs(mo->method(sigIndex), obj);
0076 if (!connectToSignal(obj, sigIndex))
0077 return;
0078
0079 sig = signalMetaMethod.methodSignature();
0080 }
0081 #endif
0082
0083 QSignalSpy(const QObject *obj, const QMetaMethod &signal)
0084 {
0085 if (isObjectValid(obj) && isSignalMetaMethodValid(signal)) {
0086 initArgs(signal, obj);
0087 if (!connectToSignal(obj, signal.methodIndex()))
0088 return;
0089
0090 sig = signal.methodSignature();
0091 }
0092 }
0093
0094 inline bool isValid() const { return !sig.isEmpty(); }
0095 inline QByteArray signal() const { return sig; }
0096
0097 bool wait(int timeout)
0098 { return wait(std::chrono::milliseconds{timeout}); }
0099
0100 bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{5})
0101 {
0102 QMutexLocker locker(&m_mutex);
0103 Q_ASSERT(!m_waiting);
0104 const qsizetype origCount = size();
0105 m_waiting = true;
0106 locker.unlock();
0107
0108 m_loop.enterLoop(timeout);
0109
0110 locker.relock();
0111 m_waiting = false;
0112 return size() > origCount;
0113 }
0114
0115 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
0116 {
0117 methodId = QObject::qt_metacall(call, methodId, a);
0118 if (methodId < 0)
0119 return methodId;
0120
0121 if (call == QMetaObject::InvokeMetaMethod) {
0122 if (methodId == 0) {
0123 appendArgs(a);
0124 }
0125 --methodId;
0126 }
0127 return methodId;
0128 }
0129
0130 private:
0131 bool connectToSignal(const QObject *sender, int sigIndex)
0132 {
0133 static const int memberOffset = QObject::staticMetaObject.methodCount();
0134 const bool connected = QMetaObject::connect(
0135 sender, sigIndex, this, memberOffset, Qt::DirectConnection, nullptr);
0136
0137 if (!connected)
0138 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
0139
0140 return connected;
0141 }
0142
0143 static bool isSignalMetaMethodValid(const QMetaMethod &signal)
0144 {
0145 const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal;
0146
0147 if (!valid)
0148 qWarning("QSignalSpy: Not a valid signal: '%s'", signal.methodSignature().constData());
0149
0150 return valid;
0151 }
0152
0153 static bool isObjectValid(const QObject *object)
0154 {
0155 const bool valid = !!object;
0156
0157 if (!valid)
0158 qWarning("QSignalSpy: Cannot spy on a null object");
0159
0160 return valid;
0161 }
0162
0163 void initArgs(const QMetaMethod &member, const QObject *obj)
0164 {
0165 QMutexLocker locker(&m_mutex);
0166 args.reserve(member.parameterCount());
0167 for (int i = 0; i < member.parameterCount(); ++i) {
0168 QMetaType tp = member.parameterMetaType(i);
0169 if (!tp.isValid() && obj) {
0170 locker.unlock();
0171 void *argv[] = { &tp, &i };
0172 QMetaObject::metacall(const_cast<QObject*>(obj),
0173 QMetaObject::RegisterMethodArgumentMetaType,
0174 member.methodIndex(), argv);
0175 locker.relock();
0176 }
0177 if (!tp.isValid()) {
0178 qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
0179 " use qRegisterMetaType to register it.",
0180 member.parameterNames().at(i).constData(),
0181 member.parameterTypes().at(i).constData(),
0182 member.name().constData());
0183 }
0184 args << tp.id();
0185 }
0186 }
0187
0188 void appendArgs(void **a)
0189 {
0190 QMutexLocker locker(&m_mutex);
0191 QList<QVariant> list;
0192 list.reserve(args.size());
0193 for (qsizetype i = 0; i < args.size(); ++i) {
0194 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
0195 if (type == QMetaType::QVariant)
0196 list << *reinterpret_cast<QVariant *>(a[i + 1]);
0197 else
0198 list << QVariant(QMetaType(type), a[i + 1]);
0199 }
0200 append(std::move(list));
0201
0202 if (m_waiting) {
0203 locker.unlock();
0204 m_loop.exitLoop();
0205 }
0206 }
0207
0208
0209 QByteArray sig;
0210
0211 QList<int> args;
0212
0213 QTestEventLoop m_loop;
0214 bool m_waiting = false;
0215 static inline QMutex m_mutex;
0216 };
0217
0218 QT_END_NAMESPACE
0219
0220 #endif