| 1 | // SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net> |
| 2 | // SPDX-License-Identifier: LGPL-2.1-or-later |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #include "function_traits.h" |
| 7 | |
| 8 | #include <QtCore/QObject> |
| 9 | #include <QtCore/QPointer> |
| 10 | |
| 11 | namespace Quotient { |
| 12 | namespace _impl { |
| 13 | enum ConnectionType { SingleShot, Until }; |
| 14 | |
| 15 | template <ConnectionType CType> |
| 16 | inline auto connect(auto* sender, auto signal, auto* context, auto slotLike, |
| 17 | Qt::ConnectionType connType) |
| 18 | { |
| 19 | auto pConn = std::make_unique<QMetaObject::Connection>(); |
| 20 | auto& c = *pConn; // Save the reference before pConn is moved from |
| 21 | c = QObject::connect( |
| 22 | sender, signal, context, |
| 23 | [slotLike, pConn = std::move(pConn)](const auto&... args) |
| 24 | // The requires-expression below is necessary to prevent Qt |
| 25 | // from eagerly trying to fill the lambda with more arguments |
| 26 | // than slotLike() (i.e., the original slot) can handle |
| 27 | requires requires { slotLike(args...); } { |
| 28 | static_assert(CType == Until || CType == SingleShot, |
| 29 | "Unsupported disconnection type" ); |
| 30 | if constexpr (CType == SingleShot) { |
| 31 | // Disconnect early to avoid re-triggers during slotLike() |
| 32 | QObject::disconnect(*pConn); |
| 33 | // Qt kindly keeps slot objects until they do their job, |
| 34 | // even if they disconnect themselves in the process (see |
| 35 | // how doActivate() in qobject.cpp handles c->slotObj). |
| 36 | slotLike(args...); |
| 37 | } else if constexpr (CType == Until) { |
| 38 | if (slotLike(args...)) |
| 39 | QObject::disconnect(*pConn); |
| 40 | } |
| 41 | }, |
| 42 | connType); |
| 43 | return c; |
| 44 | } |
| 45 | |
| 46 | template <typename SlotT, typename ReceiverT> |
| 47 | concept PmfSlot = |
| 48 | (fn_arg_count_v<SlotT> > 0 |
| 49 | && std::is_base_of_v<std::decay_t<fn_arg_t<SlotT, 0>>, ReceiverT>); |
| 50 | } // namespace _impl |
| 51 | |
| 52 | //! \brief Create a connection that self-disconnects when its slot returns true |
| 53 | //! |
| 54 | //! A slot accepted by connectUntil() is different from classic Qt slots |
| 55 | //! in that its return value must be bool, not void. Because of that different |
| 56 | //! signature connectUntil() doesn't accept member functions in the way |
| 57 | //! QObject::connect or Quotient::connectSingleShot do; you should pass a lambda |
| 58 | //! or a pre-bound member function to it. |
| 59 | //! \return whether the connection should be dropped; false means that the |
| 60 | //! connection remains; upon returning true, the slot is disconnected |
| 61 | //! from the signal. |
| 62 | inline auto connectUntil(auto* sender, auto signal, auto* context, |
| 63 | auto smartSlot, |
| 64 | Qt::ConnectionType connType = Qt::AutoConnection) |
| 65 | { |
| 66 | return _impl::connect<_impl::Until>(sender, signal, context, smartSlot, |
| 67 | connType); |
| 68 | } |
| 69 | |
| 70 | //! Create a connection that self-disconnects after triggering on the signal |
| 71 | template <typename ContextT, typename SlotT> |
| 72 | inline auto connectSingleShot(auto* sender, auto signal, ContextT* context, |
| 73 | SlotT slot, |
| 74 | Qt::ConnectionType connType = Qt::AutoConnection) |
| 75 | { |
| 76 | #if QT_VERSION_MAJOR >= 6 |
| 77 | return QObject::connect(sender, signal, context, slot, |
| 78 | Qt::ConnectionType(connType |
| 79 | | Qt::SingleShotConnection)); |
| 80 | #else |
| 81 | // In case of classic Qt pointer-to-member-function slots the receiver |
| 82 | // object has to be pre-bound to the slot to make it self-contained |
| 83 | if constexpr (_impl::PmfSlot<SlotT, ContextT>) { |
| 84 | auto&& boundSlot = |
| 85 | # if __cpp_lib_bind_front // Needs Apple Clang 13 (other platforms are fine) |
| 86 | std::bind_front(slot, context); |
| 87 | # else |
| 88 | [context, slot](const auto&... args) |
| 89 | requires requires { (context->*slot)(args...); } |
| 90 | { |
| 91 | (context->*slot)(args...); |
| 92 | }; |
| 93 | # endif |
| 94 | return _impl::connect<_impl::SingleShot>( |
| 95 | sender, signal, context, |
| 96 | std::forward<decltype(boundSlot)>(boundSlot), connType); |
| 97 | } else { |
| 98 | return _impl::connect<_impl::SingleShot>(sender, signal, context, slot, |
| 99 | connType); |
| 100 | } |
| 101 | #endif |
| 102 | } |
| 103 | |
| 104 | /*! \brief A guard pointer that disconnects an interested object upon destruction |
| 105 | * |
| 106 | * It's almost QPointer<> except that you have to initialise it with one |
| 107 | * more additional parameter - a pointer to a QObject that will be |
| 108 | * disconnected from signals of the underlying pointer upon the guard's |
| 109 | * destruction. Note that destructing the guide doesn't destruct either QObject. |
| 110 | */ |
| 111 | template <typename T> |
| 112 | class ConnectionsGuard : public QPointer<T> { |
| 113 | public: |
| 114 | ConnectionsGuard(T* publisher, QObject* subscriber) |
| 115 | : QPointer<T>(publisher), subscriber(subscriber) |
| 116 | {} |
| 117 | ~ConnectionsGuard() |
| 118 | { |
| 119 | if (*this) |
| 120 | (*this)->disconnect(subscriber); |
| 121 | } |
| 122 | ConnectionsGuard(ConnectionsGuard&&) = default; |
| 123 | ConnectionsGuard& operator=(ConnectionsGuard&&) = default; |
| 124 | Q_DISABLE_COPY(ConnectionsGuard) |
| 125 | using QPointer<T>::operator=; |
| 126 | |
| 127 | private: |
| 128 | QObject* subscriber; |
| 129 | }; |
| 130 | } // namespace Quotient |
| 131 | |