7e0b528d272fa171611b84f067ec0e9b7c727678
[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
24 #include "pbd/xml++.h"
25 #include "pbd/convert.h"
26
27 #include "gtkmm2ext/actions.h"
28 #include "gtkmm2ext/bindings.h"
29 #include "gtkmm2ext/keyboard.h"
30
31 #include "i18n.h"
32
33 using namespace std;
34 using namespace Glib;
35 using namespace Gtk;
36 using namespace Gtkmm2ext;
37
38 uint32_t Bindings::_ignored_state = 0;
39
40 MouseButton::MouseButton (uint32_t state, uint32_t keycode)
41 {
42         uint32_t ignore = Bindings::ignored_state();
43         
44         if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
45                 /* key is not subject to case, so ignore SHIFT
46                  */
47                 ignore |= GDK_SHIFT_MASK;
48         }
49
50         _val = (state & ~ignore);
51         _val <<= 32;
52         _val |= keycode;
53 };
54
55 bool
56 MouseButton::make_button (const string& str, MouseButton& b)
57 {
58         int s = 0;
59
60         if (str.find ("Primary") != string::npos) {
61                 s |= Keyboard::PrimaryModifier;
62         }
63
64         if (str.find ("Secondary") != string::npos) {
65                 s |= Keyboard::SecondaryModifier;
66         }
67
68         if (str.find ("Tertiary") != string::npos) {
69                 s |= Keyboard::TertiaryModifier;
70         }
71
72         if (str.find ("Level4") != string::npos) {
73                 s |= Keyboard::Level4Modifier;
74         }
75
76         string::size_type lastmod = str.find_last_of ('-');
77         uint32_t button_number;
78         
79         if (lastmod == string::npos) {
80                 button_number = PBD::atoi (str);
81         } else {
82                 button_number = PBD::atoi (str.substr (lastmod+1));
83         }
84
85         b = MouseButton (s, button_number);
86         return true;
87 }
88
89 string
90 MouseButton::name () const
91 {
92         int s = state();
93         
94         string str;
95
96         if (s & Keyboard::PrimaryModifier) {
97                 str += "Primary";
98         } 
99         if (s & Keyboard::SecondaryModifier) {
100                 if (!str.empty()) {
101                         str += '-';
102                 }
103                 str += "Secondary";
104         }
105         if (s & Keyboard::TertiaryModifier) {
106                 if (!str.empty()) {
107                         str += '-';
108                 }
109                 str += "Tertiary";
110         } 
111         if (s & Keyboard::Level4Modifier) {
112                 if (!str.empty()) {
113                         str += '-';
114                 }
115                 str += "Level4";
116         }
117         
118         if (!str.empty()) {
119                 str += '-';
120         }
121
122         char buf[16];
123         snprintf (buf, sizeof (buf), "%u", button());
124         str += buf;
125
126         return str;
127 }
128
129 KeyboardKey::KeyboardKey (uint32_t state, uint32_t keycode)
130 {
131         uint32_t ignore = Bindings::ignored_state();
132         
133         if (gdk_keyval_is_upper (keycode) && gdk_keyval_is_lower (keycode)) {
134                 /* key is not subject to case, so ignore SHIFT
135                  */
136                 ignore |= GDK_SHIFT_MASK;
137         }
138
139         _val = (state & ~ignore);
140         _val <<= 32;
141         _val |= keycode;
142 };
143
144
145 string
146 KeyboardKey::name () const
147 {
148         int s = state();
149         
150         string str;
151
152         if (s & Keyboard::PrimaryModifier) {
153                 str += "Primary";
154         } 
155         if (s & Keyboard::SecondaryModifier) {
156                 if (!str.empty()) {
157                         str += '-';
158                 }
159                 str += "Secondary";
160         }
161         if (s & Keyboard::TertiaryModifier) {
162                 if (!str.empty()) {
163                         str += '-';
164                 }
165                 str += "Tertiary";
166         } 
167         if (s & Keyboard::Level4Modifier) {
168                 if (!str.empty()) {
169                         str += '-';
170                 }
171                 str += "Level4";
172         }
173         
174         if (!str.empty()) {
175                 str += '-';
176         }
177
178         str += gdk_keyval_name (key());
179
180         return str;
181 }
182
183 bool
184 KeyboardKey::make_key (const string& str, KeyboardKey& k)
185 {
186         int s = 0;
187
188         if (str.find ("Primary") != string::npos) {
189                 s |= Keyboard::PrimaryModifier;
190         }
191
192         if (str.find ("Secondary") != string::npos) {
193                 s |= Keyboard::SecondaryModifier;
194         }
195
196         if (str.find ("Tertiary") != string::npos) {
197                 s |= Keyboard::TertiaryModifier;
198         }
199
200         if (str.find ("Level4") != string::npos) {
201                 s |= Keyboard::Level4Modifier;
202         }
203
204         string::size_type lastmod = str.find_last_of ('-');
205         guint keyval;
206
207         if (lastmod == string::npos) {
208                 keyval = gdk_keyval_from_name (str.c_str());
209         } else {
210                 keyval = gdk_keyval_from_name (str.substr (lastmod+1).c_str());
211         }
212
213         if (keyval == GDK_VoidSymbol) {
214                 return false;
215         }
216
217         k = KeyboardKey (s, keyval);
218         return true;
219 }
220
221 Bindings::Bindings ()
222         : action_map (0)
223 {
224 }
225
226 Bindings::~Bindings()
227 {
228 }
229
230 void
231 Bindings::set_action_map (ActionMap& am)
232 {
233         action_map = &am;
234         press_bindings.clear ();
235         release_bindings.clear ();
236 }
237
238 bool
239 Bindings::activate (KeyboardKey kb, Operation op)
240 {
241         KeybindingMap* kbm = 0;
242
243         switch (op) {
244         case Press:
245                 kbm = &press_bindings;
246                 break;
247         case Release:
248                 kbm = &release_bindings;
249                 break;
250         }
251
252         KeybindingMap::iterator k = kbm->find (kb);
253
254         if (k == kbm->end()) {
255                 /* no entry for this key in the state map */
256                 return false;
257         }
258
259         /* lets do it ... */
260
261         k->second->activate ();
262         return true;
263 }
264
265 void
266 Bindings::add (KeyboardKey kb, Operation op, RefPtr<Action> what)
267 {
268         KeybindingMap* kbm = 0;
269
270         switch (op) {
271         case Press:
272                 kbm = &press_bindings;
273                 break;
274         case Release:
275                 kbm = &release_bindings;
276                 break;
277         }
278
279         KeybindingMap::iterator k = kbm->find (kb);
280
281         if (k == kbm->end()) {
282                 pair<KeyboardKey,RefPtr<Action> > newpair (kb, what);
283                 kbm->insert (newpair);
284                 // cerr << "Bindings added " << kb.key() << " w/ " << kb.state() << " => " << what->get_name() << endl;
285         } else {
286                 k->second = what;
287         }
288 }
289
290 void
291 Bindings::remove (KeyboardKey kb, Operation op)
292 {
293         KeybindingMap* kbm = 0;
294
295         switch (op) {
296         case Press:
297                 kbm = &press_bindings;
298                 break;
299         case Release:
300                 kbm = &release_bindings;
301                 break;
302         }
303
304         KeybindingMap::iterator k = kbm->find (kb);
305
306         if (k != kbm->end()) {
307                 kbm->erase (k);
308         }
309 }
310
311 bool
312 Bindings::activate (MouseButton bb, Operation op)
313 {
314         MouseButtonBindingMap* bbm = 0;
315
316         switch (op) {
317         case Press:
318                 bbm = &button_press_bindings;
319                 break;
320         case Release:
321                 bbm = &button_release_bindings;
322                 break;
323         }
324
325         MouseButtonBindingMap::iterator b = bbm->find (bb);
326         
327         if (b == bbm->end()) {
328                 /* no entry for this key in the state map */
329                 return false;
330         }
331
332         /* lets do it ... */
333
334         b->second->activate ();
335         return true;
336 }
337
338 void
339 Bindings::add (MouseButton bb, Operation op, RefPtr<Action> what)
340 {
341         MouseButtonBindingMap* bbm = 0;
342
343         switch (op) {
344         case Press:
345                 bbm = &button_press_bindings;
346                 break;
347         case Release:
348                 bbm = &button_release_bindings;
349                 break;
350         }
351
352         MouseButtonBindingMap::iterator b = bbm->find (bb);
353
354         if (b == bbm->end()) {
355                 pair<MouseButton,RefPtr<Action> > newpair (bb, what);
356                 bbm->insert (newpair);
357                 // cerr << "Bindings added mouse button " << bb.button() << " w/ " << bb.state() << " => " << what->get_name() << endl;
358         } else {
359                 b->second = what;
360         }
361 }
362
363 void
364 Bindings::remove (MouseButton bb, Operation op)
365 {
366         MouseButtonBindingMap* bbm = 0;
367
368         switch (op) {
369         case Press:
370                 bbm = &button_press_bindings;
371                 break;
372         case Release:
373                 bbm = &button_release_bindings;
374                 break;
375         }
376         
377         MouseButtonBindingMap::iterator b = bbm->find (bb);
378
379         if (b != bbm->end()) {
380                 bbm->erase (b);
381         }
382 }
383
384 bool
385 Bindings::save (const string& path)
386 {
387         XMLTree tree;
388         XMLNode* root = new XMLNode (X_("Bindings"));
389         tree.set_root (root);
390         
391         save (*root);
392
393         if (!tree.write (path)) {
394                 ::g_unlink (path.c_str());
395                 return false;
396         }
397
398         return true;
399 }
400
401 void
402 Bindings::save (XMLNode& root)
403 {
404         XMLNode* presses = new XMLNode (X_("Press"));
405         root.add_child_nocopy (*presses);
406
407         for (KeybindingMap::iterator k = press_bindings.begin(); k != press_bindings.end(); ++k) {
408                 XMLNode* child;
409                 child = new XMLNode (X_("Binding"));
410                 child->add_property (X_("key"), k->first.name());
411                 string ap = k->second->get_accel_path();
412                 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
413                 presses->add_child_nocopy (*child);
414         }
415
416         for (MouseButtonBindingMap::iterator k = button_press_bindings.begin(); k != button_press_bindings.end(); ++k) {
417                 XMLNode* child;
418                 child = new XMLNode (X_("Binding"));
419                 child->add_property (X_("button"), k->first.name());
420                 string ap = k->second->get_accel_path();
421                 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
422                 presses->add_child_nocopy (*child);
423         }
424
425         XMLNode* releases = new XMLNode (X_("Release"));
426         root.add_child_nocopy (*releases);
427
428         for (KeybindingMap::iterator k = release_bindings.begin(); k != release_bindings.end(); ++k) {
429                 XMLNode* child;
430                 child = new XMLNode (X_("Binding"));
431                 child->add_property (X_("key"), k->first.name());
432                 string ap = k->second->get_accel_path();
433                 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
434                 releases->add_child_nocopy (*child);
435         }
436
437         for (MouseButtonBindingMap::iterator k = button_release_bindings.begin(); k != button_release_bindings.end(); ++k) {
438                 XMLNode* child;
439                 child = new XMLNode (X_("Binding"));
440                 child->add_property (X_("button"), k->first.name());
441                 string ap = k->second->get_accel_path();
442                 child->add_property (X_("action"), ap.substr (ap.find ('/') + 1));
443                 releases->add_child_nocopy (*child);
444         }
445
446 }
447
448 bool
449 Bindings::load (const string& path)
450 {
451         XMLTree tree;
452
453         if (!action_map) {
454                 return false;
455         }
456
457         if (!tree.read (path)) {
458                 return false;
459         }
460         
461         press_bindings.clear ();
462         release_bindings.clear ();
463
464         XMLNode& root (*tree.root());
465         const XMLNodeList& children (root.children());
466
467         for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
468                 load (**i);
469         }
470
471         return true;
472 }
473
474 void
475 Bindings::load (const XMLNode& node)
476 {
477         if (node.name() == X_("Press") || node.name() == X_("Release")) {
478                 
479                 Operation op;
480                 
481                 if (node.name() == X_("Press")) {
482                         op = Press;
483                 } else {
484                         op = Release;
485                 }
486                 
487                 const XMLNodeList& children (node.children());
488                 
489                 for (XMLNodeList::const_iterator p = children.begin(); p != children.end(); ++p) {
490                         
491                         XMLProperty* ap;
492                         XMLProperty* kp;
493                         XMLProperty* bp;
494                         
495                         ap = (*p)->property ("action");
496                         kp = (*p)->property ("key");
497                         bp = (*p)->property ("button");
498                         
499                         if (!ap || (!kp && !bp)) {
500                                 continue;
501                         }
502
503                         RefPtr<Action> act;
504
505                         if (action_map) {
506                                 act = action_map->find_action (ap->value());
507                         } 
508
509                         if (!act) {
510                                 string::size_type slash = ap->value().find ('/');
511                                 if (slash != string::npos) {
512                                         string group = ap->value().substr (0, slash);
513                                         string action = ap->value().substr (slash+1);
514                                         act = ActionManager::get_action (group.c_str(), action.c_str());
515                                 }
516                         }
517                         
518                         if (!act) {
519                                 continue;
520                         }
521                         
522                         if (kp) {
523                                 KeyboardKey k;
524                                 if (!KeyboardKey::make_key (kp->value(), k)) {
525                                         continue;
526                                 }
527                                 add (k, op, act);
528                         } else {
529                                 MouseButton b;
530                                 if (!MouseButton::make_button (bp->value(), b)) {
531                                         continue;
532                                 }
533                                 add (b, op, act);
534                         }
535                 }
536         }
537 }        
538
539 RefPtr<Action>
540 ActionMap::find_action (const string& name)
541 {
542         _ActionMap::iterator a = actions.find (name);
543
544         if (a != actions.end()) {
545                 return a->second;
546         }
547
548         return RefPtr<Action>();
549 }
550
551 RefPtr<Action> 
552 ActionMap::register_action (const char* path,
553                             const char* name, const char* label, sigc::slot<void> sl)
554 {
555         string fullpath;
556
557         RefPtr<Action> act = Action::create (name, label);
558
559         act->signal_activate().connect (sl);
560
561         fullpath = path;
562         fullpath += '/';
563         fullpath += name;
564
565         actions.insert (_ActionMap::value_type (fullpath, act));
566         return act;
567 }
568
569 RefPtr<Action> 
570 ActionMap::register_radio_action (const char* path, Gtk::RadioAction::Group& rgroup,
571                                   const char* name, const char* label, 
572                                   sigc::slot<void,GtkAction*> sl,
573                                   int value)
574 {
575         string fullpath;
576
577         RefPtr<Action> act = RadioAction::create (rgroup, name, label);
578         RefPtr<RadioAction> ract = RefPtr<RadioAction>::cast_dynamic(act);
579         ract->property_value() = value;
580
581         act->signal_activate().connect (sigc::bind (sl, act->gobj()));
582
583         fullpath = path;
584         fullpath += '/';
585         fullpath += name;
586
587         actions.insert (_ActionMap::value_type (fullpath, act));
588         return act;
589 }
590
591 RefPtr<Action> 
592 ActionMap::register_toggle_action (const char* path,
593                                    const char* name, const char* label, sigc::slot<void> sl)
594 {
595         string fullpath;
596
597         RefPtr<Action> act = ToggleAction::create (name, label);
598
599         act->signal_activate().connect (sl);
600
601         fullpath = path;
602         fullpath += '/';
603         fullpath += name;
604
605         actions.insert (_ActionMap::value_type (fullpath, act));
606         return act;
607 }