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