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 | |