// SPDX-FileCopyrightText: 2019 Kitsune Ral // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "util.h" #include namespace Quotient { namespace _impl { template using decorated_slot_tt = std::function; template inline QMetaObject::Connection connectDecorated(SenderT* sender, SignalT signal, ContextT* context, decorated_slot_tt decoratedSlot, Qt::ConnectionType connType) { auto pc = std::make_unique(); auto& c = *pc; // Resolve a reference before pc is moved to lambda // 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 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); } // TODO: get rid of it as soon as Apple Clang gets proper deduction guides // for std::function<> // ...or consider using QtPrivate magic used by QObject::connect() // ...for inspiration, also check a possible std::not_fn implementation // at https://en.cppreference.com/w/cpp/utility/functional/not_fn template inline auto wrap_in_function(FnT&& f) { return typename function_traits::function_type(std::forward(f)); } } // namespace _impl /*! \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 inline auto connectUntil(SenderT* sender, SignalT signal, ContextT* context, const FunctorT& slot, Qt::ConnectionType connType = Qt::AutoConnection) { return _impl::connectUntil(sender, signal, context, _impl::wrap_in_function(slot), connType); } /// 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) { #if QT_VERSION_MAJOR >= 6 return QObject::connect(sender, signal, context, slot, Qt::ConnectionType(connType | Qt::SingleShotConnection)); #else return _impl::connectSingleShot( sender, signal, context, _impl::wrap_in_function(slot), connType); } // Specialisation for usual Qt slots passed as pointers-to-members. template 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, _impl::wrap_in_function( [receiver, slot](const ArgTs&... args) { (receiver->*slot)(args...); }), connType); #endif } /*! \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 class ConnectionsGuard : public QPointer { public: ConnectionsGuard(T* publisher, QObject* subscriber) : QPointer(publisher), subscriber(subscriber) {} ~ConnectionsGuard() { if (*this) (*this)->disconnect(subscriber); } ConnectionsGuard(ConnectionsGuard&&) = default; ConnectionsGuard& operator=(ConnectionsGuard&&) = default; Q_DISABLE_COPY(ConnectionsGuard) using QPointer::operator=; private: QObject* subscriber; }; } // namespace Quotient