]> Pileus Git - ~andy/gtk/blob - gtk/gtkaccelmap.c
24dca7d5c351deca7ce1198c7c5ed3f9adfe4b63
[~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 #include "gtkaccelmap.h"
20
21 #include "gtkwindow.h"  /* in lack of GtkAcceleratable */
22
23 #include <string.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <errno.h>
27
28
29 /* --- structures --- */
30 typedef struct {
31   const gchar *accel_path;
32   guint        accel_key;
33   guint        accel_mods;
34   guint        std_accel_key;
35   guint        std_accel_mods;
36   guint        changed : 1;
37   GHookList   *hooks;
38 } AccelEntry;
39
40
41 /* --- variables --- */
42 static GHashTable *accel_entry_ht = NULL;       /* accel_path -> AccelEntry */
43 static GSList     *accel_filters = NULL;
44
45
46 /* --- functions --- */
47 static guint
48 accel_entry_hash (gconstpointer key)
49 {
50   const AccelEntry *entry = key;
51
52   return g_str_hash (entry->accel_path);
53 }
54
55 static gboolean
56 accel_entry_equal (gconstpointer key1,
57                    gconstpointer key2)
58 {
59   const AccelEntry *entry1 = key1;
60   const AccelEntry *entry2 = key2;
61
62   return g_str_equal (entry1->accel_path, entry2->accel_path);
63 }
64
65 static inline AccelEntry*
66 accel_path_lookup (const gchar *accel_path)
67 {
68   AccelEntry ekey;
69
70   ekey.accel_path = accel_path;
71
72   /* safety NULL check for return_if_fail()s */
73   return accel_path ? g_hash_table_lookup (accel_entry_ht, &ekey) : NULL;
74 }
75
76 void
77 _gtk_accel_map_init (void)
78 {
79   g_assert (accel_entry_ht == NULL);
80
81   accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
82 }
83
84 static gboolean
85 gtk_accel_path_is_valid (const gchar *accel_path)
86 {
87   gchar *p;
88
89   if (!accel_path || accel_path[0] != '<' ||
90       accel_path[1] == '<' || accel_path[1] == '>' || !accel_path[1])
91     return FALSE;
92   p = strchr (accel_path, '>');
93   if (!p || p[1] != '/')
94     return FALSE;
95   return TRUE;
96 }
97
98 /**
99  * gtk_accel_map_add_entry
100  * @accel_path: valid accelerator path
101  * @accel_key:  the accelerator key
102  * @accel_mods: the accelerator modifiers
103  * @returns:    the GQuark for the @accel_path (to ease local storage)
104  *
105  * Register a new accelerator with the global accelerator map.
106  * This function should only be called once per @accel_path
107  * with the canonical @accel_key and @accel_mods for this path.
108  * To change the accelerator during runtime programatically, use
109  * gtk_accel_map_change_entry().
110  * The accelerator path must consist of "<WINDOWTYPE>/Category1/Category2/.../Action",
111  * where WINDOWTYPE should be a unique application specifc identifier, that
112  * corresponds to the kind of window the accelerator is being used in, e.g. "Gimp-Image",
113  * "Abiword-Document" or "Gnumeric-Settings".
114  * The Category1/.../Action portion is most apropriately choosen by the action the
115  * accelerator triggers, i.e. for accelerators on menu items, choose the item's menu path,
116  * e.g. "File/Save As", "Image/View/Zoom" or "Edit/Select All".
117  * So a full valid accelerator path may look like:
118  * "<Gimp-Toolbox>/File/Dialogs/Tool Options...".
119  */
120 GQuark
121 gtk_accel_map_add_entry (const gchar *accel_path,
122                          guint        accel_key,
123                          guint        accel_mods)
124 {
125   AccelEntry *entry;
126
127   g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
128
129   accel_mods &= gtk_accelerator_get_default_mod_mask ();
130
131   entry = accel_path_lookup (accel_path);
132   if (entry)
133     {
134       if (!entry->std_accel_key && !entry->std_accel_mods &&
135           (accel_key || accel_mods))
136         {
137           entry->std_accel_key = accel_key;
138           entry->std_accel_mods = accel_mods;
139           if (!entry->changed)
140             gtk_accel_map_change_entry (entry->accel_path, accel_key, accel_mods, TRUE);
141         }
142     }
143   else
144     {
145       entry = g_new0 (AccelEntry, 1);
146       entry->accel_path = g_quark_to_string (g_quark_from_string (accel_path));
147       entry->std_accel_key = accel_key;
148       entry->std_accel_mods = accel_mods;
149       entry->accel_key = accel_key;
150       entry->accel_mods = accel_mods;
151       entry->changed = FALSE;
152       g_hash_table_insert (accel_entry_ht, entry, entry);
153     }
154   return g_quark_try_string (entry->accel_path);
155 }
156
157 typedef struct {
158   GHook          hook;
159   GtkAccelGroup *accel_group;
160 } AccelHook;
161
162 static void
163 accel_hook_finalize (GHookList *hook_list,
164                      GHook     *hook)
165 {
166   GDestroyNotify destroy = hook->destroy;
167   AccelHook *ahook = (AccelHook*) hook;
168
169   if (ahook->accel_group)
170     g_object_unref (ahook->accel_group);
171
172   if (destroy)
173     {
174       hook->destroy = NULL;
175       destroy (hook->data);
176     }
177 }
178
179 /**
180  * GtkAccelMapNotify
181  * @data:             notifier user data
182  * @accel_path_quark: accelerator path (as #GQuark) which has just changed
183  * @accel_key:        new accelerator key
184  * @accel_mods:       new accelerator modifiers
185  * @accel_group:      accelerator group of this notifier
186  * @old_accel_key:    former accelerator key
187  * @old_accel_mods):  former accelerator modifiers
188  *
189  * #GtkAccelMapNotify is the signature of user callbacks, installed via
190  * gtk_accel_map_add_notifier(). Once the accel path of the notifier changes,
191  * the notifier is invoked with this signature, where @accel_path_quark
192  * indicates the accel path that changed, and @data and @accel_group are
193  * the notifier's arguments as passed into gtk_accel_map_add_notifier().
194  */
195
196 /**
197  * gtk_accel_map_add_notifer
198  * @accel_path:  valid accelerator path
199  * @notify_data: data argument to the notifier
200  * @notify_func: the notifier function
201  * @accel_group: accelerator group used by the notifier function
202  *
203  * Install a notifier function to be called if an accelerator
204  * map entry changes. @accel_group has to be the accel group
205  * that is being affected (gets an accelerator removed or added,
206  * when the notifier function is executed).
207  */
208 void
209 gtk_accel_map_add_notifer (const gchar      *accel_path,
210                            gpointer          notify_data,
211                            GtkAccelMapNotify notify_func,
212                            GtkAccelGroup    *accel_group)
213 {
214   AccelEntry *entry;
215   AccelHook *ahook;
216   GHook *hook;
217
218   g_return_if_fail (gtk_accel_path_is_valid (accel_path));
219   g_return_if_fail (notify_func != NULL);
220   if (accel_group)
221     g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
222
223   entry = accel_path_lookup (accel_path);
224   if (!entry)
225     {
226       gtk_accel_map_add_entry (accel_path, 0, 0);
227       entry = accel_path_lookup (accel_path);
228     }
229   if (!entry->hooks)
230     {
231       entry->hooks = g_new (GHookList, 1);
232       g_hook_list_init (entry->hooks, sizeof (AccelHook));
233       entry->hooks->finalize_hook = accel_hook_finalize;
234     }
235   hook = g_hook_alloc (entry->hooks);
236   hook->data = notify_data;
237   hook->func = notify_func;
238   hook->destroy = NULL;
239   ahook = (AccelHook*) hook;
240   ahook->accel_group = accel_group ? g_object_ref (accel_group) : NULL;
241   g_hook_append (entry->hooks, hook);
242 }
243
244 /**
245  * gtk_accel_map_remove_notifer
246  * @accel_path:  valid accelerator path
247  * @notify_data: data argument to the notifier
248  * @notify_func: the notifier function
249  *
250  * Remove a notifier function, previously installed through
251  * gtk_accel_map_add_notifer().
252  */
253 void
254 gtk_accel_map_remove_notifer (const gchar      *accel_path,
255                               gpointer          notify_data,
256                               GtkAccelMapNotify notify_func)
257 {
258   AccelEntry *entry;
259
260   g_return_if_fail (gtk_accel_path_is_valid (accel_path));
261   g_return_if_fail (notify_func != NULL);
262
263   entry = accel_path_lookup (accel_path);
264   if (entry && entry->hooks)
265     {
266       GHook *hook = g_hook_find_func_data (entry->hooks, TRUE, notify_func, notify_data);
267
268       if (hook && g_hook_destroy (entry->hooks, hook->hook_id))
269         return; /* successfully removed */
270     }
271   g_warning (G_STRLOC ": no notifier %p(%p) installed for accel path \"%s\"",
272              notify_func, notify_data, accel_path);
273 }
274
275 /**
276  * gtk_accel_map_lookup_entry
277  * @accel_path:  valid accelerator path
278  * @key:         accelerator key to be filled in (optional)
279  * @returns:     #GQuark for @accel_path or (0) if @accel_path is not known
280  *
281  * Lookup the accelerator entry for @accel_path and fill in @key.
282  * If the lookup revealed no results, (0) is returned, the entry's
283  * #GQuark otherwise.
284  */
285 GQuark
286 gtk_accel_map_lookup_entry (const gchar *accel_path,
287                             GtkAccelKey *key)
288 {
289   AccelEntry *entry;
290
291   g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
292
293   entry = accel_path_lookup (accel_path);
294   if (entry && key)
295     {
296       key->accel_key = entry->accel_key;
297       key->accel_mods = entry->accel_mods;
298       key->accel_flags = 0;     // FIXME: global lock?
299     }
300
301   return entry ? g_quark_try_string (entry->accel_path) : 0;
302 }
303
304 static void
305 hash2slist_foreach (gpointer  key,
306                     gpointer  value,
307                     gpointer  user_data)
308 {
309   GSList **slist_p = user_data;
310
311   *slist_p = g_slist_prepend (*slist_p, value);
312 }
313
314 static GSList*
315 g_hash_table_slist_values (GHashTable *hash_table)
316 {
317   GSList *slist = NULL;
318
319   g_return_val_if_fail (hash_table != NULL, NULL);
320
321   g_hash_table_foreach (hash_table, hash2slist_foreach, &slist);
322
323   return slist;
324 }
325
326 static gboolean
327 internal_change_entry (const gchar    *accel_path,
328                        guint           accel_key,
329                        GdkModifierType accel_mods,
330                        gboolean        replace,
331                        gboolean        simulate)
332 {
333   GSList *node, *slist, *win_list, *group_list, *replace_list = NULL;
334   GHashTable *group_hm, *win_hm;
335   gboolean change_accel, removable, can_change = TRUE, seen_accel = FALSE, hooks_may_recurse = TRUE;
336   GQuark entry_quark;
337   GHook *hook;
338   AccelEntry *entry = accel_path_lookup (accel_path);
339
340   /* not much todo if there's no entry yet */
341   if (!entry)
342     {
343       if (!simulate)
344         {
345           gtk_accel_map_add_entry (accel_path, 0, 0);
346           entry = accel_path_lookup (accel_path);
347           entry->accel_key = accel_key;
348           entry->accel_mods = accel_mods;
349           entry->changed = TRUE;
350         }
351       return TRUE;
352     }
353
354   /* if there's nothing to change, not much todo either */
355   if (entry->accel_key == accel_key && entry->accel_mods == accel_mods)
356     return FALSE;
357
358   /* nobody's interested, easy going */
359   if (!entry->hooks)
360     {
361       if (!simulate)
362         {
363           entry->accel_key = accel_key;
364           entry->accel_mods = accel_mods;
365           entry->changed = TRUE;
366         }
367       return TRUE;
368     }
369
370   /* 1) fetch all accel groups affected by this entry */
371   entry_quark = g_quark_try_string (entry->accel_path);
372   group_hm = g_hash_table_new (NULL, NULL);
373   win_hm = g_hash_table_new (NULL, NULL);
374   hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
375   while (hook)
376     {
377       AccelHook *ahook = (AccelHook*) hook;
378       
379       if (ahook->accel_group)
380         g_hash_table_insert (group_hm, ahook->accel_group, ahook->accel_group);
381       hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
382     }
383
384   /* 2) collect acceleratables affected */
385   group_list = g_hash_table_slist_values (group_hm);
386   for (slist = group_list; slist; slist = slist->next)
387     {
388       GtkAccelGroup *group = slist->data;
389
390       for (node = group->acceleratables; node; node = node->next)
391         g_hash_table_insert (win_hm, node->data, node->data);
392     }
393   g_slist_free (group_list);
394
395   /* 3) include all accel groups used by acceleratables */
396   win_list = g_hash_table_slist_values (win_hm);
397   g_hash_table_destroy (win_hm);
398   for (slist = win_list; slist; slist = slist->next)
399     for (node = gtk_accel_groups_from_acceleratable (slist->data); node; node = node->next)
400       g_hash_table_insert (group_hm, node->data, node->data);
401   group_list = g_hash_table_slist_values (group_hm);
402   g_hash_table_destroy (group_hm);
403
404   /* 4) walk the acceleratables and figure whether they occupy accel_key&accel_mods */
405   for (slist = accel_key ? win_list : NULL; slist; slist = slist->next)
406     if (GTK_IS_WINDOW (slist->data))    /* bad kludge in lack of a GtkAcceleratable */
407       if (_gtk_window_query_nonaccels (slist->data, accel_key, accel_mods))
408         {
409           seen_accel = TRUE;
410           break;
411         }
412   removable = !seen_accel;
413
414   /* 5) walk all accel groups and search for locks */
415   for (slist = removable ? group_list : NULL; slist; slist = slist->next)
416     {
417       GtkAccelGroup *group = slist->data;
418       GtkAccelGroupEntry *ag_entry;
419       guint i, n;
420       
421       n = 0;
422       ag_entry = entry->accel_key ? gtk_accel_group_query (group, entry->accel_key, entry->accel_mods, &n) : NULL;
423       for (i = 0; i < n; i++)
424         if (ag_entry[i].accel_path_quark == entry_quark)
425           {
426             can_change = !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
427             if (!can_change)
428               goto break_loop_step5;
429           }
430
431       n = 0;
432       ag_entry = accel_key ? gtk_accel_group_query (group, accel_key, accel_mods, &n) : NULL;
433       for (i = 0; i < n; i++)
434         {
435           seen_accel = TRUE;
436           removable = !group->lock_count && !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
437           if (!removable)
438             goto break_loop_step5;
439           if (ag_entry[i].accel_path_quark)
440             replace_list = g_slist_prepend (replace_list, GUINT_TO_POINTER (ag_entry->accel_path_quark));
441         }
442     }
443  break_loop_step5:
444   
445   /* 6) check whether we can remove existing accelerators */
446   for (slist = removable ? replace_list : NULL; slist; slist = slist->next)
447     if (!internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, TRUE))
448       {
449         removable = FALSE;
450         break;
451       }
452
453   /* 7) check conditions and proceed if possible */
454   change_accel = can_change && (!seen_accel || (removable && replace));
455
456   if (change_accel && !simulate)
457     {
458       guint old_accel_key, old_accel_mods;
459
460       /* 8) remove existing accelerators */
461       for (slist = replace_list; slist; slist = slist->next)
462         internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, FALSE);
463
464       /* 9) install new accelerator */
465       old_accel_key = entry->accel_key;
466       old_accel_mods = entry->accel_mods;
467       entry->accel_key = accel_key;
468       entry->accel_mods = accel_mods;
469       entry->changed = TRUE;
470       hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
471       while (hook)
472         {
473           gboolean was_in_call, need_destroy = FALSE;
474           GtkAccelMapNotify hook_func = hook->func;
475           AccelHook *ahook = (AccelHook*) hook;
476           
477           was_in_call = G_HOOK_IN_CALL (hook);
478           hook->flags |= G_HOOK_FLAG_IN_CALL;
479           /* need_destroy = */ hook_func (hook->data, g_quark_try_string (entry->accel_path),
480                                           entry->accel_key, entry->accel_mods,
481                                           ahook->accel_group,
482                                           old_accel_key, old_accel_mods);
483           if (!was_in_call)
484             hook->flags &= ~G_HOOK_FLAG_IN_CALL;
485           if (need_destroy)
486             g_hook_destroy_link (entry->hooks, hook);
487           hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
488         }
489     }
490   g_slist_free (replace_list);
491   g_slist_free (group_list);
492   g_slist_free (win_list);
493
494   return change_accel;
495 }
496
497 /**
498  * gtk_accel_map_change_entry
499  * @accel_path:  valid accelerator path
500  * @accel_key:   new accelerator key
501  * @accel_mods:  new accelerator modifiers
502  * @replace:     %TRUE if other accelerators may be deleted upon conflicts
503  * @returns:     %TRUE if the accelerator could be changed, %FALSE otherwise
504  *
505  * Change the @accel_key and @accel_mods currently associated with @accel_path.
506  * Due to conflicts with other accelerators, a change may not alwys be possible,
507  * @replace indicates whether other accelerators may be deleted to resolve such
508  * conflicts. A change will only occour if all conflicts could be resolved (which
509  * might not be the case if conflicting accelerators are locked). Succesfull
510  * changes are indicated by a %TRUE return value.
511  * Changes occouring are also indicated by invocation of notifiers attached to
512  * @accel_path (see gtk_accel_map_add_notifer() on this) and other accelerators
513  * that are being deleted.
514  */
515 gboolean
516 gtk_accel_map_change_entry (const gchar    *accel_path,
517                             guint           accel_key,
518                             GdkModifierType accel_mods,
519                             gboolean        replace)
520 {
521   g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), FALSE);
522
523   return internal_change_entry (accel_path, accel_key, accel_key ? accel_mods : 0, replace, FALSE);
524 }
525
526 static guint
527 accel_map_parse_accel_path (GScanner *scanner)
528 {
529   guint accel_key = 0, accel_mods = 0;
530   gchar *path, *accel;
531   
532   /* parse accel path */
533   g_scanner_get_next_token (scanner);
534   if (scanner->token != G_TOKEN_STRING)
535     return G_TOKEN_STRING;
536
537   /* test if the next token is an accelerator */
538   g_scanner_peek_next_token (scanner);
539   if (scanner->next_token != G_TOKEN_STRING)
540     {
541       /* if not so, eat that token and error out */
542       g_scanner_get_next_token (scanner);
543       return G_TOKEN_STRING;
544     }
545
546   /* get the full accelerator specification */
547   path = g_strdup (scanner->value.v_string);
548   g_scanner_get_next_token (scanner);
549   accel = g_strdup (scanner->value.v_string);
550
551   /* ensure the entry is present */
552   gtk_accel_map_add_entry (path, 0, 0);
553
554   /* and propagate it */
555   gtk_accelerator_parse (accel, &accel_key, &accel_mods);
556   gtk_accel_map_change_entry (path, accel_key, accel_mods, TRUE);
557
558   g_free (accel);
559   g_free (path);
560
561   /* check correct statement end */
562   g_scanner_get_next_token (scanner);
563   if (scanner->token != ')')
564     return ')';
565   else
566     return G_TOKEN_NONE;
567 }
568
569 static void
570 accel_map_parse_statement (GScanner *scanner)
571 {
572   guint expected_token;
573
574   g_scanner_get_next_token (scanner);
575
576   if (scanner->token == G_TOKEN_SYMBOL)
577     {
578       guint (*parser_func) (GScanner*);
579
580       parser_func = scanner->value.v_symbol;
581
582       expected_token = parser_func (scanner);
583     }
584   else
585     expected_token = G_TOKEN_SYMBOL;
586
587   /* skip rest of statement on errrors
588    */
589   if (expected_token != G_TOKEN_NONE)
590     {
591       register guint level;
592
593       level = 1;
594       if (scanner->token == ')')
595         level--;
596       if (scanner->token == '(')
597         level++;
598
599       while (!g_scanner_eof (scanner) && level > 0)
600         {
601           g_scanner_get_next_token (scanner);
602
603           if (scanner->token == '(')
604             level++;
605           else if (scanner->token == ')')
606             level--;
607         }
608     }
609 }
610
611 void
612 gtk_accel_map_load_scanner (GScanner *scanner)
613 {
614   gboolean skip_comment_single;
615   gboolean symbol_2_token;
616   gchar *cpair_comment_single;
617   gpointer saved_symbol;
618   
619   g_return_if_fail (scanner != 0);
620
621   /* configure scanner */
622   skip_comment_single = scanner->config->skip_comment_single;
623   scanner->config->skip_comment_single = TRUE;
624   cpair_comment_single = scanner->config->cpair_comment_single;
625   scanner->config->cpair_comment_single = ";\n";
626   symbol_2_token = scanner->config->symbol_2_token;
627   scanner->config->symbol_2_token = FALSE;
628   saved_symbol = g_scanner_lookup_symbol (scanner, "gtk_accel_path");
629   g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", accel_map_parse_accel_path);
630
631   /* outer parsing loop
632    */
633   g_scanner_peek_next_token (scanner);
634   while (scanner->next_token == '(')
635     {
636       g_scanner_get_next_token (scanner);
637
638       accel_map_parse_statement (scanner);
639
640       g_scanner_peek_next_token (scanner);
641     }
642
643   /* restore config */
644   scanner->config->skip_comment_single = skip_comment_single;
645   scanner->config->cpair_comment_single = cpair_comment_single;
646   scanner->config->symbol_2_token = symbol_2_token;
647   g_scanner_scope_remove_symbol (scanner, 0, "gtk_accel_path");
648   if (saved_symbol)
649     g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", saved_symbol);
650 }
651
652 /**
653  * gtk_accel_map_load_fd
654  * @fd: valid readable file descriptor
655  *
656  * Filedescriptor variant of gtk_accel_map_load().
657  * Note that the file descriptor will not be closed by this function.
658  */
659 void
660 gtk_accel_map_load_fd (gint fd)
661 {
662   GScanner *scanner;
663
664   g_return_if_fail (fd >= 0);
665
666   /* create and setup scanner */
667   scanner = g_scanner_new (NULL);
668   g_scanner_input_file (scanner, fd);
669
670   gtk_accel_map_load_scanner (scanner);
671
672   g_scanner_destroy (scanner);
673 }
674
675 /**
676  * gtk_accel_map_load
677  * @file_name: a file containing accelerator specifications
678  *
679  * Parses a file previously saved with gtk_accel_map_save() for
680  * accelerator specifications, and propagates them accordingly.
681  */
682 void
683 gtk_accel_map_load (const gchar *file_name)
684 {
685   gint fd;
686
687   g_return_if_fail (file_name != NULL);
688
689   if (!g_file_test (file_name, G_FILE_TEST_IS_REGULAR))
690     return;
691
692   fd = open (file_name, O_RDONLY);
693   if (fd < 0)
694     return;
695
696   gtk_accel_map_load_fd (fd);
697
698   close (fd);
699 }
700
701 static void
702 accel_map_print (gpointer        data,
703                  const gchar    *accel_path,
704                  guint           accel_key,
705                  guint           accel_mods,
706                  gboolean        changed)
707 {
708   GString *gstring = g_string_new (changed ? NULL : "; ");
709   gint err, fd = GPOINTER_TO_INT (data);
710   gchar *tmp, *name;
711
712   g_string_append (gstring, "(gtk_accel_path \"");
713
714   tmp = g_strescape (accel_path, NULL);
715   g_string_append (gstring, tmp);
716   g_free (tmp);
717
718   g_string_append (gstring, "\" \"");
719
720   name = gtk_accelerator_name (accel_key, accel_mods);
721   tmp = g_strescape (name, NULL);
722   g_free (name);
723   g_string_append (gstring, tmp);
724   g_free (tmp);
725
726   g_string_append (gstring, "\")\n");
727
728   do
729     err = write (fd, gstring->str, gstring->len);
730   while (err < 0 && errno == EINTR);
731
732   g_string_free (gstring, TRUE);
733 }
734
735 /**
736  * gtk_accel_map_save_fd
737  * @fd: valid writable file descriptor
738  *
739  * Filedescriptor variant of gtk_accel_map_save().
740  * Note that the file descriptor will not be closed by this function.
741  */
742 void
743 gtk_accel_map_save_fd (gint fd)
744 {
745   GString *gstring;
746   gint err;
747
748   g_return_if_fail (fd >= 0);
749
750   gstring = g_string_new ("; ");
751   if (g_get_prgname ())
752     g_string_append (gstring, g_get_prgname ());
753   g_string_append (gstring, " GtkAccelMap rc-file         -*- scheme -*-\n");
754   g_string_append (gstring, "; this file is an automated accelerator map dump\n");
755   g_string_append (gstring, ";\n");
756
757   do
758     err = write (fd, gstring->str, gstring->len);
759   while (err < 0 && errno == EINTR);
760
761   gtk_accel_map_foreach (GINT_TO_POINTER (fd), accel_map_print);
762 }
763
764 /**
765  * gtk_accel_map_save
766  * @file_name: the file to contain accelerator specifications
767  *
768  * Saves current accelerator specifications (accelerator path, key
769  * and modifiers) to @file_name.
770  * The file is written in a format suitable to be read back in by
771  * gtk_accel_map_load().
772  */
773 void
774 gtk_accel_map_save (const gchar *file_name)
775 {
776   gint fd;
777
778   g_return_if_fail (file_name != NULL);
779
780   fd = open (file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
781   if (fd < 0)
782     return;
783
784   gtk_accel_map_save_fd (fd);
785
786   close (fd);
787 }
788
789 /**
790  * gtk_accel_map_foreach
791  * @data:         data to be passed into @foreach_func
792  * @foreach_func: function to be executed for each accel map entry
793  *
794  * Loop over the entries in the accelerator map, and execute
795  * @foreach_func on each. The signature of @foreach_func is that of
796  * #GtkAccelMapForeach, the @changed parameter indicates whether
797  * this accelerator was changed during runtime (thus, would need
798  * saving during an accelerator map dump).
799  */
800 void
801 gtk_accel_map_foreach (gpointer           data,
802                        GtkAccelMapForeach foreach_func)
803 {
804   GSList *entries, *slist, *node;
805
806   g_return_if_fail (foreach_func != NULL);
807
808   entries = g_hash_table_slist_values (accel_entry_ht);
809   for (slist = entries; slist; slist = slist->next)
810     {
811       AccelEntry *entry = slist->data;
812       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
813
814       for (node = accel_filters; node; node = node->next)
815         if (g_pattern_match_string (node->data, entry->accel_path))
816           goto skip_accel;
817       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
818     skip_accel:
819     }
820   g_slist_free (entries);
821 }
822
823 void
824 gtk_accel_map_foreach_unfiltered (gpointer           data,
825                                   GtkAccelMapForeach foreach_func)
826 {
827   GSList *entries, *slist;
828
829   g_return_if_fail (foreach_func != NULL);
830
831   entries = g_hash_table_slist_values (accel_entry_ht);
832   for (slist = entries; slist; slist = slist->next)
833     {
834       AccelEntry *entry = slist->data;
835       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
836
837       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
838     }
839   g_slist_free (entries);
840 }
841
842 void
843 gtk_accel_map_add_filter (const gchar *filter_pattern)
844 {
845   GPatternSpec *pspec;
846   GSList *slist;
847
848   g_return_if_fail (filter_pattern != NULL);
849
850   pspec = g_pattern_spec_new (filter_pattern);
851   for (slist = accel_filters; slist; slist = slist->next)
852     if (g_pattern_spec_equal (pspec, slist->data))
853       {
854         g_pattern_spec_free (pspec);
855         return;
856       }
857   accel_filters = g_slist_prepend (accel_filters, pspec);
858 }