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