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