]> Pileus Git - ~andy/gtk/blob - gtk/gtkaccelmap.c
Accept "<WINDOWTYPE>" as a valid accel path. (#144427, Philip Kendall)
[~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
27 #include <string.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33 #ifdef G_OS_WIN32
34 #include <io.h>
35 #endif
36 #include <errno.h>
37
38
39 /* --- structures --- */
40 struct _GtkAccelMap
41 {
42   GObject parent_instance;
43 };
44
45 struct _GtkAccelMapClass
46 {
47   GObjectClass parent_class;
48 };
49
50 typedef struct {
51   const gchar *accel_path;
52   guint        accel_key;
53   guint        accel_mods;
54   guint        std_accel_key;
55   guint        std_accel_mods;
56   guint        changed : 1;
57   guint        lock_count;
58   GSList      *groups;
59 } AccelEntry;
60
61 /* --- signals --- */
62 enum {
63   CHANGED,
64   LAST_SIGNAL
65 };
66
67 /* --- variables --- */
68
69 static GHashTable  *accel_entry_ht = NULL;      /* accel_path -> AccelEntry */
70 static GSList      *accel_filters = NULL;
71 static gulong       accel_map_signals[LAST_SIGNAL] = { 0, };
72 static GtkAccelMap *accel_map;
73
74 /* --- prototypes --- */
75 static void do_accel_map_changed (AccelEntry *entry);
76
77 /* --- functions --- */
78 static guint
79 accel_entry_hash (gconstpointer key)
80 {
81   const AccelEntry *entry = key;
82
83   return g_str_hash (entry->accel_path);
84 }
85
86 static gboolean
87 accel_entry_equal (gconstpointer key1,
88                    gconstpointer key2)
89 {
90   const AccelEntry *entry1 = key1;
91   const AccelEntry *entry2 = key2;
92
93   return g_str_equal (entry1->accel_path, entry2->accel_path);
94 }
95
96 static inline AccelEntry*
97 accel_path_lookup (const gchar *accel_path)
98 {
99   AccelEntry ekey;
100
101   ekey.accel_path = accel_path;
102
103   /* safety NULL check for return_if_fail()s */
104   return accel_path ? g_hash_table_lookup (accel_entry_ht, &ekey) : NULL;
105 }
106
107 void
108 _gtk_accel_map_init (void)
109 {
110   g_assert (accel_entry_ht == NULL);
111
112   accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
113 }
114
115 gboolean
116 _gtk_accel_path_is_valid (const gchar *accel_path)
117 {
118   gchar *p;
119
120   if (!accel_path || accel_path[0] != '<' ||
121       accel_path[1] == '<' || accel_path[1] == '>' || !accel_path[1])
122     return FALSE;
123   p = strchr (accel_path, '>');
124   if (!p || (p[1] != 0 && p[1] != '/'))
125     return FALSE;
126   return TRUE;
127 }
128
129 /**
130  * gtk_accel_map_add_entry:
131  * @accel_path: valid accelerator path
132  * @accel_key:  the accelerator key
133  * @accel_mods: the accelerator modifiers
134  *
135  * Registers a new accelerator with the global accelerator map.
136  * This function should only be called once per @accel_path
137  * with the canonical @accel_key and @accel_mods for this path.
138  * To change the accelerator during runtime programatically, use
139  * gtk_accel_map_change_entry().
140  * The accelerator path must consist of "&lt;WINDOWTYPE&gt;/Category1/Category2/.../Action",
141  * where &lt;WINDOWTYPE&gt; should be a unique application-specific identifier, that
142  * corresponds to the kind of window the accelerator is being used in, e.g. "Gimp-Image",
143  * "Abiword-Document" or "Gnumeric-Settings".
144  * The Category1/.../Action portion is most appropriately chosen by the action the
145  * accelerator triggers, i.e. for accelerators on menu items, choose the item's menu path,
146  * e.g. "File/Save As", "Image/View/Zoom" or "Edit/Select All".
147  * So a full valid accelerator path may look like:
148  * "&lt;Gimp-Toolbox&gt;/File/Dialogs/Tool Options...".
149  */
150 void
151 gtk_accel_map_add_entry (const gchar    *accel_path,
152                          guint           accel_key,
153                          GdkModifierType accel_mods)
154 {
155   AccelEntry *entry;
156
157   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
158
159   if (!accel_key)
160     accel_mods = 0;
161   else
162     accel_mods &= gtk_accelerator_get_default_mod_mask ();
163
164   entry = accel_path_lookup (accel_path);
165   if (entry)
166     {
167       if (!entry->std_accel_key && !entry->std_accel_mods &&
168           (accel_key || accel_mods))
169         {
170           entry->std_accel_key = accel_key;
171           entry->std_accel_mods = accel_mods;
172           if (!entry->changed)
173             gtk_accel_map_change_entry (entry->accel_path, accel_key, accel_mods, TRUE);
174         }
175     }
176   else
177     {
178       entry = g_new0 (AccelEntry, 1);
179       entry->accel_path = g_quark_to_string (g_quark_from_string (accel_path));
180       entry->std_accel_key = accel_key;
181       entry->std_accel_mods = accel_mods;
182       entry->accel_key = accel_key;
183       entry->accel_mods = accel_mods;
184       entry->changed = FALSE;
185       entry->lock_count = 0;
186       g_hash_table_insert (accel_entry_ht, entry, entry);
187
188       do_accel_map_changed (entry);
189     }
190 }
191
192 /**
193  * gtk_accel_map_lookup_entry:
194  * @accel_path:  a valid accelerator path
195  * @key:         the accelerator key to be filled in (optional)
196  * @returns:     %TRUE if @accel_path is known, %FALSE otherwise
197  *
198  * Looks up the accelerator entry for @accel_path and fills in @key.
199  */
200 gboolean
201 gtk_accel_map_lookup_entry (const gchar *accel_path,
202                             GtkAccelKey *key)
203 {
204   AccelEntry *entry;
205
206   g_return_val_if_fail (_gtk_accel_path_is_valid (accel_path), FALSE);
207
208   entry = accel_path_lookup (accel_path);
209   if (entry && key)
210     {
211       key->accel_key = entry->accel_key;
212       key->accel_mods = entry->accel_mods;
213       key->accel_flags = 0;
214     }
215
216   return entry ? TRUE : FALSE;
217 }
218
219 static void
220 hash2slist_foreach (gpointer  key,
221                     gpointer  value,
222                     gpointer  user_data)
223 {
224   GSList **slist_p = user_data;
225
226   *slist_p = g_slist_prepend (*slist_p, value);
227 }
228
229 static GSList*
230 g_hash_table_slist_values (GHashTable *hash_table)
231 {
232   GSList *slist = NULL;
233
234   g_return_val_if_fail (hash_table != NULL, NULL);
235
236   g_hash_table_foreach (hash_table, hash2slist_foreach, &slist);
237
238   return slist;
239 }
240
241 /* if simulate==TRUE, return whether accel_path can be changed to
242  * accel_key && accel_mods. otherwise, return whether accel_path
243  * was actually changed.
244  */
245 static gboolean
246 internal_change_entry (const gchar    *accel_path,
247                        guint           accel_key,
248                        GdkModifierType accel_mods,
249                        gboolean        replace,
250                        gboolean        simulate)
251 {
252   GSList *node, *slist, *win_list, *group_list, *replace_list = NULL;
253   GHashTable *group_hm, *window_hm;
254   gboolean change_accel, removable, can_change = TRUE, seen_accel = FALSE;
255   GQuark entry_quark;
256   AccelEntry *entry = accel_path_lookup (accel_path);
257
258   /* not much todo if there's no entry yet */
259   if (!entry)
260     {
261       if (!simulate)
262         {
263           gtk_accel_map_add_entry (accel_path, 0, 0);
264           entry = accel_path_lookup (accel_path);
265           entry->accel_key = accel_key;
266           entry->accel_mods = accel_mods;
267           entry->changed = TRUE;
268
269           do_accel_map_changed (entry);
270         }
271       return TRUE;
272     }
273
274   /* if there's nothing to change, not much todo either */
275   if (entry->accel_key == accel_key && entry->accel_mods == accel_mods)
276     {
277       if (!simulate)
278         entry->changed = TRUE;
279       return simulate ? TRUE : FALSE;
280     }
281
282   /* The no-change case has already been handled, so 
283    * simulate doesn't make a difference here.
284    */
285   if (entry->lock_count > 0)
286     return FALSE;
287
288   /* nobody's interested, easy going */
289   if (!entry->groups)
290     {
291       if (!simulate)
292         {
293           entry->accel_key = accel_key;
294           entry->accel_mods = accel_mods;
295           entry->changed = TRUE;
296
297           do_accel_map_changed (entry);
298         }
299       return TRUE;
300     }
301
302   /* 1) fetch all accel groups affected by this entry */
303   entry_quark = g_quark_try_string (entry->accel_path);
304   group_hm = g_hash_table_new (NULL, NULL);
305   window_hm = g_hash_table_new (NULL, NULL);
306   for (slist = entry->groups; slist; slist = slist->next)
307     g_hash_table_insert (group_hm, slist->data, slist->data);
308
309   /* 2) collect acceleratables affected */
310   group_list = g_hash_table_slist_values (group_hm);
311   for (slist = group_list; slist; slist = slist->next)
312     {
313       GtkAccelGroup *group = slist->data;
314
315       for (node = group->acceleratables; node; node = node->next)
316         g_hash_table_insert (window_hm, node->data, node->data);
317     }
318   g_slist_free (group_list);
319
320   /* 3) include all accel groups used by acceleratables */
321   win_list = g_hash_table_slist_values (window_hm);
322   g_hash_table_destroy (window_hm);
323   for (slist = win_list; slist; slist = slist->next)
324     for (node = gtk_accel_groups_from_object (slist->data); node; node = node->next)
325       g_hash_table_insert (group_hm, node->data, node->data);
326   group_list = g_hash_table_slist_values (group_hm);
327   g_hash_table_destroy (group_hm);
328   
329   /* 4) walk the acceleratables and figure whether they occupy accel_key&accel_mods */
330   if (accel_key)
331     for (slist = win_list; slist; slist = slist->next)
332       if (GTK_IS_WINDOW (slist->data))  /* bad kludge in lack of a GtkAcceleratable */
333         if (_gtk_window_query_nonaccels (slist->data, accel_key, accel_mods))
334           {
335             seen_accel = TRUE;
336             break;
337           }
338   removable = !seen_accel;
339   
340   /* 5) walk all accel groups and search for locks */
341   if (removable)
342     for (slist = group_list; slist; slist = slist->next)
343       {
344         GtkAccelGroup *group = slist->data;
345         GtkAccelGroupEntry *ag_entry;
346         guint i, n;
347         
348         n = 0;
349         ag_entry = entry->accel_key ? gtk_accel_group_query (group, entry->accel_key, entry->accel_mods, &n) : NULL;
350         for (i = 0; i < n; i++)
351           if (ag_entry[i].accel_path_quark == entry_quark)
352             {
353               can_change = !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
354               if (!can_change)
355                 goto break_loop_step5;
356             }
357         
358         n = 0;
359         ag_entry = accel_key ? gtk_accel_group_query (group, accel_key, accel_mods, &n) : NULL;
360         for (i = 0; i < n; i++)
361           {
362             seen_accel = TRUE;
363             removable = !group->lock_count && !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
364             if (!removable)
365               goto break_loop_step5;
366             if (ag_entry[i].accel_path_quark)
367               replace_list = g_slist_prepend (replace_list, GUINT_TO_POINTER (ag_entry->accel_path_quark));
368           }
369       }
370  break_loop_step5:
371   
372   /* 6) check whether we can remove existing accelerators */
373   if (removable && can_change)
374     for (slist = replace_list; slist; slist = slist->next)
375       if (!internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, TRUE))
376         {
377           removable = FALSE;
378           break;
379         }
380   
381   /* 7) check conditions and proceed if possible */
382   change_accel = can_change && (!seen_accel || (removable && replace));
383   
384   if (change_accel && !simulate)
385     {
386       guint old_accel_key, old_accel_mods;
387       
388       /* ref accel groups */
389       for (slist = group_list; slist; slist = slist->next)
390         g_object_ref (slist->data);
391
392       /* 8) remove existing accelerators */
393       for (slist = replace_list; slist; slist = slist->next)
394         internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, FALSE);
395
396       /* 9) install new accelerator */
397       old_accel_key = entry->accel_key;
398       old_accel_mods = entry->accel_mods;
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  *
607  * Parses a file previously saved with gtk_accel_map_save() for
608  * accelerator specifications, and propagates them accordingly.
609  */
610 void
611 gtk_accel_map_load (const gchar *file_name)
612 {
613   gint fd;
614
615   g_return_if_fail (file_name != NULL);
616
617   if (!g_file_test (file_name, G_FILE_TEST_IS_REGULAR))
618     return;
619
620   fd = open (file_name, O_RDONLY);
621   if (fd < 0)
622     return;
623
624   gtk_accel_map_load_fd (fd);
625
626   close (fd);
627 }
628
629 static gboolean
630 write_all (gint   fd,
631            gchar *buf,
632            gsize  to_write)
633 {
634   while (to_write > 0)
635     {
636       gssize count = write (fd, buf, to_write);
637       if (count < 0)
638         {
639           if (errno != EINTR)
640             return FALSE;
641         }
642       else
643         {
644           to_write -= count;
645           buf += count;
646         }
647     }
648
649   return TRUE;
650 }
651
652 static void
653 accel_map_print (gpointer        data,
654                  const gchar    *accel_path,
655                  guint           accel_key,
656                  GdkModifierType accel_mods,
657                  gboolean        changed)
658 {
659   GString *gstring = g_string_new (changed ? NULL : "; ");
660   gint fd = GPOINTER_TO_INT (data);
661   gchar *tmp, *name;
662
663   g_string_append (gstring, "(gtk_accel_path \"");
664
665   tmp = g_strescape (accel_path, NULL);
666   g_string_append (gstring, tmp);
667   g_free (tmp);
668
669   g_string_append (gstring, "\" \"");
670
671   name = gtk_accelerator_name (accel_key, accel_mods);
672   tmp = g_strescape (name, NULL);
673   g_free (name);
674   g_string_append (gstring, tmp);
675   g_free (tmp);
676
677   g_string_append (gstring, "\")\n");
678
679   write_all (fd, gstring->str, gstring->len);
680
681   g_string_free (gstring, TRUE);
682 }
683
684 /**
685  * gtk_accel_map_save_fd:
686  * @fd: a valid writable file descriptor
687  *
688  * Filedescriptor variant of gtk_accel_map_save().
689  *
690  * Note that the file descriptor will not be closed by this function.
691  */
692 void
693 gtk_accel_map_save_fd (gint fd)
694 {
695   GString *gstring;
696
697   g_return_if_fail (fd >= 0);
698
699   gstring = g_string_new ("; ");
700   if (g_get_prgname ())
701     g_string_append (gstring, g_get_prgname ());
702   g_string_append (gstring, " GtkAccelMap rc-file         -*- scheme -*-\n");
703   g_string_append (gstring, "; this file is an automated accelerator map dump\n");
704   g_string_append (gstring, ";\n");
705
706   write_all (fd, gstring->str, gstring->len);
707   
708   g_string_free (gstring, TRUE);
709
710   gtk_accel_map_foreach (GINT_TO_POINTER (fd), accel_map_print);
711 }
712
713 /**
714  * gtk_accel_map_save:
715  * @file_name: the file to contain accelerator specifications
716  *
717  * Saves current accelerator specifications (accelerator path, key
718  * and modifiers) to @file_name.
719  * The file is written in a format suitable to be read back in by
720  * gtk_accel_map_load().
721  */
722 void
723 gtk_accel_map_save (const gchar *file_name)
724 {
725   gint fd;
726
727   g_return_if_fail (file_name != NULL);
728
729   fd = open (file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
730   if (fd < 0)
731     return;
732
733   gtk_accel_map_save_fd (fd);
734
735   close (fd);
736 }
737
738 /**
739  * gtk_accel_map_foreach:
740  * @data:         data to be passed into @foreach_func
741  * @foreach_func: function to be executed for each accel map entry which
742  *                is not filtered out
743  *
744  * Loops over the entries in the accelerator map whose accel path 
745  * doesn't match any of the filters added with gtk_accel_map_add_filter(), 
746  * and execute @foreach_func on each. The signature of @foreach_func is 
747  * that of #GtkAccelMapForeach, the @changed parameter indicates whether
748  * this accelerator was changed during runtime (thus, would need
749  * saving during an accelerator map dump).
750  */
751 void
752 gtk_accel_map_foreach (gpointer           data,
753                        GtkAccelMapForeach foreach_func)
754 {
755   GSList *entries, *slist, *node;
756
757   g_return_if_fail (foreach_func != NULL);
758
759   entries = g_hash_table_slist_values (accel_entry_ht);
760   for (slist = entries; slist; slist = slist->next)
761     {
762       AccelEntry *entry = slist->data;
763       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
764
765       for (node = accel_filters; node; node = node->next)
766         if (g_pattern_match_string (node->data, entry->accel_path))
767           goto skip_accel;
768       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
769     skip_accel:
770       /* noop */;
771     }
772   g_slist_free (entries);
773 }
774
775 /**
776  * gtk_accel_map_foreach_unfiltered:
777  * @data:         data to be passed into @foreach_func
778  * @foreach_func: function to be executed for each accel map entry
779  *
780  * Loops over all entries in the accelerator map, and execute
781  * @foreach_func on each. The signature of @foreach_func is that of
782  * #GtkAccelMapForeach, the @changed parameter indicates whether
783  * this accelerator was changed during runtime (thus, would need
784  * saving during an accelerator map dump).
785  */
786 void
787 gtk_accel_map_foreach_unfiltered (gpointer           data,
788                                   GtkAccelMapForeach foreach_func)
789 {
790   GSList *entries, *slist;
791
792   g_return_if_fail (foreach_func != NULL);
793
794   entries = g_hash_table_slist_values (accel_entry_ht);
795   for (slist = entries; slist; slist = slist->next)
796     {
797       AccelEntry *entry = slist->data;
798       gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
799
800       foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
801     }
802   g_slist_free (entries);
803 }
804
805 /**
806  * gtk_accel_map_add_filter:
807  * @filter_pattern: a pattern (see #GPatternSpec)
808  *
809  * Adds a filter to the global list of accel path filters.
810  *
811  * Accel map entries whose accel path matches one of the filters
812  * are skipped by gtk_accel_map_foreach().
813  *
814  * This function is intended for GTK+ modules that create their own
815  * menus, but don't want them to be saved into the applications accelerator
816  * map dump.
817  */
818 void
819 gtk_accel_map_add_filter (const gchar *filter_pattern)
820 {
821   GPatternSpec *pspec;
822   GSList *slist;
823
824   g_return_if_fail (filter_pattern != NULL);
825
826   pspec = g_pattern_spec_new (filter_pattern);
827   for (slist = accel_filters; slist; slist = slist->next)
828     if (g_pattern_spec_equal (pspec, slist->data))
829       {
830         g_pattern_spec_free (pspec);
831         return;
832       }
833   accel_filters = g_slist_prepend (accel_filters, pspec);
834 }
835
836 void
837 _gtk_accel_map_add_group (const gchar   *accel_path,
838                           GtkAccelGroup *accel_group)
839 {
840   AccelEntry *entry;
841
842   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
843   g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
844
845   entry = accel_path_lookup (accel_path);
846   if (!entry)
847     {
848       gtk_accel_map_add_entry (accel_path, 0, 0);
849       entry = accel_path_lookup (accel_path);
850     }
851   entry->groups = g_slist_prepend (entry->groups, accel_group);
852 }
853
854 void
855 _gtk_accel_map_remove_group (const gchar   *accel_path,
856                              GtkAccelGroup *accel_group)
857 {
858   AccelEntry *entry;
859
860   entry = accel_path_lookup (accel_path);
861   g_return_if_fail (entry != NULL);
862   g_return_if_fail (g_slist_find (entry->groups, accel_group));
863
864   entry->groups = g_slist_remove (entry->groups, accel_group);
865 }
866
867
868 /**
869  * gtk_accel_map_lock_path:
870  * @accel_path: a valid accelerator path
871  * 
872  * Locks the given accelerator path. If the accelerator map doesn't yet contain
873  * an entry for @accel_path, a new one is created.
874  *
875  * Locking an accelerator path prevents its accelerator from being changed 
876  * during runtime. A locked accelerator path can be unlocked by 
877  * gtk_accel_map_unlock_path(). Refer to gtk_accel_map_change_entry() 
878  * for information about runtime accelerator changes.
879  *
880  * If called more than once, @accel_path remains locked until
881  * gtk_accel_map_unlock_path() has been called an equivalent number
882  * of times.
883  *
884  * Note that locking of individual accelerator paths is independent from 
885  * locking the #GtkAccelGroup containing them. For runtime accelerator
886  * changes to be possible both the accelerator path and its #GtkAccelGroup
887  * have to be unlocked. 
888  *
889  * Since: 2.4
890  **/
891 void 
892 gtk_accel_map_lock_path (const gchar *accel_path)
893 {
894   AccelEntry *entry;
895
896   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
897
898   entry = accel_path_lookup (accel_path);
899   
900   if (!entry)
901     {
902       gtk_accel_map_add_entry (accel_path, 0, 0);
903       entry = accel_path_lookup (accel_path);
904     }
905
906   entry->lock_count += 1;
907 }
908
909 /**
910  * gtk_accel_map_unlock_path:
911  * @accel_path: a valid accelerator path
912  * 
913  * Undoes the last call to gtk_accel_map_lock_path() on this @accel_path.
914  * Refer to gtk_accel_map_lock_path() for information about accelerator path locking.
915  *
916  * Since: 2.4
917  **/
918 void 
919 gtk_accel_map_unlock_path (const gchar *accel_path)
920 {
921   AccelEntry *entry;
922
923   g_return_if_fail (_gtk_accel_path_is_valid (accel_path));
924
925   entry = accel_path_lookup (accel_path);
926
927   g_return_if_fail (entry != NULL && entry->lock_count > 0);
928
929   entry->lock_count -= 1;  
930 }
931
932 G_DEFINE_TYPE (GtkAccelMap, gtk_accel_map, G_TYPE_OBJECT);
933
934 static void
935 gtk_accel_map_class_init (GtkAccelMapClass *accel_map_class)
936 {
937   /**
938    * GtkAccelMap::changed:
939    * @object: the global accel map object
940    * @accel_path: the path of the accelerator that changed
941    * @accel_key: the key value for the new accelerator
942    * @accel_mods: the modifier mask for the new accelerator
943    *
944    * Notifies of a change in the global accelerator map.
945    * The path is also used as the detail for the signal,
946    * so it is possible to connect to
947    * changed::<replaceable>accel_path</replaceable>.
948    *
949    * Since: 2.4
950    */
951   accel_map_signals[CHANGED] = g_signal_new ("changed",
952                                              G_TYPE_FROM_CLASS (accel_map_class),
953                                              G_SIGNAL_DETAILED|G_SIGNAL_RUN_LAST,
954                                              0,
955                                              NULL, NULL,
956                                              _gtk_marshal_VOID__STRING_UINT_FLAGS,
957                                              G_TYPE_NONE, 3,
958                                              G_TYPE_STRING, G_TYPE_UINT, GDK_TYPE_MODIFIER_TYPE);
959 }
960
961 static void
962 gtk_accel_map_init (GtkAccelMap *accel_map)
963 {
964 }
965
966 /**
967  * gtk_accel_map_get:
968  * 
969  * Gets the singleton global #GtkAccelMap object. This object
970  * is useful only for notification of changes to the accelerator
971  * map via the ::changed signal; it isn't a parameter to the
972  * other accelerator map functions.
973  * 
974  * Return value: the global #GtkAccelMap object
975  *
976  * Since: 2.4
977  **/
978 GtkAccelMap *
979 gtk_accel_map_get (void)
980 {
981   if (!accel_map)
982     accel_map = g_object_new (GTK_TYPE_ACCEL_MAP, NULL);
983
984   return accel_map;
985 }
986
987 static void
988 do_accel_map_changed (AccelEntry *entry)
989 {
990   if (accel_map)
991     g_signal_emit (accel_map,
992                    accel_map_signals[CHANGED],
993                    g_quark_from_string (entry->accel_path),
994                    entry->accel_path,
995                    entry->accel_key,
996                    entry->accel_mods);
997 }