From 708e1a95749ad61cb0167f729a77e951a30388cf Mon Sep 17 00:00:00 2001 From: Owen Taylor Date: Thu, 21 Feb 2002 17:14:10 +0000 Subject: Implement "fuzzy" key binding lookups; allow matches on key and level but Wed Feb 20 14:26:47 2002 Owen Taylor * gtk/gtkkeyhash.[ch]: Implement "fuzzy" key binding lookups; allow matches on key and level but not group. Also, implement ignoring "consumed modifiers correctly." * gtk/gtkaccelgroup.c gtk/gtkbindings.c: Convert to using GtkKeyHash. * gtk/gtkdebug.h gtk/gtkmain.c: Support GTK_DEBUG=keybindings * gdk/x11/gdkevents-x11.c (gdk_event_translate): Fill in the group for key release events as well as key press events. * gdk/gdkkeys.h gdk/x11/gdkkeys-x11.c (gdk_keymap_translate_keyboard_state): Rename unused_modifiers to consumed_modifiers, make the docs and non-Xkb implementation match the Xkb implementation. * gdk/linux-fb/gdkkeyboard-fb.c gdk/win32/gdkkeys-win32.c: Propagate doc and parameter name changes. * gdk/x11/gdkkeys-x11.c (gdk_keymap_translate_keyboard_state): XkbTranslateKeyCode doesn't handle LockMask, we need to handle it ourselves. * gdk/x11/gdkkeys-x11.c (gdk_keymap_translate_keyboard_state): Force Tab to give GDK_ISO_Left_Tab, since we need consistency to allow dealing with ISO_Left_Tab. * gtk/gtkwindow.c gtk/gtktextview.c gtk/gtkscrolledwindow.c gtk/gtkpaned.c gtk/gtkcombo.c gtk/gtknotebook.c: Remove inappropriate uses of GDK_ISO_Left_Tab. (GDK_ISO_Left_Tab or Tab both are equivalent as a binding specifier.) * gtk/gtkbutton.c (gtk_button_class_init): Make ::activate GTK_RUN_ACTION, so you can bind an accelerator to it. * gtk/gtklabel.c (gtk_label_set_uline_text_internal): Call gdk_unicode_to_keyval on the mnemonic character. * tests/testgtk.c: Add a test for the new fuzzy key binding matching. --- gtk/gtkbindings.c | 219 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 172 insertions(+), 47 deletions(-) (limited to 'gtk/gtkbindings.c') diff --git a/gtk/gtkbindings.c b/gtk/gtkbindings.c index 676ff3ed8d..95ae875d87 100644 --- a/gtk/gtkbindings.c +++ b/gtk/gtkbindings.c @@ -29,7 +29,9 @@ #include #include +#include #include "gtkbindings.h" +#include "gtkkeyhash.h" #include "gtksignal.h" #include "gtkwidget.h" #include "gtkrc.h" @@ -49,6 +51,7 @@ typedef struct { /* --- variables --- */ static GHashTable *binding_entry_hash_table = NULL; +static GSList *binding_key_hashes = NULL; static GSList *binding_set_list = NULL; static const gchar *key_class_binding_set = "gtk-class-binding-set"; static GQuark key_id_class_binding_set = 0; @@ -107,11 +110,81 @@ binding_entries_compare (gconstpointer a, return (ea->keyval == eb->keyval && ea->modifiers == eb->modifiers); } +static void +binding_key_hash_insert_entry (GtkKeyHash *key_hash, + GtkBindingEntry *entry) +{ + guint keyval = entry->keyval; + + /* We store lowercased accelerators. To deal with this, if + * was specified, uppercase. + */ + if (entry->modifiers & GDK_SHIFT_MASK) + { + if (keyval == GDK_Tab) + keyval = GDK_ISO_Left_Tab; + else + keyval = gdk_keyval_to_upper (keyval); + } + + _gtk_key_hash_add_entry (key_hash, keyval, entry->modifiers & ~GDK_RELEASE_MASK, entry); +} + +static void +binding_key_hash_destroy (gpointer data) +{ + GtkKeyHash *key_hash = data; + + binding_key_hashes = g_slist_remove (binding_key_hashes, key_hash); + _gtk_key_hash_free (key_hash); +} + +static void +insert_entries_into_key_hash (gpointer key, + gpointer value, + gpointer data) +{ + GtkKeyHash *key_hash = data; + GtkBindingEntry *entry = value; + + for (; entry; entry = entry->hash_next) + binding_key_hash_insert_entry (key_hash, entry); +} + +static GtkKeyHash * +binding_key_hash_for_keymap (GdkKeymap *keymap) +{ + static GQuark key_hash_quark = 0; + GtkKeyHash *key_hash; + + if (!key_hash_quark) + key_hash_quark = g_quark_from_static_string ("gtk-binding-key-hash"); + + key_hash = g_object_get_qdata (G_OBJECT (keymap), key_hash_quark); + + if (!key_hash) + { + key_hash = _gtk_key_hash_new (keymap, NULL); + g_object_set_qdata_full (G_OBJECT (keymap), key_hash_quark, key_hash, binding_key_hash_destroy); + + if (binding_entry_hash_table) + g_hash_table_foreach (binding_entry_hash_table, + insert_entries_into_key_hash, + key_hash); + + binding_key_hashes = g_slist_prepend (binding_key_hashes, key_hash); + } + + return key_hash; +} + + static GtkBindingEntry* binding_entry_new (GtkBindingSet *binding_set, guint keyval, GdkModifierType modifiers) { + GSList *tmp_list; GtkBindingEntry *entry; if (!binding_entry_hash_table) @@ -132,6 +205,12 @@ binding_entry_new (GtkBindingSet *binding_set, if (entry->hash_next) g_hash_table_remove (binding_entry_hash_table, entry->hash_next); g_hash_table_insert (binding_entry_hash_table, entry, entry); + + for (tmp_list = binding_key_hashes; tmp_list; tmp_list = tmp_list->next) + { + GtkKeyHash *key_hash = tmp_list->data; + binding_key_hash_insert_entry (key_hash, entry); + } return entry; } @@ -167,6 +246,7 @@ binding_entry_destroy (GtkBindingEntry *entry) register GtkBindingEntry *tmp; GtkBindingEntry *begin; register GtkBindingEntry *last; + GSList *tmp_list; /* unlink from binding set */ @@ -214,27 +294,18 @@ binding_entry_destroy (GtkBindingEntry *entry) g_hash_table_insert (binding_entry_hash_table, begin, begin); } + for (tmp_list = binding_key_hashes; tmp_list; tmp_list = tmp_list->next) + { + GtkKeyHash *key_hash = tmp_list->data; + _gtk_key_hash_remove_entry (key_hash, entry); + } + entry->destroyed = TRUE; if (!entry->in_emission) binding_entry_free (entry); } -static GtkBindingEntry* -binding_ht_lookup_list (guint keyval, - GdkModifierType modifiers) -{ - GtkBindingEntry lookup_entry = { 0 }; - - if (!binding_entry_hash_table) - return NULL; - - lookup_entry.keyval = keyval; - lookup_entry.modifiers = modifiers; - - return g_hash_table_lookup (binding_entry_hash_table, &lookup_entry); -} - static GtkBindingEntry* binding_ht_lookup_entry (GtkBindingSet *set, guint keyval, @@ -835,7 +906,7 @@ gtk_binding_set_add_path (GtkBindingSet *binding_set, } } -static inline gboolean +static gboolean binding_match_activate (GSList *pspec_list, GtkObject *object, guint path_length, @@ -877,20 +948,25 @@ gtk_binding_pattern_compare (gconstpointer new_pattern, return np->seq_id < ep->seq_id; } -static inline GSList* -gtk_binding_entries_sort_patterns (GtkBindingEntry *entries, - GtkPathType path_id) +static GSList* +gtk_binding_entries_sort_patterns (GSList *entries, + GtkPathType path_id, + gboolean is_release) { GSList *patterns; patterns = NULL; - while (entries) + for (; entries; entries = entries->next) { - register GtkBindingSet *binding_set; + GtkBindingEntry *entry = entries->data; + GtkBindingSet *binding_set; GSList *slist = NULL; - binding_set = entries->binding_set; - binding_set->current = entries; + if (is_release != ((entry->modifiers & GDK_RELEASE_MASK) != 0)) + continue; + + binding_set = entry->binding_set; + binding_set->current = entry; switch (path_id) { @@ -912,35 +988,19 @@ gtk_binding_entries_sort_patterns (GtkBindingEntry *entries, pspec = slist->data; patterns = g_slist_insert_sorted (patterns, pspec, gtk_binding_pattern_compare); } - - entries = entries->hash_next; } return patterns; } - -gboolean -gtk_bindings_activate (GtkObject *object, - guint keyval, - GdkModifierType modifiers) +static gboolean +gtk_bindings_activate_list (GtkObject *object, + GSList *entries, + gboolean is_release) { - GtkBindingEntry *entries; - GtkWidget *widget; + GtkWidget *widget = GTK_WIDGET (object); gboolean handled = FALSE; - g_return_val_if_fail (GTK_IS_OBJECT (object), FALSE); - - if (!GTK_IS_WIDGET (object)) - return FALSE; - - widget = GTK_WIDGET (object); - - keyval = gdk_keyval_to_lower (keyval); - modifiers = modifiers & BINDING_MOD_MASK (); - - entries = binding_ht_lookup_list (keyval, modifiers); - if (!entries) return FALSE; @@ -951,7 +1011,7 @@ gtk_bindings_activate (GtkObject *object, GSList *patterns; gtk_widget_path (widget, &path_length, &path, &path_reversed); - patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_WIDGET); + patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_WIDGET, is_release); handled = binding_match_activate (patterns, object, path_length, path, path_reversed); g_slist_free (patterns); g_free (path); @@ -965,7 +1025,7 @@ gtk_bindings_activate (GtkObject *object, GSList *patterns; gtk_widget_class_path (widget, &path_length, &path, &path_reversed); - patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_WIDGET_CLASS); + patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_WIDGET_CLASS, is_release); handled = binding_match_activate (patterns, object, path_length, path, path_reversed); g_slist_free (patterns); g_free (path); @@ -977,7 +1037,7 @@ gtk_bindings_activate (GtkObject *object, GSList *patterns; GtkType class_type; - patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_CLASS); + patterns = gtk_binding_entries_sort_patterns (entries, GTK_PATH_CLASS, is_release); class_type = GTK_OBJECT_TYPE (object); while (class_type && !handled) { @@ -1000,6 +1060,71 @@ gtk_bindings_activate (GtkObject *object, return handled; } +gboolean +gtk_bindings_activate (GtkObject *object, + guint keyval, + GdkModifierType modifiers) +{ + GSList *entries = NULL; + GtkKeyHash *key_hash; + gboolean handled = FALSE; + gboolean is_release; + + g_return_val_if_fail (GTK_IS_OBJECT (object), FALSE); + + if (!GTK_IS_WIDGET (object)) + return FALSE; + + is_release = (BINDING_MOD_MASK () & GDK_RELEASE_MASK) != 0; + modifiers = modifiers & BINDING_MOD_MASK () & ~GDK_RELEASE_MASK; + + key_hash = binding_key_hash_for_keymap (gdk_keymap_get_default ()); + entries = _gtk_key_hash_lookup_keyval (key_hash, keyval, modifiers); + + handled = gtk_bindings_activate_list (object, entries, is_release); + + g_slist_free (entries); + + return handled; +} + +/** + * _gtk_bindings_activate_event: + * @object: a #GtkObject (generally must be a widget) + * @event: a #GdkEventKey + * + * Looks up key bindings for @object to find one matching + * @event, and if one was found, activate it. + * + * Return value: %TRUE if a matching key binding was found + **/ +gboolean +_gtk_bindings_activate_event (GtkObject *object, + GdkEventKey *event) +{ + GSList *entries = NULL; + GtkKeyHash *key_hash; + gboolean handled = FALSE; + + g_return_val_if_fail (GTK_IS_OBJECT (object), FALSE); + + if (!GTK_IS_WIDGET (object)) + return FALSE; + + key_hash = binding_key_hash_for_keymap (gdk_keymap_get_default ()); + entries = _gtk_key_hash_lookup (key_hash, + event->hardware_keycode, + event->state & BINDING_MOD_MASK () & ~GDK_RELEASE_MASK, + event->group); + + handled = gtk_bindings_activate_list (object, entries, + event->type == GDK_KEY_RELEASE); + + g_slist_free (entries); + + return handled; +} + static guint gtk_binding_parse_signal (GScanner *scanner, GtkBindingSet *binding_set, -- cgit v1.2.1