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