From 36589cd6e4c557da7d694c5973546f29a09dee7c Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 29 Sep 2019 18:18:39 +0900 Subject: Make connectSingleShot work wherever QObject::connect works Also: doc-comment connectUntil and unify implementation of both functions. --- lib/qt_connection_util.h | 114 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 28 deletions(-) diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index 159e7522..9baf8c69 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -24,10 +24,15 @@ namespace Quotient { namespace _impl { + template + using decorated_slot_tt = + std::function; + template inline QMetaObject::Connection - connectUntil(SenderT* sender, SignalT signal, ContextT* context, - std::function slot, Qt::ConnectionType connType) + connectDecorated(SenderT* sender, SignalT signal, ContextT* context, + decorated_slot_tt decoratedSlot, + Qt::ConnectionType connType) { // See https://bugreports.qt.io/browse/QTBUG-60339 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) @@ -36,52 +41,105 @@ namespace _impl { auto pc = std::make_unique(); #endif auto& c = *pc; // Resolve a reference before pc is moved to lambda - c = QObject::connect( - sender, signal, context, - [pc = std::move(pc), slot](ArgTs... args) { + + // Perfect forwarding doesn't work through signal-slot connections - + // arguments are always copied (at best - COWed) to the context of + // the slot. Therefore the slot decorator receives const ArgTs&... + // rather than ArgTs&&... + // TODO: std::bind_front() instead of lambda. + c = QObject::connect(sender, signal, context, + [pc = std::move(pc), + decoratedSlot = std::move(decoratedSlot)](const ArgTs&... args) { Q_ASSERT(*pc); // If it's been triggered, it should exist - if (slot(std::forward(args)...)) - QObject::disconnect(*pc); + decoratedSlot(*pc, args...); }, connType); return c; } + template + inline QMetaObject::Connection + connectUntil(SenderT* sender, SignalT signal, ContextT* context, + std::function functor, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt( + [functor = std::move(functor)](QMetaObject::Connection& c, + const ArgTs&... args) { + if (functor(args...)) + QObject::disconnect(c); + }), + connType); + } + template + inline QMetaObject::Connection + connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, + std::function slot, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt( + [slot = std::move(slot)](QMetaObject::Connection& c, + const ArgTs&... args) { + QObject::disconnect(c); + slot(args...); + }), + connType); + } } // namespace _impl +/// Create a connection that self-disconnects when its "slot" returns true +/*! A slot accepted by connectUntil() is different from classic Qt slots + * in that its return value must be bool, not void. The slot's return value + * controls whether the connection should be kept; if the slot returns false, + * the connection remains; upon returning true, the slot is disconnected from + * the signal. Because of a different slot signature connectUntil() doesn't + * accept member functions as QObject::connect or Quotient::connectSingleShot + * do; you should pass a lambda or a pre-bound member function to it. + */ template inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { - return _impl::connectUntil( - sender, signal, context, - typename function_traits::function_type(slot), connType); + return _impl::connectUntil(sender, signal, context, std::function(slot), + connType); } -/** Create a single-shot connection that triggers on the signal and - * then self-disconnects - * - * Only supports DirectConnection type. - */ -template +/// Create a connection that self-disconnects after triggering on the signal +template +inline auto connectSingleShot(SenderT* sender, SignalT signal, + ContextT* context, const FunctorT& slot, + Qt::ConnectionType connType = Qt::AutoConnection) +{ + return _impl::connectSingleShot( + sender, signal, context, std::function(slot), connType); +} + +// Specialisation for usual Qt slots passed as pointers-to-members. +template inline auto connectSingleShot(SenderT* sender, SignalT signal, - ReceiverT* receiver, SlotT slot) + ReceiverT* receiver, + void (SlotObjectT::*slot)(ArgTs...), + Qt::ConnectionType connType = Qt::AutoConnection) { - QMetaObject::Connection connection; - connection = QObject::connect(sender, signal, receiver, slot, - Qt::DirectConnection); - Q_ASSERT(connection); - QObject::connect( - sender, signal, receiver, - [connection] { QObject::disconnect(connection); }, Qt::DirectConnection); - return connection; + // TODO: when switching to C++20, use std::bind_front() instead + return _impl::connectSingleShot(sender, signal, receiver, + std::function( + [receiver, slot](const ArgTs&... args) { + (receiver->*slot)(args...); + }), + connType); } -/** A guard pointer that disconnects an interested object upon destruction - * It's almost QPointer<> except that you have to initialise it with one +/// A guard pointer that disconnects an interested object upon destruction +/*! It's almost QPointer<> except that you have to initialise it with one * more additional parameter - a pointer to a QObject that will be * disconnected from signals of the underlying pointer upon the guard's - * destruction. + * destruction. Note that destructing the guide doesn't destruct either QObject. */ template class ConnectionsGuard : public QPointer { -- cgit v1.2.3