1 /***************************************************************************
2 * Copyright (C) 2005-2020 by the Quassel Project *
3 * devel@quassel-irc.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #pragma once
22
23 #include <algorithm>
24 #include <initializer_list>
25 #include <tuple>
26 #include <utility>
27
28 #include <QAbstractButton>
29 #include <QComboBox>
30 #include <QDoubleSpinBox>
31 #include <QGroupBox>
32 #include <QLineEdit>
33 #include <QObject>
34 #include <QSpinBox>
35 #include <QTextEdit>
36
37 #include "colorbutton.h"
38 #include "fontselector.h"
39 #include "funchelpers.h"
40 #include "util.h"
41
42 namespace detail {
43
44 /**
45 * Contains all supported widget changed signals.
46 */
47 static const auto supportedWidgetChangedSignals = std::make_tuple(
48 &ColorButton::colorChanged,
49 &FontSelector::fontChanged,
50 &QAbstractButton::toggled,
51 selectOverload<int>(&QComboBox::currentIndexChanged),
52 selectOverload<double>(&QDoubleSpinBox::valueChanged),
53 &QGroupBox::toggled,
54 &QLineEdit::textChanged,
55 selectOverload<int>(&QSpinBox::valueChanged),
56 &QTextEdit::textChanged
57 );
58
59 /**
60 * Tries to find a changed signal that matches the given widget type, and connects that to the given receiver/slot.
61 */
62 template<typename Receiver, typename Slot, std::size_t... Is>
tryConnectChangedSignal(const QObject * widget,const Receiver * receiver,Slot slot,std::index_sequence<Is...>)63 bool tryConnectChangedSignal(const QObject* widget, const Receiver* receiver, Slot slot, std::index_sequence<Is...>)
64 {
65 // Tries to cast the given QObject to the given signal's class type, and connects to receiver/slot if successful.
66 // If *alreadyConnected is true, just returns false to prevent multiple connections.
67 static const auto tryConnect = [](const QObject* object, auto sig, auto receiver, auto slot, bool* alreadyConnected) {
68 if (!*alreadyConnected) {
69 auto widget = qobject_cast<const typename FunctionTraits<decltype(sig)>::ClassType*>(object);
70 if (widget) {
71 *alreadyConnected = QObject::connect(widget, sig, receiver, slot);
72 return *alreadyConnected;
73 }
74 }
75 return false;
76 };
77
78 // Unpack the tuple and try to connect to each contained signal in order
79 bool alreadyConnected{false};
80 auto results = {tryConnect(widget, std::get<Is>(supportedWidgetChangedSignals), receiver, slot, &alreadyConnected)...};
81 return std::any_of(results.begin(), results.end(), [](bool result) { return result; });
82 }
83
84 } // namespace detail
85
86 /**
87 * Connects the given widget's changed signal to the given receiver/context and slot.
88 *
89 * Changed signals for supported widgets are listed in detail::supportedWidgetChangedSignals.
90 *
91 * @note The slot must not take any arguments.
92 *
93 * @param widget Pointer to the widget
94 * @param receiver Receiver of the signal (or context for the connection)
95 * @param slot Receiving slot (or functor, or whatever)
96 * @returns true if the widget type is supported, false otherwise
97 */
98 template<typename Receiver, typename Slot>
connectToWidgetChangedSignal(const QObject * widget,const Receiver * receiver,Slot slot)99 bool connectToWidgetChangedSignal(const QObject* widget, const Receiver* receiver, Slot slot)
100 {
101 return detail::tryConnectChangedSignal(widget,
102 receiver,
103 slot,
104 std::make_index_sequence<std::tuple_size<decltype(detail::supportedWidgetChangedSignals)>::value>{});
105 }
106
107 /**
108 * Connects the given widgets' changed signals to the given receiver/context and slot.
109 *
110 * Changed signals for supported widgets are listed in detail::supportedWidgetChangedSignals.
111 *
112 * @note The slot must not take any arguments.
113 *
114 * @param widget Pointer to the widget
115 * @param receiver Receiver of the signal (or context for the connection)
116 * @param slot Receiving slot (or functor, or whatever)
117 * @returns true if all given widget types are supported, false otherwise
118 */
119 template<typename Container, typename Receiver, typename Slot>
connectToWidgetsChangedSignals(const Container & widgets,const Receiver * receiver,Slot slot)120 bool connectToWidgetsChangedSignals(const Container& widgets, const Receiver* receiver, Slot slot)
121 {
122 bool success = true;
123 for (auto&& widget : widgets) {
124 success &= detail::tryConnectChangedSignal(widget,
125 receiver,
126 slot,
127 std::make_index_sequence<
128 std::tuple_size<decltype(detail::supportedWidgetChangedSignals)>::value>{});
129 }
130 return success;
131 }
132
133 /**
134 * @overload
135 * Convenience overload that allows brace-initializing the list of widgets.
136 */
137 template<typename Receiver, typename Slot>
connectToWidgetsChangedSignals(const std::initializer_list<const QObject * > & widgets,const Receiver * receiver,Slot slot)138 bool connectToWidgetsChangedSignals(const std::initializer_list<const QObject*>& widgets, const Receiver* receiver, Slot slot)
139 {
140 return connectToWidgetsChangedSignals(std::vector<const QObject*>{widgets}, receiver, slot);
141 }
142