2 Copyright (C) 2012 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #include "pbd/gstdio_compat.h"
23 #include <gtkmm/accelmap.h>
24 #include <gtkmm/uimanager.h>
26 #include "pbd/convert.h"
27 #include "pbd/debug.h"
28 #include "pbd/error.h"
29 #include "pbd/xml++.h"
31 #include "gtkmm2ext/actions.h"
32 #include "gtkmm2ext/bindings.h"
33 #include "gtkmm2ext/debug.h"
34 #include "gtkmm2ext/keyboard.h"
35 #include "gtkmm2ext/utils.h"
42 using namespace Gtkmm2ext;
45 uint32_t Bindings::_ignored_state = 0;
47 MouseButton::MouseButton (uint32_t state, uint32_t keycode)
49 uint32_t ignore = Bindings::ignored_state();
51 if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
52 /* key is not subject to case, so ignore SHIFT
54 ignore |= GDK_SHIFT_MASK;
57 _val = (state & ~ignore);
63 MouseButton::make_button (const string& str, MouseButton& b)
67 if (str.find ("Primary") != string::npos) {
68 s |= Keyboard::PrimaryModifier;
71 if (str.find ("Secondary") != string::npos) {
72 s |= Keyboard::SecondaryModifier;
75 if (str.find ("Tertiary") != string::npos) {
76 s |= Keyboard::TertiaryModifier;
79 if (str.find ("Level4") != string::npos) {
80 s |= Keyboard::Level4Modifier;
83 string::size_type lastmod = str.find_last_of ('-');
84 uint32_t button_number;
86 if (lastmod == string::npos) {
87 button_number = PBD::atoi (str);
89 button_number = PBD::atoi (str.substr (lastmod+1));
92 b = MouseButton (s, button_number);
97 MouseButton::name () const
103 if (s & Keyboard::PrimaryModifier) {
106 if (s & Keyboard::SecondaryModifier) {
112 if (s & Keyboard::TertiaryModifier) {
118 if (s & Keyboard::Level4Modifier) {
130 snprintf (buf, sizeof (buf), "%u", button());
136 KeyboardKey::KeyboardKey (uint32_t state, uint32_t keycode)
138 uint32_t ignore = Bindings::ignored_state();
140 _val = (state & ~ignore);
147 KeyboardKey::name () const
153 if (s & Keyboard::PrimaryModifier) {
156 if (s & Keyboard::SecondaryModifier) {
162 if (s & Keyboard::TertiaryModifier) {
168 if (s & Keyboard::Level4Modifier) {
179 str += gdk_keyval_name (key());
185 KeyboardKey::make_key (const string& str, KeyboardKey& k)
189 if (str.find ("Primary") != string::npos) {
190 s |= Keyboard::PrimaryModifier;
193 if (str.find ("Secondary") != string::npos) {
194 s |= Keyboard::SecondaryModifier;
197 if (str.find ("Tertiary") != string::npos) {
198 s |= Keyboard::TertiaryModifier;
201 if (str.find ("Level4") != string::npos) {
202 s |= Keyboard::Level4Modifier;
205 string::size_type lastmod = str.find_last_of ('-');
208 if (lastmod == string::npos) {
209 keyval = gdk_keyval_from_name (str.c_str());
211 keyval = gdk_keyval_from_name (str.substr (lastmod+1).c_str());
214 if (keyval == GDK_VoidSymbol) {
218 k = KeyboardKey (s, keyval);
223 Bindings::Bindings ()
228 Bindings::~Bindings()
233 Bindings::empty_keys() const
235 return press_bindings.empty() && release_bindings.empty();
239 Bindings::empty_mouse () const
241 return button_press_bindings.empty() && button_release_bindings.empty();
245 Bindings::empty() const
247 return empty_keys() && empty_mouse ();
251 Bindings::set_action_map (ActionMap& am)
254 press_bindings.clear ();
255 release_bindings.clear ();
259 Bindings::activate (KeyboardKey kb, Operation op)
261 KeybindingMap* kbm = 0;
265 kbm = &press_bindings;
268 kbm = &release_bindings;
272 KeybindingMap::iterator k = kbm->find (kb);
274 if (k == kbm->end()) {
275 /* no entry for this key in the state map */
276 DEBUG_TRACE (DEBUG::Bindings, string_compose ("no binding for %1\n", kb));
282 DEBUG_TRACE (DEBUG::Bindings, string_compose ("binding for %1: %2\n", kb, k->second->get_name()));
284 k->second->activate ();
289 Bindings::add (KeyboardKey kb, Operation op, RefPtr<Action> what)
291 KeybindingMap* kbm = 0;
295 kbm = &press_bindings;
298 kbm = &release_bindings;
302 KeybindingMap::iterator k = kbm->find (kb);
304 if (k == kbm->end()) {
305 pair<KeyboardKey,RefPtr<Action> > newpair (kb, what);
306 kbm->insert (newpair);
311 Gtk::AccelKey gtk_key;
313 /* tweak the binding so that GTK will accept it and display something
317 uint32_t gtk_legal_keyval = kb.key();
318 possibly_translate_keyval_to_make_legal_accelerator (gtk_legal_keyval);
319 KeyboardKey gtk_binding (kb.state(), gtk_legal_keyval);
322 bool entry_exists = Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key);
324 if (!entry_exists || gtk_key.get_key() == 0) {
325 Gtk::AccelMap::add_entry (what->get_accel_path(),
327 (Gdk::ModifierType) gtk_binding.state());
329 warning << string_compose (_("There is more than one key binding defined for %1. Both will work, but only the first will be visible in menus"), what->get_accel_path()) << endmsg;
332 if (!Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key) || gtk_key.get_key() == 0) {
333 cerr << "GTK binding using " << gtk_binding << " failed for " << what->get_accel_path() << " existing = " << gtk_key.get_key() << " + " << gtk_key.get_mod() << endl;
338 Bindings::remove (KeyboardKey kb, Operation op)
340 KeybindingMap* kbm = 0;
344 kbm = &press_bindings;
347 kbm = &release_bindings;
351 KeybindingMap::iterator k = kbm->find (kb);
353 if (k != kbm->end()) {
359 Bindings::activate (MouseButton bb, Operation op)
361 MouseButtonBindingMap* bbm = 0;
365 bbm = &button_press_bindings;
368 bbm = &button_release_bindings;
372 MouseButtonBindingMap::iterator b = bbm->find (bb);
374 if (b == bbm->end()) {
375 /* no entry for this key in the state map */
381 b->second->activate ();
386 Bindings::add (MouseButton bb, Operation op, RefPtr<Action> what)
388 MouseButtonBindingMap* bbm = 0;
392 bbm = &button_press_bindings;
395 bbm = &button_release_bindings;
399 MouseButtonBindingMap::iterator b = bbm->find (bb);
401 if (b == bbm->end()) {
402 pair<MouseButton,RefPtr<Action> > newpair (bb, what);
403 bbm->insert (newpair);
404 // cerr << "Bindings added mouse button " << bb.button() << " w/ " << bb.state() << " => " << what->get_name() << endl;
411 Bindings::remove (MouseButton bb, Operation op)
413 MouseButtonBindingMap* bbm = 0;
417 bbm = &button_press_bindings;
420 bbm = &button_release_bindings;
424 MouseButtonBindingMap::iterator b = bbm->find (bb);
426 if (b != bbm->end()) {
432 Bindings::save (const string& path)
435 XMLNode* root = new XMLNode (X_("Bindings"));
436 tree.set_root (root);
440 if (!tree.write (path)) {
441 ::g_unlink (path.c_str());
449 Bindings::save (XMLNode& root)
451 XMLNode* presses = new XMLNode (X_("Press"));
452 root.add_child_nocopy (*presses);
454 for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
456 child = new XMLNode (X_("Binding"));
457 child->add_property (X_("key"), k->first.name());
458 string ap = k->second->get_accel_path();
459 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
460 presses->add_child_nocopy (*child);
463 for (MouseButtonBindingMap::iterator k = button_press_bindings.begin(); k != button_press_bindings.end(); ++k) {
465 child = new XMLNode (X_("Binding"));
466 child->add_property (X_("button"), k->first.name());
467 string ap = k->second->get_accel_path();
468 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
469 presses->add_child_nocopy (*child);
472 XMLNode* releases = new XMLNode (X_("Release"));
473 root.add_child_nocopy (*releases);
475 for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
477 child = new XMLNode (X_("Binding"));
478 child->add_property (X_("key"), k->first.name());
479 string ap = k->second->get_accel_path();
480 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
481 releases->add_child_nocopy (*child);
484 for (MouseButtonBindingMap::iterator k = button_release_bindings.begin(); k != button_release_bindings.end(); ++k) {
486 child = new XMLNode (X_("Binding"));
487 child->add_property (X_("button"), k->first.name());
488 string ap = k->second->get_accel_path();
489 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
490 releases->add_child_nocopy (*child);
496 Bindings::load (string const & name)
504 XMLNode const * node = Keyboard::bindings_node();
507 error << string_compose (_("No keyboard binding information when loading bindings for \"%1\""), name) << endmsg;
511 const XMLNodeList& children (node->children());
514 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
516 if ((*i)->name() == X_("Bindings")) {
517 XMLProperty const * prop = (*i)->property (X_("name"));
523 if (prop->value() == name) {
532 error << string_compose (_("Bindings for \"%1\" not found in keyboard binding node\n"), name) << endmsg;
536 press_bindings.clear ();
537 release_bindings.clear ();
539 const XMLNodeList& bindings (node->children());
541 for (XMLNodeList::const_iterator i = bindings.begin(); i != bindings.end(); ++i) {
542 /* each node could be Press or Release */
550 Bindings::load (const XMLNode& node)
552 if (node.name() == X_("Press") || node.name() == X_("Release")) {
556 if (node.name() == X_("Press")) {
562 const XMLNodeList& children (node.children());
564 for (XMLNodeList::const_iterator p = children.begin(); p != children.end(); ++p) {
570 ap = (*p)->property ("action");
571 kp = (*p)->property ("key");
572 bp = (*p)->property ("button");
574 if (!ap || (!kp && !bp)) {
581 act = action_map->find_action (ap->value());
585 string::size_type slash = ap->value().find ('/');
586 if (slash != string::npos) {
587 string group = ap->value().substr (0, slash);
588 string action = ap->value().substr (slash+1);
589 act = ActionManager::get_action (group.c_str(), action.c_str());
599 if (!KeyboardKey::make_key (kp->value(), k)) {
605 if (!MouseButton::make_button (bp->value(), b)) {
615 Bindings::get_all_actions (std::vector<std::string>& names,
616 std::vector<std::string>& paths,
617 std::vector<std::string>& tooltips,
618 std::vector<std::string>& keys,
619 std::vector<KeyboardKey>& bindings)
625 /* build a reverse map from actions to bindings */
627 typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
630 for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
631 rmap.insert (make_pair (k->second, k->first));
634 /* get a list of all actions */
636 ActionMap::Actions actions;
637 action_map->get_actions (actions);
639 for (ActionMap::Actions::const_iterator act = actions.begin(); act != actions.end(); ++act) {
640 names.push_back ((*act)->get_name());
641 paths.push_back ((*act)->get_accel_path());
642 tooltips.push_back ((*act)->get_tooltip());
644 ReverseMap::iterator r = rmap.find (*act);
645 if (r != rmap.end()) {
646 keys.push_back (gtk_accelerator_get_label (r->second.key(), (GdkModifierType) r->second.state()));
647 bindings.push_back (r->second);
649 keys.push_back (string());
650 bindings.push_back (KeyboardKey::null_key());
656 Bindings::get_all_actions (std::vector<std::string>& groups,
657 std::vector<std::string>& paths,
658 std::vector<std::string>& tooltips,
659 std::vector<KeyboardKey>& bindings)
661 /* build a reverse map from actions to bindings */
663 typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
666 for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
667 rmap.insert (make_pair (k->second, k->first));
670 /* get a list of all actions */
672 ActionMap::Actions actions;
673 action_map->get_actions (actions);
675 for (ActionMap::Actions::const_iterator act = actions.begin(); act != actions.end(); ++act) {
676 groups.push_back ((*act)->get_name());
677 paths.push_back ((*act)->get_accel_path());
678 tooltips.push_back ((*act)->get_tooltip());
680 ReverseMap::iterator r = rmap.find (*act);
681 if (r != rmap.end()) {
682 bindings.push_back (r->second);
684 bindings.push_back (KeyboardKey::null_key());
690 ActionMap::get_actions (ActionMap::Actions& acts)
692 for (_ActionMap::iterator a = actions.begin(); a != actions.end(); ++a) {
693 acts.push_back (a->second);
698 ActionMap::find_action (const string& name)
700 _ActionMap::iterator a = actions.find (name);
702 if (a != actions.end()) {
706 return RefPtr<Action>();
710 ActionMap::create_action_group (const string& name)
712 RefPtr<ActionGroup> g = ActionGroup::create (name);
717 ActionMap::install_action_group (RefPtr<ActionGroup> group)
719 ActionManager::ui_manager->insert_action_group (group);
723 ActionMap::register_action (RefPtr<ActionGroup> group, const char* name, const char* label)
727 RefPtr<Action> act = Action::create (name, label);
729 fullpath = group->get_name();
733 if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
738 /* already registered */
739 return RefPtr<Action> ();
743 ActionMap::register_action (RefPtr<ActionGroup> group,
744 const char* name, const char* label, sigc::slot<void> sl)
748 RefPtr<Action> act = Action::create (name, label);
750 fullpath = group->get_name();
754 if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
755 group->add (act, sl);
759 /* already registered */
760 return RefPtr<Action>();
764 ActionMap::register_radio_action (RefPtr<ActionGroup> group,
765 Gtk::RadioAction::Group& rgroup,
766 const char* name, const char* label,
771 RefPtr<Action> act = RadioAction::create (rgroup, name, label);
772 RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
774 fullpath = group->get_name();
778 if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
779 group->add (act, sl);
783 /* already registered */
784 return RefPtr<Action>();
788 ActionMap::register_radio_action (RefPtr<ActionGroup> group,
789 Gtk::RadioAction::Group& rgroup,
790 const char* name, const char* label,
791 sigc::slot<void,GtkAction*> sl,
796 RefPtr<Action> act = RadioAction::create (rgroup, name, label);
797 RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
798 ract->property_value() = value;
800 fullpath = group->get_name();
804 if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
805 group->add (act, sigc::bind (sl, act->gobj()));
809 /* already registered */
811 return RefPtr<Action>();
815 ActionMap::register_toggle_action (RefPtr<ActionGroup> group,
816 const char* name, const char* label, sigc::slot<void> sl)
820 fullpath = group->get_name();
824 RefPtr<Action> act = ToggleAction::create (name, label);
826 if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
827 group->add (act, sl);
831 /* already registered */
832 return RefPtr<Action>();
835 std::ostream& operator<<(std::ostream& out, Gtkmm2ext::KeyboardKey const & k) {
836 return out << "Key " << k.key() << " (" << (k.key() > 0 ? gdk_keyval_name (k.key()) : "no-key") << ") state " << k.state();