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