add new variant of ActionMap::find_action()
[ardour.git] / libs / gtkmm2ext / bindings.cc
index 232405bb978a2f2db921ae89780af5c87b879c07..b1abcb0507f0cafc55d1d8107bfb2a0c0938c4f5 100644 (file)
@@ -1,19 +1,19 @@
 /*
-    Copyright (C) 2012 Paul Davis
+  Copyright (C) 2012 Paul Davis
 
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
 
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 */
 
@@ -26,6 +26,7 @@
 #include "pbd/convert.h"
 #include "pbd/debug.h"
 #include "pbd/error.h"
+#include "pbd/replace_all.h"
 #include "pbd/xml++.h"
 
 #include "gtkmm2ext/actions.h"
@@ -34,7 +35,7 @@
 #include "gtkmm2ext/keyboard.h"
 #include "gtkmm2ext/utils.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace std;
 using namespace Glib;
@@ -42,196 +43,337 @@ using namespace Gtk;
 using namespace Gtkmm2ext;
 using namespace PBD;
 
-ActionMap Gtkmm2ext::Actions; /* global. Gulp */
 list<Bindings*> Bindings::bindings; /* global. Gulp */
-uint32_t Bindings::_ignored_state = 0;
+list<ActionMap*> ActionMap::action_maps; /* global. Gulp */
+PBD::Signal1<void,Bindings*> Bindings::BindingsChanged;
+
+template <typename IteratorValueType>
+struct ActionNameRegistered
+{
+       ActionNameRegistered(std::string const& name)
+               : action_name(name)
+       {}
+
+       bool operator()(IteratorValueType elem) const {
+               return elem.second.action_name == action_name;
+       }
+       std::string const& action_name;
+};
 
 MouseButton::MouseButton (uint32_t state, uint32_t keycode)
 {
-        uint32_t ignore = Bindings::ignored_state();
+       uint32_t ignore = ~Keyboard::RelevantModifierKeyMask;
 
-        if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
-                /* key is not subject to case, so ignore SHIFT
-                 */
-                ignore |= GDK_SHIFT_MASK;
-        }
+       /* this is a slightly wierd test that relies on
+        * gdk_keyval_is_{upper,lower}() returning true for keys that have no
+        * case-sensitivity. This covers mostly non-alphanumeric keys.
+        */
 
-        _val = (state & ~ignore);
-        _val <<= 32;
-        _val |= keycode;
+       if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
+               /* key is not subject to case, so ignore SHIFT
+                */
+               ignore |= GDK_SHIFT_MASK;
+       }
+
+       _val = (state & ~ignore);
+       _val <<= 32;
+       _val |= keycode;
 };
 
 bool
 MouseButton::make_button (const string& str, MouseButton& b)
 {
-        int s = 0;
+       int s = 0;
 
-        if (str.find ("Primary") != string::npos) {
-                s |= Keyboard::PrimaryModifier;
-        }
+       if (str.find ("Primary") != string::npos) {
+               s |= Keyboard::PrimaryModifier;
+       }
 
-        if (str.find ("Secondary") != string::npos) {
-                s |= Keyboard::SecondaryModifier;
-        }
+       if (str.find ("Secondary") != string::npos) {
+               s |= Keyboard::SecondaryModifier;
+       }
 
-        if (str.find ("Tertiary") != string::npos) {
-                s |= Keyboard::TertiaryModifier;
-        }
+       if (str.find ("Tertiary") != string::npos) {
+               s |= Keyboard::TertiaryModifier;
+       }
 
-        if (str.find ("Level4") != string::npos) {
-                s |= Keyboard::Level4Modifier;
-        }
+       if (str.find ("Level4") != string::npos) {
+               s |= Keyboard::Level4Modifier;
+       }
 
-        string::size_type lastmod = str.find_last_of ('-');
-        uint32_t button_number;
+       string::size_type lastmod = str.find_last_of ('-');
+       uint32_t button_number;
 
-        if (lastmod == string::npos) {
-                button_number = PBD::atoi (str);
-        } else {
-                button_number = PBD::atoi (str.substr (lastmod+1));
-        }
+       if (lastmod == string::npos) {
+               button_number = PBD::atoi (str);
+       } else {
+               button_number = PBD::atoi (str.substr (lastmod+1));
+       }
 
-        b = MouseButton (s, button_number);
-        return true;
+       b = MouseButton (s, button_number);
+       return true;
 }
 
 string
 MouseButton::name () const
 {
-        int s = state();
-
-        string str;
-
-        if (s & Keyboard::PrimaryModifier) {
-                str += "Primary";
-        }
-        if (s & Keyboard::SecondaryModifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Secondary";
-        }
-        if (s & Keyboard::TertiaryModifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Tertiary";
-        }
-        if (s & Keyboard::Level4Modifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Level4";
-        }
-
-        if (!str.empty()) {
-                str += '-';
-        }
-
-        char buf[16];
-        snprintf (buf, sizeof (buf), "%u", button());
-        str += buf;
-
-        return str;
+       int s = state();
+
+       string str;
+
+       if (s & Keyboard::PrimaryModifier) {
+               str += "Primary";
+       }
+       if (s & Keyboard::SecondaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Secondary";
+       }
+       if (s & Keyboard::TertiaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Tertiary";
+       }
+       if (s & Keyboard::Level4Modifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Level4";
+       }
+
+       if (!str.empty()) {
+               str += '-';
+       }
+
+       char buf[16];
+       snprintf (buf, sizeof (buf), "%u", button());
+       str += buf;
+
+       return str;
 }
 
+/*================================ KeyboardKey ================================*/
 KeyboardKey::KeyboardKey (uint32_t state, uint32_t keycode)
 {
-        uint32_t ignore = Bindings::ignored_state();
+       uint32_t ignore = ~Keyboard::RelevantModifierKeyMask;
 
-        _val = (state & ~ignore);
-        _val <<= 32;
-        _val |= keycode;
-};
+       _val = (state & ~ignore);
+       _val <<= 32;
+       _val |= keycode;
+}
+
+string
+KeyboardKey::display_label () const
+{
+       if (key() == 0) {
+               return string();
+       }
+
+       /* This magically returns a string that will display the right thing
+        *  on all platforms, notably the command key on OS X.
+        */
+
+       uint32_t mod = state();
 
+       return gtk_accelerator_get_label (key(), (GdkModifierType) mod);
+}
 
 string
 KeyboardKey::name () const
 {
-        int s = state();
-
-        string str;
-
-        if (s & Keyboard::PrimaryModifier) {
-                str += "Primary";
-        }
-        if (s & Keyboard::SecondaryModifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Secondary";
-        }
-        if (s & Keyboard::TertiaryModifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Tertiary";
-        }
-        if (s & Keyboard::Level4Modifier) {
-                if (!str.empty()) {
-                        str += '-';
-                }
-                str += "Level4";
-        }
-
-        if (!str.empty()) {
-                str += '-';
-        }
-
-        char const *gdk_name = gdk_keyval_name (key());
-
-        if (gdk_name) {
-               str += gdk_name;
-        } else {
-               /* fail! */
-               return string();
-        }
-               
-        return str;
+       int s = state();
+
+       string str;
+
+       if (s & Keyboard::PrimaryModifier) {
+               str += "Primary";
+       }
+       if (s & Keyboard::SecondaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Secondary";
+       }
+       if (s & Keyboard::TertiaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Tertiary";
+       }
+       if (s & Keyboard::Level4Modifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += "Level4";
+       }
+
+       if (!str.empty()) {
+               str += '-';
+       }
+
+       char const *gdk_name = gdk_keyval_name (key());
+
+       if (gdk_name) {
+               str += gdk_name;
+       } else {
+               /* fail! */
+               return string();
+       }
+
+       return str;
+}
+
+string
+KeyboardKey::native_name () const
+{
+       int s = state();
+
+       string str;
+
+       if (s & Keyboard::PrimaryModifier) {
+               str += Keyboard::primary_modifier_name ();
+       }
+       if (s & Keyboard::SecondaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::secondary_modifier_name ();
+       }
+       if (s & Keyboard::TertiaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::tertiary_modifier_name ();
+       }
+       if (s & Keyboard::Level4Modifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::level4_modifier_name ();
+       }
+
+       if (!str.empty()) {
+               str += '-';
+       }
+
+       char const *gdk_name = gdk_keyval_name (key());
+
+       if (gdk_name) {
+               str += gdk_name;
+       } else {
+               /* fail! */
+               return string();
+       }
+
+       return str;
+}
+
+string
+KeyboardKey::native_short_name () const
+{
+       int s = state();
+
+       string str;
+
+       if (s & Keyboard::PrimaryModifier) {
+               str += Keyboard::primary_modifier_short_name ();
+       }
+       if (s & Keyboard::SecondaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::secondary_modifier_short_name ();
+       }
+       if (s & Keyboard::TertiaryModifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::tertiary_modifier_short_name ();
+       }
+       if (s & Keyboard::Level4Modifier) {
+               if (!str.empty()) {
+                       str += '-';
+               }
+               str += Keyboard::level4_modifier_short_name ();
+       }
+
+       if (!str.empty()) {
+               str += '-';
+       }
+
+       char const *gdk_name = gdk_keyval_name (key());
+
+       if (gdk_name) {
+               str += gdk_name;
+       } else {
+               /* fail! */
+               return string();
+       }
+
+       return str;
 }
 
 bool
 KeyboardKey::make_key (const string& str, KeyboardKey& k)
 {
-        int s = 0;
+       int s = 0;
+
+       if (str.find ("Primary") != string::npos) {
+               s |= Keyboard::PrimaryModifier;
+       }
+
+       if (str.find ("Secondary") != string::npos) {
+               s |= Keyboard::SecondaryModifier;
+       }
+
+       if (str.find ("Tertiary") != string::npos) {
+               s |= Keyboard::TertiaryModifier;
+       }
+
+       if (str.find ("Level4") != string::npos) {
+               s |= Keyboard::Level4Modifier;
+       }
+
+       /* since all SINGLE key events keycodes are changed to lower case
+        * before looking them up, make sure we only store lower case here. The
+        * Shift part will be stored in the modifier part of the KeyboardKey.
+        *
+        * And yes Mildred, this doesn't cover CapsLock cases. Oh well.
+        */
 
-        if (str.find ("Primary") != string::npos) {
-                s |= Keyboard::PrimaryModifier;
-        }
+       string actual;
 
-        if (str.find ("Secondary") != string::npos) {
-                s |= Keyboard::SecondaryModifier;
-        }
+       string::size_type lastmod = str.find_last_of ('-');
 
-        if (str.find ("Tertiary") != string::npos) {
-                s |= Keyboard::TertiaryModifier;
-        }
+       if (lastmod != string::npos) {
+               actual = str.substr (lastmod+1);
+       }
+       else {
+               actual = str;
+       }
 
-        if (str.find ("Level4") != string::npos) {
-                s |= Keyboard::Level4Modifier;
-        }
+       if (actual.size() == 1) {
+               actual = PBD::downcase (actual);
+       }
 
-        string::size_type lastmod = str.find_last_of ('-');
-        guint keyval;
-        
-        if (lastmod == string::npos) {
-                keyval = gdk_keyval_from_name (str.c_str());
-        } else {
-                keyval = gdk_keyval_from_name (str.substr (lastmod+1).c_str());
-        }
+       guint keyval;
+       keyval = gdk_keyval_from_name (actual.c_str());
 
-        if (keyval == GDK_VoidSymbol || keyval == 0) {
-               return false;
-        }
+       if (keyval == GDK_VoidSymbol || keyval == 0) {
+               return false;
+       }
 
-        k = KeyboardKey (s, keyval);
+       k = KeyboardKey (s, keyval);
 
-        return true;
+       return true;
 }
 
+/*================================= Bindings =================================*/
 Bindings::Bindings (std::string const& name)
        : _name (name)
-       , _action_map (Actions)
+       , _action_map (0)
 {
        bindings.push_back (this);
 }
@@ -241,10 +383,73 @@ Bindings::~Bindings()
        bindings.remove (this);
 }
 
+string
+Bindings::ardour_action_name (RefPtr<Action> action)
+{
+       /* Skip "<Actions>/" */
+       return action->get_accel_path ().substr (10);
+}
+
+KeyboardKey
+Bindings::get_binding_for_action (RefPtr<Action> action, Operation& op)
+{
+       const string action_name = ardour_action_name (action);
+
+       for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
+
+               /* option one: action has already been associated with the
+                * binding
+                */
+
+               if (k->second.action == action) {
+                       return k->first;
+               }
+
+               /* option two: action name matches, so lookup the action,
+                * setup the association while we're here, and return the binding.
+                */
+
+               if (_action_map && k->second.action_name == action_name) {
+                       k->second.action = _action_map->find_action (action_name);
+                       return k->first;
+               }
+
+       }
+
+       for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
+
+               /* option one: action has already been associated with the
+                * binding
+                */
+
+               if (k->second.action == action) {
+                       return k->first;
+               }
+
+               /* option two: action name matches, so lookup the action,
+                * setup the association while we're here, and return the binding.
+                */
+
+               if (_action_map && k->second.action_name == action_name) {
+                       k->second.action = _action_map->find_action (action_name);
+                       return k->first;
+               }
+
+       }
+
+       return KeyboardKey::null_key();
+}
+
 void
 Bindings::set_action_map (ActionMap& actions)
 {
-       _action_map = actions;
+       if (_action_map) {
+               _action_map->set_bindings (0);
+       }
+
+       _action_map = &actions;
+       _action_map->set_bindings (this);
+
        dissociate ();
        associate ();
 }
@@ -270,40 +475,42 @@ Bindings::empty() const
 bool
 Bindings::activate (KeyboardKey kb, Operation op)
 {
-        KeybindingMap* kbm = 0;
+       KeybindingMap& kbm = get_keymap (op);
 
-        switch (op) {
-        case Press:
-                kbm = &press_bindings;
-                break;
-        case Release:
-                kbm = &release_bindings;
-                break;
-        }
-        
-        KeybindingMap::iterator k = kbm->find (kb);
+       /* if shift was pressed, GDK will send us (e.g) 'E' rather than 'e'.
+          Our bindings all use the lower case character/keyname, so switch
+          to the lower case before doing the lookup.
+       */
+
+       KeyboardKey unshifted (kb.state(), gdk_keyval_to_lower (kb.key()));
 
-        if (k == kbm->end()) {
-                /* no entry for this key in the state map */
-               DEBUG_TRACE (DEBUG::Bindings, string_compose ("no binding for %1\n", kb));
-               return false;
-        }
+       KeybindingMap::iterator k = kbm.find (unshifted);
 
-        RefPtr<Action> action;
-        
-        if (!k->second.action) {
-               action = _action_map.find_action (k->second.action_name);
-        }
+       if (k == kbm.end()) {
+               /* no entry for this key in the state map */
+               DEBUG_TRACE (DEBUG::Bindings, string_compose ("no binding for %1\n", unshifted));
+               return false;
+       }
 
-        if (action) {
-               /* lets do it ... */
-               DEBUG_TRACE (DEBUG::Bindings, string_compose ("binding for %1: %2\n", kb, k->second.action_name));
-               action->activate ();
-        }
+       RefPtr<Action> action;
+
+       if (k->second.action) {
+               action = k->second.action;
+       } else {
+               if (_action_map) {
+                       action = _action_map->find_action (k->second.action_name);
+               }
+       }
+
+       if (action) {
+               /* lets do it ... */
+               DEBUG_TRACE (DEBUG::Bindings, string_compose ("binding for %1: %2\n", unshifted, k->second.action_name));
+               action->activate ();
+       }
 
-        /* return true even if the action could not be found */
+       /* return true even if the action could not be found */
 
-        return true;
+       return true;
 }
 
 void
@@ -311,26 +518,32 @@ Bindings::associate ()
 {
        KeybindingMap::iterator k;
 
+       if (!_action_map) {
+               return;
+       }
+
        for (k = press_bindings.begin(); k != press_bindings.end(); ++k) {
-               k->second.action = _action_map.find_action (k->second.action_name);
+               k->second.action = _action_map->find_action (k->second.action_name);
                if (k->second.action) {
                        push_to_gtk (k->first, k->second.action);
+               } else {
+                       cerr << _name << " didn't find " << k->second.action_name << " in " << _action_map->name() << endl;
                }
        }
 
        for (k = release_bindings.begin(); k != release_bindings.end(); ++k) {
-               k->second.action = _action_map.find_action (k->second.action_name);
+               k->second.action = _action_map->find_action (k->second.action_name);
                /* no working support in GTK for release bindings */
        }
 
        MouseButtonBindingMap::iterator b;
-       
+
        for (b = button_press_bindings.begin(); b != button_press_bindings.end(); ++b) {
-               b->second.action = _action_map.find_action (b->second.action_name);
+               b->second.action = _action_map->find_action (b->second.action_name);
        }
 
        for (b = button_release_bindings.begin(); b != button_release_bindings.end(); ++b) {
-               b->second.action = _action_map.find_action (b->second.action_name);
+               b->second.action = _action_map->find_action (b->second.action_name);
        }
 }
 
@@ -350,419 +563,475 @@ Bindings::dissociate ()
 void
 Bindings::push_to_gtk (KeyboardKey kb, RefPtr<Action> what)
 {
-        /* GTK has the useful feature of showing key bindings for actions in
-         * menus. As of August 2015, we have no interest in trying to
-         * reimplement this functionality, so we will use it even though we no
-         * longer use GTK accelerators for handling key events. To do this, we
-         * need to make sure that there is a fully populated GTK AccelMap set
-         * up with all bindings/actions. 
-         */
-
-        Gtk::AccelKey gtk_key;
-
-        /* tweak the modifier used in the binding so that GTK will accept it
-         * and display something acceptable. The actual keyval should display
-         * correctly even if it involves a key that GTK would not allow
-         * as an accelerator.
-         */
-
-        uint32_t gtk_legal_keyval = kb.key();
-        possibly_translate_keyval_to_make_legal_accelerator (gtk_legal_keyval);
-        KeyboardKey gtk_binding (kb.state(), gtk_legal_keyval);
-        
-
-        bool entry_exists = Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key);
-
-        if (!entry_exists || gtk_key.get_key() == 0) {
-
-               /* there is a trick happening here. It turns out that
-                * gtk_accel_map_add_entry() performs no validation checks on
-                * the accelerator keyval. This means we can use it to define
-                * ANY accelerator, even if they violate GTK's rules
-                * (e.g. about not using navigation keys). This works ONLY when
-                * the entry in the GTK accelerator map has not already been
-                * added. The entries will be added by the GTK UIManager when
-                * building menus, so this code must be called before that
-                * happens.
-                */
-
-               Gtk::AccelMap::add_entry (what->get_accel_path(),
-                                         gtk_binding.key(),
-                                         (Gdk::ModifierType) gtk_binding.state());
-        } else {
-               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;
-        }
-
-        if (!Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key) || gtk_key.get_key() == 0) {
-               cerr << "GTK binding using " << gtk_binding << " failed for " << what->get_accel_path() << " existing = " << gtk_key.get_key() << " + " << gtk_key.get_mod() << endl;
-        }
-}
-
-bool
-Bindings::replace (KeyboardKey kb, Operation op, string const & action_name, bool can_save)
-{
-       /* We have to search the existing binding map by both action and
-        * keybinding, because the following are possible:
-        *
-        *   - key is already used for a different action
-        *   - action has a different binding
-        *   - key is not used
-        *   - action is not bound
+       /* GTK has the useful feature of showing key bindings for actions in
+        * menus. As of August 2015, we have no interest in trying to
+        * reimplement this functionality, so we will use it even though we no
+        * longer use GTK accelerators for handling key events. To do this, we
+        * need to make sure that there is a fully populated GTK AccelMap set
+        * up with all bindings/actions.
         */
-       
-       RefPtr<Action> action = Actions.find_action (action_name);
-
-        if (!action) {
-               return false;
-        }
 
-        KeybindingMap* kbm = 0;
+       Gtk::AccelKey gtk_key;
+       bool entry_exists = Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key);
 
-        switch (op) {
-        case Press:
-                kbm = &press_bindings;
-                break;
-        case Release:
-                kbm = &release_bindings;
-                break;
-        }
+       if (!entry_exists) {
 
-        KeybindingMap::iterator k = kbm->find (kb);
+               /* there is a trick happening here. It turns out that
+                * gtk_accel_map_add_entry() performs no validation checks on
+                * the accelerator keyval. This means we can use it to define
+                * ANY accelerator, even if they violate GTK's rules
+                * (e.g. about not using navigation keys). This works ONLY when
+                * the entry in the GTK accelerator map has not already been
+                * added. The entries will be added by the GTK UIManager when
+                * building menus, so this code must be called before that
+                * happens.
+                */
 
-        if (k != kbm->end()) {
-               kbm->erase (k);
-        }
 
-        /* now linear search by action */
+               int mod = kb.state();
 
-        for (k = kbm->begin(); k != kbm->end(); ++k) {
-               if (k->second.action_name == action_name) {
-                       kbm->erase (k);
-                       break;
-               }
-        }
-
-        add (kb, op, action_name, can_save);
-
-        /* for now, this never fails */
-        
-        return true;
+               Gtk::AccelMap::add_entry (what->get_accel_path(), kb.key(), (Gdk::ModifierType) mod);
+       }
 }
 
-void
-Bindings::add (KeyboardKey kb, Operation op, string const& action_name, bool can_save)
+bool
+Bindings::replace (KeyboardKey kb, Operation op, string const & action_name, bool can_save)
 {
-        KeybindingMap* kbm = 0;
-
-        switch (op) {
-        case Press:
-               kbm = &press_bindings;
-                break;
-        case Release:
-                kbm = &release_bindings;
-                break;
-        }
+       if (!_action_map) {
+               return false;
+       }
 
-        KeybindingMap::iterator k = kbm->find (kb);
+       if (is_registered(op, action_name)) {
+               remove (op, action_name, can_save);
+       }
 
-        if (k != kbm->end()) {
-               kbm->erase (k);
-        } 
-        KeybindingMap::value_type new_pair (kb, ActionInfo (action_name));
-        
-        kbm->insert (new_pair).first;
+       /* XXX need a way to get the old group name */
+       add (kb, op, action_name, 0, can_save);
 
-        if (can_save) {
-               Keyboard::keybindings_changed ();
-        }
+       return true;
 }
 
-void
-Bindings::remove (KeyboardKey kb, Operation op, bool can_save)
+bool
+Bindings::add (KeyboardKey kb, Operation op, string const& action_name, XMLProperty const* group, bool can_save)
 {
-        KeybindingMap* kbm = 0;
+       if (is_registered (op, action_name)) {
+               return false;
+       }
 
-        switch (op) {
-        case Press:
-                kbm = &press_bindings;
-                break;
-        case Release:
-                kbm = &release_bindings;
-                break;
-        }
+       KeybindingMap& kbm = get_keymap (op);
+       if (group) {
+               KeybindingMap::value_type new_pair = make_pair (kb, ActionInfo (action_name, group->value()));
+               (void) kbm.insert (new_pair).first;
+       } else {
+               KeybindingMap::value_type new_pair = make_pair (kb, ActionInfo (action_name));
+               (void) kbm.insert (new_pair).first;
+       }
 
-        KeybindingMap::iterator k = kbm->find (kb);
+       DEBUG_TRACE (DEBUG::Bindings, string_compose ("add binding between %1 and %2, group [%3]\n",
+                                                     kb, action_name, (group ? group->value() : string())));
 
-        if (k != kbm->end()) {
-                kbm->erase (k);
-        }
+       if (can_save) {
+               Keyboard::keybindings_changed ();
+       }
 
-        if (can_save) {
-               Keyboard::keybindings_changed ();
-        }
+       BindingsChanged (this); /* EMIT SIGNAL */
+       return true;
 }
 
-void
-Bindings::remove (RefPtr<Action> action, Operation op, bool can_save)
+bool
+Bindings::remove (Operation op, std::string const& action_name, bool can_save)
 {
-        KeybindingMap* kbm = 0;
+       bool erased_action = false;
+       KeybindingMap& kbm = get_keymap (op);
+       for (KeybindingMap::iterator k = kbm.begin(); k != kbm.end(); ++k) {
+               if (k->second.action_name == action_name) {
+                       kbm.erase (k);
+                       erased_action = true;
+                       break;
+               }
+       }
 
-        switch (op) {
-        case Press:
-                kbm = &press_bindings;
-                break;
-        case Release:
-                kbm = &release_bindings;
-                break;
-        }
+       if (!erased_action) {
+               return erased_action;
+       }
 
-        for (KeybindingMap::iterator k = kbm->begin(); k != kbm->end(); ++k) {
-               if (k->second.action == action) {
-                       kbm->erase (k);
-                       break;
-               }
-        }
+       if (can_save) {
+               Keyboard::keybindings_changed ();
+       }
 
-        if (can_save) {
-               Keyboard::keybindings_changed ();
-        }
+       BindingsChanged (this); /* EMIT SIGNAL */
+       return erased_action;
 }
 
+
 bool
 Bindings::activate (MouseButton bb, Operation op)
 {
-        MouseButtonBindingMap* bbm = 0;
+       MouseButtonBindingMap& bbm = get_mousemap(op);
 
-        switch (op) {
-        case Press:
-                bbm = &button_press_bindings;
-                break;
-        case Release:
-                bbm = &button_release_bindings;
-                break;
-        }
+       MouseButtonBindingMap::iterator b = bbm.find (bb);
 
-        MouseButtonBindingMap::iterator b = bbm->find (bb);
+       if (b == bbm.end()) {
+               /* no entry for this key in the state map */
+               return false;
+       }
+
+       RefPtr<Action> action;
 
-        if (b == bbm->end()) {
-                /* no entry for this key in the state map */
-                return false;
-        }
+       if (b->second.action) {
+               action = b->second.action;
+       } else {
+               if (_action_map) {
+                       action = _action_map->find_action (b->second.action_name);
+               }
+       }
 
-        RefPtr<Action> action;
-        
-        if (!b->second.action) {
-               action = _action_map.find_action (b->second.action_name);
-        }
+       if (action) {
+               /* lets do it ... */
+               DEBUG_TRACE (DEBUG::Bindings, string_compose ("activating action %1\n", ardour_action_name (action)));
+               action->activate ();
+       }
 
-        if (action) {
-               /* lets do it ... */
-               action->activate ();
-        }
+       /* return true even if the action could not be found */
 
-        /* return true even if the action could not be found */
-        
-        return true;
+       return true;
 }
 
 void
-Bindings::add (MouseButton bb, Operation op, string const& action_name)
+Bindings::add (MouseButton bb, Operation op, string const& action_name, XMLProperty const* /*group*/)
 {
-        MouseButtonBindingMap* bbm = 0;
-
-        switch (op) {
-        case Press:
-                bbm = &button_press_bindings;
-                break;
-        case Release:
-                bbm = &button_release_bindings;
-                break;
-        }
+       MouseButtonBindingMap& bbm = get_mousemap(op);
 
-        MouseButtonBindingMap::value_type newpair (bb, ActionInfo (action_name));
-        bbm->insert (newpair);
+       MouseButtonBindingMap::value_type newpair (bb, ActionInfo (action_name));
+       bbm.insert (newpair);
 }
 
 void
 Bindings::remove (MouseButton bb, Operation op)
 {
-        MouseButtonBindingMap* bbm = 0;
+       MouseButtonBindingMap& bbm = get_mousemap(op);
+       MouseButtonBindingMap::iterator b = bbm.find (bb);
 
-        switch (op) {
-        case Press:
-                bbm = &button_press_bindings;
-                break;
-        case Release:
-                bbm = &button_release_bindings;
-                break;
-        }
-
-        MouseButtonBindingMap::iterator b = bbm->find (bb);
-
-        if (b != bbm->end()) {
-                bbm->erase (b);
-        }
+       if (b != bbm.end()) {
+               bbm.erase (b);
+       }
 }
 
 void
 Bindings::save (XMLNode& root)
 {
-        XMLNode* presses = new XMLNode (X_("Press"));
+       XMLNode* presses = new XMLNode (X_("Press"));
 
-        for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
-                XMLNode* child;
+       for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
+               XMLNode* child;
 
-                if (k->first.name().empty()) {
-                       continue;
-                }
+               if (k->first.name().empty()) {
+                       continue;
+               }
 
-                child = new XMLNode (X_("Binding"));
-                child->add_property (X_("key"), k->first.name());
-                child->add_property (X_("action"), k->second.action_name);
-                presses->add_child_nocopy (*child);
-        }
+               child = new XMLNode (X_("Binding"));
+               child->set_property (X_("key"), k->first.name());
+               child->set_property (X_("action"), k->second.action_name);
+               presses->add_child_nocopy (*child);
+       }
 
-        for (MouseButtonBindingMap::iterator k = button_press_bindings.begin(); k != button_press_bindings.end(); ++k) {
-                XMLNode* child;
-                child = new XMLNode (X_("Binding"));
-                child->add_property (X_("button"), k->first.name());
-                child->add_property (X_("action"), k->second.action_name);
-                presses->add_child_nocopy (*child);
-        }
+       for (MouseButtonBindingMap::iterator k = button_press_bindings.begin(); k != button_press_bindings.end(); ++k) {
+               XMLNode* child;
+               child = new XMLNode (X_("Binding"));
+               child->set_property (X_("button"), k->first.name());
+               child->set_property (X_("action"), k->second.action_name);
+               presses->add_child_nocopy (*child);
+       }
 
-        XMLNode* releases = new XMLNode (X_("Release"));
+       XMLNode* releases = new XMLNode (X_("Release"));
 
-        for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
-                XMLNode* child;
+       for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
+               XMLNode* child;
 
-                if (k->first.name().empty()) {
-                       continue;
-                }
+               if (k->first.name().empty()) {
+                       continue;
+               }
 
-                child = new XMLNode (X_("Binding"));
-                child->add_property (X_("key"), k->first.name());
-                child->add_property (X_("action"), k->second.action_name);
-                releases->add_child_nocopy (*child);
-        }
+               child = new XMLNode (X_("Binding"));
+               child->set_property (X_("key"), k->first.name());
+               child->set_property (X_("action"), k->second.action_name);
+               releases->add_child_nocopy (*child);
+       }
 
-        for (MouseButtonBindingMap::iterator k = button_release_bindings.begin(); k != button_release_bindings.end(); ++k) {
-                XMLNode* child;
-                child = new XMLNode (X_("Binding"));
-                child->add_property (X_("button"), k->first.name());
-                child->add_property (X_("action"), k->second.action_name);
-                releases->add_child_nocopy (*child);
-        }
+       for (MouseButtonBindingMap::iterator k = button_release_bindings.begin(); k != button_release_bindings.end(); ++k) {
+               XMLNode* child;
+               child = new XMLNode (X_("Binding"));
+               child->set_property (X_("button"), k->first.name());
+               child->set_property (X_("action"), k->second.action_name);
+               releases->add_child_nocopy (*child);
+       }
 
-        root.add_child_nocopy (*presses);
-        root.add_child_nocopy (*releases);
+       root.add_child_nocopy (*presses);
+       root.add_child_nocopy (*releases);
 }
 
-bool
-Bindings::load (XMLNode const& node)
+void
+Bindings::save_all_bindings_as_html (ostream& ostr)
 {
-        const XMLNodeList& children (node.children());
-        
-        press_bindings.clear ();
-        release_bindings.clear ();
+       if (bindings.empty()) {
+               return;
+       }
+
+
+       ostr << "<html>\n<head>\n<title>";
+       ostr << PROGRAM_NAME;
+       ostr << "</title>\n";
+       ostr << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n";
+
+       ostr << "</head>\n<body>\n";
 
-        for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
-               /* each node could be Press or Release */
-               load_operation (**i);
-        }
+       ostr << "<table border=\"2\" cellpadding=\"6\"><tbody>\n\n";
+       ostr << "<tr>\n\n";
 
-        return true;
+       /* first column: separate by group */
+       ostr << "<td>\n\n";
+       for (list<Bindings*>::const_iterator b = bindings.begin(); b != bindings.end(); ++b) {
+               (*b)->save_as_html (ostr, true);
+       }
+       ostr << "</td>\n\n";
+
+       //second column
+       ostr << "<td style=\"vertical-align:top\">\n\n";
+       for (list<Bindings*>::const_iterator b = bindings.begin(); b != bindings.end(); ++b) {
+               (*b)->save_as_html (ostr, false);
+       }
+       ostr << "</td>\n\n";
+
+
+       ostr << "</tr>\n\n";
+       ostr << "</tbody></table>\n\n";
+
+       ostr << "</br></br>\n\n";
+       ostr << "<table border=\"2\" cellpadding=\"6\"><tbody>\n\n";
+       ostr << "<tr>\n\n";
+       ostr << "<td>\n\n";
+       ostr << "<h2><u> Partial List of Available Actions { => with current shortcut, where applicable } </u></h2>\n\n";
+       {
+               vector<string> paths;
+               vector<string> labels;
+               vector<string> tooltips;
+               vector<string> keys;
+               vector<Glib::RefPtr<Gtk::Action> > actions;
+
+               Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
+
+               vector<string>::iterator k;
+               vector<string>::iterator p;
+               vector<string>::iterator l;
+
+               for (p = paths.begin(), k = keys.begin(), l = labels.begin(); p != paths.end(); ++k, ++p, ++l) {
+
+                       string print_path = *p;
+                       /* strip <Actions>/ from the start */
+                       print_path = print_path.substr (10);
+
+                       if ((*k).empty()) {
+                               ostr << print_path  << " ( " << *l << " ) "  << "</br>" << endl;
+                       } else {
+                               ostr << print_path << " ( " << *l << " ) " << " => " << *k << "</br>" << endl;
+                       }
+               }
+       }
+       ostr << "</td>\n\n";
+       ostr << "</tr>\n\n";
+       ostr << "</tbody></table>\n\n";
+
+       ostr << "</body>\n";
+       ostr << "</html>\n";
 }
 
 void
-Bindings::load_operation (XMLNode const& node)
+Bindings::save_as_html (ostream& ostr, bool categorize) const
 {
-        if (node.name() == X_("Press") || node.name() == X_("Release")) {
 
-                Operation op;
+       if (!press_bindings.empty()) {
+
+               ostr << "<h2><u>";
+               if (categorize)
+                       ostr << _("Window") << ": " << name() << _(" (Categorized)");
+               else
+                       ostr << _("Window") << ": " << name() << _(" (Alphabetical)");
+               ostr << "</u></h2>\n\n";
+
+               typedef std::map<std::string, std::vector<KeybindingMap::const_iterator> > GroupMap;
+               GroupMap group_map;
+
+               for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
+
+                       if (k->first.name().empty()) {
+                               continue;
+                       }
+
+                       string group_name;
+                       if (categorize && !k->second.group_name.empty()) {
+                               group_name = k->second.group_name;
+                       } else {
+                               group_name = _("Uncategorized");
+                       }
+
+                       GroupMap::iterator gm = group_map.find (group_name);
+                       if (gm == group_map.end()) {
+                               std::vector<KeybindingMap::const_iterator> li;
+                               li.push_back (k);
+                               group_map.insert (make_pair (group_name,li));
+                       } else {
+                               gm->second.push_back (k);
+                       }
+               }
+
+
+               for (GroupMap::const_iterator gm = group_map.begin(); gm != group_map.end(); ++gm) {
+
+                       if (categorize) {
+                               ostr << "<h3>" << gm->first << "</h3>\n";
+                       }
+
+                       for (vector<KeybindingMap::const_iterator>::const_iterator k = gm->second.begin(); k != gm->second.end(); ++k) {
+
+                               if ((*k)->first.name().empty()) {
+                                       continue;
+                               }
 
-                if (node.name() == X_("Press")) {
-                        op = Press;
-                } else {
-                        op = Release;
-                }
+                               RefPtr<Action> action;
 
-                const XMLNodeList& children (node.children());
+                               if ((*k)->second.action) {
+                                       action = (*k)->second.action;
+                               } else {
+                                       if (_action_map) {
+                                               action = _action_map->find_action ((*k)->second.action_name);
+                                       }
+                               }
 
-                for (XMLNodeList::const_iterator p = children.begin(); p != children.end(); ++p) {
+                               if (!action) {
+                                       continue;
+                               }
 
-                        XMLProperty* ap;
-                        XMLProperty* kp;
-                        XMLProperty* bp;
+                               string key_name = (*k)->first.native_short_name ();
+                               replace_all (key_name, X_("KP_"), X_("Numpad "));
+                               replace_all (key_name, X_("nabla"), X_("Tab"));
 
-                        ap = (*p)->property ("action");
-                        kp = (*p)->property ("key");
-                        bp = (*p)->property ("button");
+                               string::size_type pos;
 
-                        if (!ap || (!kp && !bp)) {
-                                continue;
-                        }
+                               char const *targets[] = { X_("Separator"), X_("Add"), X_("Subtract"), X_("Decimal"), X_("Divide"),
+                                                         X_("grave"), X_("comma"), X_("period"), X_("asterisk"), X_("backslash"),
+                                                         X_("apostrophe"), X_("minus"), X_("plus"), X_("slash"), X_("semicolon"),
+                                                         X_("colon"), X_("equal"), X_("bracketleft"), X_("bracketright"),
+                                                         X_("ampersand"), X_("numbersign"), X_("parenleft"), X_("parenright"),
+                                                         X_("quoteright"), X_("quoteleft"), X_("exclam"), X_("quotedbl"),
+                                                         0
+                               };
 
-                        if (kp) {
-                                KeyboardKey k;
-                                if (!KeyboardKey::make_key (kp->value(), k)) {
-                                        continue;
-                                }
-                                add (k, op, ap->value());
-                        } else {
-                                MouseButton b;
-                                if (!MouseButton::make_button (bp->value(), b)) {
-                                        continue;
-                                }
-                                add (b, op, ap->value());
-                        }
-                }
-        }
+                               char const *replacements[] = { X_("-"), X_("+"), X_("-"), X_("."), X_("/"),
+                                                              X_("`"), X_(","), X_("."), X_("*"), X_("\\"),
+                                                              X_("'"), X_("-"), X_("+"), X_("/"), X_(";"),
+                                                              X_(":"), X_("="), X_("{"), X_("{"),
+                                                              X_("&"), X_("#"), X_("("), X_(")"),
+                                                              X_("`"), X_("'"), X_("!"), X_("\""),
+                               };
+
+                               for (size_t n = 0; targets[n]; ++n) {
+                                       if ((pos = key_name.find (targets[n])) != string::npos) {
+                                               key_name.replace (pos, strlen (targets[n]), replacements[n]);
+                                       }
+                               }
+
+                               key_name.append(" ");
+
+                               while (key_name.length()<28)
+                                       key_name.append("-");
+
+                               ostr << "<span style=\"font-family:monospace;\">" << key_name;
+                               ostr << "<i>" << action->get_label() << "</i></span></br>\n";
+                       }
+                       ostr << "\n\n";
+
+               }
+
+               ostr << "\n";
+       }
 }
 
-void
-Bindings::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> >& actions)
+bool
+Bindings::load (XMLNode const& node)
 {
-       /* build a reverse map from actions to bindings */
+       const XMLNodeList& children (node.children());
 
-       typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
-       ReverseMap rmap;
+       press_bindings.clear ();
+       release_bindings.clear ();
 
-       for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
-               rmap.insert (make_pair (k->second.action, k->first));
+       for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
+               /* each node could be Press or Release */
+               load_operation (**i);
        }
 
-       /* get a list of all actions */
-
-       ActionMap::Actions all_actions;
-       _action_map.get_actions (all_actions);
-       
-       for (ActionMap::Actions::const_iterator act = all_actions.begin(); act != all_actions.end(); ++act) {
+       return true;
+}
 
-               paths.push_back ((*act)->get_accel_path());
-               labels.push_back ((*act)->get_label());
-               tooltips.push_back ((*act)->get_tooltip());
+void
+Bindings::load_operation (XMLNode const& node)
+{
+       if (node.name() == X_("Press") || node.name() == X_("Release")) {
 
-               ReverseMap::iterator r = rmap.find (*act);
+               Operation op;
 
-               if (r != rmap.end()) {
-                       keys.push_back (gtk_accelerator_get_label (r->second.key(), (GdkModifierType) r->second.state()));
+               if (node.name() == X_("Press")) {
+                       op = Press;
                } else {
-                       keys.push_back (string());
+                       op = Release;
                }
 
-               actions.push_back (*act);
+               const XMLNodeList& children (node.children());
+
+               for (XMLNodeList::const_iterator p = children.begin(); p != children.end(); ++p) {
+
+                       XMLProperty const * ap;
+                       XMLProperty const * kp;
+                       XMLProperty const * bp;
+                       XMLProperty const * gp;
+                       XMLNode const * child = *p;
+
+                       ap = child->property ("action");
+                       kp = child->property ("key");
+                       bp = child->property ("button");
+                       gp = child->property ("group");
+
+                       if (!ap || (!kp && !bp)) {
+                               continue;
+                       }
+
+                       if (kp) {
+                               KeyboardKey k;
+                               if (!KeyboardKey::make_key (kp->value(), k)) {
+                                       continue;
+                               }
+                               add (k, op, ap->value(), gp);
+                       } else {
+                               MouseButton b;
+                               if (!MouseButton::make_button (bp->value(), b)) {
+                                       continue;
+                               }
+                               add (b, op, ap->value(), gp);
+                       }
+               }
        }
 }
 
 void
-Bindings::get_all_actions (std::vector<std::string>& names,
-                           std::vector<std::string>& paths,
-                           std::vector<std::string>& keys)
+Bindings::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> >& actions)
 {
+       if (!_action_map) {
+               return;
+       }
+
        /* build a reverse map from actions to bindings */
 
        typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
@@ -775,27 +1044,32 @@ Bindings::get_all_actions (std::vector<std::string>& names,
        /* get a list of all actions */
 
        ActionMap::Actions all_actions;
-       _action_map.get_actions (all_actions);
-       
+       _action_map->get_actions (all_actions);
+
        for (ActionMap::Actions::const_iterator act = all_actions.begin(); act != all_actions.end(); ++act) {
-               
-               names.push_back ((*act)->get_name());
+
                paths.push_back ((*act)->get_accel_path());
+               labels.push_back ((*act)->get_label());
+               tooltips.push_back ((*act)->get_tooltip());
 
                ReverseMap::iterator r = rmap.find (*act);
+
                if (r != rmap.end()) {
-                       keys.push_back (gtk_accelerator_get_label (r->second.key(), (GdkModifierType) r->second.state()));
+                       keys.push_back (r->second.display_label());
                } else {
                        keys.push_back (string());
                }
+
+               actions.push_back (*act);
        }
 }
 
 Bindings*
-Bindings::get_bindings (string const& name)
+Bindings::get_bindings (string const& name, ActionMap& map)
 {
        for (list<Bindings*>::iterator b = bindings.begin(); b != bindings.end(); b++) {
                if ((*b)->name() == name) {
+                       (*b)->set_action_map (map);
                        return *b;
                }
        }
@@ -811,8 +1085,87 @@ Bindings::associate_all ()
        }
 }
 
+bool
+Bindings::is_bound (KeyboardKey const& kb, Operation op) const
+{
+       const KeybindingMap& km = get_keymap(op);
+       return km.find(kb) != km.end();
+}
+
+std::string
+Bindings::bound_name (KeyboardKey const& kb, Operation op) const
+{
+       const KeybindingMap& km = get_keymap(op);
+       KeybindingMap::const_iterator b = km.find(kb);
+       if (b == km.end()) {
+               return "";
+       }
+       return b->second.action_name;
+}
+
+bool
+Bindings::is_registered (Operation op, std::string const& action_name) const
+{
+       const KeybindingMap& km = get_keymap(op);
+       return std::find_if(km.begin(),  km.end(),  ActionNameRegistered<KeybindingMap::const_iterator::value_type>(action_name)) != km.end();
+}
+
+Bindings::KeybindingMap&
+Bindings::get_keymap (Operation op)
+{
+       switch (op) {
+       case Press:
+               return press_bindings;
+       case Release:
+       default:
+               return release_bindings;
+       }
+}
+
+const Bindings::KeybindingMap&
+Bindings::get_keymap (Operation op) const
+{
+       switch (op) {
+       case Press:
+               return press_bindings;
+       case Release:
+       default:
+               return release_bindings;
+       }
+}
+
+Bindings::MouseButtonBindingMap&
+Bindings::get_mousemap (Operation op)
+{
+       switch (op) {
+       case Press:
+               return button_press_bindings;
+       case Release:
+       default:
+               return button_release_bindings;
+       }
+}
+
 /*==========================================ACTION MAP =========================================*/
 
+ActionMap::ActionMap (string const & name)
+       : _name (name)
+       , _bindings (0)
+{
+       action_maps.push_back (this);
+}
+
+ActionMap::~ActionMap ()
+{
+       action_maps.remove (this);
+}
+
+void
+ActionMap::set_bindings (Bindings* b)
+{
+       _bindings = b;
+}
+
 void
 ActionMap::get_actions (ActionMap::Actions& acts)
 {
@@ -824,27 +1177,54 @@ ActionMap::get_actions (ActionMap::Actions& acts)
 RefPtr<Action>
 ActionMap::find_action (const string& name)
 {
-        _ActionMap::iterator a = _actions.find (name);
+       _ActionMap::iterator a = _actions.find (name);
 
-        if (a != _actions.end()) {
-                return a->second;
-        }
+       if (a != _actions.end()) {
+               return a->second;
+       }
 
-        return RefPtr<Action>();
+       cerr << "Failed to find action: [" << name << ']' << endl;
+       return RefPtr<Action>();
+}
+
+RefPtr<Action>
+ActionMap::find_action (char const * group_name, char const * action_name)
+{
+       string fullpath;
+
+       fullpath = group_name;
+       fullpath += '/';
+       fullpath += action_name;
+
+       _ActionMap::iterator a = _actions.find (fullpath);
+
+       if (a != _actions.end()) {
+               return a->second;
+       }
+
+       cerr << "Failed to find action (2): [" << fullpath << ']' << endl;
+       return RefPtr<Action>();
 }
 
 RefPtr<ActionGroup>
 ActionMap::create_action_group (const string& name)
 {
+       Glib::ListHandle<Glib::RefPtr<ActionGroup> > agl =  ActionManager::ui_manager->get_action_groups ();
+       for (Glib::ListHandle<Glib::RefPtr<ActionGroup> >::iterator i = agl.begin (); i != agl.end (); ++i) {
+               if ((*i)->get_name () == name) {
+                       return *i;
+               }
+       }
+
        RefPtr<ActionGroup> g = ActionGroup::create (name);
 
        /* this is one of the places where our own Action management code
           has to touch the GTK one, because we want the GtkUIManager to
           be able to create widgets (particularly Menus) from our actions.
-          
+
           This is a a necessary step for that to happen.
        */
-       
+
        if (g) {
                ActionManager::ui_manager->insert_action_group (g);
        }
@@ -852,121 +1232,163 @@ ActionMap::create_action_group (const string& name)
        return g;
 }
 
-RefPtr<Action> 
+RefPtr<Action>
 ActionMap::register_action (RefPtr<ActionGroup> group, const char* name, const char* label)
 {
-        string fullpath;
+       string fullpath;
 
-        RefPtr<Action> act = Action::create (name, label);
+       RefPtr<Action> act = Action::create (name, label);
 
-        fullpath = group->get_name();
-        fullpath += '/';
-        fullpath += name;
-        
-        if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
-               group->add (act);
-               return act;
-        }
+       fullpath = group->get_name();
+       fullpath += '/';
+       fullpath += name;
 
-        /* already registered */
-        return RefPtr<Action> ();
+       if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
+               group->add (act);
+               return act;
+       }
+
+       /* already registered */
+       return RefPtr<Action> ();
 }
 
-RefPtr<Action> 
+RefPtr<Action>
 ActionMap::register_action (RefPtr<ActionGroup> group,
                             const char* name, const char* label, sigc::slot<void> sl)
 {
-        string fullpath;
+       string fullpath;
 
-        RefPtr<Action> act = Action::create (name, label);
+       RefPtr<Action> act = Action::create (name, label);
 
-        fullpath = group->get_name();
-        fullpath += '/';
-        fullpath += name;
+       fullpath = group->get_name();
+       fullpath += '/';
+       fullpath += name;
 
-        if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
-               group->add (act, sl);
-               return act;
-        }
+       if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
+               group->add (act, sl);
+               return act;
+       }
 
-        /* already registered */
-        return RefPtr<Action>();
+       /* already registered */
+       return RefPtr<Action>();
 }
 
-RefPtr<Action> 
+RefPtr<Action>
 ActionMap::register_radio_action (RefPtr<ActionGroup> group,
                                   Gtk::RadioAction::Group& rgroup,
-                                  const char* name, const char* label, 
+                                  const char* name, const char* label,
                                   sigc::slot<void> sl)
 {
-        string fullpath;
+       string fullpath;
 
-        RefPtr<Action> act = RadioAction::create (rgroup, name, label);
-        RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
-        
-        fullpath = group->get_name();
-        fullpath += '/';
-        fullpath += name;
+       RefPtr<Action> act = RadioAction::create (rgroup, name, label);
+       RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
 
-        if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
-               group->add (act, sl);
-               return act;
-        }
+       fullpath = group->get_name();
+       fullpath += '/';
+       fullpath += name;
 
-        /* already registered */
-        return RefPtr<Action>();
+       if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
+               group->add (act, sl);
+               return act;
+       }
+
+       /* already registered */
+       return RefPtr<Action>();
 }
 
-RefPtr<Action> 
+RefPtr<Action>
 ActionMap::register_radio_action (RefPtr<ActionGroup> group,
                                   Gtk::RadioAction::Group& rgroup,
-                                  const char* name, const char* label, 
+                                  const char* name, const char* label,
                                   sigc::slot<void,GtkAction*> sl,
                                   int value)
 {
-        string fullpath;
+       string fullpath;
 
-        RefPtr<Action> act = RadioAction::create (rgroup, name, label);
-        RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
-        ract->property_value() = value;
+       RefPtr<Action> act = RadioAction::create (rgroup, name, label);
+       RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
+       ract->property_value() = value;
 
-        fullpath = group->get_name();
-        fullpath += '/';
-        fullpath += name;
+       fullpath = group->get_name();
+       fullpath += '/';
+       fullpath += name;
 
-        if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
-               group->add (act, sigc::bind (sl, act->gobj()));
-               return act;
-        }
+       if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
+               group->add (act, sigc::bind (sl, act->gobj()));
+               return act;
+       }
 
-        /* already registered */
+       /* already registered */
 
-        return RefPtr<Action>();
+       return RefPtr<Action>();
 }
 
-RefPtr<Action> 
+RefPtr<Action>
 ActionMap::register_toggle_action (RefPtr<ActionGroup> group,
                                    const char* name, const char* label, sigc::slot<void> sl)
 {
-        string fullpath;
+       string fullpath;
 
-        fullpath = group->get_name();
-        fullpath += '/';
-        fullpath += name;
+       fullpath = group->get_name();
+       fullpath += '/';
+       fullpath += name;
 
-        RefPtr<Action> act = ToggleAction::create (name, label);
+       RefPtr<Action> act = ToggleAction::create (name, label);
 
-        if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
-               group->add (act, sl);
-               return act;
-        }
+       if (_actions.insert (_ActionMap::value_type (fullpath, act)).second) {
+               group->add (act, sl);
+               return act;
+       }
 
-        /* already registered */
-        return RefPtr<Action>();
+       /* already registered */
+       return RefPtr<Action>();
+}
+
+void
+ActionMap::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> >& actions)
+{
+       for (list<ActionMap*>::const_iterator map = action_maps.begin(); map != action_maps.end(); ++map) {
+
+               ActionMap::Actions these_actions;
+               (*map)->get_actions (these_actions);
+
+               for (ActionMap::Actions::const_iterator act = these_actions.begin(); act != these_actions.end(); ++act) {
+
+                       paths.push_back ((*act)->get_accel_path());
+                       labels.push_back ((*act)->get_label());
+                       tooltips.push_back ((*act)->get_tooltip());
+                       actions.push_back (*act);
+
+                       Bindings* bindings = (*map)->bindings();
+
+                       if (bindings) {
+
+                               KeyboardKey key;
+                               Bindings::Operation op;
+
+                               key = bindings->get_binding_for_action (*act, op);
+
+                               if (key == KeyboardKey::null_key()) {
+                                       keys.push_back (string());
+                               } else {
+                                       keys.push_back (key.display_label());
+                               }
+                       } else {
+                               keys.push_back (string());
+                       }
+               }
+
+               these_actions.clear ();
+       }
 }
 
 std::ostream& operator<<(std::ostream& out, Gtkmm2ext::KeyboardKey const & k) {
        char const *gdk_name = gdk_keyval_name (k.key());
-       return out << "Key " << k.key() << " (" << (gdk_name ? gdk_name : "no-key") << ") state " << k.state();
+       return out << "Key " << k.key() << " (" << (gdk_name ? gdk_name : "no-key") << ") state "
+                  << hex << k.state() << dec << ' ' << show_gdk_event_state (k.state());
 }
-