]> Pileus Git - ~andy/gtk/blob - gtk/gtkaccelmap.c
Make accelmap initialization more forgiving
[~andy/gtk] / gtk / gtkaccelmap.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1998, 2001 Tim Janik
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkaccelmap.h"
23
24 #include "gtkmarshalers.h"
25 #include "gtkwindowprivate.h"
26 #include "gtkintl.h"
27
28 #include <glib/gstdio.h>
29
30 #include <string.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifdef G_OS_WIN32
37 #include <io.h>
38 #endif
39
40
41 /**
42  * SECTION:gtkaccelmap
43  * @Short_description: Loadable keyboard accelerator specifications
44  * @Title: Accelerator Maps
45  * @See_also: #GtkAccelGroup, #GtkAccelKey, #GtkUIManager, gtk_widget_set_accel_path(), gtk_menu_item_set_accel_path(), #GtkSettings:gtk-can-change-accels
46  *
47  * Accelerator maps are used to define runtime configurable accelerators.
48  * Functions for manipulating them are are usually used by higher level
49  * convenience mechanisms like #GtkUIManager and are thus considered
50  * "low-level". You'll want to use them if you're manually creating menus that
51  * should have user-configurable accelerators.
52  *
53  * Accelerator is uniquely defined by:
54  *
55  * <itemizedlist>
56  *   <listitem><para>accelerator path</para></listitem>
57  *   <listitem><para>accelerator key</para></listitem>
58  *   <listitem><para>accelerator modifiers</para></listitem>
59  * </itemizedlist>
60  *
61  * The accelerator path must consist of
62  * "&lt;WINDOWTYPE&gt;/Category1/Category2/.../Action", where WINDOWTYPE
63  * should be a unique application-specific identifier that corresponds to the
64  * kind of window the accelerator is being used in, e.g. "Gimp-Image",
65  * "Abiword-Document" or "Gnumeric-Settings".
66  * The "Category1/.../Action" portion is most appropriately chosen by the action
67  * the accelerator triggers, i.e. for accelerators on menu items, choose the
68  * item's menu path, e.g. "File/Save As", "Image/View/Zoom" or
69  * "Edit/Select All". So a full valid accelerator path may look like:
70  * "&lt;Gimp-Toolbox&gt;/File/Dialogs/Tool Options...".
71  *
72  * All accelerators are stored inside one global #GtkAccelMap that can be
73  * obtained using gtk_accel_map_get(). See <link
74  * linkend="monitoring-changes">Monitoring changes</link> for additional
75  * details.
76  *
77  * <refsect2 id="manipulating-accelerators">
78  * <title>Manipulating accelerators</title>
79  * <para>
80  * New accelerators can be added using gtk_accel_map_add_entry(). To search for
81  * specific accelerator, use gtk_accel_map_lookup_entry(). Modifications of
82  * existing accelerators should be done using gtk_accel_map_change_entry().
83  *
84  * In order to avoid having some accelerators changed, they can be locked using
85  * gtk_accel_map_lock_path(). Unlocking is done using
86  * gtk_accel_map_unlock_path().
87  * </para>
88  * </refsect2>
89  * <refsect2 id="saving-and-loading">
90  * <title>Saving and loading accelerator maps</title>
91  * <para>
92  * Accelerator maps can be saved to and loaded from some external resource. For
93  * simple saving and loading from file, gtk_accel_map_save() and
94  * gtk_accel_map_load() are provided. Saving and loading can also be done by
95  * providing file descriptor to gtk_accel_map_save_fd() and
96  * gtk_accel_map_load_fd().
97  * </para>
98  * </refsect2>
99  * <refsect2 id="monitoring-changes">
100  * <title>Monitoring changes</title>
101  * <para>
102  * #GtkAccelMap object is only useful for monitoring changes of accelerators. By
103  * connecting to #GtkAccelMap::changed signal, one can monitor changes of all
104  * accelerators. It is also possible to monitor only single accelerator path by
105  * using it as a detail of the #GtkAccelMap::changed signal.
106  * </para>
107  * </refsect2>
108  */
109
110
111 /* --- structures --- */
112 struct _GtkAccelMap
113 {
114   GObject parent_instance;
115 };
116
117 struct _GtkAccelMapClass
118 {
119   GObjectClass parent_class;
120 };
121
122 typedef struct {
123   const gchar *accel_path;
124   guint        accel_key;
125   guint        accel_mods;
126   guint        std_accel_key;
127   guint        std_accel_mods;
128   guint        changed    :  1;
129   guint        lock_count : 15;
130   GSList      *groups;
131 } AccelEntry;
132
133 /* --- signals --- */
134 enum {
135   CHANGED,
136   LAST_SIGNAL
137 };
138
139 /* --- variables --- */
140
141 static GHashTable  *accel_entry_ht = NULL;      /* accel_path -> AccelEntry */
142 static GSList      *accel_filters = NULL;
143 static gulong       accel_map_signals[LAST_SIGNAL] = { 0, };
144 static GtkAccelMap *accel_map;
145
146 /* --- prototypes --- */
147 static void do_accel_map_changed (AccelEntry *entry);
148
149 /* --- functions --- */
150 static guint
151 accel_entry_hash (gconstpointer key)
152 {
153   const AccelEntry *entry = key;
154
155   return g_str_hash (entry->accel_path);
156 }
157
158 static gboolean
159 accel_entry_equal (gconstpointer key1,
160                    gconstpointer key2)
161 {
162   const AccelEntry *entry1 = key1;
163   const AccelEntry *entry2 = key2;
164
165   return g_str_equal (entry1->accel_path, entry2->accel_path);
166 }
167
168 static inline AccelEntry*
169 accel_path_lookup (const gchar *accel_path)
170 {
171   AccelEntry ekey;
172
173   ekey.accel_path = accel_path;
174
175   /* safety NULL check for return_if_fail()s */
176   return accel_path ? g_hash_table_lookup (accel_entry_ht, &ekey) : NULL;
177 }
178
179 void
180 _gtk_accel_map_init (void)
181 {
182   if (accel_entry_ht == NULL)
183     accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
184 }
185
186 gboolean
187 _gtk_accel_path_is_valid (const gchar *accel_path)
188 {
189   gchar *p;
190
191   if (!accel_path || accel_path[0] != '<' ||
192       accel_path[1] == '<' || accel_path[1] == '>' || !accel_path[1])
193     return FALSE;
194   p = strchr (accel_path, '>');
195   if (!p || (p[1] != 0 && p[1] != '/'))
196     return FALSE;
197   return TRUE;
198 }
199
200 /**
201  * gtk_accel_map_add_entry:
202  * @accel_path: valid accelerator path
203  * @accel_key:  the accelerator key
204  * @accel_mods: the accelerator modifiers
205  *
206  * Registers a new accelerator with the global accelerator map.
207  * This function should only be called once per @accel_path
208  * with the canonical @accel_key and @accel_mods for this path.
209  * To change the accelerator during runtime programatically, use
210  * gtk_accel_map_change_entry().
211  * 
212  * Note that @accel_path string will be stored in a #GQuark. Therefore, if you
213  * pass a static string, you can save some memory by interning it first with 
214  * g_intern_static_string().
215  */
216 void
217 gtk_accel_map_add_entry (const gchar    *accel_path,
218                          guint           accel_key,
219                          GdkModifierType accel_mods)
220 {
221   AccelEntry *entry;
222
223   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
224
225   if (!accel_key)
226     accel_mods = 0;
227   else
228     accel_mods &= gtk_accelerator_get_default_mod_mask ();
229
230   entry = accel_path_lookup (accel_path);
231   if (entry)
232     {
233       if (!entry->std_accel_key && !entry->std_accel_mods &&
234           (accel_key || accel_mods))
235         {
236           entry->std_accel_key = accel_key;
237           entry->std_accel_mods = accel_mods;
238           if (!entry->changed)
239             gtk_accel_map_change_entry (entry->accel_path, accel_key, accel_mods, TRUE);
240         }
241     }
242   else
243     {
244       entry = g_slice_new0 (AccelEntry);
245       entry->accel_path = g_intern_string (accel_path);
246       entry->std_accel_key = accel_key;
247       entry->std_accel_mods = accel_mods;
248       entry->accel_key = accel_key;
249       entry->accel_mods = accel_mods;
250       entry->changed = FALSE;
251       entry->lock_count = 0;
252       g_hash_table_insert (accel_entry_ht, entry, entry);
253
254       do_accel_map_changed (entry);
255     }
256 }
257
258 /**
259  * gtk_accel_map_lookup_entry:
260  * @accel_path: a valid accelerator path
261  * @key: (allow-none) (out): the accelerator key to be filled in (optional)
262  *
263  * Looks up the accelerator entry for @accel_path and fills in @key.
264  *
265  * Returns: %TRUE if @accel_path is known, %FALSE otherwise
266  */
267 gboolean
268 gtk_accel_map_lookup_entry (const gchar *accel_path,
269                             GtkAccelKey *key)
270 {
271   AccelEntry *entry;
272
273   g_return_val_if_fail (_gtk_accel_path_is_valid (accel_path), FALSE);
274
275   entry = accel_path_lookup (accel_path);
276   if (entry && key)
277     {
278       key->accel_key = entry->accel_key;
279       key->accel_mods = entry->accel_mods;
280       key->accel_flags = 0;
281     }
282
283   return entry ? TRUE : FALSE;
284 }
285
286 static void
287 hash2slist_foreach (gpointer  key,
288                     gpointer  value,
289                     gpointer  user_data)
290 {
291   GSList **slist_p = user_data;
292
293   *slist_p = g_slist_prepend (*slist_p, value);
294 }
295
296 static GSList*
297 g_hash_table_slist_values (GHashTable *hash_table)
298 {
299   GSList *slist = NULL;
300
301   g_return_val_if_fail (hash_table != NULL, NULL);
302
303   g_hash_table_foreach (hash_table, hash2slist_foreach, &slist);
304
305   return slist;
306 }
307
308 /* if simulate==TRUE, return whether accel_path can be changed to
309  * accel_key && accel_mods. otherwise, return whether accel_path
310  * was actually changed.
311  */
312 static gboolean
313 internal_change_entry (const gchar    *accel_path,
314                        guint           accel_key,
315                        GdkModifierType accel_mods,
316                        gboolean        replace,
317                        gboolean        simulate)
318 {
319   GSList *node, *slist, *win_list, *group_list, *replace_list = NULL;
320   GHashTable *group_hm, *window_hm;
321   gboolean change_accel, removable, can_change = TRUE, seen_accel = FALSE;
322   GQuark entry_quark;
323   AccelEntry *entry = accel_path_lookup (accel_path);
324
325   /* not much todo if there's no entry yet */
326   if (!entry)
327     {
328       if (!simulate)
329         {
330           gtk_accel_map_add_entry (accel_path, 0, 0);
331           entry = accel_path_lookup (accel_path);
332           entry->accel_key = accel_key;
333           entry->accel_mods = accel_mods;
334           entry->changed = TRUE;
335
336           do_accel_map_changed (entry);
337         }
338       return TRUE;
339     }
340
341   /* if there's nothing to change, not much todo either */
342   if (entry->accel_key == accel_key && entry->accel_mods == accel_mods)
343     {
344       if (!simulate)
345         entry->changed = TRUE;
346       return simulate ? TRUE : FALSE;
347     }
348
349   /* The no-change case has already been handled, so 
350    * simulate doesn't make a difference here.
351    */
352   if (entry->lock_count > 0)
353     return FALSE;
354
355   /* nobody's interested, easy going */
356   if (!entry->groups)
357     {
358       if (!simulate)
359         {
360           entry->accel_key = accel_key;
361           entry->accel_mods = accel_mods;
362           entry->changed = TRUE;
363
364           do_accel_map_changed (entry);
365         }
366       return TRUE;
367     }
368
369   /* 1) fetch all accel groups affected by this entry */
370   entry_quark = g_quark_try_string (entry->accel_path);
371   group_hm = g_hash_table_new (NULL, NULL);
372   window_hm = g_hash_table_new (NULL, NULL);
373   for (slist = entry->groups; slist; slist = slist->next)
374     g_hash_table_insert (group_hm, slist->data, slist->data);
375
376   /* 2) collect acceleratables affected */
377   group_list = g_hash_table_slist_values (group_hm);
378   for (slist = group_list; slist; slist = slist->next)
379     {
380       GtkAccelGroup *group = slist->data;
381
382       for (node = _gtk_accel_group_get_accelerables (group); node; node = node->next)
383         g_hash_table_insert (window_hm, node->data, node->data);
384     }
385   g_slist_free (group_list);
386
387   /* 3) include all accel groups used by acceleratables */
388   win_list = g_hash_table_slist_values (window_hm);
389   g_hash_table_destroy (window_hm);
390   for (slist = win_list; slist; slist = slist->next)
391     for (node = gtk_accel_groups_from_object (slist->data); node; node = node->next)
392       g_hash_table_insert (group_hm, node->data, node->data);
393   group_list = g_hash_table_slist_values (group_hm);
394   g_hash_table_destroy (group_hm);
395   
396   /* 4) walk the acceleratables and figure whether they occupy accel_key&accel_mods */
397   if (accel_key)
398     for (slist = win_list; slist; slist = slist->next)
399       if (GTK_IS_WINDOW (slist->data))  /* bad kludge in lack of a GtkAcceleratable */
400         if (_gtk_window_query_nonaccels (slist->data, accel_key, accel_mods))
401           {
402             seen_accel = TRUE;
403             break;
404           }
405   removable = !seen_accel;
406   
407   /* 5) walk all accel groups and search for locks */
408   if (removable)
409     for (slist = group_list; slist; slist = slist->next)
410       {
411         GtkAccelGroup *group = slist->data;
412         GtkAccelGroupEntry *ag_entry;
413         guint i, n;
414         
415         n = 0;
416         ag_entry = entry->accel_key ? gtk_accel_group_query (group, entry->accel_key, entry->accel_mods, &n) : NULL;
417         for (i = 0; i < n; i++)
418           if (ag_entry[i].accel_path_quark == entry_quark)
419             {
420               can_change = !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
421               if (!can_change)
422                 goto break_loop_step5;
423             }
424         
425         n = 0;
426         ag_entry = accel_key ? gtk_accel_group_query (group, accel_key, accel_mods, &n) : NULL;
427         for (i = 0; i < n; i++)
428           {
429             seen_accel = TRUE;
430             removable = !gtk_accel_group_get_is_locked (group) && !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
431             if (!removable)
432               goto break_loop_step5;
433             if (ag_entry[i].accel_path_quark)
434               replace_list = g_slist_prepend (replace_list, GUINT_TO_POINTER (ag_entry[i].accel_path_quark));
435           }
436       }
437  break_loop_step5:
438   
439   /* 6) check whether we can remove existing accelerators */
440   if (removable && can_change)
441     for (slist = replace_list; slist; slist = slist->next)
442       if (!internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, TRUE))
443         {
444           removable = FALSE;
445           break;
446         }
447   
448   /* 7) check conditions and proceed if possible */
449   change_accel = can_change && (!seen_accel || (removable && replace));
450   
451   if (change_accel && !simulate)
452     {
453       /* ref accel groups */
454       for (slist = group_list; slist; slist = slist->next)
455         g_object_ref (slist->data);
456
457       /* 8) remove existing accelerators */
458       for (slist = replace_list; slist; slist = slist->next)
459         internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, FALSE);
460
461       /* 9) install new accelerator */
462       entry->accel_key = accel_key;
463       entry->accel_mods = accel_mods;
464       entry->changed = TRUE;
465
466       for (slist = group_list; slist; slist = slist->next)
467         _gtk_accel_group_reconnect (slist->data, g_quark_from_string (entry->accel_path));
468
469       /* unref accel groups */
470       for (slist = group_list; slist; slist = slist->next)
471         g_object_unref (slist->data);
472
473       do_accel_map_changed (entry);
474     }
475   g_slist_free (replace_list);
476   g_slist_free (group_list);
477   g_slist_free (win_list);
478
479   return change_accel;
480 }
481
482 /**
483  * gtk_accel_map_change_entry:
484  * @accel_path:  a valid accelerator path
485  * @accel_key:   the new accelerator key
486  * @accel_mods:  the new accelerator modifiers
487  * @replace:     %TRUE if other accelerators may be deleted upon conflicts
488  *
489  * Changes the @accel_key and @accel_mods currently associated with @accel_path.
490  * Due to conflicts with other accelerators, a change may not always be possible,
491  * @replace indicates whether other accelerators may be deleted to resolve such
492  * conflicts. A change will only occur if all conflicts could be resolved (which
493  * might not be the case if conflicting accelerators are locked). Successful
494  * changes are indicated by a %TRUE return value.
495  *
496  * Note that @accel_path string will be stored in a #GQuark. Therefore, if you
497  * pass a static string, you can save some memory by interning it first with
498  * g_intern_static_string().
499  *
500  * Returns: %TRUE if the accelerator could be changed, %FALSE otherwise
501  */
502 gboolean
503 gtk_accel_map_change_entry (const gchar    *accel_path,
504                             guint           accel_key,
505                             GdkModifierType accel_mods,
506                             gboolean        replace)
507 {
508   g_return_val_if_fail (_gtk_accel_path_is_valid (accel_path), FALSE);
509
510   return internal_change_entry (accel_path, accel_key, accel_key ? accel_mods : 0, replace, FALSE);
511 }
512
513 static guint
514 accel_map_parse_accel_path (GScanner *scanner)
515 {
516   guint accel_key = 0;
517   GdkModifierType accel_mods = 0;
518   gchar *path, *accel;
519   
520   /* parse accel path */
521   g_scanner_get_next_token (scanner);
522   if (scanner->token != G_TOKEN_STRING)
523     return G_TOKEN_STRING;
524
525   /* test if the next token is an accelerator */
526   g_scanner_peek_next_token (scanner);
527   if (scanner->next_token != G_TOKEN_STRING)
528     {
529       /* if not so, eat that token and error out */
530       g_scanner_get_next_token (scanner);
531       return G_TOKEN_STRING;
532     }
533
534   /* get the full accelerator specification */
535   path = g_strdup (scanner->value.v_string);
536   g_scanner_get_next_token (scanner);
537   accel = g_strdup (scanner->value.v_string);
538
539   /* ensure the entry is present */
540   gtk_accel_map_add_entry (path, 0, 0);
541
542   /* and propagate it */
543   gtk_accelerator_parse (accel, &accel_key, &accel_mods);
544   gtk_accel_map_change_entry (path, accel_key, accel_mods, TRUE);
545
546   g_free (accel);
547   g_free (path);
548
549   /* check correct statement end */
550   g_scanner_get_next_token (scanner);
551   if (scanner->token != ')')
552     return ')';
553   else
554     return G_TOKEN_NONE;
555 }
556
557 static void
558 accel_map_parse_statement (GScanner *scanner)
559 {
560   guint expected_token;
561
562   g_scanner_get_next_token (scanner);
563
564   if (scanner->token == G_TOKEN_SYMBOL)
565     {
566       guint (*parser_func) (GScanner*);
567
568       parser_func = (guint (*) (GScanner *))scanner->value.v_symbol;
569
570       expected_token = parser_func (scanner);
571     }
572   else
573     expected_token = G_TOKEN_SYMBOL;
574
575   /* skip rest of statement on errrors
576    */
577   if (expected_token != G_TOKEN_NONE)
578     {
579       register guint level;
580
581       level = 1;
582       if (scanner->token == ')')
583         level--;
584       if (scanner->token == '(')
585         level++;
586
587       while (!g_scanner_eof (scanner) && level > 0)
588         {
589           g_scanner_get_next_token (scanner);
590
591           if (scanner->token == '(')
592             level++;
593           else if (scanner->token == ')')
594             level--;
595         }
596     }
597 }
598
599 /**
600  * gtk_accel_map_load_scanner:
601  * @scanner: a #GScanner which has already been provided with an input file
602  *
603  * #GScanner variant of gtk_accel_map_load().
604  */
605 void
606 gtk_accel_map_load_scanner (GScanner *scanner)
607 {
608   gboolean skip_comment_single;
609   gboolean symbol_2_token;
610   gchar *cpair_comment_single;
611   gpointer saved_symbol;
612   
613   g_return_if_fail (scanner != NULL);
614
615   /* configure scanner */
616   skip_comment_single = scanner->config->skip_comment_single;
617   scanner->config->skip_comment_single = TRUE;
618   cpair_comment_single = scanner->config->cpair_comment_single;
619   scanner->config->cpair_comment_single = ";\n";
620   symbol_2_token = scanner->config->symbol_2_token;
621   scanner->config->symbol_2_token = FALSE;
622   saved_symbol = g_scanner_lookup_symbol (scanner, "gtk_accel_path");
623   g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", 
624                               accel_map_parse_accel_path);
625
626   /* outer parsing loop
627    */
628   g_scanner_peek_next_token (scanner);
629   while (scanner->next_token == '(')
630     {
631       g_scanner_get_next_token (scanner);
632
633       accel_map_parse_statement (scanner);
634
635       g_scanner_peek_next_token (scanner);
636     }
637
638   /* restore config */
639   scanner->config->skip_comment_single = skip_comment_single;
640   scanner->config->cpair_comment_single = cpair_comment_single;
641   scanner->config->symbol_2_token = symbol_2_token;
642   g_scanner_scope_remove_symbol (scanner, 0, "gtk_accel_path");
643   if (saved_symbol)
644     g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", saved_symbol);
645 }
646
647 /**
648  * gtk_accel_map_load_fd:
649  * @fd: a valid readable file descriptor
650  *
651  * Filedescriptor variant of gtk_accel_map_load().
652  *
653  * Note that the file descriptor will not be closed by this function.
654  */
655 void
656 gtk_accel_map_load_fd (gint fd)
657 {
658   GScanner *scanner;
659
660   g_return_if_fail (fd >= 0);
661
662   /* create and setup scanner */
663   scanner = g_scanner_new (NULL);
664   g_scanner_input_file (scanner, fd);
665
666   gtk_accel_map_load_scanner (scanner);
667
668   g_scanner_destroy (scanner);
669 }
670
671 /**
672  * gtk_accel_map_load:
673  * @file_name: (type filename): a file containing accelerator specifications,
674  *   in the GLib file name encoding
675  *
676  * Parses a file previously saved with gtk_accel_map_save() for
677  * accelerator specifications, and propagates them accordingly.
678  */
679 void
680 gtk_accel_map_load (const gchar *file_name)
681 {
682   gint fd;
683
684   g_return_if_fail (file_name != NULL);
685
686   if (!g_file_test (file_name, G_FILE_TEST_IS_REGULAR))
687     return;
688
689   fd = g_open (file_name, O_RDONLY, 0);
690   if (fd < 0)
691     return;
692
693   gtk_accel_map_load_fd (fd);
694
695   close (fd);
696 }
697
698 static gboolean
699 write_all (gint   fd,
700            gchar *buf,
701            gsize  to_write)
702 {
703   while (to_write > 0)
704     {
705       gssize count = write (fd, buf, to_write);
706       if (count < 0)
707         {
708           if (errno != EINTR)
709             return FALSE;
710         }
711       else
712         {
713           to_write -= count;
714           buf += count;
715         }
716     }
717
718   return TRUE;
719 }
720
721 static void
722 accel_map_print (gpointer        data,
723                  const gchar    *accel_path,
724                  guint           accel_key,
725                  GdkModifierType accel_mods,
726                  gboolean        changed)
727 {
728   GString *gstring = g_string_new (changed ? NULL : "; ");
729   gint fd = GPOINTER_TO_INT (data);
730   gchar *tmp, *name;
731
732   g_string_append (gstring, "(gtk_accel_path \"");
733
734   tmp = g_strescape (accel_path, NULL);
735   g_string_append (gstring, tmp);
736   g_free (tmp);
737
738   g_string_append (gstring, "\" \"");
739
740   name = gtk_accelerator_name (accel_key, accel_mods);
741   tmp = g_strescape (name, NULL);
742   g_free (name);
743   g_string_append (gstring, tmp);
744   g_free (tmp);
745
746   g_string_append (gstring, "\")\n");
747
748   write_all (fd, gstring->str, gstring->len);
749
750   g_string_free (gstring, TRUE);
751 }
752
753 /**
754  * gtk_accel_map_save_fd:
755  * @fd: a valid writable file descriptor
756  *
757  * Filedescriptor variant of gtk_accel_map_save().
758  *
759  * Note that the file descriptor will not be closed by this function.
760  */
761 void
762 gtk_accel_map_save_fd (gint fd)
763 {
764   GString *gstring;
765
766   g_return_if_fail (fd >= 0);
767
768   gstring = g_string_new ("; ");
769   if (g_get_prgname ())
770     g_string_append (gstring, g_get_prgname ());
771   g_string_append (gstring, " GtkAccelMap rc-file         -*- scheme -*-\n");
772   g_string_append (gstring, "; this file is an automated accelerator map dump\n");
773   g_string_append (gstring, ";\n");
774
775   write_all (fd, gstring->str, gstring->len);
776   
777   g_string_free (gstring, TRUE);
778
779   gtk_accel_map_foreach (GINT_TO_POINTER (fd), accel_map_print);
780 }
781
782 /**
783  * gtk_accel_map_save:
784  * @file_name: (type filename): the name of the file to contain
785  *   accelerator specifications, in the GLib file name encoding
786  *
787  * Saves current accelerator specifications (accelerator path, key
788  * and modifiers) to @file_name.
789  * The file is written in a format suitable to be read back in by
790  * gtk_accel_map_load().
791  */
792 void
793 gtk_accel_map_save (const gchar *file_name)
794 {
795   gint fd;
796
797   g_return_if_fail (file_name != NULL);
798
799   fd = g_open (file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
800   if (fd < 0)
801     return;
802
803   gtk_accel_map_save_fd (fd);
804
805   close (fd);
806 }
807
808 /**
809  * gtk_accel_map_foreach:
810  * @data: (allow-none): data to be passed into @foreach_func
811  * @foreach_func: (scope call): function to be executed for each accel
812  *                map entry which is not filtered out
813  *
814  * Loops over the entries in the accelerator map whose accel path 
815  * doesn't match any of the filters added with gtk_accel_map_add_filter(), 
816  * and execute @foreach_func on each. The signature of @foreach_func is 
817  * that of #GtkAccelMapForeach, the @changed parameter indicates whether
818  * this accelerator was changed during runtime (thus, would need
819  * saving during an accelerator map dump).
820  */
821 void
822 gtk_accel_map_foreach (gpointer           data,
823                        GtkAccelMapForeach foreach_func)
824 {
825   GSList *entries, *slist, *node;
826
827   g_return_if_fail (foreach_func != NULL);
828
829   entries = g_hash_table_slist_values (accel_entry_ht);
830   for (slist = entries; slist; slist = slist->next)
831     {
832       AccelEntry *entry = slist->data;
833       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
834
835       for (node = accel_filters; node; node = node->next)
836         if (g_pattern_match_string (node->data, entry->accel_path))
837           goto skip_accel;
838       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
839     skip_accel:
840       /* noop */;
841     }
842   g_slist_free (entries);
843 }
844
845 /**
846  * gtk_accel_map_foreach_unfiltered:
847  * @data:         data to be passed into @foreach_func
848  * @foreach_func: (scope call): function to be executed for each accel
849  *                map entry
850  *
851  * Loops over all entries in the accelerator map, and execute
852  * @foreach_func on each. The signature of @foreach_func is that of
853  * #GtkAccelMapForeach, the @changed parameter indicates whether
854  * this accelerator was changed during runtime (thus, would need
855  * saving during an accelerator map dump).
856  */
857 void
858 gtk_accel_map_foreach_unfiltered (gpointer           data,
859                                   GtkAccelMapForeach foreach_func)
860 {
861   GSList *entries, *slist;
862
863   g_return_if_fail (foreach_func != NULL);
864
865   entries = g_hash_table_slist_values (accel_entry_ht);
866   for (slist = entries; slist; slist = slist->next)
867     {
868       AccelEntry *entry = slist->data;
869       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
870
871       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
872     }
873   g_slist_free (entries);
874 }
875
876 /**
877  * gtk_accel_map_add_filter:
878  * @filter_pattern: a pattern (see #GPatternSpec)
879  *
880  * Adds a filter to the global list of accel path filters.
881  *
882  * Accel map entries whose accel path matches one of the filters
883  * are skipped by gtk_accel_map_foreach().
884  *
885  * This function is intended for GTK+ modules that create their own
886  * menus, but don't want them to be saved into the applications accelerator
887  * map dump.
888  */
889 void
890 gtk_accel_map_add_filter (const gchar *filter_pattern)
891 {
892   GPatternSpec *pspec;
893   GSList *slist;
894
895   g_return_if_fail (filter_pattern != NULL);
896
897   pspec = g_pattern_spec_new (filter_pattern);
898   for (slist = accel_filters; slist; slist = slist->next)
899     if (g_pattern_spec_equal (pspec, slist->data))
900       {
901         g_pattern_spec_free (pspec);
902         return;
903       }
904   accel_filters = g_slist_prepend (accel_filters, pspec);
905 }
906
907 void
908 _gtk_accel_map_add_group (const gchar   *accel_path,
909                           GtkAccelGroup *accel_group)
910 {
911   AccelEntry *entry;
912
913   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
914   g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
915
916   entry = accel_path_lookup (accel_path);
917   if (!entry)
918     {
919       gtk_accel_map_add_entry (accel_path, 0, 0);
920       entry = accel_path_lookup (accel_path);
921     }
922   entry->groups = g_slist_prepend (entry->groups, accel_group);
923 }
924
925 void
926 _gtk_accel_map_remove_group (const gchar   *accel_path,
927                              GtkAccelGroup *accel_group)
928 {
929   AccelEntry *entry;
930
931   entry = accel_path_lookup (accel_path);
932   g_return_if_fail (entry != NULL);
933   g_return_if_fail (g_slist_find (entry->groups, accel_group));
934
935   entry->groups = g_slist_remove (entry->groups, accel_group);
936 }
937
938
939 /**
940  * gtk_accel_map_lock_path:
941  * @accel_path: a valid accelerator path
942  * 
943  * Locks the given accelerator path. If the accelerator map doesn't yet contain
944  * an entry for @accel_path, a new one is created.
945  *
946  * Locking an accelerator path prevents its accelerator from being changed 
947  * during runtime. A locked accelerator path can be unlocked by 
948  * gtk_accel_map_unlock_path(). Refer to gtk_accel_map_change_entry() 
949  * for information about runtime accelerator changes.
950  *
951  * If called more than once, @accel_path remains locked until
952  * gtk_accel_map_unlock_path() has been called an equivalent number
953  * of times.
954  *
955  * Note that locking of individual accelerator paths is independent from 
956  * locking the #GtkAccelGroup containing them. For runtime accelerator
957  * changes to be possible both the accelerator path and its #GtkAccelGroup
958  * have to be unlocked. 
959  *
960  * Since: 2.4
961  **/
962 void 
963 gtk_accel_map_lock_path (const gchar *accel_path)
964 {
965   AccelEntry *entry;
966
967   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
968
969   entry = accel_path_lookup (accel_path);
970   
971   if (!entry)
972     {
973       gtk_accel_map_add_entry (accel_path, 0, 0);
974       entry = accel_path_lookup (accel_path);
975     }
976
977   entry->lock_count += 1;
978 }
979
980 /**
981  * gtk_accel_map_unlock_path:
982  * @accel_path: a valid accelerator path
983  * 
984  * Undoes the last call to gtk_accel_map_lock_path() on this @accel_path.
985  * Refer to gtk_accel_map_lock_path() for information about accelerator path locking.
986  *
987  * Since: 2.4
988  **/
989 void 
990 gtk_accel_map_unlock_path (const gchar *accel_path)
991 {
992   AccelEntry *entry;
993
994   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
995
996   entry = accel_path_lookup (accel_path);
997
998   g_return_if_fail (entry != NULL && entry->lock_count > 0);
999
1000   entry->lock_count -= 1;  
1001 }
1002
1003 G_DEFINE_TYPE (GtkAccelMap, gtk_accel_map, G_TYPE_OBJECT)
1004
1005 static void
1006 gtk_accel_map_class_init (GtkAccelMapClass *accel_map_class)
1007 {
1008   /**
1009    * GtkAccelMap::changed:
1010    * @object: the global accel map object
1011    * @accel_path: the path of the accelerator that changed
1012    * @accel_key: the key value for the new accelerator
1013    * @accel_mods: the modifier mask for the new accelerator
1014    *
1015    * Notifies of a change in the global accelerator map.
1016    * The path is also used as the detail for the signal,
1017    * so it is possible to connect to
1018    * changed::<replaceable>accel_path</replaceable>.
1019    *
1020    * Since: 2.4
1021    */
1022   accel_map_signals[CHANGED] = g_signal_new (I_("changed"),
1023                                              G_TYPE_FROM_CLASS (accel_map_class),
1024                                              G_SIGNAL_DETAILED|G_SIGNAL_RUN_LAST,
1025                                              0,
1026                                              NULL, NULL,
1027                                              _gtk_marshal_VOID__STRING_UINT_FLAGS,
1028                                              G_TYPE_NONE, 3,
1029                                              G_TYPE_STRING, G_TYPE_UINT, GDK_TYPE_MODIFIER_TYPE);
1030 }
1031
1032 static void
1033 gtk_accel_map_init (GtkAccelMap *accel_map)
1034 {
1035 }
1036
1037 /**
1038  * gtk_accel_map_get:
1039  * 
1040  * Gets the singleton global #GtkAccelMap object. This object
1041  * is useful only for notification of changes to the accelerator
1042  * map via the ::changed signal; it isn't a parameter to the
1043  * other accelerator map functions.
1044  * 
1045  * Return value: (transfer none): the global #GtkAccelMap object
1046  *
1047  * Since: 2.4
1048  **/
1049 GtkAccelMap *
1050 gtk_accel_map_get (void)
1051 {
1052   if (!accel_map)
1053     accel_map = g_object_new (GTK_TYPE_ACCEL_MAP, NULL);
1054
1055   return accel_map;
1056 }
1057
1058 static void
1059 do_accel_map_changed (AccelEntry *entry)
1060 {
1061   if (accel_map)
1062     g_signal_emit (accel_map,
1063                    accel_map_signals[CHANGED],
1064                    g_quark_from_string (entry->accel_path),
1065                    entry->accel_path,
1066                    entry->accel_key,
1067                    entry->accel_mods);
1068 }