]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/accelerator.cpp
reformat code! now the code is only ugly on the *inside*
[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/gtk.h>
29
30 #include "generic/callback.h"
31 #include "generic/bitfield.h"
32 #include "string/string.h"
33
34 #include "pointer.h"
35 #include "closure.h"
36
37 #include <gdk/gdkkeysyms.h>
38
39
40 const char *global_keys_find(unsigned int key)
41 {
42     const char *s;
43     if (key == 0) {
44         return "";
45     }
46     s = gdk_keyval_name(key);
47     if (!s) {
48         return "";
49     }
50     return s;
51 }
52
53 unsigned int global_keys_find(const char *name)
54 {
55     guint k;
56     if (!name || !*name) {
57         return 0;
58     }
59     k = gdk_keyval_from_name(name);
60     if (k == GDK_KEY_VoidSymbol) {
61         return 0;
62     }
63     return k;
64 }
65
66 void accelerator_write(const Accelerator &accelerator, TextOutputStream &ostream)
67 {
68 #if 0
69     if ( accelerator.modifiers & GDK_SHIFT_MASK ) {
70         ostream << "Shift + ";
71     }
72     if ( accelerator.modifiers & GDK_MOD1_MASK ) {
73         ostream << "Alt + ";
74     }
75     if ( accelerator.modifiers & GDK_CONTROL_MASK ) {
76         ostream << "Control + ";
77     }
78
79     const char* keyName = global_keys_find( accelerator.key );
80     if ( !string_empty( keyName ) ) {
81         ostream << keyName;
82     }
83     else
84     {
85         ostream << static_cast<char>( accelerator.key );
86     }
87 #endif
88     ostream << gtk_accelerator_get_label(accelerator.key, accelerator.modifiers);
89 }
90
91 typedef std::map<Accelerator, Callback<void()>> AcceleratorMap;
92 typedef std::set<Accelerator> AcceleratorSet;
93
94 bool accelerator_map_insert(AcceleratorMap &acceleratorMap, Accelerator accelerator, const Callback<void()> &callback)
95 {
96     if (accelerator.key != 0) {
97         return acceleratorMap.insert(AcceleratorMap::value_type(accelerator, callback)).second;
98     }
99     return true;
100 }
101
102 bool accelerator_map_erase(AcceleratorMap &acceleratorMap, Accelerator accelerator)
103 {
104     if (accelerator.key != 0) {
105         AcceleratorMap::iterator i = acceleratorMap.find(accelerator);
106         if (i == acceleratorMap.end()) {
107             return false;
108         }
109         acceleratorMap.erase(i);
110     }
111     return true;
112 }
113
114 Accelerator accelerator_for_event_key(guint keyval, guint state)
115 {
116     keyval = gdk_keyval_to_upper(keyval);
117     if (keyval == GDK_KEY_ISO_Left_Tab) {
118         keyval = GDK_KEY_Tab;
119     }
120     return Accelerator(keyval, (GdkModifierType) (state & gtk_accelerator_get_default_mod_mask()));
121 }
122
123 bool AcceleratorMap_activate(const AcceleratorMap &acceleratorMap, const Accelerator &accelerator)
124 {
125     AcceleratorMap::const_iterator i = acceleratorMap.find(accelerator);
126     if (i != acceleratorMap.end()) {
127         (*i).second();
128         return true;
129     }
130
131     return false;
132 }
133
134 static gboolean accelerator_key_event(ui::Window window, GdkEventKey *event, AcceleratorMap *acceleratorMap)
135 {
136     return AcceleratorMap_activate(*acceleratorMap, accelerator_for_event_key(event->keyval, event->state));
137 }
138
139
140 AcceleratorMap g_special_accelerators;
141
142
143 namespace MouseButton {
144     enum {
145         Left = 1 << 0,
146         Right = 1 << 1,
147         Middle = 1 << 2,
148     };
149 }
150
151 typedef unsigned int ButtonMask;
152
153 void print_buttons(ButtonMask mask)
154 {
155     globalOutputStream() << "button state: ";
156     if ((mask & MouseButton::Left) != 0) {
157         globalOutputStream() << "Left ";
158     }
159     if ((mask & MouseButton::Right) != 0) {
160         globalOutputStream() << "Right ";
161     }
162     if ((mask & MouseButton::Middle) != 0) {
163         globalOutputStream() << "Middle ";
164     }
165     globalOutputStream() << "\n";
166 }
167
168 ButtonMask ButtonMask_for_event_button(guint button)
169 {
170     switch (button) {
171         case 1:
172             return MouseButton::Left;
173         case 2:
174             return MouseButton::Middle;
175         case 3:
176             return MouseButton::Right;
177     }
178     return 0;
179 }
180
181 bool window_has_accel(ui::Window toplevel)
182 {
183     return g_slist_length(gtk_accel_groups_from_object(G_OBJECT(toplevel))) != 0;
184 }
185
186 namespace {
187     bool g_accel_enabled = true;
188 }
189
190 bool global_accel_enabled()
191 {
192     return g_accel_enabled;
193 }
194
195
196 GClosure *accel_group_add_accelerator(ui::AccelGroup group, Accelerator accelerator, const Callback<void()> &callback);
197
198 void accel_group_remove_accelerator(ui::AccelGroup group, Accelerator accelerator);
199
200 AcceleratorMap g_queuedAcceleratorsAdd;
201 AcceleratorSet g_queuedAcceleratorsRemove;
202
203 void globalQueuedAccelerators_add(Accelerator accelerator, const Callback<void()> &callback)
204 {
205     if (!g_queuedAcceleratorsAdd.insert(AcceleratorMap::value_type(accelerator, callback)).second) {
206         globalErrorStream() << "globalQueuedAccelerators_add: accelerator already queued: " << accelerator << "\n";
207     }
208 }
209
210 void globalQueuedAccelerators_remove(Accelerator accelerator)
211 {
212     if (g_queuedAcceleratorsAdd.erase(accelerator) == 0) {
213         if (!g_queuedAcceleratorsRemove.insert(accelerator).second) {
214             globalErrorStream() << "globalQueuedAccelerators_remove: accelerator already queued: " << accelerator
215                                 << "\n";
216         }
217     }
218 }
219
220 void globalQueuedAccelerators_commit()
221 {
222     for (AcceleratorSet::const_iterator i = g_queuedAcceleratorsRemove.begin();
223          i != g_queuedAcceleratorsRemove.end(); ++i) {
224         //globalOutputStream() << "removing: " << (*i).first << "\n";
225         accel_group_remove_accelerator(global_accel, *i);
226     }
227     g_queuedAcceleratorsRemove.clear();
228     for (AcceleratorMap::const_iterator i = g_queuedAcceleratorsAdd.begin(); i != g_queuedAcceleratorsAdd.end(); ++i) {
229         //globalOutputStream() << "adding: " << (*i).first << "\n";
230         accel_group_add_accelerator(global_accel, (*i).first, (*i).second);
231     }
232     g_queuedAcceleratorsAdd.clear();
233 }
234
235 typedef std::set<ui::Window> WindowSet;
236 WindowSet g_accel_windows;
237
238 bool Buttons_press(ButtonMask &buttons, guint button, guint state)
239 {
240     if (buttons == 0 && bitfield_enable(buttons, ButtonMask_for_event_button(button)) != 0) {
241         ASSERT_MESSAGE(g_accel_enabled, "Buttons_press: accelerators not enabled");
242         g_accel_enabled = false;
243         for (WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) {
244             ui::Window toplevel = *i;
245             ASSERT_MESSAGE(window_has_accel(toplevel), "ERROR");
246             ASSERT_MESSAGE(gtk_widget_is_toplevel(toplevel), "disabling accel for non-toplevel window");
247             gtk_window_remove_accel_group(toplevel, global_accel);
248 #if 0
249             globalOutputStream() << reinterpret_cast<unsigned int>( toplevel ) << ": disabled global accelerators\n";
250 #endif
251         }
252     }
253     buttons = bitfield_enable(buttons, ButtonMask_for_event_button(button));
254 #if 0
255     globalOutputStream() << "Buttons_press: ";
256     print_buttons( buttons );
257 #endif
258     return false;
259 }
260
261 bool Buttons_release(ButtonMask &buttons, guint button, guint state)
262 {
263     if (buttons != 0 && bitfield_disable(buttons, ButtonMask_for_event_button(button)) == 0) {
264         ASSERT_MESSAGE(!g_accel_enabled, "Buttons_release: accelerators are enabled");
265         g_accel_enabled = true;
266         for (WindowSet::iterator i = g_accel_windows.begin(); i != g_accel_windows.end(); ++i) {
267             ui::Window toplevel = *i;
268             ASSERT_MESSAGE(!window_has_accel(toplevel), "ERROR");
269             ASSERT_MESSAGE(gtk_widget_is_toplevel(toplevel), "enabling accel for non-toplevel window");
270             toplevel.add_accel_group(global_accel);
271 #if 0
272             globalOutputStream() << reinterpret_cast<unsigned int>( toplevel ) << ": enabled global accelerators\n";
273 #endif
274         }
275         globalQueuedAccelerators_commit();
276     }
277     buttons = bitfield_disable(buttons, ButtonMask_for_event_button(button));
278 #if 0
279     globalOutputStream() << "Buttons_release: ";
280     print_buttons( buttons );
281 #endif
282     return false;
283 }
284
285 bool Buttons_releaseAll(ButtonMask &buttons)
286 {
287     Buttons_release(buttons, MouseButton::Left | MouseButton::Middle | MouseButton::Right, 0);
288     return false;
289 }
290
291 struct PressedButtons {
292     ButtonMask buttons;
293
294     PressedButtons() : buttons(0)
295     {
296     }
297 };
298
299 gboolean PressedButtons_button_press(ui::Widget widget, GdkEventButton *event, PressedButtons *pressed)
300 {
301     if (event->type == GDK_BUTTON_PRESS) {
302         return Buttons_press(pressed->buttons, event->button, event->state);
303     }
304     return FALSE;
305 }
306
307 gboolean PressedButtons_button_release(ui::Widget widget, GdkEventButton *event, PressedButtons *pressed)
308 {
309     if (event->type == GDK_BUTTON_RELEASE) {
310         return Buttons_release(pressed->buttons, event->button, event->state);
311     }
312     return FALSE;
313 }
314
315 gboolean PressedButtons_focus_out(ui::Widget widget, GdkEventFocus *event, PressedButtons *pressed)
316 {
317     Buttons_releaseAll(pressed->buttons);
318     return FALSE;
319 }
320
321 void PressedButtons_connect(PressedButtons &pressedButtons, ui::Widget widget)
322 {
323     widget.connect("button_press_event", G_CALLBACK(PressedButtons_button_press), &pressedButtons);
324     widget.connect("button_release_event", G_CALLBACK(PressedButtons_button_release), &pressedButtons);
325     widget.connect("focus_out_event", G_CALLBACK(PressedButtons_focus_out), &pressedButtons);
326 }
327
328 PressedButtons g_pressedButtons;
329
330
331 #include <set>
332 #include <uilib/uilib.h>
333
334 struct PressedKeys {
335     typedef std::set<guint> Keys;
336     Keys keys;
337     std::size_t refcount;
338
339     PressedKeys() : refcount(0)
340     {
341     }
342 };
343
344 AcceleratorMap g_keydown_accelerators;
345 AcceleratorMap g_keyup_accelerators;
346
347 bool Keys_press(PressedKeys::Keys &keys, guint keyval)
348 {
349     if (keys.insert(keyval).second) {
350         return AcceleratorMap_activate(g_keydown_accelerators, accelerator_for_event_key(keyval, 0));
351     }
352     return g_keydown_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keydown_accelerators.end();
353 }
354
355 bool Keys_release(PressedKeys::Keys &keys, guint keyval)
356 {
357     if (keys.erase(keyval) != 0) {
358         return AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(keyval, 0));
359     }
360     return g_keyup_accelerators.find(accelerator_for_event_key(keyval, 0)) != g_keyup_accelerators.end();
361 }
362
363 void Keys_releaseAll(PressedKeys::Keys &keys, guint state)
364 {
365     for (PressedKeys::Keys::iterator i = keys.begin(); i != keys.end(); ++i) {
366         AcceleratorMap_activate(g_keyup_accelerators, accelerator_for_event_key(*i, state));
367     }
368     keys.clear();
369 }
370
371 gboolean PressedKeys_key_press(ui::Widget widget, GdkEventKey *event, PressedKeys *pressedKeys)
372 {
373     //globalOutputStream() << "pressed: " << event->keyval << "\n";
374     return event->state == 0 && Keys_press(pressedKeys->keys, event->keyval);
375 }
376
377 gboolean PressedKeys_key_release(ui::Widget widget, GdkEventKey *event, PressedKeys *pressedKeys)
378 {
379     //globalOutputStream() << "released: " << event->keyval << "\n";
380     return Keys_release(pressedKeys->keys, event->keyval);
381 }
382
383 gboolean PressedKeys_focus_in(ui::Widget widget, GdkEventFocus *event, PressedKeys *pressedKeys)
384 {
385     ++pressedKeys->refcount;
386     return FALSE;
387 }
388
389 gboolean PressedKeys_focus_out(ui::Widget widget, GdkEventFocus *event, PressedKeys *pressedKeys)
390 {
391     if (--pressedKeys->refcount == 0) {
392         Keys_releaseAll(pressedKeys->keys, 0);
393     }
394     return FALSE;
395 }
396
397 PressedKeys g_pressedKeys;
398
399 void GlobalPressedKeys_releaseAll()
400 {
401     Keys_releaseAll(g_pressedKeys.keys, 0);
402 }
403
404 void GlobalPressedKeys_connect(ui::Window window)
405 {
406     unsigned int key_press_handler = window.connect("key_press_event", G_CALLBACK(PressedKeys_key_press),
407                                                     &g_pressedKeys);
408     unsigned int key_release_handler = window.connect("key_release_event", G_CALLBACK(PressedKeys_key_release),
409                                                       &g_pressedKeys);
410     g_object_set_data(G_OBJECT(window), "key_press_handler", gint_to_pointer(key_press_handler));
411     g_object_set_data(G_OBJECT(window), "key_release_handler", gint_to_pointer(key_release_handler));
412     unsigned int focus_in_handler = window.connect("focus_in_event", G_CALLBACK(PressedKeys_focus_in), &g_pressedKeys);
413     unsigned int focus_out_handler = window.connect("focus_out_event", G_CALLBACK(PressedKeys_focus_out),
414                                                     &g_pressedKeys);
415     g_object_set_data(G_OBJECT(window), "focus_in_handler", gint_to_pointer(focus_in_handler));
416     g_object_set_data(G_OBJECT(window), "focus_out_handler", gint_to_pointer(focus_out_handler));
417 }
418
419 void GlobalPressedKeys_disconnect(ui::Window window)
420 {
421     g_signal_handler_disconnect(G_OBJECT(window),
422                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_press_handler")));
423     g_signal_handler_disconnect(G_OBJECT(window),
424                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "key_release_handler")));
425     g_signal_handler_disconnect(G_OBJECT(window),
426                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_in_handler")));
427     g_signal_handler_disconnect(G_OBJECT(window),
428                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "focus_out_handler")));
429 }
430
431
432 void special_accelerators_add(Accelerator accelerator, const Callback<void()> &callback)
433 {
434     //globalOutputStream() << "special_accelerators_add: " << makeQuoted(accelerator) << "\n";
435     if (!accelerator_map_insert(g_special_accelerators, accelerator, callback)) {
436         globalErrorStream() << "special_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
437     }
438 }
439
440 void special_accelerators_remove(Accelerator accelerator)
441 {
442     //globalOutputStream() << "special_accelerators_remove: " << makeQuoted(accelerator) << "\n";
443     if (!accelerator_map_erase(g_special_accelerators, accelerator)) {
444         globalErrorStream() << "special_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
445     }
446 }
447
448 void keydown_accelerators_add(Accelerator accelerator, const Callback<void()> &callback)
449 {
450     //globalOutputStream() << "keydown_accelerators_add: " << makeQuoted(accelerator) << "\n";
451     if (!accelerator_map_insert(g_keydown_accelerators, accelerator, callback)) {
452         globalErrorStream() << "keydown_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
453     }
454 }
455
456 void keydown_accelerators_remove(Accelerator accelerator)
457 {
458     //globalOutputStream() << "keydown_accelerators_remove: " << makeQuoted(accelerator) << "\n";
459     if (!accelerator_map_erase(g_keydown_accelerators, accelerator)) {
460         globalErrorStream() << "keydown_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
461     }
462 }
463
464 void keyup_accelerators_add(Accelerator accelerator, const Callback<void()> &callback)
465 {
466     //globalOutputStream() << "keyup_accelerators_add: " << makeQuoted(accelerator) << "\n";
467     if (!accelerator_map_insert(g_keyup_accelerators, accelerator, callback)) {
468         globalErrorStream() << "keyup_accelerators_add: already exists: " << makeQuoted(accelerator) << "\n";
469     }
470 }
471
472 void keyup_accelerators_remove(Accelerator accelerator)
473 {
474     //globalOutputStream() << "keyup_accelerators_remove: " << makeQuoted(accelerator) << "\n";
475     if (!accelerator_map_erase(g_keyup_accelerators, accelerator)) {
476         globalErrorStream() << "keyup_accelerators_remove: not found: " << makeQuoted(accelerator) << "\n";
477     }
478 }
479
480
481 gboolean
482 accel_closure_callback(ui::AccelGroup group, ui::Widget widget, guint key, GdkModifierType modifiers, gpointer data)
483 {
484     (*reinterpret_cast<Callback<void()> *>( data ))();
485     return TRUE;
486 }
487
488 GClosure *accel_group_add_accelerator(ui::AccelGroup group, Accelerator accelerator, const Callback<void()> &callback)
489 {
490     if (accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) {
491         //globalOutputStream() << "global_accel_connect: " << makeQuoted(accelerator) << "\n";
492         GClosure *closure = create_cclosure(G_CALLBACK(accel_closure_callback), callback);
493         gtk_accel_group_connect(group, accelerator.key, accelerator.modifiers, GTK_ACCEL_VISIBLE, closure);
494         return closure;
495     } else {
496         special_accelerators_add(accelerator, callback);
497         return 0;
498     }
499 }
500
501 void accel_group_remove_accelerator(ui::AccelGroup group, Accelerator accelerator)
502 {
503     if (accelerator.key != 0 && gtk_accelerator_valid(accelerator.key, accelerator.modifiers)) {
504         //globalOutputStream() << "global_accel_disconnect: " << makeQuoted(accelerator) << "\n";
505         gtk_accel_group_disconnect_key(group, accelerator.key, accelerator.modifiers);
506     } else {
507         special_accelerators_remove(accelerator);
508     }
509 }
510
511 ui::AccelGroup global_accel{ui::New};
512
513 GClosure *global_accel_group_add_accelerator(Accelerator accelerator, const Callback<void()> &callback)
514 {
515     if (!global_accel_enabled()) {
516         // workaround: cannot add to GtkAccelGroup while it is disabled
517         //globalOutputStream() << "queued for add: " << accelerator << "\n";
518         globalQueuedAccelerators_add(accelerator, callback);
519         return 0;
520     }
521     return accel_group_add_accelerator(global_accel, accelerator, callback);
522 }
523
524 void global_accel_group_remove_accelerator(Accelerator accelerator)
525 {
526     if (!global_accel_enabled()) {
527         //globalOutputStream() << "queued for remove: " << accelerator << "\n";
528         globalQueuedAccelerators_remove(accelerator);
529         return;
530     }
531     accel_group_remove_accelerator(global_accel, accelerator);
532 }
533
534 /// \brief Propagates key events to the focus-widget, overriding global accelerators.
535 static gboolean override_global_accelerators(ui::Window window, GdkEventKey *event, gpointer data)
536 {
537     gboolean b = gtk_window_propagate_key_event(window, event);
538     return b;
539 }
540
541 void global_accel_connect_window(ui::Window window)
542 {
543 #if 1
544     unsigned int override_handler = window.connect("key_press_event", G_CALLBACK(override_global_accelerators), 0);
545     g_object_set_data(G_OBJECT(window), "override_handler", gint_to_pointer(override_handler));
546
547     unsigned int special_key_press_handler = window.connect("key_press_event", G_CALLBACK(accelerator_key_event),
548                                                             &g_special_accelerators);
549     g_object_set_data(G_OBJECT(window), "special_key_press_handler", gint_to_pointer(special_key_press_handler));
550
551     GlobalPressedKeys_connect(window);
552 #else
553     unsigned int key_press_handler = window.connect( "key_press_event", G_CALLBACK( accelerator_key_event ), &g_keydown_accelerators );
554     unsigned int key_release_handler = window.connect( "key_release_event", G_CALLBACK( accelerator_key_event ), &g_keyup_accelerators );
555     g_object_set_data( G_OBJECT( window ), "key_press_handler", gint_to_pointer( key_press_handler ) );
556     g_object_set_data( G_OBJECT( window ), "key_release_handler", gint_to_pointer( key_release_handler ) );
557 #endif
558     g_accel_windows.insert(window);
559     window.add_accel_group(global_accel);
560 }
561
562 void global_accel_disconnect_window(ui::Window window)
563 {
564 #if 1
565     GlobalPressedKeys_disconnect(window);
566
567     g_signal_handler_disconnect(G_OBJECT(window),
568                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "override_handler")));
569     g_signal_handler_disconnect(G_OBJECT(window),
570                                 gpointer_to_int(g_object_get_data(G_OBJECT(window), "special_key_press_handler")));
571 #else
572     g_signal_handler_disconnect( G_OBJECT( window ), gpointer_to_int( g_object_get_data( G_OBJECT( window ), "key_press_handler" ) ) );
573     g_signal_handler_disconnect( G_OBJECT( window ), gpointer_to_int( g_object_get_data( G_OBJECT( window ), "key_release_handler" ) ) );
574 #endif
575     gtk_window_remove_accel_group(window, global_accel);
576     std::size_t count = g_accel_windows.erase(window);
577     ASSERT_MESSAGE(count == 1, "failed to remove accel group\n");
578 }
579
580
581 GClosure *global_accel_group_find(Accelerator accelerator)
582 {
583     guint numEntries = 0;
584     GtkAccelGroupEntry *entry = gtk_accel_group_query(global_accel, accelerator.key, accelerator.modifiers,
585                                                       &numEntries);
586     if (numEntries != 0) {
587         if (numEntries != 1) {
588             char *name = gtk_accelerator_name(accelerator.key, accelerator.modifiers);
589             globalErrorStream() << "accelerator already in-use: " << name << "\n";
590             g_free(name);
591         }
592         return entry->closure;
593     }
594     return 0;
595 }
596
597 void global_accel_group_connect(const Accelerator &accelerator, const Callback<void()> &callback)
598 {
599     if (accelerator.key != 0) {
600         global_accel_group_add_accelerator(accelerator, callback);
601     }
602 }
603
604 void global_accel_group_disconnect(const Accelerator &accelerator, const Callback<void()> &callback)
605 {
606     if (accelerator.key != 0) {
607         global_accel_group_remove_accelerator(accelerator);
608     }
609 }