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
11namespace Quotient {
12namespace _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.
62inline 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
71template <typename ContextT, typename SlotT>
72inline 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 */
111template <typename T>
112class ConnectionsGuard : public QPointer<T> {
113public:
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
127private:
128 QObject* subscriber;
129};
130} // namespace Quotient
131