Consistent use of abort() /* NOTREACHED */
[ardour.git] / libs / gtkmm2ext / actions.cc
1 /*
2  * Copyright (C) 2009-2019 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <cstring>
22 #include <vector>
23 #include <string>
24 #include <list>
25 #include <stack>
26 #include <stdint.h>
27
28 #include <boost/shared_ptr.hpp>
29
30 #include <gtk/gtkaccelmap.h>
31 #include <gtk/gtkuimanager.h>
32 #include <gtk/gtkactiongroup.h>
33
34 #include <gtkmm.h>
35 #include <gtkmm/accelmap.h>
36 #include <gtkmm/uimanager.h>
37
38 #include <glibmm/miscutils.h>
39
40 #include "pbd/error.h"
41 #include "pbd/stacktrace.h"
42
43 #include "gtkmm2ext/actions.h"
44 #include "gtkmm2ext/utils.h"
45
46 #include "pbd/i18n.h"
47
48 using namespace std;
49 using namespace Gtk;
50 using namespace Glib;
51 using namespace sigc;
52 using namespace PBD;
53 using namespace Gtkmm2ext;
54
55 typedef std::map<std::string, Glib::RefPtr<Gtk::Action> > ActionMap;
56 static ActionMap actions;
57 typedef std::vector<Glib::RefPtr<Gtk::ActionGroup> > ActionGroups;
58 static ActionGroups groups;
59
60 RefPtr<UIManager> ActionManager::ui_manager;
61 string ActionManager::unbound_string = X_("--");
62
63 struct ActionState {
64         GtkAction* action;
65         bool       sensitive;
66         ActionState (GtkAction* a, bool s) : action (a), sensitive (s) {}
67 };
68
69 typedef std::vector<ActionState> ActionStates;
70
71 static ActionStates action_states_to_restore;
72 static bool actions_disabled = false;
73
74
75 ActionManager::MissingActionException::MissingActionException (std::string const & str)
76         : missing_action_name (str)
77 {
78         std::cerr << "MAE: " << str << std::endl;
79 }
80
81 const char *
82 ActionManager::MissingActionException::what () const throw ()
83 {
84         /* XXX memory leak */
85         return strdup (string_compose ("missing action: %1", missing_action_name).c_str());
86 }
87
88 void
89 ActionManager::init ()
90 {
91         ui_manager = UIManager::create ();
92 }
93
94 void
95 ActionManager::save_action_states ()
96 {
97         for (ActionGroups::iterator g = groups.begin(); g != groups.end(); ++g) {
98
99                 /* the C++ API for functions used here appears to be broken in
100                    gtkmm2.6, so we fall back to the C level.
101                 */
102
103                 GtkActionGroup* group = (*g)->gobj();
104
105                 for (GList* acts = gtk_action_group_list_actions (group); acts; acts = g_list_next (acts)) {
106                         GtkAction* action = (GtkAction*) acts->data;
107                         action_states_to_restore.push_back (ActionState (action, gtk_action_get_sensitive (action)));
108                 }
109         }
110 }
111
112 void
113 ActionManager::set_sensitive (Glib::RefPtr<ActionGroup> group, bool yn)
114 {
115         /* the C++ API for functions used here appears to be broken in
116            gtkmm2.6, so we fall back to the C level.
117         */
118
119         GtkActionGroup* grp = group->gobj();
120
121         for (GList* acts = gtk_action_group_list_actions (grp); acts; acts = g_list_next (acts)) {
122                 GtkAction* action = (GtkAction*) acts->data;
123                 gtk_action_set_sensitive (action, yn);
124         }
125 }
126
127 void
128 ActionManager::enable_active_actions ()
129 {
130         if (!actions_disabled) {
131                 return ;
132         }
133
134         for (ActionStates::iterator i = action_states_to_restore.begin(); i != action_states_to_restore.end(); ++i) {
135                 if ((*i).action && (*i).sensitive) {
136                         gtk_action_set_sensitive ((*i).action, true);
137                 }
138         }
139
140         action_states_to_restore.clear ();
141         actions_disabled = false;
142 }
143
144 void
145 ActionManager::disable_active_actions ()
146 {
147         if (actions_disabled == true ) {
148                 return ;
149         }
150         // save all action's states to action_states_to_restore
151         save_action_states ();
152
153         // set all action's states disabled
154         for (ActionStates::iterator i = action_states_to_restore.begin(); i != action_states_to_restore.end(); ++i) {
155                 if ((*i).sensitive) {
156                         gtk_action_set_sensitive ((*i).action, false);
157                 }
158         }
159         actions_disabled = true;
160 }
161
162 Widget*
163 ActionManager::get_widget (const char * name)
164 {
165         return ui_manager->get_widget (name);
166 }
167
168 void
169 ActionManager::set_sensitive (vector<RefPtr<Action> >& actions, bool state)
170 {
171         // if actions weren't disabled
172         if (!actions_disabled) {
173                 for (vector<RefPtr<Action> >::iterator i = actions.begin(); i != actions.end(); ++i) {
174                         (*i)->set_sensitive (state);
175                 }
176         }
177         else {
178                 // actions were disabled
179                 // so we should just set necessary action's states in action_states_to_restore
180                 for (vector<RefPtr<Action> >::iterator i = actions.begin(); i != actions.end(); ++i) {
181                         // go through action_states_to_restore and set state of actions
182                         for (ActionStates::iterator j = action_states_to_restore.begin(); j != action_states_to_restore.end(); ++j) {
183                                 // all actions should have their individual name, so we can use it for comparison
184                                 if (gtk_action_get_name ((*j).action) == (*i)->get_name ()) {
185                                         (*j).sensitive = state;
186                                 }
187                         }
188                 }
189         }
190 }
191
192 void
193 ActionManager::check_toggleaction (const string& n)
194 {
195         set_toggleaction_state (n, true);
196 }
197
198 void
199 ActionManager::uncheck_toggleaction (const string& n)
200 {
201         set_toggleaction_state (n, false);
202 }
203
204 void
205 ActionManager::set_toggleaction_state (const string& n, bool s)
206 {
207         string::size_type pos = n.find ('/');
208
209         if (pos == string::npos || pos == n.size() - 1) {
210                 error << string_compose ("illegal action name \"%1\" passed to ActionManager::set_toggleaction_state()", n) << endmsg;
211                 return;
212         }
213
214         if (!set_toggleaction_state (n.substr (0, pos).c_str(), n.substr (pos+1).c_str(), s)) {
215                 error << string_compose (_("Unknown action name: %1/%2"), n.substr (0, pos), n.substr (pos+1)) << endmsg;
216         }
217 }
218
219 bool
220 ActionManager::set_toggleaction_state (const char* group_name, const char* action_name, bool s)
221 {
222         RefPtr<Action> act = get_action (group_name, action_name);
223         if (act) {
224                 RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic(act);
225                 if (tact) {
226                         tact->set_active (s);
227                         return true;
228                 }
229         }
230         return false;
231 }
232
233 void
234 ActionManager::do_action (const char* group, const char*action)
235 {
236         Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (group, action);
237         if (act) {
238                 act->activate ();
239         }
240 }
241
242 void
243 ActionManager::set_toggle_action (const char* group, const char*action, bool yn)
244 {
245         Glib::RefPtr<Gtk::ToggleAction> tact = ActionManager::get_toggle_action (group, action);
246         tact->set_active (yn);
247 }
248
249 RefPtr<Action>
250 ActionManager::get_action (const string& name, bool or_die)
251 {
252         ActionMap::const_iterator a = actions.find (name);
253
254         if (a != actions.end()) {
255                 return a->second;
256         }
257
258         if (or_die) {
259                 throw MissingActionException (name);
260         }
261
262         cerr << "Failed to find action: [" << name << ']' << endl;
263         return RefPtr<Action>();
264 }
265
266 RefPtr<ToggleAction>
267 ActionManager::get_toggle_action (const string& name, bool or_die)
268 {
269         RefPtr<Action> act = get_action (name, or_die);
270
271         if (!act) {
272                 return RefPtr<ToggleAction>();
273         }
274
275         return Glib::RefPtr<ToggleAction>::cast_dynamic (act);
276 }
277
278 RefPtr<RadioAction>
279 ActionManager::get_radio_action (const string& name, bool or_die)
280 {
281         RefPtr<Action> act = get_action (name, or_die);
282
283         if (!act) {
284                 return RefPtr<RadioAction>();
285         }
286
287         return Glib::RefPtr<RadioAction>::cast_dynamic (act);
288 }
289
290 RefPtr<Action>
291 ActionManager::get_action (char const * group_name, char const * action_name, bool or_die)
292 {
293         string fullpath (group_name);
294         fullpath += '/';
295         fullpath += action_name;
296
297         ActionMap::const_iterator a = actions.find (fullpath);
298
299         if (a != actions.end()) {
300                 return a->second;
301         }
302
303         if (or_die) {
304                 throw MissingActionException (string_compose ("%1/%2", group_name, action_name));
305         }
306
307         cerr << "Failed to find action (2): [" << fullpath << ']' << endl;
308         PBD::stacktrace (std::cerr, 20);
309         return RefPtr<Action>();
310 }
311
312 RefPtr<ToggleAction>
313 ActionManager::get_toggle_action (char const * group_name, char const * action_name, bool or_die)
314 {
315         RefPtr<ToggleAction> act = Glib::RefPtr<ToggleAction>::cast_dynamic (get_action (group_name, action_name, or_die));
316
317         if (act) {
318                 return act;
319         }
320
321         if (or_die) {
322                 throw MissingActionException (string_compose ("%1/%2", group_name, action_name));
323         }
324
325         return RefPtr<ToggleAction>();
326 }
327
328 RefPtr<RadioAction>
329 ActionManager::get_radio_action (char const * group_name, char const * action_name, bool or_die)
330 {
331         RefPtr<RadioAction> act = Glib::RefPtr<RadioAction>::cast_dynamic (get_action (group_name, action_name, or_die));
332
333         if (act) {
334                 return act;
335         }
336
337         if (or_die) {
338                 throw MissingActionException (string_compose ("%1/%2", group_name, action_name));
339         }
340
341         return RefPtr<RadioAction>();
342 }
343
344 RefPtr<ActionGroup>
345 ActionManager::create_action_group (void * owner, string const & name)
346 {
347         for (ActionGroups::iterator g = groups.begin(); g != groups.end(); ++g) {
348                 if ((*g)->get_name () == name) {
349                         return *g;
350                 }
351         }
352
353         RefPtr<ActionGroup> g = ActionGroup::create (name);
354
355         g->set_data (X_("owner"), owner);
356         groups.push_back (g);
357
358         /* this is one of the places where our own Action management code
359            has to touch the GTK one, because we want the GtkUIManager to
360            be able to create widgets (particularly Menus) from our actions.
361
362            This is a a necessary step for that to happen.
363         */
364
365         if (g) {
366                 ActionManager::ui_manager->insert_action_group (g);
367         }
368
369         return g;
370 }
371
372 RefPtr<ActionGroup>
373 ActionManager::get_action_group (string const & name)
374 {
375         for (ActionGroups::iterator g = groups.begin(); g != groups.end(); ++g) {
376                 if ((*g)->get_name () == name) {
377                         return *g;
378                 }
379         }
380
381         return RefPtr<ActionGroup> ();
382 }
383
384 RefPtr<Action>
385 ActionManager::register_action (RefPtr<ActionGroup> group, const char* name, const char* label)
386 {
387         string fullpath;
388
389         RefPtr<Action> act = Action::create (name, label);
390
391         fullpath = group->get_name();
392         fullpath += '/';
393         fullpath += name;
394
395         if (actions.insert (ActionMap::value_type (fullpath, act)).second) {
396                 group->add (act);
397                 return act;
398         }
399
400         /* already registered */
401         return RefPtr<Action> ();
402 }
403
404 RefPtr<Action>
405 ActionManager::register_action (RefPtr<ActionGroup> group,
406                                 const char* name, const char* label,
407                                 sigc::slot<void> sl)
408 {
409         string fullpath;
410
411         RefPtr<Action> act = Action::create (name, label);
412
413         fullpath = group->get_name();
414         fullpath += '/';
415         fullpath += name;
416
417         if (actions.insert (ActionMap::value_type (fullpath, act)).second) {
418                 group->add (act, sl);
419                 return act;
420         }
421
422         /* already registered */
423         return RefPtr<Action>();
424 }
425
426 RefPtr<Action>
427 ActionManager::register_radio_action (RefPtr<ActionGroup> group,
428                                       Gtk::RadioAction::Group& rgroup,
429                                       const char* name, const char* label,
430                                       sigc::slot<void> sl)
431 {
432         string fullpath;
433
434         RefPtr<Action> act = RadioAction::create (rgroup, name, label);
435         RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
436
437         fullpath = group->get_name();
438         fullpath += '/';
439         fullpath += name;
440
441         if (actions.insert (ActionMap::value_type (fullpath, act)).second) {
442                 group->add (act, sl);
443                 return act;
444         }
445
446         /* already registered */
447         return RefPtr<Action>();
448 }
449
450 RefPtr<Action>
451 ActionManager::register_radio_action (RefPtr<ActionGroup> group,
452                                       Gtk::RadioAction::Group& rgroup,
453                                       const char* name, const char* label,
454                                       sigc::slot<void,GtkAction*> sl,
455                                       int value)
456 {
457         string fullpath;
458
459         RefPtr<Action> act = RadioAction::create (rgroup, name, label);
460         RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
461         ract->property_value() = value;
462
463         fullpath = group->get_name();
464         fullpath += '/';
465         fullpath += name;
466
467         if (actions.insert (ActionMap::value_type (fullpath, act)).second) {
468                 group->add (act, sigc::bind (sl, act->gobj()));
469                 return act;
470         }
471
472         /* already registered */
473
474         return RefPtr<Action>();
475 }
476
477 RefPtr<Action>
478 ActionManager::register_toggle_action (RefPtr<ActionGroup> group,
479                                    const char* name, const char* label, sigc::slot<void> sl)
480 {
481         string fullpath;
482
483         fullpath = group->get_name();
484         fullpath += '/';
485         fullpath += name;
486
487         RefPtr<Action> act = ToggleAction::create (name, label);
488
489         if (actions.insert (ActionMap::value_type (fullpath, act)).second) {
490                 group->add (act, sl);
491                 return act;
492         }
493
494         /* already registered */
495         return RefPtr<Action>();
496 }
497
498 void
499 ActionManager::get_actions (void* owner, std::vector<Glib::RefPtr<Gtk::Action> >& acts)
500 {
501         for (ActionMap::const_iterator a = actions.begin(); a != actions.end(); ++a) {
502                 if (owner) {
503                         Glib::RefPtr<Gtk::ActionGroup> group = a->second->property_action_group ();
504                         if (group->get_data (X_("owner")) == owner) {
505                                 acts.push_back (a->second);
506                         }
507                 } else {
508                         acts.push_back (a->second);
509                 }
510         }
511 }
512
513 void
514 ActionManager::get_all_actions (std::vector<std::string>& paths,
515                             std::vector<std::string>& labels,
516                             std::vector<std::string>& tooltips,
517                             std::vector<std::string>& keys,
518                             std::vector<RefPtr<Action> >& acts)
519 {
520         for (ActionMap::const_iterator a = actions.begin(); a != actions.end(); ++a) {
521
522                 Glib::RefPtr<Action> act = a->second;
523
524                 /* strip the GTK-added <Actions>/ from the front */
525                 paths.push_back (act->get_accel_path().substr (10));
526                 labels.push_back (act->get_label());
527                 tooltips.push_back (act->get_tooltip());
528                 acts.push_back (act);
529
530                 /* foreach binding */
531
532 #if 0
533                 Bindings* bindings = (*map)->bindings();
534
535                 if (bindings) {
536
537                         KeyboardKey key;
538                         Bindings::Operation op;
539
540                         key = bindings->get_binding_for_action (*act, op);
541
542                         if (key == KeyboardKey::null_key()) {
543                                 keys.push_back (string());
544                         } else {
545                                 keys.push_back (key.display_label());
546                         }
547                 } else {
548                         keys.push_back (string());
549                 }
550 #else
551                 keys.push_back (string());
552 #endif
553         }
554 }