diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index e2446c83d26..48baa96e104 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -3910,6 +3910,96 @@ void QMetaObject::connectSlotsByName(QObject *o) } } +/*! + \fn template 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 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. diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h index 41eba0814c8..3bff5c3393c 100644 --- a/src/corelib/kernel/qobjectdefs.h +++ b/src/corelib/kernel/qobjectdefs.h @@ -292,6 +292,19 @@ struct Q_CORE_EXPORT QMetaObject // internal slot-name based connect static void connectSlotsByName(QObject *o); +#ifdef Q_QDOC + template + static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection); + template + static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection); +#else + template + static inline Connection + connect(const QObject *sender, const QMetaMethod &signal, + const typename QtPrivate::ContextTypeForFunctor::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 +QMetaObject::Connection + QMetaObject::connect(const QObject *sender, const QMetaMethod &signal, + const typename QtPrivate::ContextTypeForFunctor::ContextType *context, Func &&slot, + Qt::ConnectionType type) +{ + using Slot = std::decay_t; + using FunctionSlotType = QtPrivate::FunctionPointer; + void **pSlot = nullptr; + QtPrivate::QSlotObjectBase *slotObject; + if constexpr (FunctionSlotType::ArgumentCount != -1) { + slotObject = new QtPrivate::QCallableObject(std::forward(slot)); + if constexpr (FunctionSlotType::IsPointerToMemberFunction) { + pSlot = const_cast(reinterpret_cast(&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; + slotObject = new QtPrivate::QCallableObject(std::forward(slot)); + } + return QMetaObject::connectImpl(sender, signal, context, pSlot, slotObject, type); +} + inline void swap(QMetaObject::Connection &lhs, QMetaObject::Connection &rhs) noexcept { lhs.swap(rhs); diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index 6b2b98e0eac..5e6b54c6123 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -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"