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