summaryrefslogtreecommitdiff
path: root/gtk/gtkaccelmap.c
diff options
context:
space:
mode:
authorTim Janik <timj@gtk.org>2001-11-13 00:53:47 +0000
committerTim Janik <timj@src.gnome.org>2001-11-13 00:53:47 +0000
commitd07573c090f8ca8c3fdb8f4d3f63b32be96ea7d6 (patch)
treeea660dfa3bec08f14714db1b2e9699bf5f126394 /gtk/gtkaccelmap.c
parentaebe24f2bba1a41b23ab62b7adfc867e28adb3fc (diff)
downloadgtk+-d07573c090f8ca8c3fdb8f4d3f63b32be96ea7d6.tar.gz
added gtkaccelmap.sgml. other updates.
Mon Nov 12 23:06:38 2001 Tim Janik <timj@gtk.org> * added gtkaccelmap.sgml. other updates. Mon Nov 12 23:08:37 2001 Tim Janik <timj@gtk.org> * gtk/maketypes.awk: fix type utils generation on unix. * gtk/gtkaccelmap.[hc]: new files, implementing a global accelerator registry. * gtk/gtkaccelgroup.[hc]: major API/implementation revamp: removed GTK_ACCEL_SIGNAL_VISIBLE, gtk_accel_group_get_default, gtk_accel_group_get_entry, gtk_accel_group_(un)lock_entry, gtk_accel_group_add/remove, gtk_accel_group_handle_add/remove, gtk_accel_group_create_add/remove, gtk_accel_group_entries_from_object. introduced ::accel_changed signal for change notification, and gtk_accel_group_connect/disconnect to connect closures to accel groups. made gtk_accel_group_attach/detach and gtk_accel_group_activate private functions. deprecated gtk_accel_group_ref/unref. * gtk/gtkaccellabel.[hc]: changes to make accellabels pay attention to accel group changed notification and basically operate on closures. removed gtk_accel_label_get_accel_object and gtk_accel_label_set_accel_object. introduced gtk_accel_label_set_accel_closure, and for convenience, gtk_accel_label_set_accel_widget. * gtk/gtkitemfactory.[hc]: removed accelerator propagation code which mostly moved into gtkaccelmap.[hc]. removed gtk_item_factory_parse_rc*, gtk_item_factory_dump_* and gtk_item_factory_print_func. * gtk/gtkmain.c: call _gtk_accel_map_init(). * gtk/gtkmenuitem.[hc]: introduced gtk_menu_item_set_accel_path(), that associates an accelerator path with menu items, through which persistent accelerator settings on menu items are enabled. * gtk/gtkmenu.[hc]: added gtk_menu_set_accel_path() so accelerator paths of menu item can be default constructed to allow installation of accelerators on menu items that don't come with an accelerator binding by default. * gtk/gtksettings.c: fix STRING type rc settings by special casing them appropriately in the parser. * gtk/gtksignal.[hc]: allow a class function offset of 0 for gtk_signal_newv(). * gtk/gtkwidget.[hc]: accelerator API revamp. removed ::accelerator_add/remove signals, gtk_widget_accelerator_signal, gtk_widget_accelerators_locked, gtk_widget_remove_accelerators and gtk_widget_(un)lock_accelerators. accelerators maintained through gtk_widget_add/remove_accelerator() are not runtime changable now, the correct sequence to setup a widget for runtime changable accelerators is now: gtk_accel_map_add_entry(accel_path, key, mods); _gtk_widget_set_accel_path(widget, accel_path, accel_group); * gtk/gtkwindow.[hc]: accelerator changes, proxy and coalesce accel group changes (as well as mnemonic changes) through the new signal ::accels_changed. Sat Nov 10 12:08:56 2001 Tim Janik <timj@gtk.org> * gtk/gtksettings.c (_gtk_settings_parse_convert): properly handle GString->string conversions.
Diffstat (limited to 'gtk/gtkaccelmap.c')
-rw-r--r--gtk/gtkaccelmap.c858
1 files changed, 858 insertions, 0 deletions
diff --git a/gtk/gtkaccelmap.c b/gtk/gtkaccelmap.c
new file mode 100644
index 0000000000..24dca7d5c3
--- /dev/null
+++ b/gtk/gtkaccelmap.c
@@ -0,0 +1,858 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1998, 2001 Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include "gtkaccelmap.h"
+
+#include "gtkwindow.h" /* in lack of GtkAcceleratable */
+
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+
+/* --- structures --- */
+typedef struct {
+ const gchar *accel_path;
+ guint accel_key;
+ guint accel_mods;
+ guint std_accel_key;
+ guint std_accel_mods;
+ guint changed : 1;
+ GHookList *hooks;
+} AccelEntry;
+
+
+/* --- variables --- */
+static GHashTable *accel_entry_ht = NULL; /* accel_path -> AccelEntry */
+static GSList *accel_filters = NULL;
+
+
+/* --- functions --- */
+static guint
+accel_entry_hash (gconstpointer key)
+{
+ const AccelEntry *entry = key;
+
+ return g_str_hash (entry->accel_path);
+}
+
+static gboolean
+accel_entry_equal (gconstpointer key1,
+ gconstpointer key2)
+{
+ const AccelEntry *entry1 = key1;
+ const AccelEntry *entry2 = key2;
+
+ return g_str_equal (entry1->accel_path, entry2->accel_path);
+}
+
+static inline AccelEntry*
+accel_path_lookup (const gchar *accel_path)
+{
+ AccelEntry ekey;
+
+ ekey.accel_path = accel_path;
+
+ /* safety NULL check for return_if_fail()s */
+ return accel_path ? g_hash_table_lookup (accel_entry_ht, &ekey) : NULL;
+}
+
+void
+_gtk_accel_map_init (void)
+{
+ g_assert (accel_entry_ht == NULL);
+
+ accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
+}
+
+static gboolean
+gtk_accel_path_is_valid (const gchar *accel_path)
+{
+ gchar *p;
+
+ if (!accel_path || accel_path[0] != '<' ||
+ accel_path[1] == '<' || accel_path[1] == '>' || !accel_path[1])
+ return FALSE;
+ p = strchr (accel_path, '>');
+ if (!p || p[1] != '/')
+ return FALSE;
+ return TRUE;
+}
+
+/**
+ * gtk_accel_map_add_entry
+ * @accel_path: valid accelerator path
+ * @accel_key: the accelerator key
+ * @accel_mods: the accelerator modifiers
+ * @returns: the GQuark for the @accel_path (to ease local storage)
+ *
+ * Register a new accelerator with the global accelerator map.
+ * This function should only be called once per @accel_path
+ * with the canonical @accel_key and @accel_mods for this path.
+ * To change the accelerator during runtime programatically, use
+ * gtk_accel_map_change_entry().
+ * The accelerator path must consist of "<WINDOWTYPE>/Category1/Category2/.../Action",
+ * where WINDOWTYPE should be a unique application specifc identifier, that
+ * corresponds to the kind of window the accelerator is being used in, e.g. "Gimp-Image",
+ * "Abiword-Document" or "Gnumeric-Settings".
+ * The Category1/.../Action portion is most apropriately choosen by the action the
+ * accelerator triggers, i.e. for accelerators on menu items, choose the item's menu path,
+ * e.g. "File/Save As", "Image/View/Zoom" or "Edit/Select All".
+ * So a full valid accelerator path may look like:
+ * "<Gimp-Toolbox>/File/Dialogs/Tool Options...".
+ */
+GQuark
+gtk_accel_map_add_entry (const gchar *accel_path,
+ guint accel_key,
+ guint accel_mods)
+{
+ AccelEntry *entry;
+
+ g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
+
+ accel_mods &= gtk_accelerator_get_default_mod_mask ();
+
+ entry = accel_path_lookup (accel_path);
+ if (entry)
+ {
+ if (!entry->std_accel_key && !entry->std_accel_mods &&
+ (accel_key || accel_mods))
+ {
+ entry->std_accel_key = accel_key;
+ entry->std_accel_mods = accel_mods;
+ if (!entry->changed)
+ gtk_accel_map_change_entry (entry->accel_path, accel_key, accel_mods, TRUE);
+ }
+ }
+ else
+ {
+ entry = g_new0 (AccelEntry, 1);
+ entry->accel_path = g_quark_to_string (g_quark_from_string (accel_path));
+ entry->std_accel_key = accel_key;
+ entry->std_accel_mods = accel_mods;
+ entry->accel_key = accel_key;
+ entry->accel_mods = accel_mods;
+ entry->changed = FALSE;
+ g_hash_table_insert (accel_entry_ht, entry, entry);
+ }
+ return g_quark_try_string (entry->accel_path);
+}
+
+typedef struct {
+ GHook hook;
+ GtkAccelGroup *accel_group;
+} AccelHook;
+
+static void
+accel_hook_finalize (GHookList *hook_list,
+ GHook *hook)
+{
+ GDestroyNotify destroy = hook->destroy;
+ AccelHook *ahook = (AccelHook*) hook;
+
+ if (ahook->accel_group)
+ g_object_unref (ahook->accel_group);
+
+ if (destroy)
+ {
+ hook->destroy = NULL;
+ destroy (hook->data);
+ }
+}
+
+/**
+ * GtkAccelMapNotify
+ * @data: notifier user data
+ * @accel_path_quark: accelerator path (as #GQuark) which has just changed
+ * @accel_key: new accelerator key
+ * @accel_mods: new accelerator modifiers
+ * @accel_group: accelerator group of this notifier
+ * @old_accel_key: former accelerator key
+ * @old_accel_mods): former accelerator modifiers
+ *
+ * #GtkAccelMapNotify is the signature of user callbacks, installed via
+ * gtk_accel_map_add_notifier(). Once the accel path of the notifier changes,
+ * the notifier is invoked with this signature, where @accel_path_quark
+ * indicates the accel path that changed, and @data and @accel_group are
+ * the notifier's arguments as passed into gtk_accel_map_add_notifier().
+ */
+
+/**
+ * gtk_accel_map_add_notifer
+ * @accel_path: valid accelerator path
+ * @notify_data: data argument to the notifier
+ * @notify_func: the notifier function
+ * @accel_group: accelerator group used by the notifier function
+ *
+ * Install a notifier function to be called if an accelerator
+ * map entry changes. @accel_group has to be the accel group
+ * that is being affected (gets an accelerator removed or added,
+ * when the notifier function is executed).
+ */
+void
+gtk_accel_map_add_notifer (const gchar *accel_path,
+ gpointer notify_data,
+ GtkAccelMapNotify notify_func,
+ GtkAccelGroup *accel_group)
+{
+ AccelEntry *entry;
+ AccelHook *ahook;
+ GHook *hook;
+
+ g_return_if_fail (gtk_accel_path_is_valid (accel_path));
+ g_return_if_fail (notify_func != NULL);
+ if (accel_group)
+ g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group));
+
+ entry = accel_path_lookup (accel_path);
+ if (!entry)
+ {
+ gtk_accel_map_add_entry (accel_path, 0, 0);
+ entry = accel_path_lookup (accel_path);
+ }
+ if (!entry->hooks)
+ {
+ entry->hooks = g_new (GHookList, 1);
+ g_hook_list_init (entry->hooks, sizeof (AccelHook));
+ entry->hooks->finalize_hook = accel_hook_finalize;
+ }
+ hook = g_hook_alloc (entry->hooks);
+ hook->data = notify_data;
+ hook->func = notify_func;
+ hook->destroy = NULL;
+ ahook = (AccelHook*) hook;
+ ahook->accel_group = accel_group ? g_object_ref (accel_group) : NULL;
+ g_hook_append (entry->hooks, hook);
+}
+
+/**
+ * gtk_accel_map_remove_notifer
+ * @accel_path: valid accelerator path
+ * @notify_data: data argument to the notifier
+ * @notify_func: the notifier function
+ *
+ * Remove a notifier function, previously installed through
+ * gtk_accel_map_add_notifer().
+ */
+void
+gtk_accel_map_remove_notifer (const gchar *accel_path,
+ gpointer notify_data,
+ GtkAccelMapNotify notify_func)
+{
+ AccelEntry *entry;
+
+ g_return_if_fail (gtk_accel_path_is_valid (accel_path));
+ g_return_if_fail (notify_func != NULL);
+
+ entry = accel_path_lookup (accel_path);
+ if (entry && entry->hooks)
+ {
+ GHook *hook = g_hook_find_func_data (entry->hooks, TRUE, notify_func, notify_data);
+
+ if (hook && g_hook_destroy (entry->hooks, hook->hook_id))
+ return; /* successfully removed */
+ }
+ g_warning (G_STRLOC ": no notifier %p(%p) installed for accel path \"%s\"",
+ notify_func, notify_data, accel_path);
+}
+
+/**
+ * gtk_accel_map_lookup_entry
+ * @accel_path: valid accelerator path
+ * @key: accelerator key to be filled in (optional)
+ * @returns: #GQuark for @accel_path or (0) if @accel_path is not known
+ *
+ * Lookup the accelerator entry for @accel_path and fill in @key.
+ * If the lookup revealed no results, (0) is returned, the entry's
+ * #GQuark otherwise.
+ */
+GQuark
+gtk_accel_map_lookup_entry (const gchar *accel_path,
+ GtkAccelKey *key)
+{
+ AccelEntry *entry;
+
+ g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), 0);
+
+ entry = accel_path_lookup (accel_path);
+ if (entry && key)
+ {
+ key->accel_key = entry->accel_key;
+ key->accel_mods = entry->accel_mods;
+ key->accel_flags = 0; // FIXME: global lock?
+ }
+
+ return entry ? g_quark_try_string (entry->accel_path) : 0;
+}
+
+static void
+hash2slist_foreach (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GSList **slist_p = user_data;
+
+ *slist_p = g_slist_prepend (*slist_p, value);
+}
+
+static GSList*
+g_hash_table_slist_values (GHashTable *hash_table)
+{
+ GSList *slist = NULL;
+
+ g_return_val_if_fail (hash_table != NULL, NULL);
+
+ g_hash_table_foreach (hash_table, hash2slist_foreach, &slist);
+
+ return slist;
+}
+
+static gboolean
+internal_change_entry (const gchar *accel_path,
+ guint accel_key,
+ GdkModifierType accel_mods,
+ gboolean replace,
+ gboolean simulate)
+{
+ GSList *node, *slist, *win_list, *group_list, *replace_list = NULL;
+ GHashTable *group_hm, *win_hm;
+ gboolean change_accel, removable, can_change = TRUE, seen_accel = FALSE, hooks_may_recurse = TRUE;
+ GQuark entry_quark;
+ GHook *hook;
+ AccelEntry *entry = accel_path_lookup (accel_path);
+
+ /* not much todo if there's no entry yet */
+ if (!entry)
+ {
+ if (!simulate)
+ {
+ gtk_accel_map_add_entry (accel_path, 0, 0);
+ entry = accel_path_lookup (accel_path);
+ entry->accel_key = accel_key;
+ entry->accel_mods = accel_mods;
+ entry->changed = TRUE;
+ }
+ return TRUE;
+ }
+
+ /* if there's nothing to change, not much todo either */
+ if (entry->accel_key == accel_key && entry->accel_mods == accel_mods)
+ return FALSE;
+
+ /* nobody's interested, easy going */
+ if (!entry->hooks)
+ {
+ if (!simulate)
+ {
+ entry->accel_key = accel_key;
+ entry->accel_mods = accel_mods;
+ entry->changed = TRUE;
+ }
+ return TRUE;
+ }
+
+ /* 1) fetch all accel groups affected by this entry */
+ entry_quark = g_quark_try_string (entry->accel_path);
+ group_hm = g_hash_table_new (NULL, NULL);
+ win_hm = g_hash_table_new (NULL, NULL);
+ hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
+ while (hook)
+ {
+ AccelHook *ahook = (AccelHook*) hook;
+
+ if (ahook->accel_group)
+ g_hash_table_insert (group_hm, ahook->accel_group, ahook->accel_group);
+ hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
+ }
+
+ /* 2) collect acceleratables affected */
+ group_list = g_hash_table_slist_values (group_hm);
+ for (slist = group_list; slist; slist = slist->next)
+ {
+ GtkAccelGroup *group = slist->data;
+
+ for (node = group->acceleratables; node; node = node->next)
+ g_hash_table_insert (win_hm, node->data, node->data);
+ }
+ g_slist_free (group_list);
+
+ /* 3) include all accel groups used by acceleratables */
+ win_list = g_hash_table_slist_values (win_hm);
+ g_hash_table_destroy (win_hm);
+ for (slist = win_list; slist; slist = slist->next)
+ for (node = gtk_accel_groups_from_acceleratable (slist->data); node; node = node->next)
+ g_hash_table_insert (group_hm, node->data, node->data);
+ group_list = g_hash_table_slist_values (group_hm);
+ g_hash_table_destroy (group_hm);
+
+ /* 4) walk the acceleratables and figure whether they occupy accel_key&accel_mods */
+ for (slist = accel_key ? win_list : NULL; slist; slist = slist->next)
+ if (GTK_IS_WINDOW (slist->data)) /* bad kludge in lack of a GtkAcceleratable */
+ if (_gtk_window_query_nonaccels (slist->data, accel_key, accel_mods))
+ {
+ seen_accel = TRUE;
+ break;
+ }
+ removable = !seen_accel;
+
+ /* 5) walk all accel groups and search for locks */
+ for (slist = removable ? group_list : NULL; slist; slist = slist->next)
+ {
+ GtkAccelGroup *group = slist->data;
+ GtkAccelGroupEntry *ag_entry;
+ guint i, n;
+
+ n = 0;
+ ag_entry = entry->accel_key ? gtk_accel_group_query (group, entry->accel_key, entry->accel_mods, &n) : NULL;
+ for (i = 0; i < n; i++)
+ if (ag_entry[i].accel_path_quark == entry_quark)
+ {
+ can_change = !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
+ if (!can_change)
+ goto break_loop_step5;
+ }
+
+ n = 0;
+ ag_entry = accel_key ? gtk_accel_group_query (group, accel_key, accel_mods, &n) : NULL;
+ for (i = 0; i < n; i++)
+ {
+ seen_accel = TRUE;
+ removable = !group->lock_count && !(ag_entry[i].key.accel_flags & GTK_ACCEL_LOCKED);
+ if (!removable)
+ goto break_loop_step5;
+ if (ag_entry[i].accel_path_quark)
+ replace_list = g_slist_prepend (replace_list, GUINT_TO_POINTER (ag_entry->accel_path_quark));
+ }
+ }
+ break_loop_step5:
+
+ /* 6) check whether we can remove existing accelerators */
+ for (slist = removable ? replace_list : NULL; slist; slist = slist->next)
+ if (!internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, TRUE))
+ {
+ removable = FALSE;
+ break;
+ }
+
+ /* 7) check conditions and proceed if possible */
+ change_accel = can_change && (!seen_accel || (removable && replace));
+
+ if (change_accel && !simulate)
+ {
+ guint old_accel_key, old_accel_mods;
+
+ /* 8) remove existing accelerators */
+ for (slist = replace_list; slist; slist = slist->next)
+ internal_change_entry (g_quark_to_string (GPOINTER_TO_UINT (slist->data)), 0, 0, FALSE, FALSE);
+
+ /* 9) install new accelerator */
+ old_accel_key = entry->accel_key;
+ old_accel_mods = entry->accel_mods;
+ entry->accel_key = accel_key;
+ entry->accel_mods = accel_mods;
+ entry->changed = TRUE;
+ hook = g_hook_first_valid (entry->hooks, hooks_may_recurse);
+ while (hook)
+ {
+ gboolean was_in_call, need_destroy = FALSE;
+ GtkAccelMapNotify hook_func = hook->func;
+ AccelHook *ahook = (AccelHook*) hook;
+
+ was_in_call = G_HOOK_IN_CALL (hook);
+ hook->flags |= G_HOOK_FLAG_IN_CALL;
+ /* need_destroy = */ hook_func (hook->data, g_quark_try_string (entry->accel_path),
+ entry->accel_key, entry->accel_mods,
+ ahook->accel_group,
+ old_accel_key, old_accel_mods);
+ if (!was_in_call)
+ hook->flags &= ~G_HOOK_FLAG_IN_CALL;
+ if (need_destroy)
+ g_hook_destroy_link (entry->hooks, hook);
+ hook = g_hook_next_valid (entry->hooks, hook, hooks_may_recurse);
+ }
+ }
+ g_slist_free (replace_list);
+ g_slist_free (group_list);
+ g_slist_free (win_list);
+
+ return change_accel;
+}
+
+/**
+ * gtk_accel_map_change_entry
+ * @accel_path: valid accelerator path
+ * @accel_key: new accelerator key
+ * @accel_mods: new accelerator modifiers
+ * @replace: %TRUE if other accelerators may be deleted upon conflicts
+ * @returns: %TRUE if the accelerator could be changed, %FALSE otherwise
+ *
+ * Change the @accel_key and @accel_mods currently associated with @accel_path.
+ * Due to conflicts with other accelerators, a change may not alwys be possible,
+ * @replace indicates whether other accelerators may be deleted to resolve such
+ * conflicts. A change will only occour if all conflicts could be resolved (which
+ * might not be the case if conflicting accelerators are locked). Succesfull
+ * changes are indicated by a %TRUE return value.
+ * Changes occouring are also indicated by invocation of notifiers attached to
+ * @accel_path (see gtk_accel_map_add_notifer() on this) and other accelerators
+ * that are being deleted.
+ */
+gboolean
+gtk_accel_map_change_entry (const gchar *accel_path,
+ guint accel_key,
+ GdkModifierType accel_mods,
+ gboolean replace)
+{
+ g_return_val_if_fail (gtk_accel_path_is_valid (accel_path), FALSE);
+
+ return internal_change_entry (accel_path, accel_key, accel_key ? accel_mods : 0, replace, FALSE);
+}
+
+static guint
+accel_map_parse_accel_path (GScanner *scanner)
+{
+ guint accel_key = 0, accel_mods = 0;
+ gchar *path, *accel;
+
+ /* parse accel path */
+ g_scanner_get_next_token (scanner);
+ if (scanner->token != G_TOKEN_STRING)
+ return G_TOKEN_STRING;
+
+ /* test if the next token is an accelerator */
+ g_scanner_peek_next_token (scanner);
+ if (scanner->next_token != G_TOKEN_STRING)
+ {
+ /* if not so, eat that token and error out */
+ g_scanner_get_next_token (scanner);
+ return G_TOKEN_STRING;
+ }
+
+ /* get the full accelerator specification */
+ path = g_strdup (scanner->value.v_string);
+ g_scanner_get_next_token (scanner);
+ accel = g_strdup (scanner->value.v_string);
+
+ /* ensure the entry is present */
+ gtk_accel_map_add_entry (path, 0, 0);
+
+ /* and propagate it */
+ gtk_accelerator_parse (accel, &accel_key, &accel_mods);
+ gtk_accel_map_change_entry (path, accel_key, accel_mods, TRUE);
+
+ g_free (accel);
+ g_free (path);
+
+ /* check correct statement end */
+ g_scanner_get_next_token (scanner);
+ if (scanner->token != ')')
+ return ')';
+ else
+ return G_TOKEN_NONE;
+}
+
+static void
+accel_map_parse_statement (GScanner *scanner)
+{
+ guint expected_token;
+
+ g_scanner_get_next_token (scanner);
+
+ if (scanner->token == G_TOKEN_SYMBOL)
+ {
+ guint (*parser_func) (GScanner*);
+
+ parser_func = scanner->value.v_symbol;
+
+ expected_token = parser_func (scanner);
+ }
+ else
+ expected_token = G_TOKEN_SYMBOL;
+
+ /* skip rest of statement on errrors
+ */
+ if (expected_token != G_TOKEN_NONE)
+ {
+ register guint level;
+
+ level = 1;
+ if (scanner->token == ')')
+ level--;
+ if (scanner->token == '(')
+ level++;
+
+ while (!g_scanner_eof (scanner) && level > 0)
+ {
+ g_scanner_get_next_token (scanner);
+
+ if (scanner->token == '(')
+ level++;
+ else if (scanner->token == ')')
+ level--;
+ }
+ }
+}
+
+void
+gtk_accel_map_load_scanner (GScanner *scanner)
+{
+ gboolean skip_comment_single;
+ gboolean symbol_2_token;
+ gchar *cpair_comment_single;
+ gpointer saved_symbol;
+
+ g_return_if_fail (scanner != 0);
+
+ /* configure scanner */
+ skip_comment_single = scanner->config->skip_comment_single;
+ scanner->config->skip_comment_single = TRUE;
+ cpair_comment_single = scanner->config->cpair_comment_single;
+ scanner->config->cpair_comment_single = ";\n";
+ symbol_2_token = scanner->config->symbol_2_token;
+ scanner->config->symbol_2_token = FALSE;
+ saved_symbol = g_scanner_lookup_symbol (scanner, "gtk_accel_path");
+ g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", accel_map_parse_accel_path);
+
+ /* outer parsing loop
+ */
+ g_scanner_peek_next_token (scanner);
+ while (scanner->next_token == '(')
+ {
+ g_scanner_get_next_token (scanner);
+
+ accel_map_parse_statement (scanner);
+
+ g_scanner_peek_next_token (scanner);
+ }
+
+ /* restore config */
+ scanner->config->skip_comment_single = skip_comment_single;
+ scanner->config->cpair_comment_single = cpair_comment_single;
+ scanner->config->symbol_2_token = symbol_2_token;
+ g_scanner_scope_remove_symbol (scanner, 0, "gtk_accel_path");
+ if (saved_symbol)
+ g_scanner_scope_add_symbol (scanner, 0, "gtk_accel_path", saved_symbol);
+}
+
+/**
+ * gtk_accel_map_load_fd
+ * @fd: valid readable file descriptor
+ *
+ * Filedescriptor variant of gtk_accel_map_load().
+ * Note that the file descriptor will not be closed by this function.
+ */
+void
+gtk_accel_map_load_fd (gint fd)
+{
+ GScanner *scanner;
+
+ g_return_if_fail (fd >= 0);
+
+ /* create and setup scanner */
+ scanner = g_scanner_new (NULL);
+ g_scanner_input_file (scanner, fd);
+
+ gtk_accel_map_load_scanner (scanner);
+
+ g_scanner_destroy (scanner);
+}
+
+/**
+ * gtk_accel_map_load
+ * @file_name: a file containing accelerator specifications
+ *
+ * Parses a file previously saved with gtk_accel_map_save() for
+ * accelerator specifications, and propagates them accordingly.
+ */
+void
+gtk_accel_map_load (const gchar *file_name)
+{
+ gint fd;
+
+ g_return_if_fail (file_name != NULL);
+
+ if (!g_file_test (file_name, G_FILE_TEST_IS_REGULAR))
+ return;
+
+ fd = open (file_name, O_RDONLY);
+ if (fd < 0)
+ return;
+
+ gtk_accel_map_load_fd (fd);
+
+ close (fd);
+}
+
+static void
+accel_map_print (gpointer data,
+ const gchar *accel_path,
+ guint accel_key,
+ guint accel_mods,
+ gboolean changed)
+{
+ GString *gstring = g_string_new (changed ? NULL : "; ");
+ gint err, fd = GPOINTER_TO_INT (data);
+ gchar *tmp, *name;
+
+ g_string_append (gstring, "(gtk_accel_path \"");
+
+ tmp = g_strescape (accel_path, NULL);
+ g_string_append (gstring, tmp);
+ g_free (tmp);
+
+ g_string_append (gstring, "\" \"");
+
+ name = gtk_accelerator_name (accel_key, accel_mods);
+ tmp = g_strescape (name, NULL);
+ g_free (name);
+ g_string_append (gstring, tmp);
+ g_free (tmp);
+
+ g_string_append (gstring, "\")\n");
+
+ do
+ err = write (fd, gstring->str, gstring->len);
+ while (err < 0 && errno == EINTR);
+
+ g_string_free (gstring, TRUE);
+}
+
+/**
+ * gtk_accel_map_save_fd
+ * @fd: valid writable file descriptor
+ *
+ * Filedescriptor variant of gtk_accel_map_save().
+ * Note that the file descriptor will not be closed by this function.
+ */
+void
+gtk_accel_map_save_fd (gint fd)
+{
+ GString *gstring;
+ gint err;
+
+ g_return_if_fail (fd >= 0);
+
+ gstring = g_string_new ("; ");
+ if (g_get_prgname ())
+ g_string_append (gstring, g_get_prgname ());
+ g_string_append (gstring, " GtkAccelMap rc-file -*- scheme -*-\n");
+ g_string_append (gstring, "; this file is an automated accelerator map dump\n");
+ g_string_append (gstring, ";\n");
+
+ do
+ err = write (fd, gstring->str, gstring->len);
+ while (err < 0 && errno == EINTR);
+
+ gtk_accel_map_foreach (GINT_TO_POINTER (fd), accel_map_print);
+}
+
+/**
+ * gtk_accel_map_save
+ * @file_name: the file to contain accelerator specifications
+ *
+ * Saves current accelerator specifications (accelerator path, key
+ * and modifiers) to @file_name.
+ * The file is written in a format suitable to be read back in by
+ * gtk_accel_map_load().
+ */
+void
+gtk_accel_map_save (const gchar *file_name)
+{
+ gint fd;
+
+ g_return_if_fail (file_name != NULL);
+
+ fd = open (file_name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ if (fd < 0)
+ return;
+
+ gtk_accel_map_save_fd (fd);
+
+ close (fd);
+}
+
+/**
+ * gtk_accel_map_foreach
+ * @data: data to be passed into @foreach_func
+ * @foreach_func: function to be executed for each accel map entry
+ *
+ * Loop over the entries in the accelerator map, and execute
+ * @foreach_func on each. The signature of @foreach_func is that of
+ * #GtkAccelMapForeach, the @changed parameter indicates whether
+ * this accelerator was changed during runtime (thus, would need
+ * saving during an accelerator map dump).
+ */
+void
+gtk_accel_map_foreach (gpointer data,
+ GtkAccelMapForeach foreach_func)
+{
+ GSList *entries, *slist, *node;
+
+ g_return_if_fail (foreach_func != NULL);
+
+ entries = g_hash_table_slist_values (accel_entry_ht);
+ for (slist = entries; slist; slist = slist->next)
+ {
+ AccelEntry *entry = slist->data;
+ gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
+
+ for (node = accel_filters; node; node = node->next)
+ if (g_pattern_match_string (node->data, entry->accel_path))
+ goto skip_accel;
+ foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
+ skip_accel:
+ }
+ g_slist_free (entries);
+}
+
+void
+gtk_accel_map_foreach_unfiltered (gpointer data,
+ GtkAccelMapForeach foreach_func)
+{
+ GSList *entries, *slist;
+
+ g_return_if_fail (foreach_func != NULL);
+
+ entries = g_hash_table_slist_values (accel_entry_ht);
+ for (slist = entries; slist; slist = slist->next)
+ {
+ AccelEntry *entry = slist->data;
+ gboolean changed = entry->accel_key != entry->std_accel_key || entry->accel_mods != entry->std_accel_mods;
+
+ foreach_func (data, entry->accel_path, entry->accel_key, entry->accel_mods, changed);
+ }
+ g_slist_free (entries);
+}
+
+void
+gtk_accel_map_add_filter (const gchar *filter_pattern)
+{
+ GPatternSpec *pspec;
+ GSList *slist;
+
+ g_return_if_fail (filter_pattern != NULL);
+
+ pspec = g_pattern_spec_new (filter_pattern);
+ for (slist = accel_filters; slist; slist = slist->next)
+ if (g_pattern_spec_equal (pspec, slist->data))
+ {
+ g_pattern_spec_free (pspec);
+ return;
+ }
+ accel_filters = g_slist_prepend (accel_filters, pspec);
+}