bindings: prevent double registration and double signal connection, and provide get_a...
[ardour.git] / libs / gtkmm2ext / bindings.cc
1 /*
2     Copyright (C) 2012 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #include <iostream>
21
22 #include "pbd/gstdio_compat.h"
23 #include <gtkmm/accelmap.h>
24 #include <gtkmm/uimanager.h>
25
26 #include "pbd/convert.h"
27 #include "pbd/debug.h"
28 #include "pbd/error.h"
29 #include "pbd/xml++.h"
30
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"
36
37 #include "i18n.h"
38
39 using namespace std;
40 using namespace Glib;
41 using namespace Gtk;
42 using namespace Gtkmm2ext;
43 using namespace PBD;
44
45 uint32_t Bindings::_ignored_state = 0;
46
47 MouseButton::MouseButton (uint32_t state, uint32_t keycode)
48 {
49         uint32_t ignore = Bindings::ignored_state();
50
51         if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
52                 /* key is not subject to case, so ignore SHIFT
53                  */
54                 ignore |= GDK_SHIFT_MASK;
55         }
56
57         _val = (state & ~ignore);
58         _val <<= 32;
59         _val |= keycode;
60 };
61
62 bool
63 MouseButton::make_button (const string& str, MouseButton& b)
64 {
65         int s = 0;
66
67         if (str.find ("Primary") != string::npos) {
68                 s |= Keyboard::PrimaryModifier;
69         }
70
71         if (str.find ("Secondary") != string::npos) {
72                 s |= Keyboard::SecondaryModifier;
73         }
74
75         if (str.find ("Tertiary") != string::npos) {
76                 s |= Keyboard::TertiaryModifier;
77         }
78
79         if (str.find ("Level4") != string::npos) {
80                 s |= Keyboard::Level4Modifier;
81         }
82
83         string::size_type lastmod = str.find_last_of ('-');
84         uint32_t button_number;
85
86         if (lastmod == string::npos) {
87                 button_number = PBD::atoi (str);
88         } else {
89                 button_number = PBD::atoi (str.substr (lastmod+1));
90         }
91
92         b = MouseButton (s, button_number);
93         return true;
94 }
95
96 string
97 MouseButton::name () const
98 {
99         int s = state();
100
101         string str;
102
103         if (s & Keyboard::PrimaryModifier) {
104                 str += "Primary";
105         }
106         if (s & Keyboard::SecondaryModifier) {
107                 if (!str.empty()) {
108                         str += '-';
109                 }
110                 str += "Secondary";
111         }
112         if (s & Keyboard::TertiaryModifier) {
113                 if (!str.empty()) {
114                         str += '-';
115                 }
116                 str += "Tertiary";
117         }
118         if (s & Keyboard::Level4Modifier) {
119                 if (!str.empty()) {
120                         str += '-';
121                 }
122                 str += "Level4";
123         }
124
125         if (!str.empty()) {
126                 str += '-';
127         }
128
129         char buf[16];
130         snprintf (buf, sizeof (buf), "%u", button());
131         str += buf;
132
133         return str;
134 }
135
136 KeyboardKey::KeyboardKey (uint32_t state, uint32_t keycode)
137 {
138         uint32_t ignore = Bindings::ignored_state();
139
140         _val = (state & ~ignore);
141         _val <<= 32;
142         _val |= keycode;
143 };
144
145
146 string
147 KeyboardKey::name () const
148 {
149         int s = state();
150
151         string str;
152
153         if (s & Keyboard::PrimaryModifier) {
154                 str += "Primary";
155         }
156         if (s & Keyboard::SecondaryModifier) {
157                 if (!str.empty()) {
158                         str += '-';
159                 }
160                 str += "Secondary";
161         }
162         if (s & Keyboard::TertiaryModifier) {
163                 if (!str.empty()) {
164                         str += '-';
165                 }
166                 str += "Tertiary";
167         }
168         if (s & Keyboard::Level4Modifier) {
169                 if (!str.empty()) {
170                         str += '-';
171                 }
172                 str += "Level4";
173         }
174
175         if (!str.empty()) {
176                 str += '-';
177         }
178
179         str += gdk_keyval_name (key());
180
181         return str;
182 }
183
184 bool
185 KeyboardKey::make_key (const string& str, KeyboardKey& k)
186 {
187         int s = 0;
188
189         if (str.find ("Primary") != string::npos) {
190                 s |= Keyboard::PrimaryModifier;
191         }
192
193         if (str.find ("Secondary") != string::npos) {
194                 s |= Keyboard::SecondaryModifier;
195         }
196
197         if (str.find ("Tertiary") != string::npos) {
198                 s |= Keyboard::TertiaryModifier;
199         }
200
201         if (str.find ("Level4") != string::npos) {
202                 s |= Keyboard::Level4Modifier;
203         }
204
205         string::size_type lastmod = str.find_last_of ('-');
206         guint keyval;
207         
208         if (lastmod == string::npos) {
209                 keyval = gdk_keyval_from_name (str.c_str());
210         } else {
211                 keyval = gdk_keyval_from_name (str.substr (lastmod+1).c_str());
212         }
213
214         if (keyval == GDK_VoidSymbol) {
215                 return false;
216         }
217
218         k = KeyboardKey (s, keyval);
219
220         return true;
221 }
222
223 Bindings::Bindings ()
224         : action_map (0)
225 {
226 }
227
228 Bindings::~Bindings()
229 {
230 }
231
232 bool
233 Bindings::empty_keys() const
234 {
235         return press_bindings.empty() && release_bindings.empty();
236 }
237
238 bool
239 Bindings::empty_mouse () const
240 {
241         return button_press_bindings.empty() && button_release_bindings.empty();
242 }
243
244 bool
245 Bindings::empty() const
246 {
247         return empty_keys() && empty_mouse ();
248 }
249
250 void
251 Bindings::set_action_map (ActionMap& am)
252 {
253         action_map = &am;
254         press_bindings.clear ();
255         release_bindings.clear ();
256 }
257
258 bool
259 Bindings::activate (KeyboardKey kb, Operation op)
260 {
261         KeybindingMap* kbm = 0;
262
263         switch (op) {
264         case Press:
265                 kbm = &press_bindings;
266                 break;
267         case Release:
268                 kbm = &release_bindings;
269                 break;
270         }
271         
272         KeybindingMap::iterator k = kbm->find (kb);
273
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));
277                 return false;
278         }
279
280         /* lets do it ... */
281
282         DEBUG_TRACE (DEBUG::Bindings, string_compose ("binding for %1: %2\n", kb, k->second->get_name()));
283
284         k->second->activate ();
285         return true;
286 }
287
288 void
289 Bindings::add (KeyboardKey kb, Operation op, RefPtr<Action> what)
290 {
291         KeybindingMap* kbm = 0;
292
293         switch (op) {
294         case Press:
295                 kbm = &press_bindings;
296                 break;
297         case Release:
298                 kbm = &release_bindings;
299                 break;
300         }
301
302         KeybindingMap::iterator k = kbm->find (kb);
303
304         if (k == kbm->end()) {
305                 pair<KeyboardKey,RefPtr<Action> > newpair (kb, what);
306                 kbm->insert (newpair);
307         } else {
308                 k->second = what;
309         }
310
311         Gtk::AccelKey gtk_key;
312
313         /* tweak the binding so that GTK will accept it and display something
314          * acceptable 
315          */
316
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);
320         
321
322         bool entry_exists = Gtk::AccelMap::lookup_entry (what->get_accel_path(), gtk_key);
323
324         if (!entry_exists || gtk_key.get_key() == 0) {
325                 Gtk::AccelMap::add_entry (what->get_accel_path(),
326                                           gtk_binding.key(),
327                                           (Gdk::ModifierType) gtk_binding.state());
328         } else {
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;
330         }
331
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;
334         }
335 }
336
337 void
338 Bindings::remove (KeyboardKey kb, Operation op)
339 {
340         KeybindingMap* kbm = 0;
341
342         switch (op) {
343         case Press:
344                 kbm = &press_bindings;
345                 break;
346         case Release:
347                 kbm = &release_bindings;
348                 break;
349         }
350
351         KeybindingMap::iterator k = kbm->find (kb);
352
353         if (k != kbm->end()) {
354                 kbm->erase (k);
355         }
356 }
357
358 bool
359 Bindings::activate (MouseButton bb, Operation op)
360 {
361         MouseButtonBindingMap* bbm = 0;
362
363         switch (op) {
364         case Press:
365                 bbm = &button_press_bindings;
366                 break;
367         case Release:
368                 bbm = &button_release_bindings;
369                 break;
370         }
371
372         MouseButtonBindingMap::iterator b = bbm->find (bb);
373
374         if (b == bbm->end()) {
375                 /* no entry for this key in the state map */
376                 return false;
377         }
378
379         /* lets do it ... */
380
381         b->second->activate ();
382         return true;
383 }
384
385 void
386 Bindings::add (MouseButton bb, Operation op, RefPtr<Action> what)
387 {
388         MouseButtonBindingMap* bbm = 0;
389
390         switch (op) {
391         case Press:
392                 bbm = &button_press_bindings;
393                 break;
394         case Release:
395                 bbm = &button_release_bindings;
396                 break;
397         }
398
399         MouseButtonBindingMap::iterator b = bbm->find (bb);
400
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;
405         } else {
406                 b->second = what;
407         }
408 }
409
410 void
411 Bindings::remove (MouseButton bb, Operation op)
412 {
413         MouseButtonBindingMap* bbm = 0;
414
415         switch (op) {
416         case Press:
417                 bbm = &button_press_bindings;
418                 break;
419         case Release:
420                 bbm = &button_release_bindings;
421                 break;
422         }
423
424         MouseButtonBindingMap::iterator b = bbm->find (bb);
425
426         if (b != bbm->end()) {
427                 bbm->erase (b);
428         }
429 }
430
431 bool
432 Bindings::save (const string& path)
433 {
434         XMLTree tree;
435         XMLNode* root = new XMLNode (X_("Bindings"));
436         tree.set_root (root);
437
438         save (*root);
439
440         if (!tree.write (path)) {
441                 ::g_unlink (path.c_str());
442                 return false;
443         }
444
445         return true;
446 }
447
448 void
449 Bindings::save (XMLNode& root)
450 {
451         XMLNode* presses = new XMLNode (X_("Press"));
452         root.add_child_nocopy (*presses);
453
454         for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
455                 XMLNode* child;
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);
461         }
462
463         for (MouseButtonBindingMap::iterator k = button_press_bindings.begin(); k != button_press_bindings.end(); ++k) {
464                 XMLNode* child;
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);
470         }
471
472         XMLNode* releases = new XMLNode (X_("Release"));
473         root.add_child_nocopy (*releases);
474
475         for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
476                 XMLNode* child;
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);
482         }
483
484         for (MouseButtonBindingMap::iterator k = button_release_bindings.begin(); k != button_release_bindings.end(); ++k) {
485                 XMLNode* child;
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);
491         }
492
493 }
494
495 bool
496 Bindings::load (string const & name)
497 {
498         XMLTree tree;
499
500         if (!action_map) {
501                 return false;
502         }
503
504         XMLNode const * node = Keyboard::bindings_node();
505
506         if (!node) {
507                 error << string_compose (_("No keyboard binding information when loading bindings for \"%1\""), name) << endmsg;
508                 return false;
509         }
510         
511         const XMLNodeList& children (node->children());
512         bool found = false;
513         
514         for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
515
516                 if ((*i)->name() == X_("Bindings")) {
517                         XMLProperty const * prop = (*i)->property (X_("name"));
518
519                         if (!prop) {
520                                 continue;
521                         }
522
523                         if (prop->value() == name) {
524                                 found = true;
525                                 node = *i;
526                                 break;
527                         }
528                 }
529         }
530         
531         if (!found) {
532                 error << string_compose (_("Bindings for \"%1\" not found in keyboard binding node\n"), name) << endmsg;
533                 return false;
534         }
535
536         press_bindings.clear ();
537         release_bindings.clear ();
538
539         const XMLNodeList& bindings (node->children());
540
541         for (XMLNodeList::const_iterator i = bindings.begin(); i != bindings.end(); ++i) {
542                 /* each node could be Press or Release */
543                 load (**i);
544         }
545
546         return true;
547 }
548
549 void
550 Bindings::load (const XMLNode& node)
551 {
552         if (node.name() == X_("Press") || node.name() == X_("Release")) {
553
554                 Operation op;
555
556                 if (node.name() == X_("Press")) {
557                         op = Press;
558                 } else {
559                         op = Release;
560                 }
561
562                 const XMLNodeList& children (node.children());
563
564                 for (XMLNodeList::const_iterator p = children.begin(); p != children.end(); ++p) {
565
566                         XMLProperty* ap;
567                         XMLProperty* kp;
568                         XMLProperty* bp;
569
570                         ap = (*p)->property ("action");
571                         kp = (*p)->property ("key");
572                         bp = (*p)->property ("button");
573
574                         if (!ap || (!kp && !bp)) {
575                                 continue;
576                         }
577
578                         RefPtr<Action> act;
579
580                         if (action_map) {
581                                 act = action_map->find_action (ap->value());
582                         } 
583
584                         if (!act) {
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());
590                                 }
591                         }
592
593                         if (!act) {
594                                 continue;
595                         }
596
597                         if (kp) {
598                                 KeyboardKey k;
599                                 if (!KeyboardKey::make_key (kp->value(), k)) {
600                                         continue;
601                                 }
602                                 add (k, op, act);
603                         } else {
604                                 MouseButton b;
605                                 if (!MouseButton::make_button (bp->value(), b)) {
606                                         continue;
607                                 }
608                                 add (b, op, act);
609                         }
610                 }
611         }
612 }
613
614 void
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)
620 {
621         if (!action_map) {
622                 return;
623         }
624         
625         /* build a reverse map from actions to bindings */
626
627         typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
628         ReverseMap rmap;
629
630         for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
631                 rmap.insert (make_pair (k->second, k->first));
632         }
633
634         /* get a list of all actions */
635
636         ActionMap::Actions actions;
637         action_map->get_actions (actions);
638         
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());
643
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);
648                 } else {
649                         keys.push_back (string());
650                         bindings.push_back (KeyboardKey::null_key());
651                 }
652         }
653 }
654         
655 void
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)
660 {
661         /* build a reverse map from actions to bindings */
662
663         typedef map<Glib::RefPtr<Gtk::Action>,KeyboardKey> ReverseMap;
664         ReverseMap rmap;
665
666         for (KeybindingMap::const_iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
667                 rmap.insert (make_pair (k->second, k->first));
668         }
669
670         /* get a list of all actions */
671
672         ActionMap::Actions actions;
673         action_map->get_actions (actions);
674         
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());
679
680                 ReverseMap::iterator r = rmap.find (*act);
681                 if (r != rmap.end()) {
682                         bindings.push_back (r->second);
683                 } else {
684                         bindings.push_back (KeyboardKey::null_key());
685                 }
686         }
687 }
688
689 void
690 ActionMap::get_actions (ActionMap::Actions& acts)
691 {
692         for (_ActionMap::iterator a = actions.begin(); a != actions.end(); ++a) {
693                 acts.push_back (a->second);
694         }
695 }
696
697 RefPtr<Action>
698 ActionMap::find_action (const string& name)
699 {
700         _ActionMap::iterator a = actions.find (name);
701
702         if (a != actions.end()) {
703                 return a->second;
704         }
705
706         return RefPtr<Action>();
707 }
708
709 RefPtr<ActionGroup>
710 ActionMap::create_action_group (const string& name)
711 {
712         RefPtr<ActionGroup> g = ActionGroup::create (name);
713         return g;
714 }
715
716 void
717 ActionMap::install_action_group (RefPtr<ActionGroup> group)
718 {
719         ActionManager::ui_manager->insert_action_group (group);
720 }
721
722 RefPtr<Action> 
723 ActionMap::register_action (RefPtr<ActionGroup> group, const char* name, const char* label)
724 {
725         string fullpath;
726
727         RefPtr<Action> act = Action::create (name, label);
728
729         fullpath = group->get_name();
730         fullpath += '/';
731         fullpath += name;
732         
733         if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
734                 group->add (act);
735                 return act;
736         }
737
738         /* already registered */
739         return RefPtr<Action> ();
740 }
741
742 RefPtr<Action> 
743 ActionMap::register_action (RefPtr<ActionGroup> group,
744                             const char* name, const char* label, sigc::slot<void> sl)
745 {
746         string fullpath;
747
748         RefPtr<Action> act = Action::create (name, label);
749
750         fullpath = group->get_name();
751         fullpath += '/';
752         fullpath += name;
753
754         if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
755                 group->add (act, sl);
756                 return act;
757         }
758
759         /* already registered */
760         return RefPtr<Action>();
761 }
762
763 RefPtr<Action> 
764 ActionMap::register_radio_action (RefPtr<ActionGroup> group,
765                                   Gtk::RadioAction::Group& rgroup,
766                                   const char* name, const char* label, 
767                                   sigc::slot<void> sl)
768 {
769         string fullpath;
770
771         RefPtr<Action> act = RadioAction::create (rgroup, name, label);
772         RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
773         
774         fullpath = group->get_name();
775         fullpath += '/';
776         fullpath += name;
777
778         if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
779                 group->add (act, sl);
780                 return act;
781         }
782
783         /* already registered */
784         return RefPtr<Action>();
785 }
786
787 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,
792                                   int value)
793 {
794         string fullpath;
795
796         RefPtr<Action> act = RadioAction::create (rgroup, name, label);
797         RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
798         ract->property_value() = value;
799
800         fullpath = group->get_name();
801         fullpath += '/';
802         fullpath += name;
803
804         if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
805                 group->add (act, sigc::bind (sl, act->gobj()));
806                 return act;
807         }
808
809         /* already registered */
810
811         return RefPtr<Action>();
812 }
813
814 RefPtr<Action> 
815 ActionMap::register_toggle_action (RefPtr<ActionGroup> group,
816                                    const char* name, const char* label, sigc::slot<void> sl)
817 {
818         string fullpath;
819
820         fullpath = group->get_name();
821         fullpath += '/';
822         fullpath += name;
823
824         RefPtr<Action> act = ToggleAction::create (name, label);
825
826         if (actions.insert (_ActionMap::value_type (fullpath, act)).second) {
827                 group->add (act, sl);
828                 return act;
829         }
830
831         /* already registered */
832         return RefPtr<Action>();
833 }
834
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();
837 }
838