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