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