Add a QMetaObject::connect function
This adds a QMetaObject::connect function allowing to connect a QMetaMethod to a PMF or a functor. This is most useful when connecting a signal retrieved by QMetaObject introspection to a lambda with a capture. This aims to replace the remaining usecases of QSignalMapper with a lighter alternative not relying on sender()/senderSignalIndex(). Note that this function doesn't support implicit conversion between argument or return types. It does support lambda with captures and signals returning values. [ChangeLog][QtCore][QMetaObject] Added a QMetaObject::connect function for connecting a signal's QMetaMethod to a member function or a functor (lambdas, etc.) Fixes: QTBUG-120631 Change-Id: Ifca73cd047478f6a262728493b16a942bff36e40 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
aad0ab897f
commit
d8b4eb1a17
@ -3910,6 +3910,96 @@ void QMetaObject::connectSlotsByName(QObject *o)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn template<typename PointerToMemberFunction> QMetaObject::Connection QMetaObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
|
||||
|
||||
\threadsafe
|
||||
\overload connect()
|
||||
|
||||
\since 6.10
|
||||
|
||||
Creates a connection of the given \a type from the \a signal in
|
||||
the \a sender object to the \a method in the \a receiver object.
|
||||
Returns a handle to the connection that can be used to disconnect
|
||||
it later.
|
||||
|
||||
The Connection handle will be invalid if it cannot create the
|
||||
connection, for example, the parameters were invalid.
|
||||
You can check if the QMetaObject::Connection is valid by casting
|
||||
it to a bool.
|
||||
Pass the returned handle to QObject::disconnect() to disconnect
|
||||
the connection.
|
||||
|
||||
A slot can be connected to a given signal if the signal has at
|
||||
least as many arguments as the slot. There must be an exact match
|
||||
between the corresponding signal and slot arguments, implicit
|
||||
conversions and type checking are not handled by this function.
|
||||
Overloaded slots need to be explicitly be resolved with
|
||||
help of \l qOverload.
|
||||
|
||||
\sa QObject::connect(), QObject::disconnect()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template<typename Functor> QMetaObject::Connection QMetaObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *context, Functor functor, Qt::ConnectionType type)
|
||||
|
||||
\threadsafe
|
||||
\overload connect()
|
||||
|
||||
\since 6.10
|
||||
|
||||
Creates a connection of a given \a type from \a signal in
|
||||
\a sender object to \a functor to be placed in a specific event
|
||||
loop of \a context.
|
||||
Returns a handle to the connection that can be used to disconnect
|
||||
it later.
|
||||
This can be useful for connecting a signal retrieved from
|
||||
meta-object introspection to a lambda capturing local variables.
|
||||
|
||||
\note Qt::UniqueConnections do not work for lambdas, non-member
|
||||
functions and functors; they only apply to member functions.
|
||||
|
||||
The slot function can be any function or functor with with equal
|
||||
or fewer arguments than the signal. There must be an exact match
|
||||
between the corresponding signal and slot arguments, implicit
|
||||
conversions and type checking are not handled by this function.
|
||||
Overloaded functors need to be explicitly be resolved with
|
||||
help of \l qOverload.
|
||||
|
||||
The connection will automatically disconnect if the sender or
|
||||
the context is destroyed.
|
||||
However, you should take care that any objects used within
|
||||
the functor are still alive when the signal is emitted.
|
||||
|
||||
\sa QObject::connect(), QObject::disconnect()
|
||||
*/
|
||||
QMetaObject::Connection QMetaObject::connectImpl(const QObject *sender, const QMetaMethod &signal,
|
||||
const QObject *receiver, void **slot,
|
||||
QtPrivate::QSlotObjectBase *slotObjRaw, Qt::ConnectionType type)
|
||||
{
|
||||
QtPrivate::SlotObjUniquePtr slotObj(slotObjRaw);
|
||||
|
||||
if (!signal.isValid() || signal.methodType() != QMetaMethod::Signal) {
|
||||
qCWarning(lcConnect, "QObject::connect: invalid signal parameter");
|
||||
return QMetaObject::Connection();
|
||||
}
|
||||
|
||||
int signal_index;
|
||||
{
|
||||
int dummy;
|
||||
QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
|
||||
}
|
||||
|
||||
const QMetaObject *senderMetaObject = sender->metaObject();
|
||||
if (signal_index == -1) {
|
||||
qCWarning(lcConnect, "QObject::connect: Can't find signal %s on instance of class %s",
|
||||
signal.methodSignature().constData(), senderMetaObject->className());
|
||||
return QMetaObject::Connection();
|
||||
}
|
||||
|
||||
return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj.release(), type, nullptr, senderMetaObject);
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
A small RAII helper for QSlotObjectBase.
|
||||
|
@ -292,6 +292,19 @@ struct Q_CORE_EXPORT QMetaObject
|
||||
// internal slot-name based connect
|
||||
static void connectSlotsByName(QObject *o);
|
||||
|
||||
#ifdef Q_QDOC
|
||||
template<typename PointerToMemberFunction>
|
||||
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);
|
||||
template<typename PointerToMemberFunction, typename Functor>
|
||||
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection);
|
||||
#else
|
||||
template <typename Func>
|
||||
static inline Connection
|
||||
connect(const QObject *sender, const QMetaMethod &signal,
|
||||
const typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *context, Func &&slot,
|
||||
Qt::ConnectionType type = Qt::AutoConnection);
|
||||
#endif // Q_QDOC
|
||||
|
||||
// internal index-based signal activation
|
||||
static void activate(QObject *sender, int signal_index, void **argv);
|
||||
static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
|
||||
@ -647,6 +660,11 @@ private:
|
||||
static QObject *newInstanceImpl(const QMetaObject *mobj, qsizetype parameterCount,
|
||||
const void **parameters, const char **typeNames,
|
||||
const QtPrivate::QMetaTypeInterface **metaTypes);
|
||||
|
||||
static QMetaObject::Connection connectImpl(const QObject *sender, const QMetaMethod& signal,
|
||||
const QObject *receiver, void **slotPtr,
|
||||
QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type);
|
||||
|
||||
friend class QTimer;
|
||||
friend class QChronoTimer;
|
||||
};
|
||||
@ -677,6 +695,32 @@ public:
|
||||
void swap(Connection &other) noexcept { qt_ptr_swap(d_ptr, other.d_ptr); }
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
QMetaObject::Connection
|
||||
QMetaObject::connect(const QObject *sender, const QMetaMethod &signal,
|
||||
const typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *context, Func &&slot,
|
||||
Qt::ConnectionType type)
|
||||
{
|
||||
using Slot = std::decay_t<Func>;
|
||||
using FunctionSlotType = QtPrivate::FunctionPointer<Slot>;
|
||||
void **pSlot = nullptr;
|
||||
QtPrivate::QSlotObjectBase *slotObject;
|
||||
if constexpr (FunctionSlotType::ArgumentCount != -1) {
|
||||
slotObject = new QtPrivate::QCallableObject<Slot, typename FunctionSlotType::Arguments, typename FunctionSlotType::ReturnType>(std::forward<Func>(slot));
|
||||
if constexpr (FunctionSlotType::IsPointerToMemberFunction) {
|
||||
pSlot = const_cast<void **>(reinterpret_cast<void *const *>(&slot));
|
||||
} else {
|
||||
Q_ASSERT_X((type & Qt::UniqueConnection) == 0, "",
|
||||
"QObject::connect: Unique connection requires the slot to be a pointer to "
|
||||
"a member function of a QObject subclass.");
|
||||
}
|
||||
} else {
|
||||
using FunctorSlotType = QtPrivate::FunctionPointer<decltype(&Slot::operator())>;
|
||||
slotObject = new QtPrivate::QCallableObject<Slot, typename FunctorSlotType::Arguments, typename FunctorSlotType::ReturnType>(std::forward<Func>(slot));
|
||||
}
|
||||
return QMetaObject::connectImpl(sender, signal, context, pSlot, slotObject, type);
|
||||
}
|
||||
|
||||
inline void swap(QMetaObject::Connection &lhs, QMetaObject::Connection &rhs) noexcept
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
|
@ -354,6 +354,10 @@ private slots:
|
||||
|
||||
void notifySignalsInParentClass();
|
||||
|
||||
void connectByMetaMethodToPMF();
|
||||
void connectByMetaMethodToFunctor();
|
||||
void connectByMetaMethodToFreeFunction();
|
||||
|
||||
signals:
|
||||
void value6Changed();
|
||||
void value7Changed(const QString &);
|
||||
@ -561,6 +565,7 @@ signals:
|
||||
QString sig1(QString s1);
|
||||
void sig10(QString s1, QString s2, QString s3, QString s4, QString s5, QString s6, QString s7,
|
||||
QString s8, QString s9, QString s10);
|
||||
void sigWithOneUnregisteredParameterType(QString a1, const MyForwardDeclaredType &a2);
|
||||
|
||||
protected:
|
||||
QtTestObject(QVariant) {}
|
||||
@ -3039,5 +3044,124 @@ void tst_QMetaObject::notifySignalsInParentClass()
|
||||
obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value3")).notifySignal();
|
||||
}
|
||||
|
||||
void tst_QMetaObject::connectByMetaMethodToPMF()
|
||||
{
|
||||
QtTestObject o;
|
||||
QObject::disconnect(&o, nullptr, nullptr, nullptr);
|
||||
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
|
||||
|
||||
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, &QtTestObject::sl0);
|
||||
QVERIFY(connection);
|
||||
|
||||
QVERIFY(o.slotResult.isEmpty());
|
||||
emit o.sig0();
|
||||
QCOMPARE(o.slotResult, u"sl0"_s);
|
||||
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
o.slotResult = QString();
|
||||
emit o.sig0();
|
||||
QVERIFY(o.slotResult.isEmpty());
|
||||
|
||||
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
|
||||
connection = QMetaObject::connect(&o, sig1Signal, &o, &QtTestObject::sl1);
|
||||
QVERIFY(connection);
|
||||
QCOMPARE(emit o.sig1(u"toto"_s), u"yessir"_s);
|
||||
QCOMPARE(o.slotResult, u"sl1:toto"_s);
|
||||
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
o.slotResult = QString();
|
||||
QCOMPARE(emit o.sig1(u"tata"_s), QString());
|
||||
QVERIFY(o.slotResult.isEmpty());
|
||||
|
||||
connection = QMetaObject::connect(&o, sig0Signal, &o, qOverload<>(&QtTestObject::overloadedSlot));
|
||||
QVERIFY(connection);
|
||||
emit o.sig0();
|
||||
QCOMPARE(o.slotResult, u"overloadedSlot"_s);
|
||||
|
||||
o.slotResult = QString();
|
||||
connection = QMetaObject::connect(&o, sig1Signal, &o, &QtTestObject::sl1, Qt::QueuedConnection);
|
||||
QVERIFY(connection);
|
||||
QCOMPARE(emit o.sig1(u"titi"_s), QString());
|
||||
QVERIFY(o.slotResult.isEmpty());
|
||||
|
||||
qApp->processEvents(QEventLoop::AllEvents);
|
||||
QCOMPARE(o.slotResult, u"sl1:titi"_s);
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
|
||||
QMetaMethod sigWithOneUnregisteredParameterTypeSignal = QMetaMethod::fromSignal(&QtTestObject::sigWithOneUnregisteredParameterType);
|
||||
connection = QMetaObject::connect(&o, sigWithOneUnregisteredParameterTypeSignal, &o, &QtTestObject::slotWithOneUnregisteredParameterType);
|
||||
QVERIFY(connection);
|
||||
emit o.sigWithOneUnregisteredParameterType(u"tutu"_s, getForwardDeclaredType());
|
||||
QCOMPARE(o.slotResult, u"slotWithUnregisteredReturnType-tutu"_s);
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
}
|
||||
|
||||
void tst_QMetaObject::connectByMetaMethodToFunctor()
|
||||
{
|
||||
QtTestObject o;
|
||||
QObject::disconnect(&o, nullptr, nullptr, nullptr);
|
||||
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
|
||||
|
||||
int called = 0;
|
||||
|
||||
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, [&called]() { ++called; });
|
||||
QVERIFY(connection);
|
||||
|
||||
emit o.sig0();
|
||||
QCOMPARE(called, 1);
|
||||
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
|
||||
emit o.sig0();
|
||||
QCOMPARE(called, 1);
|
||||
|
||||
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
|
||||
QString receivedValue;
|
||||
connection = QMetaObject::connect(&o, sig1Signal, &o, [&receivedValue](QString s1) { receivedValue = s1; return s1; });
|
||||
QVERIFY(connection);
|
||||
QString returnValue = emit o.sig1(u"foo"_s);
|
||||
QCOMPARE(receivedValue, u"foo"_s);
|
||||
QCOMPARE(returnValue, u"foo"_s);
|
||||
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
receivedValue = QString();
|
||||
returnValue = emit o.sig1(u"bar"_s);
|
||||
QVERIFY(receivedValue.isEmpty());
|
||||
QVERIFY(returnValue.isEmpty());
|
||||
|
||||
connection = QMetaObject::connect(&o, sig1Signal, &o, [&receivedValue](QString s1) { receivedValue = s1; return s1; }, Qt::QueuedConnection);
|
||||
QVERIFY(connection);
|
||||
returnValue = emit o.sig1(u"baz"_s);
|
||||
QVERIFY(receivedValue.isEmpty());
|
||||
QVERIFY(returnValue.isEmpty());
|
||||
|
||||
qApp->processEvents(QEventLoop::AllEvents);
|
||||
QCOMPARE(receivedValue, u"baz"_s);
|
||||
}
|
||||
|
||||
QString freeFunction(const QString &s)
|
||||
{
|
||||
return s + s;
|
||||
}
|
||||
|
||||
void tst_QMetaObject::connectByMetaMethodToFreeFunction()
|
||||
{
|
||||
QtTestObject o;
|
||||
QObject::disconnect(&o, nullptr, nullptr, nullptr);
|
||||
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
|
||||
|
||||
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, &QtTestObject::staticFunction0);
|
||||
QVERIFY(connection);
|
||||
|
||||
emit o.sig0();
|
||||
QCOMPARE(o.staticResult, u"staticFunction0"_s);
|
||||
QVERIFY(QObject::disconnect(connection));
|
||||
|
||||
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
|
||||
connection = QMetaObject::connect(&o, sig1Signal, &o, freeFunction);
|
||||
QVERIFY(connection);
|
||||
QCOMPARE(emit o.sig1(u"foo"_s), u"foofoo"_s);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QMetaObject)
|
||||
#include "tst_qmetaobject.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user