cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / uisupport / actioncollection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2022 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) any later version.                                   *
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  * Parts of this implementation are based on KDE's KActionCollection.      *
21  ***************************************************************************/
22
23 #include "actioncollection.h"
24
25 #include <QAction>
26 #include <QDebug>
27 #include <QMetaMethod>
28
29 #include "action.h"
30 #include "uisettings.h"
31
32 void ActionCollection::addActions(const std::vector<std::pair<QString, Action*>>& actions)
33 {
34     for (auto&& p : actions) {
35         addAction(p.first, p.second);
36     }
37 }
38
39 #ifndef HAVE_KDE
40
41 int ActionCollection::count() const
42 {
43     return actions().count();
44 }
45
46 bool ActionCollection::isEmpty() const
47 {
48     return actions().count();
49 }
50
51 void ActionCollection::clear()
52 {
53     _actionByName.clear();
54     qDeleteAll(_actions);
55     _actions.clear();
56 }
57
58 QAction* ActionCollection::action(const QString& name) const
59 {
60     return _actionByName.value(name, 0);
61 }
62
63 QList<QAction*> ActionCollection::actions() const
64 {
65     return _actions;
66 }
67
68 QAction* ActionCollection::addAction(const QString& name, QAction* action)
69 {
70     if (!action)
71         return action;
72
73     const QString origName = action->objectName();
74     QString indexName = name;
75
76     if (indexName.isEmpty())
77         indexName = action->objectName();
78     else
79         action->setObjectName(indexName);
80     if (indexName.isEmpty())
81         indexName = indexName.asprintf("unnamed-%p", (void*)action);
82
83     // do we already have this action?
84     if (_actionByName.value(indexName, 0) == action)
85         return action;
86     // or maybe another action under this name?
87     if (QAction* oldAction = _actionByName.value(indexName))
88         takeAction(oldAction);
89
90     // do we already have this action under a different name?
91     int oldIndex = _actions.indexOf(action);
92     if (oldIndex != -1) {
93         _actionByName.remove(origName);
94         _actions.removeAt(oldIndex);
95     }
96
97     // add action
98     _actionByName.insert(indexName, action);
99     _actions.append(action);
100
101     foreach (QWidget* widget, _associatedWidgets) {
102         widget->addAction(action);
103     }
104
105     connect(action, &QObject::destroyed, this, &ActionCollection::actionDestroyed);
106     if (_connectHovered)
107         connect(action, &QAction::hovered, this, &ActionCollection::slotActionHovered);
108     if (_connectTriggered)
109         connect(action, &QAction::triggered, this, &ActionCollection::slotActionTriggered);
110
111     emit inserted(action);
112     return action;
113 }
114
115 void ActionCollection::removeAction(QAction* action)
116 {
117     delete takeAction(action);
118 }
119
120 QAction* ActionCollection::takeAction(QAction* action)
121 {
122     if (!unlistAction(action))
123         return nullptr;
124
125     foreach (QWidget* widget, _associatedWidgets) {
126         widget->removeAction(action);
127     }
128
129     action->disconnect(this);
130     return action;
131 }
132
133 void ActionCollection::readSettings()
134 {
135     ShortcutSettings s;
136     QStringList savedShortcuts = s.savedShortcuts();
137
138     foreach (const QString& name, _actionByName.keys()) {
139         if (!savedShortcuts.contains(name))
140             continue;
141         auto* action = qobject_cast<Action*>(_actionByName.value(name));
142         if (action)
143             action->setShortcut(s.loadShortcut(name), Action::ActiveShortcut);
144     }
145 }
146
147 void ActionCollection::writeSettings() const
148 {
149     ShortcutSettings s;
150     foreach (const QString& name, _actionByName.keys()) {
151         auto* action = qobject_cast<Action*>(_actionByName.value(name));
152         if (!action)
153             continue;
154         if (!action->isShortcutConfigurable())
155             continue;
156         if (action->shortcut(Action::ActiveShortcut) == action->shortcut(Action::DefaultShortcut))
157             continue;
158         s.saveShortcut(name, action->shortcut(Action::ActiveShortcut));
159     }
160 }
161
162 void ActionCollection::slotActionTriggered()
163 {
164     auto* action = qobject_cast<QAction*>(sender());
165     if (action)
166         emit actionTriggered(action);
167 }
168
169 void ActionCollection::slotActionHovered()
170 {
171     auto* action = qobject_cast<QAction*>(sender());
172     if (action)
173         emit actionHovered(action);
174 }
175
176 void ActionCollection::actionDestroyed(QObject* obj)
177 {
178     // remember that this is not an QAction anymore at this point
179     auto* action = static_cast<QAction*>(obj);
180
181     unlistAction(action);
182 }
183
184 void ActionCollection::connectNotify(const QMetaMethod& signal)
185 {
186     if (_connectHovered && _connectTriggered)
187         return;
188
189     if (QMetaMethod::fromSignal(&ActionCollection::actionHovered) == signal) {
190         if (!_connectHovered) {
191             _connectHovered = true;
192             foreach (QAction* action, actions())
193                 connect(action, &QAction::hovered, this, &ActionCollection::slotActionHovered);
194         }
195     }
196     else if (QMetaMethod::fromSignal(&ActionCollection::actionTriggered) == signal) {
197         if (!_connectTriggered) {
198             _connectTriggered = true;
199             foreach (QAction* action, actions())
200                 connect(action, &QAction::triggered, this, &ActionCollection::slotActionTriggered);
201         }
202     }
203
204     QObject::connectNotify(signal);
205 }
206
207 void ActionCollection::associateWidget(QWidget* widget) const
208 {
209     foreach (QAction* action, actions()) {
210         if (!widget->actions().contains(action))
211             widget->addAction(action);
212     }
213 }
214
215 void ActionCollection::addAssociatedWidget(QWidget* widget)
216 {
217     if (!_associatedWidgets.contains(widget)) {
218         widget->addActions(actions());
219         _associatedWidgets.append(widget);
220         connect(widget, &QObject::destroyed, this, &ActionCollection::associatedWidgetDestroyed);
221     }
222 }
223
224 void ActionCollection::removeAssociatedWidget(QWidget* widget)
225 {
226     foreach (QAction* action, actions())
227         widget->removeAction(action);
228     _associatedWidgets.removeAll(widget);
229     disconnect(widget, &QObject::destroyed, this, &ActionCollection::associatedWidgetDestroyed);
230 }
231
232 QList<QWidget*> ActionCollection::associatedWidgets() const
233 {
234     return _associatedWidgets;
235 }
236
237 void ActionCollection::clearAssociatedWidgets()
238 {
239     foreach (QWidget* widget, _associatedWidgets)
240         foreach (QAction* action, actions())
241             widget->removeAction(action);
242
243     _associatedWidgets.clear();
244 }
245
246 void ActionCollection::associatedWidgetDestroyed(QObject* obj)
247 {
248     _associatedWidgets.removeAll(static_cast<QWidget*>(obj));
249 }
250
251 bool ActionCollection::unlistAction(QAction* action)
252 {
253     // This might be called with a partly destroyed QAction!
254
255     int index = _actions.indexOf(action);
256     if (index == -1)
257         return false;
258
259     QString name = action->objectName();
260     _actionByName.remove(name);
261     _actions.removeAt(index);
262
263     // TODO: remove from ActionCategory if we ever get that
264
265     return true;
266 }
267
268 #endif /* HAVE_KDE */