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;
ActionStateActionState66 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
MissingActionException(std::string const & str)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 *
what() const82 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
init()89 ActionManager::init ()
90 {
91 ui_manager = UIManager::create ();
92 }
93
94 void
save_action_states()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
set_sensitive(Glib::RefPtr<ActionGroup> group,bool yn)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
enable_active_actions()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
disable_active_actions()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*
get_widget(const char * name)163 ActionManager::get_widget (const char * name)
164 {
165 return ui_manager->get_widget (name);
166 }
167
168 void
set_sensitive(vector<RefPtr<Action>> & actions,bool state)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
check_toggleaction(const string & n)193 ActionManager::check_toggleaction (const string& n)
194 {
195 set_toggleaction_state (n, true);
196 }
197
198 void
uncheck_toggleaction(const string & n)199 ActionManager::uncheck_toggleaction (const string& n)
200 {
201 set_toggleaction_state (n, false);
202 }
203
204 void
set_toggleaction_state(const string & n,bool s)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
set_toggleaction_state(const char * group_name,const char * action_name,bool s)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
do_action(const char * group,const char * action)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
set_toggle_action(const char * group,const char * action,bool yn)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>
get_action(const string & name,bool or_die)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>
get_toggle_action(const string & name,bool or_die)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>
get_radio_action(const string & name,bool or_die)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>
get_action(char const * group_name,char const * action_name,bool or_die)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>
get_toggle_action(char const * group_name,char const * action_name,bool or_die)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>
get_radio_action(char const * group_name,char const * action_name,bool or_die)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>
create_action_group(void * owner,string const & name)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>
get_action_group(string const & name)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>
register_action(RefPtr<ActionGroup> group,const char * name,const char * label)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>
register_action(RefPtr<ActionGroup> group,const char * name,const char * label,sigc::slot<void> sl)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>
register_radio_action(RefPtr<ActionGroup> group,Gtk::RadioAction::Group & rgroup,const char * name,const char * label,sigc::slot<void> sl)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>
register_radio_action(RefPtr<ActionGroup> group,Gtk::RadioAction::Group & rgroup,const char * name,const char * label,sigc::slot<void,GtkAction * > sl,int value)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>
register_toggle_action(RefPtr<ActionGroup> group,const char * name,const char * label,sigc::slot<void> sl)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
get_actions(void * owner,std::vector<Glib::RefPtr<Gtk::Action>> & acts)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
get_all_actions(std::vector<std::string> & paths,std::vector<std::string> & labels,std::vector<std::string> & tooltips,std::vector<std::string> & keys,std::vector<RefPtr<Action>> & acts)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 }
555