diff options
Diffstat (limited to 'lib/qt_connection_util.h')
-rw-r--r-- | lib/qt_connection_util.h | 204 |
1 files changed, 131 insertions, 73 deletions
diff --git a/lib/qt_connection_util.h b/lib/qt_connection_util.h index c2bde8df..699735d4 100644 --- a/lib/qt_connection_util.h +++ b/lib/qt_connection_util.h @@ -13,7 +13,7 @@ * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #pragma once @@ -22,86 +22,144 @@ #include <QtCore/QPointer> -namespace QMatrixClient { - namespace _impl { - 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) - { - // See https://bugreports.qt.io/browse/QTBUG-60339 +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 + 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) - auto pc = std::make_shared<QMetaObject::Connection>(); + auto pc = std::make_shared<QMetaObject::Connection>(); #else - auto pc = std::make_unique<QMetaObject::Connection>(); + 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) { - Q_ASSERT(*pc); // If it's been triggered, it should exist - if (slot(std::forward<ArgTs>(args)...)) - QObject::disconnect(*pc); - }, connType); - return c; - } - } + auto& c = *pc; // Resolve a reference before pc is moved to lambda - 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) + // 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 (C++20): 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 + 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 _impl::connectUntil(sender, signal, context, - typename function_traits<FunctorT>::function_type(slot), - 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); } - - /** 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> - inline auto connectSingleShot(SenderT* sender, SignalT signal, - ReceiverT* receiver, SlotT slot) + 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) { - 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; + 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 - /** 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. - */ - template <typename T> - class ConnectionsGuard : public QPointer<T> - { - public: - ConnectionsGuard(T* publisher, QObject* subscriber) - : QPointer<T>(publisher), subscriber(subscriber) - { } - ~ConnectionsGuard() - { - if (*this) - (*this)->disconnect(subscriber); - } - ConnectionsGuard(ConnectionsGuard&&) = default; - ConnectionsGuard& operator=(ConnectionsGuard&&) = default; - Q_DISABLE_COPY(ConnectionsGuard) - using QPointer<T>::operator=; +/*! \brief 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, wrap_in_function(slot), + connType); +} - private: - QObject* subscriber; - }; +/// 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, wrap_in_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, + void (SlotObjectT::*slot)(ArgTs...), + Qt::ConnectionType connType = Qt::AutoConnection) +{ + // TODO: when switching to C++20, use std::bind_front() instead + return _impl::connectSingleShot(sender, signal, receiver, + wrap_in_function( + [receiver, slot](const ArgTs&... args) { + (receiver->*slot)(args...); + }), + connType); +} + +/*! \brief 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. Note that destructing the guide doesn't destruct either QObject. + */ +template <typename T> +class ConnectionsGuard : public QPointer<T> { +public: + ConnectionsGuard(T* publisher, QObject* subscriber) + : QPointer<T>(publisher), subscriber(subscriber) + {} + ~ConnectionsGuard() + { + if (*this) + (*this)->disconnect(subscriber); + } + ConnectionsGuard(ConnectionsGuard&&) = default; + ConnectionsGuard& operator=(ConnectionsGuard&&) = default; + Q_DISABLE_COPY(ConnectionsGuard) + using QPointer<T>::operator=; + +private: + QObject* subscriber; +}; +} // namespace Quotient |