bbb2793c412625c64e8f1241b04051035a263211
[xonotic/netradiant.git] / libs / gtkutil / accelerator.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "accelerator.h"
23
24 #include "debugging/debugging.h"
25
26 #include <map>
27 #include <set>
28 #include <gtk/gtkwindow.h>
29 #include <gtk/gtkaccelgroup.h>
30
31 #include "generic/callback.h"
32 #include "generic/bitfield.h"
33 #include "string/string.h"
34
35 #include "pointer.h"
36 #include "closure.h"
37
38 #include <gdk/gdkkeysyms.h>
39
40
41
42 struct SKeyInfo
43 {
44   const char* m_strName;
45   unsigned int m_nVKKey;
46 };
47
48 SKeyInfo g_Keys[] =
49 {
50   {"Space", GDK_space},
51   {"Backspace", GDK_BackSpace},
52   {"Escape", GDK_Escape},
53   {"End", GDK_End},
54   {"Insert", GDK_Insert},
55   {"Delete", GDK_Delete},
56   {"PageUp", GDK_Prior},
57   {"PageDown", GDK_Next},
58   {"Up", GDK_Up},
59   {"Down", GDK_Down},
60   {"Left", GDK_Left},
61   {"Right", GDK_Right},
62   {"F1", GDK_F1},
63   {"F2", GDK_F2},
64   {"F3", GDK_F3},
65   {"F4", GDK_F4},
66   {"F5", GDK_F5},
67   {"F6", GDK_F6},
68   {"F7", GDK_F7},
69   {"F8", GDK_F8},
70   {"F9", GDK_F9},
71   {"F10", GDK_F10},
72   {"F11", GDK_F11},
73   {"F12", GDK_F12},
74   {"Tab", GDK_Tab},
75   {"Return", GDK_Return},                           
76   {"Comma", GDK_comma},
77   {"Period", GDK_period},
78   {"Plus", GDK_KP_Add},
79   {"Multiply", GDK_multiply},
80   {"Minus", GDK_KP_Subtract},
81   {"NumPad0", GDK_KP_0},
82   {"NumPad1", GDK_KP_1},
83   {"NumPad2", GDK_KP_2},
84   {"NumPad3", GDK_KP_3},
85   {"NumPad4", GDK_KP_4},
86   {"NumPad5", GDK_KP_5},
87   {"NumPad6", GDK_KP_6},
88   {"NumPad7", GDK_KP_7},
89   {"NumPad8", GDK_KP_8},
90   {"NumPad9", GDK_KP_9},
91   {"[", 219},
92   {"]", 221},
93   {"\\", 220},
94   {"Home", GDK_Home}
95 };
96
97 int g_nKeyCount = sizeof(g_Keys) / sizeof(SKeyInfo);
98
99 const char* global_keys_find(unsigned int key)
100 {
101   for(int i = 0; i < g_nKeyCount; ++i)
102   {
103     if(g_Keys[i].m_nVKKey == key)
104     {
105       return g_Keys[i].m_strName;
106     }
107   }
108   return "";
109 }
110
111 unsigned int global_keys_find(const char* name)
112 {
113   for(int i = 0; i < g_nKeyCount; ++i)
114   {
115     if(string_equal_nocase(g_Keys[i].m_strName, name))
116     {
117       return g_Keys[i].m_nVKKey;
118     }
119   }
120   return 0;
121 }
122
123 void accelerator_write(const Accelerator& accelerator, TextOutputStream& ostream)
124 {
125   if(accelerator.modifiers & GDK_SHIFT_MASK)
126   {
127     ostream << "Shift + ";
128   }
129   if(accelerator.modifiers & GDK_MOD1_MASK)
130   {
131     ostream << "Alt + ";
132   }
133   if(accelerator.modifiers & GDK_CONTROL_MASK)
134   {
135     ostream << "Control + ";
136   }
137
138   const char* keyName = global_keys_find(accelerator.key);
139   if(!string_empty(keyName))
140   {
141     ostream << keyName;
142   }
143   else
144   {
145     ostream << static_cast<char>(accelerator.key);
146   }
147 }
148
149 typedef std::map<Accelerator, Callback> AcceleratorMap;
150
151 bool accelerator_map_insert(AcceleratorMap& acceleratorMap, Accelerator accelerator, const Callback& callback)
152 {
153   if(accelerator.key != 0)
154   {
155     return acceleratorMap.insert(AcceleratorMap::value_type(accelerator, callback)).second;
156   }
157   return true;
158 }
159
160 bool accelerator_map_erase(AcceleratorMap& acceleratorMap, Accelerator accelerator)
161 {
162   if(accelerator.key != 0)
163   {
164     AcceleratorMap::iterator i = acceleratorMap.find(accelerator);
165     if(i == acceleratorMap.end())
166     {
167       return false;
168     }
169     acceleratorMap.erase(i);
170   }
171   return true;
172 }
173
174 Accelerator accelerator_for_event_key(guint keyval, guint state)
175 {
176   keyval = gdk_keyval_to_upper(keyval);
177   if(keyval == GDK_ISO_Left_Tab)
178     keyval = GDK_Tab;
179   return Accelerator(keyval, (GdkModifierType)(state & gtk_accelerator_get_default_mod_mask()));
180 }
181
182 bool AcceleratorMap_activate(const AcceleratorMap& acceleratorMap, const Accelerator& accelerator)
183 {
184   AcceleratorMap::const_iterator i = acceleratorMap.find(accelerator);
185   if(i != acceleratorMap.end())
186   {
187     (*i).second();
188     return true;
189   }
190
191   return false;
192 }
193
194 static gboolean accelerator_key_event(GtkWindow* window, GdkEventKey* event, AcceleratorMap* acceleratorMap)
195 {
196   return AcceleratorMap_activate(*acceleratorMap, accelerator_for_event_key(event->keyval, event->state));
197 }
198
199
200 AcceleratorMap g_special_accelerators;
201
202
203 namespace MouseButton
204 {
205   enum 
206   {
207     Left = 1 << 0,
208     Right = 1 << 1,
209     Middle = 1 << 2,
210   };
211 }
212
213 typedef unsigned int ButtonMask;
214
215 void print_buttons(ButtonMask mask)
216 {
217   globalOutputStream() << "button state: ";
218   if((mask & MouseButton::Left) != 0)
219   {
220     globalOutputStream() << "Left ";
221   }
222   if((mask & MouseButton::Right) != 0)
223   {
224     globalOutputStream() << "Right ";
225   }
226   if((mask & MouseButton::Middle) != 0)
227   {
228     globalOutputStream() << "Middle ";
229   }
230   globalOutputStream() << "\n";
231 }
232
233 ButtonMask ButtonMask_for_event_button(guint button)
234 {
235   switch(button)
236   {
237   case 1:
238     return MouseButton::Left;
239   case 2:
240     return MouseButton::Middle;
241   case 3:
242     return MouseButton::Right;
243   }
244   return 0;
245 }
246
247 bool window_has_accel(GtkWindow* toplevel)
248 {
249   return g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) != 0;
250 }
251
252 namespace
253 {
254   bool g_accel_enabled = true;
255 }
256
257 bool global_accel_enabled()
258 {
259   return g_accel_enabled;
260 }
261
262
263 AcceleratorMap g_queuedAccelerators;
264
265 GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback);
266
267 void GlobalQueuedAccelerators_commit()
268 {
269   for(AcceleratorMap::const_iterator i = g_queuedAccelerators.begin(); i != g_queuedAccelerators.end(); ++i)
270   {
271     accel_group_add_accelerator(global_accel, (*i).first, (*i).second);
272   }
273   g_queuedAccelerators.clear();
274 }
275
276 void GlobalQueuedAccelerators_add(Accelerator accelerator, const Callback& callback)
277 {
278   g_queuedAccelerators.insert(AcceleratorMap::value_type(accelerator, callback));
279 }
280
281 void accel_group_test(GtkWindow* toplevel, GtkAccelGroup* accel)
282 {
283   guint n_entries;
284   gtk_accel_group_query(accel, '4', (GdkModifierType)0, &n_entries);
285   globalOutputStream() << "grid4: " << n_entries << "\n";
286   globalOutputStream() << "toplevel accelgroups: " << g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) << "\n";
287 }
288
289 typedef std::set<GtkWindow*> WindowSet;
290 WindowSet g_accel_windows;
291
292 bool Buttons_press(ButtonMask& buttons, guint button, guint state)
293 {
294   if(buttons == 0 && bitfield_enable(buttons, ButtonMask_for_event_button(button)) != 0)
295   {
296     ASSERT_MESSAGE(g_accel_enabled, "Buttons_press: accelerators not enabled");
297     g_accel_enabled = false;
298     for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i)
299     {
300       GtkWindow* toplevel = *i;
301       ASSERT_MESSAGE(window_has_accel(toplevel), "ERROR");
302       ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "disabling accel for non-toplevel window");
303       gtk_window_remove_accel_group(toplevel,  global_accel);
304 #if 0
305       globalOutputStream() << reinterpret_cast<unsigned int>(toplevel) << ": disabled global accelerators\n";
306 #endif
307 #if 0
308       accel_group_test(toplevel, global_accel);
309 #endif
310     }
311   }
312   buttons = bitfield_enable(buttons, ButtonMask_for_event_button(button));
313 #if 0
314   globalOutputStream() << "Buttons_press: ";
315   print_buttons(buttons);
316 #endif
317   return false;
318 }
319
320 bool Buttons_release(ButtonMask& buttons, guint button, guint state)
321 {
322   if(buttons != 0 && bitfield_disable(buttons, ButtonMask_for_event_button(button)) == 0)
323   {
324     ASSERT_MESSAGE(!g_accel_enabled, "Buttons_release: accelerators are enabled");
325     g_accel_enabled = true;
326     for(WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i)
327     {
328       GtkWindow* toplevel = *i;
329       ASSERT_MESSAGE(!window_has_accel(toplevel), "ERROR");
330       ASSERT_MESSAGE(GTK_WIDGET_TOPLEVEL(toplevel), "enabling accel for non-toplevel window");
331       gtk_window_add_accel_group(toplevel, global_accel);
332 #if 0
333       globalOutputStream() << reinterpret_cast<unsigned int>(toplevel) << ": enabled global accelerators\n";
334 #endif
335 #if 0
336       accel_group_test(toplevel, global_accel);
337 #endif
338     }
339     GlobalQueuedAccelerators_commit();
340   }
341   buttons = bitfield_disable(buttons, ButtonMask_for_event_button(button));
342 #if 0
343   globalOutputStream() << "Buttons_release: ";
344   print_buttons(buttons);
345 #endif
346   return false;
347 }
348
349 bool Buttons_releaseAll(ButtonMask& buttons)
350 {
351   Buttons_release(buttons, MouseButton::Left | MouseButton::Middle | MouseButton::Right, 0);
352   return false;
353 }
354
355 struct PressedButtons
356 {
357   ButtonMask buttons;
358
359   PressedButtons() : buttons(0)
360   {
361   }
362 };
363
364 gboolean PressedButtons_button_press(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed)
365 {
366   if(event->type == GDK_BUTTON_PRESS)
367   {
368     return Buttons_press(pressed->buttons, event->button, event->state);
369   }
370   return FALSE;
371 }
372
373 gboolean PressedButtons_button_release(GtkWidget* widget, GdkEventButton* event, PressedButtons* pressed)
374 {
375   if(event->type == GDK_BUTTON_RELEASE)
376   {
377     return Buttons_release(pressed->buttons, event->button, event->state);
378   }
379   return FALSE;
380 }
381
382 gboolean PressedButtons_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedButtons* pressed)
383 {
384   Buttons_releaseAll(pressed->buttons);
385   return FALSE;
386 }
387
388 void PressedButtons_connect(PressedButtons& pressedButtons, GtkWidget* widget)
389 {
390   g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(PressedButtons_button_press), &pressedButtons);
391   g_signal_connect(G_OBJECT(widget), "button_release_event", G_CALLBACK(PressedButtons_button_release), &pressedButtons);
392   g_signal_connect(G_OBJECT(widget), "focus_out_event", G_CALLBACK(PressedButtons_focus_out), &pressedButtons);
393 }
394
395 PressedButtons g_pressedButtons;
396
397
398 #include <set>
399
400 struct PressedKeys
401 {
402   typedef std::set<guint> Keys;
403   Keys keys;
404   std::size_t refcount;
405
406   PressedKeys() : refcount(0)
407   {
408   }
409 };
410
411 AcceleratorMap g_keydown_accelerators;
412 AcceleratorMap g_keyup_accelerators;
413
414 bool Keys_press(PressedKeys::Keys& keys, guint keyval)
415 {
416   if(keys.insert(keyval).second)
417   {
418     return AcceleratorMap_activate(g_keydown_accelerators, accelerator_for_event_key(keyval, 0));
419   }
420   return g_keydown_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keydown_accelerators.end();
421 }
422
423 bool Keys_release(PressedKeys::Keys& keys, guint keyval)
424 {
425   if(keys.erase(keyval) != 0)
426   {
427     return AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(keyval, 0));
428   }
429   return g_keyup_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keyup_accelerators.end();
430 }
431
432 void Keys_releaseAll(PressedKeys::Keys& keys, guint state)
433 {
434   for(PressedKeys::Keys::iterator i = keys.begin(); i != keys.end(); ++i)
435   {
436     AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(*i, state));
437   }
438   keys.clear();
439 }
440
441 gboolean PressedKeys_key_press(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys)
442 {
443   //globalOutputStream() << "pressed: " << event->keyval << "\n";
444   return event->state == 0 && Keys_press(pressedKeys->keys, event->keyval);
445 }
446
447 gboolean PressedKeys_key_release(GtkWidget* widget, GdkEventKey* event, PressedKeys* pressedKeys)
448 {
449   //globalOutputStream() << "released: " << event->keyval << "\n";
450   return Keys_release(pressedKeys->keys, event->keyval);
451 }
452
453 gboolean PressedKeys_focus_in(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys)
454 {
455   ++pressedKeys->refcount;
456   return FALSE;
457 }
458
459 gboolean PressedKeys_focus_out(GtkWidget* widget, GdkEventFocus* event, PressedKeys* pressedKeys)
460 {
461   if(--pressedKeys->refcount == 0)
462   {
463     Keys_releaseAll(pressedKeys->keys, 0);
464   }
465   return FALSE;
466 }
467
468 PressedKeys g_pressedKeys;
469
470 void GlobalPressedKeys_releaseAll()
471 {
472   Keys_releaseAll(g_pressedKeys.keys, 0);
473 }
474
475 void GlobalPressedKeys_connect(GtkWindow* window)
476 {
477   unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(PressedKeys_key_press), &g_pressedKeys);
478   unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(PressedKeys_key_release), &g_pressedKeys);
479   g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler));
480   g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler));
481   unsigned int focus_in_handler = g_signal_connect(G_OBJECT(window), "focus_in_event", G_CALLBACK(PressedKeys_focus_in), &g_pressedKeys);
482   unsigned int focus_out_handler = g_signal_connect(G_OBJECT(window), "focus_out_event", G_CALLBACK(PressedKeys_focus_out), &g_pressedKeys);
483   g_object_set_data(G_OBJECT(window), "focus_in_handler", gint_to_pointer(focus_in_handler));
484   g_object_set_data(G_OBJECT(window), "focus_out_handler", gint_to_pointer(focus_out_handler));
485 }
486
487 void GlobalPressedKeys_disconnect(GtkWindow* window)
488 {
489   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler")));
490   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler")));
491   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_in_handler")));
492   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_out_handler")));
493 }
494
495
496
497 void special_accelerators_add(Accelerator accelerator, const Callback& callback)
498 {
499   if(!accelerator_map_insert(g_special_accelerators, accelerator, callback))
500   {
501     globalErrorStream() << "special_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
502   }
503 }
504 void special_accelerators_remove(Accelerator accelerator)
505 {
506   if(!accelerator_map_erase(g_special_accelerators, accelerator))
507   {
508     globalErrorStream() << "special_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
509   }
510 }
511
512 void keydown_accelerators_add(Accelerator accelerator, const Callback& callback)
513 {
514   if(!accelerator_map_insert(g_keydown_accelerators, accelerator, callback))
515   {
516     globalErrorStream() << "keydown_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
517   }
518 }
519 void keydown_accelerators_remove(Accelerator accelerator)
520 {
521   if(!accelerator_map_erase(g_keydown_accelerators, accelerator))
522   {
523     globalErrorStream() << "keydown_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
524   }
525 }
526
527 void keyup_accelerators_add(Accelerator accelerator, const Callback& callback)
528 {
529   if(!accelerator_map_insert(g_keyup_accelerators, accelerator, callback))
530   {
531     globalErrorStream() << "keyup_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
532   }
533 }
534 void keyup_accelerators_remove(Accelerator accelerator)
535 {
536   if(!accelerator_map_erase(g_keyup_accelerators, accelerator))
537   {
538     globalErrorStream() << "keyup_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
539   }
540 }
541
542
543 gboolean accel_closure_callback(GtkAccelGroup* group, GtkWidget* widget, guint key, GdkModifierType modifiers, gpointer data)
544 {
545   (*reinterpret_cast<Callback*>(data))();
546   return TRUE;
547 }
548
549 GClosure* accel_group_add_accelerator(GtkAccelGroup* group, Accelerator accelerator, const Callback& callback)
550 {
551   if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers))
552   {
553     //globalOutputStream() << "adding accelerator: " << accelerator.key << " " << accelerator.modifiers << "\n";
554     GClosure* closure = create_cclosure(G_CALLBACK(accel_closure_callback), callback);
555     gtk_accel_group_connect(group, accelerator.key, accelerator.modifiers, GTK_ACCEL_VISIBLE, closure);
556     return closure;
557   }
558   else
559   {
560     special_accelerators_add(accelerator, callback);
561     return 0;
562   }
563 }
564
565 void accel_group_remove_accelerator(GtkAccelGroup* group, Accelerator accelerator)
566 {
567   if(accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers))
568   {
569     gtk_accel_group_disconnect_key(group, accelerator.key, accelerator.modifiers);
570   }
571   else
572   {
573     special_accelerators_remove(accelerator);
574   }
575 }
576
577 GtkAccelGroup* global_accel = 0;
578
579 void global_accel_init()
580 {
581   global_accel = gtk_accel_group_new();
582 }
583
584 void global_accel_destroy()
585 {
586   g_object_unref(global_accel);
587 }
588
589 GClosure* global_accel_group_add_accelerator(Accelerator accelerator, const Callback& callback)
590 {
591   if(!global_accel_enabled())
592   {
593     // workaround: cannot add to GtkAccelGroup while it is disabled
594     GlobalQueuedAccelerators_add(accelerator, callback);
595     return 0;
596   }
597   return accel_group_add_accelerator(global_accel, accelerator, callback);
598 }
599 void global_accel_group_remove_accelerator(Accelerator accelerator)
600 {
601   //ASSERT_MESSAGE(global_accel_enabled(), "removing accelerator while global accel is disabled");
602   accel_group_remove_accelerator(global_accel, accelerator);
603 }
604
605 /// \brief Propagates key events to the focus-widget, overriding global accelerators.
606 static gboolean override_global_accelerators(GtkWindow* window, GdkEventKey* event, gpointer data)
607 {
608   return gtk_window_propagate_key_event(window, event);
609 }
610
611 void global_accel_connect_window(GtkWindow* window)
612 {
613 #if 1
614   unsigned int override_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(override_global_accelerators), 0);
615   g_object_set_data(G_OBJECT(window), "override_handler", gint_to_pointer(override_handler));
616
617   unsigned int special_key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_special_accelerators);
618   g_object_set_data(G_OBJECT(window), "special_key_press_handler", gint_to_pointer(special_key_press_handler));
619
620   GlobalPressedKeys_connect(window);
621 #else
622   unsigned int key_press_handler = g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(accelerator_key_event), &g_keydown_accelerators);
623   unsigned int key_release_handler = g_signal_connect(G_OBJECT(window), "key_release_event", G_CALLBACK(accelerator_key_event), &g_keyup_accelerators);
624   g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler));
625   g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler));
626 #endif
627   g_accel_windows.insert(window);
628   gtk_window_add_accel_group(window, global_accel);
629 }
630 void global_accel_disconnect_window(GtkWindow* window)
631 {
632 #if 1
633   GlobalPressedKeys_disconnect(window);
634
635   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "override_handler")));
636   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "special_key_press_handler")));
637 #else
638   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler")));
639   g_signal_handler_disconnect(G_OBJECT(window), gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler")));
640 #endif
641   gtk_window_remove_accel_group(window, global_accel);
642   std::size_t count = g_accel_windows.erase(window);
643   ASSERT_MESSAGE(count == 1, "failed to remove accel group\n");
644 }
645
646
647 GClosure* global_accel_group_find(Accelerator accelerator)
648 {
649   guint numEntries = 0;
650   GtkAccelGroupEntry* entry = gtk_accel_group_query(global_accel, accelerator.key, accelerator.modifiers, &numEntries);
651   if(numEntries != 0)
652   {
653     if(numEntries != 1)
654     {
655       char* name = gtk_accelerator_name(accelerator.key, accelerator.modifiers);
656       globalErrorStream() << "accelerator already in-use: " << name << "\n";
657       g_free(name);
658     }
659     return entry->closure;
660   }
661   return 0;
662 }
663
664 void global_accel_group_connect(const Accelerator& accelerator, const Callback& callback)
665 {
666   if(accelerator.key != 0)
667   {
668     global_accel_group_add_accelerator(accelerator, callback);
669   }
670 }
671
672 void global_accel_group_disconnect(const Accelerator& accelerator, const Callback& callback)
673 {
674   if(accelerator.key != 0)
675   {
676     global_accel_group_remove_accelerator(accelerator);
677   }
678 }
679
680