diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2019-09-29 18:18:39 +0900 |
---|---|---|
committer | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2019-09-29 23:35:50 +0900 |
commit | 36589cd6e4c557da7d694c5973546f29a09dee7c (patch) | |
tree | 82ccd30273525373ec78f5f81568476632233fe6 /lib/qt_connection_util.h | |
parent | 1e6cbd34eff9817b99e02d2dfee4e0b0c8250794 (diff) | |
download | libquotient-36589cd6e4c557da7d694c5973546f29a09dee7c.tar.gz libquotient-36589cd6e4c557da7d694c5973546f29a09dee7c.zip |
Make connectSingleShot work wherever QObject::connect works
Also: doc-comment connectUntil and unify implementation of both functions.
Diffstat (limited to 'lib/qt_connection_util.h')
-rw-r--r-- | lib/qt_connection_util.h | 114 |
1 files 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 <typename... ArgTs> + using decorated_slot_tt = + std::function<void(QMetaObject::Connection&, const ArgTs&...)>; + template <typename SenderT, typename SignalT, typename ContextT, typename... ArgTs> inline QMetaObject::Connection - connectUntil(SenderT* sender, SignalT signal, ContextT* context, - std::function<bool(ArgTs...)> slot, Qt::ConnectionType connType) + connectDecorated(SenderT* sender, SignalT signal, ContextT* context, + decorated_slot_tt<ArgTs...> 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<QMetaObject::Connection>(); #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<ArgTs>(args)...)) - QObject::disconnect(*pc); + decoratedSlot(*pc, args...); }, connType); return c; } + template <typename SenderT, typename SignalT, typename ContextT, + typename... ArgTs> + inline QMetaObject::Connection + connectUntil(SenderT* sender, SignalT signal, ContextT* context, + std::function<bool(ArgTs...)> functor, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt<ArgTs...>( + [functor = std::move(functor)](QMetaObject::Connection& c, + const ArgTs&... args) { + if (functor(args...)) + QObject::disconnect(c); + }), + connType); + } + template <typename SenderT, typename SignalT, typename ContextT, + typename... ArgTs> + inline QMetaObject::Connection + connectSingleShot(SenderT* sender, SignalT signal, ContextT* context, + std::function<void(ArgTs...)> slot, + Qt::ConnectionType connType) + { + return connectDecorated(sender, signal, context, + decorated_slot_tt<ArgTs...>( + [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 <typename SenderT, typename SignalT, typename ContextT, typename FunctorT> 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<FunctorT>::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 <typename SenderT, typename SignalT, typename ReceiverT, typename SlotT> +/// Create a connection that self-disconnects after triggering on the signal +template <typename SenderT, typename SignalT, typename ContextT, typename FunctorT> +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 <typename SenderT, typename SignalT, typename ReceiverT, + typename SlotObjectT, typename... ArgTs> 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 <typename T> class ConnectionsGuard : public QPointer<T> { |