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