// SPDX-FileCopyrightText: 2019 Kitsune Ral // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include "function_traits.h" #include namespace Quotient { namespace _impl { enum ConnectionType { SingleShot, Until }; template inline auto connect(auto* sender, auto signal, auto* context, auto slotLike, Qt::ConnectionType connType) { std::unique_ptr pConn = std::make_unique(); auto& c = *pConn; // Save the reference before pConn is moved from c = QObject::connect( sender, signal, context, [slotLike, pConn = std::move(pConn)](const auto&... args) // The requires-expression below is necessary to prevent Qt // from eagerly trying to fill the lambda with more arguments // than slotLike() (i.e., the original slot) can handle requires requires { slotLike(args...); } { static_assert(CType == Until || CType == SingleShot, "Unsupported disconnection type"); if constexpr (CType == SingleShot) { // Disconnect early to avoid re-triggers during slotLike() QObject::disconnect(*pConn); // Qt kindly keeps slot objects until they do their job, // even if they disconnect themselves in the process (see // how doActivate() in qobject.cpp handles c->slotObj). slotLike(args...); } else if constexpr (CType == Until) { if (slotLike(args...)) QObject::disconnect(*pConn); } }, connType); return c; } template concept PmfSlot = (fn_arg_count_v > 0 && std::is_base_of_v>, ReceiverT>); } // 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. Because of that different //! signature connectUntil() doesn't accept member functions in the way //! QObject::connect or Quotient::connectSingleShot do; you should pass a lambda //! or a pre-bound member function to it. //! \return whether the connection should be dropped; false means that the //! connection remains; upon returning true, the slot is disconnected //! from the signal. inline auto connectUntil(auto* sender, auto signal, auto* context, auto smartSlot, Qt::ConnectionType connType = Qt::AutoConnection) { return _impl::connect<_impl::Until>(sender, signal, context, smartSlot, connType); } //! Create a connection that self-disconnects after triggering on the signal template inline auto connectSingleShot(auto* sender, auto signal, ContextT* context, SlotT slot, Qt::ConnectionType connType = Qt::AutoConnection) { #if QT_VERSION_MAJOR >= 6 return QObject::connect(sender, signal, context, slot, Qt::ConnectionType(connType | Qt::SingleShotConnection)); #else // In case of classic Qt pointer-to-member-function slots the receiver // object has to be pre-bound to the slot to make it self-contained if constexpr (_impl::PmfSlot) { auto&& boundSlot = # if __cpp_lib_bind_front // Needs Apple Clang 13 (other platforms are fine) std::bind_front(slot, context); # else [context, slot](const auto&... args) requires requires { (context->*slot)(args...); } { (context->*slot)(args...); }; # endif return _impl::connect<_impl::SingleShot>( sender, signal, context, std::forward(boundSlot), connType); } else { return _impl::connect<_impl::SingleShot>(sender, signal, context, slot, 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