1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1998, 2001 Tim Janik
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.
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.
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.
22 #include "gtkaccelmap.h"
24 #include "gtkwindow.h" /* in lack of GtkAcceleratable */
37 /* --- structures --- */
39 const gchar *accel_path;
49 /* --- variables --- */
50 static GHashTable *accel_entry_ht = NULL; /* accel_path -> AccelEntry */
51 static GSList *accel_filters = NULL;
54 /* --- functions --- */
56 accel_entry_hash (gconstpointer key)
58 const AccelEntry *entry = key;
60 return g_str_hash (entry->accel_path);
64 accel_entry_equal (gconstpointer key1,
67 const AccelEntry *entry1 = key1;
68 const AccelEntry *entry2 = key2;
70 return g_str_equal (entry1->accel_path, entry2->accel_path);
73 static inline AccelEntry*
74 accel_path_lookup (const gchar *accel_path)
78 ekey.accel_path = accel_path;
80 /* safety NULL check for return_if_fail()s */
81 return accel_path ? g_hash_table_lookup (accel_entry_ht, &ekey) : NULL;
85 _gtk_accel_map_init (void)
87 g_assert (accel_entry_ht == NULL);
89 accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
93 gtk_accel_path_is_valid (const gchar *accel_path)
97 if (!accel_path || accel_path[0] != '<' ||
98 accel_path[1] == '<' || accel_path[1] == '>' || !accel_path[1])
100 p = strchr (accel_path, '>');
101 if (!p || p[1] != '/')
107 * gtk_accel_map_add_entry
108 * @accel_path: valid accelerator path
109 * @accel_key: the accelerator key
110 * @accel_mods: the accelerator modifiers
111 * @returns: the GQuark for the @accel_path (to ease local storage)
113 * Register a new accelerator with the global accelerator map.
114 * This function should only be called once per @accel_path
115 * with the canonical @accel_key and @accel_mods for this path.
116 * To change the accelerator during runtime programatically, use
117 * gtk_accel_map_change_entry().
118 * The accelerator path must consist of "<WINDOWTYPE>/Category1/Category2/.../Action",
119 * where WINDOWTYPE should be a unique application specifc identifier, that
120 * corresponds to the kind of window the accelerator is being used in, e.g. "Gimp-Image",
121 * "Abiword-Document" or "Gnumeric-Settings".
122 * The Category1/.../Action portion is most apropriately choosen by the action the
123 * accelerator triggers, i.e. for accelerators on menu items, choose the item's menu path,
124 * e.g. "File/Save As", "Image/View/Zoom" or "Edit/Select All".
125 * So a full valid accelerator path may look like:
126 * "<Gimp-Toolbox>/File/Dialogs/Tool Options...".
129 gtk_accel_map_add_entry (const gchar *accel_path,
135 g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
137 accel_mods &= gtk_accelerator_get_default_mod_mask ();
139 entry = accel_path_lookup (accel_path);
142 if (!entry->std_accel_key && !entry->std_accel_mods &&
143 (accel_key || accel_mods))
145 entry->std_accel_key = accel_key;
146 entry->std_accel_mods = accel_mods;
148 gtk_accel_map_change_entry (entry->accel_path, accel_key, accel_mods, TRUE);
153 entry = g_new0 (AccelEntry, 1);
154 entry->accel_path = g_quark_to_string (g_quark_from_string (accel_path));
155 entry->std_accel_key = accel_key;
156 entry->std_accel_mods = accel_mods;
157 entry->accel_key = accel_key;
158 entry->accel_mods = accel_mods;
159 entry->changed = FALSE;
160 g_hash_table_insert (accel_entry_ht, entry, entry);
162 return g_quark_try_string (entry->accel_path);
167 GtkAccelGroup *accel_group;
171 accel_hook_finalize (GHookList *hook_list,
174 GDestroyNotify destroy = hook->destroy;
175 AccelHook *ahook = (AccelHook*) hook;
177 if (ahook->accel_group)
178 g_object_unref (ahook->accel_group);
182 hook->destroy = NULL;
183 destroy (hook->data);
189 * @data: notifier user data
190 * @accel_path_quark: accelerator path (as #GQuark) which has just changed
191 * @accel_key: new accelerator key
192 * @accel_mods: new accelerator modifiers
193 * @accel_group: accelerator group of this notifier
194 * @old_accel_key: former accelerator key
195 * @old_accel_mods): former accelerator modifiers
197 * #GtkAccelMapNotify is the signature of user callbacks, installed via
198 * gtk_accel_map_add_notifier(). Once the accel path of the notifier changes,
199 * the notifier is invoked with this signature, where @accel_path_quark
200 * indicates the accel path that changed, and @data and @accel_group are
201 * the notifier's arguments as passed into gtk_accel_map_add_notifier().
205 * gtk_accel_map_add_notifer
206 * @accel_path: valid accelerator path
207 * @notify_data: data argument to the notifier
208 * @notify_func: the notifier function
209 * @accel_group: accelerator group used by the notifier function
211 * Install a notifier function to be called if an accelerator
212 * map entry changes. @accel_group has to be the accel group
213 * that is being affected (gets an accelerator removed or added,
214 * when the notifier function is executed).
217 gtk_accel_map_add_notifer (const gchar *accel_path,
218 gpointer notify_data,
219 GtkAccelMapNotify notify_func,
220 GtkAccelGroup *accel_group)
226 g_return_if_fail (gtk_accel_path_is_valid (accel_path));
227 g_return_if_fail (notify_func != NULL);
229 g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
231 entry = accel_path_lookup (accel_path);
234 gtk_accel_map_add_entry (accel_path, 0, 0);
235 entry = accel_path_lookup (accel_path);
239 entry->hooks = g_new (GHookList, 1);
240 g_hook_list_init (entry->hooks, sizeof (AccelHook));
241 entry->hooks->finalize_hook = accel_hook_finalize;
243 hook = g_hook_alloc (entry->hooks);
244 hook->data = notify_data;
245 hook->func = notify_func;
246 hook->destroy = NULL;
247 ahook = (AccelHook*) hook;
248 ahook->accel_group = accel_group ? g_object_ref (accel_group) : NULL;
249 g_hook_append (entry->hooks, hook);
253 * gtk_accel_map_remove_notifer
254 * @accel_path: valid accelerator path
255 * @notify_data: data argument to the notifier
256 * @notify_func: the notifier function
258 * Remove a notifier function, previously installed through
259 * gtk_accel_map_add_notifer().
262 gtk_accel_map_remove_notifer (const gchar *accel_path,
263 gpointer notify_data,
264 GtkAccelMapNotify notify_func)
268 g_return_if_fail (gtk_accel_path_is_valid (accel_path));
269 g_return_if_fail (notify_func != NULL);
271 entry = accel_path_lookup (accel_path);
272 if (entry && entry->hooks)
274 GHook *hook = g_hook_find_func_data (entry->hooks, TRUE, notify_func, notify_data);
276 if (hook && g_hook_destroy (entry->hooks, hook->hook_id))
277 return; /* successfully removed */
279 g_warning (G_STRLOC ": no notifier %p(%p) installed for accel path \"%s\"",
280 notify_func, notify_data, accel_path);
284 * gtk_accel_map_lookup_entry
285 * @accel_path: valid accelerator path
286 * @key: accelerator key to be filled in (optional)
287 * @returns: #GQuark for @accel_path or (0) if @accel_path is not known
289 * Lookup the accelerator entry for @accel_path and fill in @key.
290 * If the lookup revealed no results, (0) is returned, the entry's
294 gtk_accel_map_lookup_entry (const gchar *accel_path,
299 g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
301 entry = accel_path_lookup (accel_path);
304 key->accel_key = entry->accel_key;
305 key->accel_mods = entry->accel_mods;
306 key->accel_flags = 0; // FIXME: global lock?
309 return entry ? g_quark_try_string (entry->accel_path) : 0;
313 hash2slist_foreach (gpointer key,
317 GSList **slist_p = user_data;
319 *slist_p = g_slist_prepend (*slist_p, value);
323 g_hash_table_slist_values (GHashTable *hash_table)
325 GSList *slist = NULL;
327 g_return_val_if_fail (hash_table != NULL, NULL);
329 g_hash_table_foreach (hash_table, hash2slist_foreach, &slist);
335 internal_change_entry (const gchar *accel_path,
337 GdkModifierType accel_mods,
341 GSList *node, *slist, *win_list, *group_list, *replace_list = NULL;
342 GHashTable *group_hm, *win_hm;
343 gboolean change_accel, removable, can_change = TRUE, seen_accel = FALSE, hooks_may_recurse = TRUE;
346 AccelEntry *entry = accel_path_lookup (accel_path);
348 /* not much todo if there's no entry yet */
353 gtk_accel_map_add_entry (accel_path, 0, 0);
354 entry = accel_path_lookup (accel_path);
355 entry->accel_key = accel_key;
356 entry->accel_mods = accel_mods;
357 entry->changed = TRUE;
362 /* if there's nothing to change, not much todo either */
363 if (entry->accel_key == accel_key && entry->accel_mods == accel_mods)
366 /* nobody's interested, easy going */
371 entry->accel_key = accel_key;
372 entry->accel_mods = accel_mods;
373 entry->changed = TRUE;
378 /* 1) fetch all accel groups affected by this entry */
379 entry_quark = g_quark_try_string (entry->accel_path);
380 group_hm = g_hash_table_new (NULL, NULL);
381 win_hm = g_hash_table_new (NULL, NULL);
382 hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
385 AccelHook *ahook = (AccelHook*) hook;
387 if (ahook->accel_group)
388 g_hash_table_insert (group_hm, ahook->accel_group, ahook->accel_group);
389 hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
392 /* 2) collect acceleratables affected */
393 group_list = g_hash_table_slist_values (group_hm);
394 for (slist = group_list; slist; slist = slist->next)
396 GtkAccelGroup *group = slist->data;
398 for (node = group->acceleratables; node; node = node->next)
399 g_hash_table_insert (win_hm, node->data, node->data);
401 g_slist_free (group_list);
403 /* 3) include all accel groups used by acceleratables */
404 win_list = g_hash_table_slist_values (win_hm);
405 g_hash_table_destroy (win_hm);
406 for (slist = win_list; slist; slist = slist->next)
407 for (node = gtk_accel_groups_from_acceleratable (slist->data); node; node = node->next)
408 g_hash_table_insert (group_hm, node->data, node->data);
409 group_list = g_hash_table_slist_values (group_hm);
410 g_hash_table_destroy (group_hm);
412 /* 4) walk the acceleratables and figure whether they occupy accel_key&accel_mods */
413 for (slist = accel_key ? win_list : NULL; slist; slist = slist->next)
414 if (GTK_IS_WINDOW (slist->data)) /* bad kludge in lack of a GtkAcceleratable */
415 if (_gtk_window_query_nonaccels (slist->data, accel_key, accel_mods))
420 removable = !seen_accel;
422 /* 5) walk all accel groups and search for locks */
423 for (slist = removable ? group_list : NULL; slist; slist = slist->next)
425 GtkAccelGroup *group = slist->data;
426 GtkAccelGroupEntry *ag_entry;
430 ag_entry = entry->accel_key ? gtk_accel_group_query (group, entry->accel_key, entry->accel_mods, &n) : NULL;
431 for (i = 0; i < n; i++)
432 if (ag_entry[i].accel_path_quark == entry_quark)
434 can_change = !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
436 goto break_loop_step5;
440 ag_entry = accel_key ? gtk_accel_group_query (group, accel_key, accel_mods, &n) : NULL;
441 for (i = 0; i < n; i++)
444 removable = !group->lock_count && !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
446 goto break_loop_step5;
447 if (ag_entry[i].accel_path_quark)
448 replace_list = g_slist_prepend (replace_list, GUINT_TO_POINTER (ag_entry->accel_path_quark));
453 /* 6) check whether we can remove existing accelerators */
454 for (slist = removable ? replace_list : NULL; slist; slist = slist->next)
455 if (!internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, TRUE))
461 /* 7) check conditions and proceed if possible */
462 change_accel = can_change && (!seen_accel || (removable && replace));
464 if (change_accel && !simulate)
466 guint old_accel_key, old_accel_mods;
468 /* 8) remove existing accelerators */
469 for (slist = replace_list; slist; slist = slist->next)
470 internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, FALSE);
472 /* 9) install new accelerator */
473 old_accel_key = entry->accel_key;
474 old_accel_mods = entry->accel_mods;
475 entry->accel_key = accel_key;
476 entry->accel_mods = accel_mods;
477 entry->changed = TRUE;
478 hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
481 gboolean was_in_call, need_destroy = FALSE;
482 GtkAccelMapNotify hook_func = hook->func;
483 AccelHook *ahook = (AccelHook*) hook;
485 was_in_call = G_HOOK_IN_CALL (hook);
486 hook->flags |= G_HOOK_FLAG_IN_CALL;
487 /* need_destroy = */ hook_func (hook->data, g_quark_try_string (entry->accel_path),
488 entry->accel_key, entry->accel_mods,
490 old_accel_key, old_accel_mods);
492 hook->flags &= ~G_HOOK_FLAG_IN_CALL;
494 g_hook_destroy_link (entry->hooks, hook);
495 hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
498 g_slist_free (replace_list);
499 g_slist_free (group_list);
500 g_slist_free (win_list);
506 * gtk_accel_map_change_entry
507 * @accel_path: valid accelerator path
508 * @accel_key: new accelerator key
509 * @accel_mods: new accelerator modifiers
510 * @replace: %TRUE if other accelerators may be deleted upon conflicts
511 * @returns: %TRUE if the accelerator could be changed, %FALSE otherwise
513 * Change the @accel_key and @accel_mods currently associated with @accel_path.
514 * Due to conflicts with other accelerators, a change may not alwys be possible,
515 * @replace indicates whether other accelerators may be deleted to resolve such
516 * conflicts. A change will only occour if all conflicts could be resolved (which
517 * might not be the case if conflicting accelerators are locked). Succesfull
518 * changes are indicated by a %TRUE return value.
519 * Changes occouring are also indicated by invocation of notifiers attached to
520 * @accel_path (see gtk_accel_map_add_notifer() on this) and other accelerators
521 * that are being deleted.
524 gtk_accel_map_change_entry (const gchar *accel_path,
526 GdkModifierType accel_mods,
529 g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), FALSE);
531 return internal_change_entry (accel_path, accel_key, accel_key ? accel_mods : 0, replace, FALSE);
535 accel_map_parse_accel_path (GScanner *scanner)
537 guint accel_key = 0, accel_mods = 0;
540 /* parse accel path */
541 g_scanner_get_next_token (scanner);
542 if (scanner->token != G_TOKEN_STRING)
543 return G_TOKEN_STRING;
545 /* test if the next token is an accelerator */
546 g_scanner_peek_next_token (scanner);
547 if (scanner->next_token != G_TOKEN_STRING)
549 /* if not so, eat that token and error out */
550 g_scanner_get_next_token (scanner);
551 return G_TOKEN_STRING;
554 /* get the full accelerator specification */
555 path = g_strdup (scanner->value.v_string);
556 g_scanner_get_next_token (scanner);
557 accel = g_strdup (scanner->value.v_string);
559 /* ensure the entry is present */
560 gtk_accel_map_add_entry (path, 0, 0);
562 /* and propagate it */
563 gtk_accelerator_parse (accel, &accel_key, &accel_mods);
564 gtk_accel_map_change_entry (path, accel_key, accel_mods, TRUE);
569 /* check correct statement end */
570 g_scanner_get_next_token (scanner);
571 if (scanner->token != ')')
578 accel_map_parse_statement (GScanner *scanner)
580 guint expected_token;
582 g_scanner_get_next_token (scanner);
584 if (scanner->token == G_TOKEN_SYMBOL)
586 guint (*parser_func) (GScanner*);
588 parser_func = scanner->value.v_symbol;
590 expected_token = parser_func (scanner);
593 expected_token = G_TOKEN_SYMBOL;
595 /* skip rest of statement on errrors
597 if (expected_token != G_TOKEN_NONE)
599 register guint level;
602 if (scanner->token == ')')
604 if (scanner->token == '(')
607 while (!g_scanner_eof (scanner) && level > 0)
609 g_scanner_get_next_token (scanner);
611 if (scanner->token == '(')
613 else if (scanner->token == ')')
620 gtk_accel_map_load_scanner (GScanner *scanner)
622 gboolean skip_comment_single;
623 gboolean symbol_2_token;
624 gchar *cpair_comment_single;
625 gpointer saved_symbol;
627 g_return_if_fail (scanner != 0);
629 /* configure scanner */
630 skip_comment_single = scanner->config->skip_comment_single;
631 scanner->config->skip_comment_single = TRUE;
632 cpair_comment_single = scanner->config->cpair_comment_single;
633 scanner->config->cpair_comment_single = ";\n";
634 symbol_2_token = scanner->config->symbol_2_token;
635 scanner->config->symbol_2_token = FALSE;
636 saved_symbol = g_scanner_lookup_symbol (scanner, "gtk_accel_path");
637 g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", accel_map_parse_accel_path);
639 /* outer parsing loop
641 g_scanner_peek_next_token (scanner);
642 while (scanner->next_token == '(')
644 g_scanner_get_next_token (scanner);
646 accel_map_parse_statement (scanner);
648 g_scanner_peek_next_token (scanner);
652 scanner->config->skip_comment_single = skip_comment_single;
653 scanner->config->cpair_comment_single = cpair_comment_single;
654 scanner->config->symbol_2_token = symbol_2_token;
655 g_scanner_scope_remove_symbol (scanner, 0, "gtk_accel_path");
657 g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", saved_symbol);
661 * gtk_accel_map_load_fd
662 * @fd: valid readable file descriptor
664 * Filedescriptor variant of gtk_accel_map_load().
665 * Note that the file descriptor will not be closed by this function.
668 gtk_accel_map_load_fd (gint fd)
672 g_return_if_fail (fd >= 0);
674 /* create and setup scanner */
675 scanner = g_scanner_new (NULL);
676 g_scanner_input_file (scanner, fd);
678 gtk_accel_map_load_scanner (scanner);
680 g_scanner_destroy (scanner);
685 * @file_name: a file containing accelerator specifications
687 * Parses a file previously saved with gtk_accel_map_save() for
688 * accelerator specifications, and propagates them accordingly.
691 gtk_accel_map_load (const gchar *file_name)
695 g_return_if_fail (file_name != NULL);
697 if (!g_file_test (file_name, G_FILE_TEST_IS_REGULAR))
700 fd = open (file_name, O_RDONLY);
704 gtk_accel_map_load_fd (fd);
710 accel_map_print (gpointer data,
711 const gchar *accel_path,
716 GString *gstring = g_string_new (changed ? NULL : "; ");
717 gint err, fd = GPOINTER_TO_INT (data);
720 g_string_append (gstring, "(gtk_accel_path \"");
722 tmp = g_strescape (accel_path, NULL);
723 g_string_append (gstring, tmp);
726 g_string_append (gstring, "\" \"");
728 name = gtk_accelerator_name (accel_key, accel_mods);
729 tmp = g_strescape (name, NULL);
731 g_string_append (gstring, tmp);
734 g_string_append (gstring, "\")\n");
737 err = write (fd, gstring->str, gstring->len);
738 while (err < 0 && errno == EINTR);
740 g_string_free (gstring, TRUE);
744 * gtk_accel_map_save_fd
745 * @fd: valid writable file descriptor
747 * Filedescriptor variant of gtk_accel_map_save().
748 * Note that the file descriptor will not be closed by this function.
751 gtk_accel_map_save_fd (gint fd)
756 g_return_if_fail (fd >= 0);
758 gstring = g_string_new ("; ");
759 if (g_get_prgname ())
760 g_string_append (gstring, g_get_prgname ());
761 g_string_append (gstring, " GtkAccelMap rc-file -*- scheme -*-\n");
762 g_string_append (gstring, "; this file is an automated accelerator map dump\n");
763 g_string_append (gstring, ";\n");
766 err = write (fd, gstring->str, gstring->len);
767 while (err < 0 && errno == EINTR);
769 gtk_accel_map_foreach (GINT_TO_POINTER (fd), accel_map_print);
774 * @file_name: the file to contain accelerator specifications
776 * Saves current accelerator specifications (accelerator path, key
777 * and modifiers) to @file_name.
778 * The file is written in a format suitable to be read back in by
779 * gtk_accel_map_load().
782 gtk_accel_map_save (const gchar *file_name)
786 g_return_if_fail (file_name != NULL);
788 fd = open (file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
792 gtk_accel_map_save_fd (fd);
798 * gtk_accel_map_foreach
799 * @data: data to be passed into @foreach_func
800 * @foreach_func: function to be executed for each accel map entry
802 * Loop over the entries in the accelerator map, and execute
803 * @foreach_func on each. The signature of @foreach_func is that of
804 * #GtkAccelMapForeach, the @changed parameter indicates whether
805 * this accelerator was changed during runtime (thus, would need
806 * saving during an accelerator map dump).
809 gtk_accel_map_foreach (gpointer data,
810 GtkAccelMapForeach foreach_func)
812 GSList *entries, *slist, *node;
814 g_return_if_fail (foreach_func != NULL);
816 entries = g_hash_table_slist_values (accel_entry_ht);
817 for (slist = entries; slist; slist = slist->next)
819 AccelEntry *entry = slist->data;
820 gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
822 for (node = accel_filters; node; node = node->next)
823 if (g_pattern_match_string (node->data, entry->accel_path))
825 foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
829 g_slist_free (entries);
833 gtk_accel_map_foreach_unfiltered (gpointer data,
834 GtkAccelMapForeach foreach_func)
836 GSList *entries, *slist;
838 g_return_if_fail (foreach_func != NULL);
840 entries = g_hash_table_slist_values (accel_entry_ht);
841 for (slist = entries; slist; slist = slist->next)
843 AccelEntry *entry = slist->data;
844 gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
846 foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
848 g_slist_free (entries);
852 gtk_accel_map_add_filter (const gchar *filter_pattern)
857 g_return_if_fail (filter_pattern != NULL);
859 pspec = g_pattern_spec_new (filter_pattern);
860 for (slist = accel_filters; slist; slist = slist->next)
861 if (g_pattern_spec_equal (pspec, slist->data))
863 g_pattern_spec_free (pspec);
866 accel_filters = g_slist_prepend (accel_filters, pspec);