aboutsummaryrefslogtreecommitdiff
path: root/lib/qt_connection_util.h
blob: 90bc3f9b75d2e7f015f7b832f1c1fa8a4426a6e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
// SPDX-License-Identifier: LGPL-2.1-or-later

#pragma once

#include "function_traits.h"

#include <QtCore/QPointer>

namespace Quotient {
namespace _impl {
    enum ConnectionType { SingleShot, Until };

    template <ConnectionType CType>
    inline auto connect(auto* sender, auto signal, auto* context, auto slotLike,
                        Qt::ConnectionType connType)
    {
        std::unique_ptr<QMetaObject::Connection> pConn =
            std::make_unique<QMetaObject::Connection>();
        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 <typename SlotT, typename ReceiverT>
    concept PmfSlot =
        (fn_arg_count_v<SlotT> > 0
         && std::is_base_of_v<std::decay_t<fn_arg_t<SlotT, 0>>, 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 <typename ContextT, typename SlotT>
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<SlotT, ContextT>) {
        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<decltype(boundSlot)>(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 <typename T>
class ConnectionsGuard : public QPointer<T> {
public:
    ConnectionsGuard(T* publisher, QObject* subscriber)
        : QPointer<T>(publisher), subscriber(subscriber)
    {}
    ~ConnectionsGuard()
    {
        if (*this)
            (*this)->disconnect(subscriber);
    }
    ConnectionsGuard(ConnectionsGuard&&) = default;
    ConnectionsGuard& operator=(ConnectionsGuard&&) = default;
    Q_DISABLE_COPY(ConnectionsGuard)
    using QPointer<T>::operator=;

private:
    QObject* subscriber;
};
} // namespace Quotient