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