From cc15b73336f4d01aa6997a31accf9644b36e4759 Mon Sep 17 00:00:00 2001 From: Rodrigo Moya Date: Thu, 9 Dec 2010 14:27:54 +0100 Subject: [PATCH] keyboard: Match with previous functionality --- panels/keyboard/gnome-keyboard-panel.c | 1138 ------------------------ panels/keyboard/keyboard-shortcuts.c | 1070 +++++++++++++++++++++- 2 files changed, 1023 insertions(+), 1185 deletions(-) diff --git a/panels/keyboard/gnome-keyboard-panel.c b/panels/keyboard/gnome-keyboard-panel.c index ac5c383ee..bf6aa2adb 100644 --- a/panels/keyboard/gnome-keyboard-panel.c +++ b/panels/keyboard/gnome-keyboard-panel.c @@ -15,239 +15,9 @@ #include "gnome-keyboard-panel.h" #define MAX_ELEMENTS_BEFORE_SCROLLING 10 -#define MAX_CUSTOM_SHORTCUTS 1000 #define RESPONSE_ADD 0 #define RESPONSE_REMOVE 1 -static gboolean block_accels = FALSE; -static GHashTable *keyb_sections = NULL; - -static char* -binding_name (guint keyval, - guint keycode, - EggVirtualModifierType mask, - gboolean translate) -{ - if (keyval != 0 || keycode != 0) - return translate ? - egg_virtual_accelerator_label (keyval, keycode, mask) : - egg_virtual_accelerator_name (keyval, keycode, mask); - else - return g_strdup (translate ? _("Disabled") : ""); -} - -static gboolean -keybinding_key_changed_foreach (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) -{ - KeyEntry *key_entry; - KeyEntry *tmp_key_entry; - - key_entry = (KeyEntry *)user_data; - gtk_tree_model_get (key_entry->model, iter, - KEYENTRY_COLUMN, &tmp_key_entry, - -1); - - if (key_entry == tmp_key_entry) - { - gtk_tree_model_row_changed (key_entry->model, path, iter); - return TRUE; - } - return FALSE; -} - -static void -keybinding_key_changed (GConfClient *client, - guint cnxn_id, - GConfEntry *entry, - gpointer user_data) -{ - KeyEntry *key_entry; - const gchar *key_value; - - key_entry = (KeyEntry *) user_data; - key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; - - binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); - key_entry->editable = gconf_entry_get_is_writable (entry); - - /* update the model */ - gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); -} - -static void -keybinding_description_changed (GConfClient *client, - guint cnxn_id, - GConfEntry *entry, - gpointer user_data) -{ - KeyEntry *key_entry; - const gchar *key_value; - - key_entry = (KeyEntry *) user_data; - key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; - - g_free (key_entry->description); - key_entry->description = key_value ? g_strdup (key_value) : NULL; - key_entry->desc_editable = gconf_entry_get_is_writable (entry); - - /* update the model */ - gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); -} - -static void -keybinding_command_changed (GConfClient *client, - guint cnxn_id, - GConfEntry *entry, - gpointer user_data) -{ - KeyEntry *key_entry; - const gchar *key_value; - - key_entry = (KeyEntry *) user_data; - key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; - - g_free (key_entry->command); - key_entry->command = key_value ? g_strdup (key_value) : NULL; - key_entry->cmd_editable = gconf_entry_get_is_writable (entry); - - /* update the model */ - gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); -} - -static int -keyentry_sort_func (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer user_data) -{ - KeyEntry *key_entry_a; - KeyEntry *key_entry_b; - int retval; - - key_entry_a = NULL; - gtk_tree_model_get (model, a, - KEYENTRY_COLUMN, &key_entry_a, - -1); - - key_entry_b = NULL; - gtk_tree_model_get (model, b, - KEYENTRY_COLUMN, &key_entry_b, - -1); - - if (key_entry_a && key_entry_b) - { - if ((key_entry_a->keyval || key_entry_a->keycode) && - (key_entry_b->keyval || key_entry_b->keycode)) - { - gchar *name_a, *name_b; - - name_a = binding_name (key_entry_a->keyval, - key_entry_a->keycode, - key_entry_a->mask, - TRUE); - - name_b = binding_name (key_entry_b->keyval, - key_entry_b->keycode, - key_entry_b->mask, - TRUE); - - retval = g_utf8_collate (name_a, name_b); - - g_free (name_a); - g_free (name_b); - } - else if (key_entry_a->keyval || key_entry_a->keycode) - retval = -1; - else if (key_entry_b->keyval || key_entry_b->keycode) - retval = 1; - else - retval = 0; - } - else if (key_entry_a) - retval = -1; - else if (key_entry_b) - retval = 1; - else - retval = 0; - - return retval; -} - -static void -clear_old_model (GtkBuilder *builder) -{ - GtkWidget *tree_view; - GtkWidget *actions_swindow; - GtkTreeModel *model; - - tree_view = WID (builder, "shortcut_treeview"); - model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); - - if (model == NULL) - { - /* create a new model */ - model = (GtkTreeModel *) gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER); - - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model), - KEYENTRY_COLUMN, - keyentry_sort_func, - NULL, NULL); - - gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model); - - g_object_unref (model); - } - else - { - /* clear the existing model */ - GConfClient *client; - gboolean valid; - GtkTreeIter iter; - KeyEntry *key_entry; - - client = gconf_client_get_default (); - /* we need the schema name below; - * cached values do not have that set, though */ - gconf_client_clear_cache (client); - - for (valid = gtk_tree_model_get_iter_first (model, &iter); - valid; - valid = gtk_tree_model_iter_next (model, &iter)) - { - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - if (key_entry != NULL) - { - gconf_client_remove_dir (client, key_entry->gconf_key, NULL); - gconf_client_notify_remove (client, key_entry->gconf_cnxn); - if (key_entry->gconf_cnxn_desc != 0) - gconf_client_notify_remove (client, key_entry->gconf_cnxn_desc); - if (key_entry->gconf_cnxn_cmd != 0) - gconf_client_notify_remove (client, key_entry->gconf_cnxn_cmd); - g_free (key_entry->gconf_key); - g_free (key_entry->description); - g_free (key_entry->desc_gconf_key); - g_free (key_entry->command); - g_free (key_entry->cmd_gconf_key); - g_free (key_entry); - } - } - - gtk_tree_store_clear (GTK_TREE_STORE (model)); - g_object_unref (client); - } - - actions_swindow = WID (builder, "actions_swindow"); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (actions_swindow), - GTK_POLICY_NEVER, GTK_POLICY_NEVER); - gtk_widget_set_size_request (actions_swindow, -1, -1); -} - typedef struct { const char *key; gboolean found; @@ -311,911 +81,3 @@ ensure_scrollbar (GtkBuilder *builder, int i) GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); } } - -static void -find_section (GtkTreeModel *model, - GtkTreeIter *iter, - const char *title) -{ - gboolean success; - - success = gtk_tree_model_get_iter_first (model, iter); - while (success) - { - char *description = NULL; - - gtk_tree_model_get (model, iter, - DESCRIPTION_COLUMN, &description, - -1); - - if (g_strcmp0 (description, title) == 0) - return; - success = gtk_tree_model_iter_next (model, iter); - } - - gtk_tree_store_append (GTK_TREE_STORE (model), iter, NULL); - gtk_tree_store_set (GTK_TREE_STORE (model), iter, - DESCRIPTION_COLUMN, title, - -1); -} - -static void -append_keys_to_tree (GtkBuilder *builder, - const gchar *title, - const KeyListEntry *keys_list) -{ - GtkTreeIter parent_iter, iter; - GtkTreeModel *model; - gint i, j; - - /* Try to find a section parent iter, if it already exists */ - find_section (model, &iter, title); - parent_iter = iter; - - i = 0; - gtk_tree_model_foreach (model, count_rows_foreach, &i); - - /* If the header we just added is the MAX_ELEMENTS_BEFORE_SCROLLING th, - * then we need to scroll now */ - ensure_scrollbar (builder, i - 1); - -=============================================================== - - /* Don't show an empty section */ - if (gtk_tree_model_iter_n_children (model, &parent_iter) == 0) - gtk_tree_store_remove (GTK_TREE_STORE (model), &parent_iter); - - if (i == 0) - gtk_widget_hide (WID (builder, "shortcuts_vbox")); - else - gtk_widget_show (WID (builder, "shortcuts_vbox")); -} - -static void -key_entry_controlling_key_changed (GConfClient *client, - guint cnxn_id, - GConfEntry *entry, - gpointer user_data) -{ - reload_key_entries (user_data); -} - -static gboolean -cb_check_for_uniqueness (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - KeyEntry *new_key) -{ - KeyEntry *element; - - gtk_tree_model_get (new_key->model, iter, - KEYENTRY_COLUMN, &element, - -1); - - /* no conflict for : blanks, different modifiers, or ourselves */ - if (element == NULL || new_key->mask != element->mask || - !strcmp (new_key->gconf_key, element->gconf_key)) - return FALSE; - - if (new_key->keyval != 0) { - if (new_key->keyval != element->keyval) - return FALSE; - } else if (element->keyval != 0 || new_key->keycode != element->keycode) - return FALSE; - - new_key->editable = FALSE; - new_key->gconf_key = element->gconf_key; - new_key->description = element->description; - new_key->desc_gconf_key = element->desc_gconf_key; - new_key->desc_editable = element->desc_editable; - return TRUE; -} - -static const guint forbidden_keyvals[] = { - /* Navigation keys */ - GDK_KEY_Home, - GDK_KEY_Left, - GDK_KEY_Up, - GDK_KEY_Right, - GDK_KEY_Down, - GDK_KEY_Page_Up, - GDK_KEY_Page_Down, - GDK_KEY_End, - GDK_KEY_Tab, - - /* Return */ - GDK_KEY_KP_Enter, - GDK_KEY_Return, - - GDK_KEY_space, - GDK_KEY_Mode_switch -}; - -static gboolean -keyval_is_forbidden (guint keyval) -{ - guint i; - - for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) { - if (keyval == forbidden_keyvals[i]) - return TRUE; - } - - return FALSE; -} - -static void -show_error (GtkWindow *parent, - GError *err) -{ - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (parent, - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_OK, - _("Error saving the new shortcut")); - - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - "%s", err->message); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -static void -accel_edited_callback (GtkCellRendererText *cell, - const char *path_string, - guint keyval, - EggVirtualModifierType mask, - guint keycode, - gpointer data) -{ - GConfClient *client; - GtkTreeView *view = (GtkTreeView *)data; - GtkTreeModel *model; - GtkTreePath *path = gtk_tree_path_new_from_string (path_string); - GtkTreeIter iter; - KeyEntry *key_entry, tmp_key; - GError *err = NULL; - char *str; - - block_accels = FALSE; - - model = gtk_tree_view_get_model (view); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_path_free (path); - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - /* sanity check */ - if (key_entry == NULL) - return; - - /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ - mask &= ~EGG_VIRTUAL_LOCK_MASK; - - tmp_key.model = model; - tmp_key.keyval = keyval; - tmp_key.keycode = keycode; - tmp_key.mask = mask; - tmp_key.gconf_key = key_entry->gconf_key; - tmp_key.description = NULL; - tmp_key.editable = TRUE; /* kludge to stuff in a return flag */ - - if (keyval != 0 || keycode != 0) /* any number of keys can be disabled */ - gtk_tree_model_foreach (model, - (GtkTreeModelForeachFunc) cb_check_for_uniqueness, - &tmp_key); - - /* Check for unmodified keys */ - if (tmp_key.mask == 0 && tmp_key.keycode != 0) - { - if ((tmp_key.keyval >= GDK_KEY_a && tmp_key.keyval <= GDK_KEY_z) - || (tmp_key.keyval >= GDK_KEY_A && tmp_key.keyval <= GDK_KEY_Z) - || (tmp_key.keyval >= GDK_KEY_0 && tmp_key.keyval <= GDK_KEY_9) - || (tmp_key.keyval >= GDK_KEY_kana_fullstop && tmp_key.keyval <= GDK_KEY_semivoicedsound) - || (tmp_key.keyval >= GDK_KEY_Arabic_comma && tmp_key.keyval <= GDK_KEY_Arabic_sukun) - || (tmp_key.keyval >= GDK_KEY_Serbian_dje && tmp_key.keyval <= GDK_KEY_Cyrillic_HARDSIGN) - || (tmp_key.keyval >= GDK_KEY_Greek_ALPHAaccent && tmp_key.keyval <= GDK_KEY_Greek_omega) - || (tmp_key.keyval >= GDK_KEY_hebrew_doublelowline && tmp_key.keyval <= GDK_KEY_hebrew_taf) - || (tmp_key.keyval >= GDK_KEY_Thai_kokai && tmp_key.keyval <= GDK_KEY_Thai_lekkao) - || (tmp_key.keyval >= GDK_KEY_Hangul && tmp_key.keyval <= GDK_KEY_Hangul_Special) - || (tmp_key.keyval >= GDK_KEY_Hangul_Kiyeog && tmp_key.keyval <= GDK_KEY_Hangul_J_YeorinHieuh) - || keyval_is_forbidden (tmp_key.keyval)) { - GtkWidget *dialog; - char *name; - - name = binding_name (keyval, keycode, mask, TRUE); - - dialog = - gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CANCEL, - _("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n" - "Please try with a key such as Control, Alt or Shift at the same time."), - name); - - g_free (name); - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - - /* set it back to its previous value. */ - egg_cell_renderer_keys_set_accelerator - (EGG_CELL_RENDERER_KEYS (cell), - key_entry->keyval, key_entry->keycode, key_entry->mask); - return; - } - } - - /* flag to see if the new accelerator was in use by something */ - if (!tmp_key.editable) - { - GtkWidget *dialog; - char *name; - int response; - - name = binding_name (keyval, keycode, mask, TRUE); - - dialog = - gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CANCEL, - _("The shortcut \"%s\" is already used for\n\"%s\""), - name, tmp_key.description ? - tmp_key.description : tmp_key.gconf_key); - g_free (name); - - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - _("If you reassign the shortcut to \"%s\", the \"%s\" shortcut " - "will be disabled."), - key_entry->description ? - key_entry->description : key_entry->gconf_key, - tmp_key.description ? - tmp_key.description : tmp_key.gconf_key); - - gtk_dialog_add_button (GTK_DIALOG (dialog), - _("_Reassign"), - GTK_RESPONSE_ACCEPT); - - gtk_dialog_set_default_response (GTK_DIALOG (dialog), - GTK_RESPONSE_ACCEPT); - - response = gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); - - if (response == GTK_RESPONSE_ACCEPT) - { - GConfClient *client; - - client = gconf_client_get_default (); - - gconf_client_set_string (client, - tmp_key.gconf_key, - "", &err); - - if (err != NULL) - { - show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), - err); - g_error_free (err); - g_object_unref (client); - return; - } - - str = binding_name (keyval, keycode, mask, FALSE); - gconf_client_set_string (client, - key_entry->gconf_key, - str, &err); - - if (err != NULL) - { - show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), - err); - g_error_free (err); - - /* reset the previous shortcut */ - gconf_client_set_string (client, - tmp_key.gconf_key, - str, NULL); - } - - g_free (str); - g_object_unref (client); - } - else - { - /* set it back to its previous value. */ - egg_cell_renderer_keys_set_accelerator (EGG_CELL_RENDERER_KEYS (cell), - key_entry->keyval, - key_entry->keycode, - key_entry->mask); - } - - return; - } - - str = binding_name (keyval, keycode, mask, FALSE); - - client = gconf_client_get_default (); - gconf_client_set_string (client, - key_entry->gconf_key, - str, - &err); - g_free (str); - g_object_unref (client); - - if (err != NULL) - { - show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), err); - g_error_free (err); - key_entry->editable = FALSE; - } -} - -static void -accel_cleared_callback (GtkCellRendererText *cell, - const char *path_string, - gpointer data) -{ - GConfClient *client; - GtkTreeView *view = (GtkTreeView *) data; - GtkTreePath *path = gtk_tree_path_new_from_string (path_string); - KeyEntry *key_entry; - GtkTreeIter iter; - GError *err = NULL; - GtkTreeModel *model; - - block_accels = FALSE; - - model = gtk_tree_view_get_model (view); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_path_free (path); - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - /* sanity check */ - if (key_entry == NULL) - return; - - /* Unset the key */ - client = gconf_client_get_default(); - gconf_client_set_string (client, - key_entry->gconf_key, - "", - &err); - g_object_unref (client); - - if (err != NULL) - { - GtkWidget *dialog; - - dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_OK, - _("Error unsetting accelerator in configuration database: %s"), - err->message); - gtk_dialog_run (GTK_DIALOG (dialog)); - - gtk_widget_destroy (dialog); - g_error_free (err); - key_entry->editable = FALSE; - } -} - -static void -description_edited_callback (GtkCellRendererText *renderer, - gchar *path_string, - gchar *new_text, - gpointer user_data) -{ - GConfClient *client; - GtkTreeView *view = GTK_TREE_VIEW (user_data); - GtkTreeModel *model; - GtkTreePath *path = gtk_tree_path_new_from_string (path_string); - GtkTreeIter iter; - KeyEntry *key_entry; - - model = gtk_tree_view_get_model (view); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_path_free (path); - - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - /* sanity check */ - if (key_entry == NULL || key_entry->desc_gconf_key == NULL) - return; - - client = gconf_client_get_default (); - if (!gconf_client_set_string (client, key_entry->desc_gconf_key, new_text, NULL)) - key_entry->desc_editable = FALSE; - - g_object_unref (client); -} - - -typedef struct -{ - GtkTreeView *tree_view; - GtkTreePath *path; - GtkTreeViewColumn *column; -} IdleData; - -static gboolean -real_start_editing_cb (IdleData *idle_data) -{ - gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view)); - gtk_tree_view_set_cursor (idle_data->tree_view, - idle_data->path, - idle_data->column, - TRUE); - gtk_tree_path_free (idle_data->path); - g_free (idle_data); - return FALSE; -} - -static gboolean -edit_custom_shortcut (KeyEntry *key) -{ - gint result; - const gchar *text; - gboolean ret; - - gtk_entry_set_text (GTK_ENTRY (custom_shortcut_name_entry), key->description ? key->description : ""); - gtk_widget_set_sensitive (custom_shortcut_name_entry, key->desc_editable); - gtk_widget_grab_focus (custom_shortcut_name_entry); - gtk_entry_set_text (GTK_ENTRY (custom_shortcut_command_entry), key->command ? key->command : ""); - gtk_widget_set_sensitive (custom_shortcut_command_entry, key->cmd_editable); - - gtk_window_present (GTK_WINDOW (custom_shortcut_dialog)); - result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog)); - switch (result) - { - case GTK_RESPONSE_OK: - text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_name_entry)); - g_free (key->description); - key->description = g_strdup (text); - text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_command_entry)); - g_free (key->command); - key->command = g_strdup (text); - ret = TRUE; - break; - default: - ret = FALSE; - break; - } - - gtk_widget_hide (custom_shortcut_dialog); - - return ret; -} - -static gboolean -remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) -{ - GtkTreeIter parent; - GConfClient *client; - gchar *base; - KeyEntry *key; - - gtk_tree_model_get (model, iter, - KEYENTRY_COLUMN, &key, - -1); - - /* not a custom shortcut */ - if (key->command == NULL) - return FALSE; - - client = gconf_client_get_default (); - - gconf_client_notify_remove (client, key->gconf_cnxn); - if (key->gconf_cnxn_desc != 0) - gconf_client_notify_remove (client, key->gconf_cnxn_desc); - if (key->gconf_cnxn_cmd != 0) - gconf_client_notify_remove (client, key->gconf_cnxn_cmd); - - base = g_path_get_dirname (key->gconf_key); - gconf_client_recursive_unset (client, base, 0, NULL); - g_free (base); - /* suggest sync now so the unset directory actually gets dropped; - * if we don't do this we may end up with 'zombie' shortcuts when - * restarting the app */ - gconf_client_suggest_sync (client, NULL); - g_object_unref (client); - - g_free (key->gconf_key); - g_free (key->description); - g_free (key->desc_gconf_key); - g_free (key->command); - g_free (key->cmd_gconf_key); - g_free (key); - - gtk_tree_model_iter_parent (model, &parent, iter); - gtk_tree_store_remove (GTK_TREE_STORE (model), iter); - if (!gtk_tree_model_iter_has_child (model, &parent)) - gtk_tree_store_remove (GTK_TREE_STORE (model), &parent); - - return TRUE; -} - -static void -update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) -{ - KeyEntry *key; - - gtk_tree_model_get (model, iter, - KEYENTRY_COLUMN, &key, - -1); - - edit_custom_shortcut (key); - if (key->command == NULL || key->command[0] == '\0') - { - remove_custom_shortcut (model, iter); - } - else - { - GConfClient *client; - - gtk_tree_store_set (GTK_TREE_STORE (model), iter, - KEYENTRY_COLUMN, key, -1); - client = gconf_client_get_default (); - if (key->description != NULL) - gconf_client_set_string (client, key->desc_gconf_key, key->description, NULL); - else - gconf_client_unset (client, key->desc_gconf_key, NULL); - gconf_client_set_string (client, key->cmd_gconf_key, key->command, NULL); - g_object_unref (client); - } -} - -static gchar * -find_free_gconf_key (GError **error) -{ - GConfClient *client; - - gchar *dir; - int i; - - client = gconf_client_get_default (); - - for (i = 0; i < MAX_CUSTOM_SHORTCUTS; i++) - { - dir = g_strdup_printf ("%s/custom%d", GCONF_BINDING_DIR, i); - if (!gconf_client_dir_exists (client, dir, NULL)) - break; - g_free (dir); - } - - if (i == MAX_CUSTOM_SHORTCUTS) - { - dir = NULL; - g_set_error_literal (error, - g_quark_from_string ("Keyboard Shortcuts"), - 0, - _("Too many custom shortcuts")); - } - - g_object_unref (client); - - return dir; -} - -static void -add_custom_shortcut (GtkTreeView *tree_view, - GtkTreeModel *model) -{ - KeyEntry *key_entry; - GtkTreeIter iter; - GtkTreeIter parent_iter; - GtkTreePath *path; - gchar *dir; - GConfClient *client; - GError *error; - - error = NULL; - dir = find_free_gconf_key (&error); - if (dir == NULL) - { - show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree_view))), error); - - g_error_free (error); - return; - } - - key_entry = g_new0 (KeyEntry, 1); - key_entry->gconf_key = g_strconcat (dir, "/binding", NULL); - key_entry->editable = TRUE; - key_entry->model = model; - key_entry->desc_gconf_key = g_strconcat (dir, "/name", NULL); - key_entry->description = g_strdup (""); - key_entry->desc_editable = TRUE; - key_entry->cmd_gconf_key = g_strconcat (dir, "/action", NULL); - key_entry->command = g_strdup (""); - key_entry->cmd_editable = TRUE; - g_free (dir); - - if (edit_custom_shortcut (key_entry) && - key_entry->command && key_entry->command[0]) - { - find_section (model, &iter, _("Custom Shortcuts")); - parent_iter = iter; - gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter); - gtk_tree_store_set (GTK_TREE_STORE (model), &iter, KEYENTRY_COLUMN, key_entry, -1); - - /* store in gconf */ - client = gconf_client_get_default (); - gconf_client_set_string (client, key_entry->gconf_key, "", NULL); - gconf_client_set_string (client, key_entry->desc_gconf_key, key_entry->description, NULL); - gconf_client_set_string (client, key_entry->cmd_gconf_key, key_entry->command, NULL); - - /* add gconf watches */ - key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, - key_entry->desc_gconf_key, - (GConfClientNotifyFunc) &keybinding_description_changed, - key_entry, NULL, NULL); - key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, - key_entry->cmd_gconf_key, - (GConfClientNotifyFunc) &keybinding_command_changed, - key_entry, NULL, NULL); - key_entry->gconf_cnxn = gconf_client_notify_add (client, - key_entry->gconf_key, - (GConfClientNotifyFunc) &keybinding_key_changed, - key_entry, NULL, NULL); - - - g_object_unref (client); - - /* make the new shortcut visible */ - path = gtk_tree_model_get_path (model, &iter); - gtk_tree_view_expand_to_path (tree_view, path); - gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0); - gtk_tree_path_free (path); - } - else - { - g_free (key_entry->gconf_key); - g_free (key_entry->description); - g_free (key_entry->desc_gconf_key); - g_free (key_entry->command); - g_free (key_entry->cmd_gconf_key); - g_free (key_entry); - } -} - -static void -start_editing_kb_cb (GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data) -{ - GtkTreeModel *model; - GtkTreeIter iter; - KeyEntry *key; - - model = gtk_tree_view_get_model (treeview); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key, - -1); - - if (key == NULL) - { - /* This is a section heading - expand or collapse */ - if (gtk_tree_view_row_expanded (treeview, path)) - gtk_tree_view_collapse_row (treeview, path); - else - gtk_tree_view_expand_row (treeview, path, FALSE); - return; - } - - /* if only the accel can be edited on the selected row - * always select the accel column */ - if (key->desc_editable && - column == gtk_tree_view_get_column (treeview, 0)) - { - gtk_widget_grab_focus (GTK_WIDGET (treeview)); - gtk_tree_view_set_cursor (treeview, path, - gtk_tree_view_get_column (treeview, 0), - FALSE); - update_custom_shortcut (model, &iter); - } - else - { - gtk_widget_grab_focus (GTK_WIDGET (treeview)); - gtk_tree_view_set_cursor (treeview, - path, - gtk_tree_view_get_column (treeview, 1), - TRUE); - } -} - -static gboolean -start_editing_cb (GtkTreeView *tree_view, - GdkEventButton *event, - gpointer user_data) -{ - GtkTreePath *path; - GtkTreeViewColumn *column; - - if (event->window != gtk_tree_view_get_bin_window (tree_view)) - return FALSE; - - if (gtk_tree_view_get_path_at_pos (tree_view, - (gint) event->x, - (gint) event->y, - &path, &column, - NULL, NULL)) - { - IdleData *idle_data; - GtkTreeModel *model; - GtkTreeIter iter; - KeyEntry *key; - - if (gtk_tree_path_get_depth (path) == 1) - { - gtk_tree_path_free (path); - return FALSE; - } - - model = gtk_tree_view_get_model (tree_view); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, - KEYENTRY_COLUMN, &key, - -1); - - /* if only the accel can be edited on the selected row - * always select the accel column */ - if (key->desc_editable && - column == gtk_tree_view_get_column (tree_view, 0)) - { - gtk_widget_grab_focus (GTK_WIDGET (tree_view)); - gtk_tree_view_set_cursor (tree_view, path, - gtk_tree_view_get_column (tree_view, 0), - FALSE); - update_custom_shortcut (model, &iter); - } - else - { - idle_data = g_new (IdleData, 1); - idle_data->tree_view = tree_view; - idle_data->path = path; - idle_data->column = key->desc_editable ? column : - gtk_tree_view_get_column (tree_view, 1); - g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data); - block_accels = TRUE; - } - g_signal_stop_emission_by_name (tree_view, "button_press_event"); - } - return TRUE; -} - -/* this handler is used to keep accels from activating while the user - * is assigning a new shortcut so that he won't accidentally trigger one - * of the widgets */ -static gboolean -maybe_block_accels (GtkWidget *widget, - GdkEventKey *event, - gpointer user_data) -{ - if (block_accels) - { - return gtk_window_propagate_key_event (GTK_WINDOW (widget), event); - } - return FALSE; -} - -#if 0 -static void -cb_dialog_response (GtkWidget *widget, gint response_id, gpointer data) -{ - GtkBuilder *builder = data; - GtkTreeView *treeview; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeIter iter; - - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - model = gtk_tree_view_get_model (treeview); - - if (response_id == GTK_RESPONSE_HELP) - { - capplet_help (GTK_WINDOW (widget), - "goscustdesk-39"); - } - else if (response_id == RESPONSE_ADD) - { - add_custom_shortcut (treeview, model); - } - else if (response_id == RESPONSE_REMOVE) - { - selection = gtk_tree_view_get_selection (treeview); - if (gtk_tree_selection_get_selected (selection, NULL, &iter)) - { - remove_custom_shortcut (model, &iter); - } - } - else - { - clear_old_model (builder); - gtk_main_quit (); - } -} -#endif - -static void -add_button_clicked (GtkWidget *button, - GtkBuilder *builder) -{ - GtkTreeView *treeview; - GtkTreeModel *model; - - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - model = gtk_tree_view_get_model (treeview); - - add_custom_shortcut (treeview, model); -} - -static void -remove_button_clicked (GtkWidget *button, - GtkBuilder *builder) -{ - GtkTreeView *treeview; - GtkTreeModel *model; - GtkTreeSelection *selection; - GtkTreeIter iter; - - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - model = gtk_tree_view_get_model (treeview); - - selection = gtk_tree_view_get_selection (treeview); - if (gtk_tree_selection_get_selected (selection, NULL, &iter)) - { - remove_custom_shortcut (model, &iter); - } -} - -static void -on_window_manager_change (const char *wm_name, GtkBuilder *builder) -{ - reload_key_entries (builder); -} - -void -gnome_keybinding_properties_init (CcPanel *panel, GtkBuilder *builder) -{ - wm_common_register_window_manager_change ((GFunc) on_window_manager_change, - builder); - -} - -void -gnome_keybinding_properties_dispose (CcPanel *panel) -{ - if (maybe_block_accels_id != 0) - { - CcShell *shell; - GtkWidget *toplevel; - - shell = cc_panel_get_shell (CC_PANEL (panel)); - toplevel = cc_shell_get_toplevel (shell); - - g_signal_handler_disconnect (toplevel, maybe_block_accels_id); - maybe_block_accels_id = 0; - - if (keyb_sections != NULL) - g_hash_table_destroy (keyb_sections); - } -} - -/* - * vim: sw=2 ts=8 cindent noai bs=2 - */ diff --git a/panels/keyboard/keyboard-shortcuts.c b/panels/keyboard/keyboard-shortcuts.c index 8f603569d..86dc43335 100644 --- a/panels/keyboard/keyboard-shortcuts.c +++ b/panels/keyboard/keyboard-shortcuts.c @@ -25,6 +25,7 @@ #include "keyboard-shortcuts.h" #include "wm-common.h" +#define MAX_CUSTOM_SHORTCUTS 1000 #define GCONF_BINDING_DIR "/desktop/gnome/keybindings" #define WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name))) @@ -82,6 +83,7 @@ enum }; static guint maybe_block_accels_id = 0; +static gboolean block_accels = FALSE; static GtkWidget *custom_shortcut_dialog = NULL; static GtkWidget *custom_shortcut_name_entry = NULL; static GtkWidget *custom_shortcut_command_entry = NULL; @@ -96,14 +98,30 @@ free_key_array (GPtrArray *keys) for (i = 0; i < keys->len; i++) { - KeyEntry *entry; + KeyEntry *key_entry; + GConfClient *client; - entry = g_ptr_array_index (keys, i); - g_free (entry->gconf_key); - g_free (entry->description); - g_free (entry->desc_gconf_key); - g_free (entry->command); - g_free (entry->cmd_gconf_key); + key_entry = g_ptr_array_index (keys, i); + + /* Remove GConf watches */ + client = gconf_client_get_default (); + gconf_client_remove_dir (client, key_entry->gconf_key, NULL); + gconf_client_notify_remove (client, key_entry->gconf_cnxn); + if (key_entry->gconf_cnxn_desc != 0) + gconf_client_notify_remove (client, key_entry->gconf_cnxn_desc); + if (key_entry->gconf_cnxn_cmd != 0) + gconf_client_notify_remove (client, key_entry->gconf_cnxn_cmd); + + g_object_unref (client); + + /* Free memory */ + g_free (key_entry->gconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_gconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_gconf_key); + + g_free (key_entry); } g_ptr_array_free (keys, TRUE); @@ -171,6 +189,87 @@ binding_from_string (const char *str, return TRUE; } +static gboolean +keybinding_key_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + KeyEntry *key_entry; + KeyEntry *tmp_key_entry; + + key_entry = (KeyEntry *)user_data; + gtk_tree_model_get (key_entry->model, iter, + KEYENTRY_COLUMN, &tmp_key_entry, + -1); + + if (key_entry == tmp_key_entry) + { + gtk_tree_model_row_changed (key_entry->model, path, iter); + return TRUE; + } + return FALSE; +} + +static void +keybinding_description_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + g_free (key_entry->description); + key_entry->description = key_value ? g_strdup (key_value) : NULL; + key_entry->desc_editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); +} + +static void +keybinding_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); + key_entry->editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); +} + +static void +keybinding_command_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + g_free (key_entry->command); + key_entry->command = key_value ? g_strdup (key_value) : NULL; + key_entry->cmd_editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); +} + static void append_section (GtkBuilder *builder, const gchar *title, const KeyListEntry *keys_list) { @@ -258,26 +357,26 @@ append_section (GtkBuilder *builder, const gchar *title, const KeyListEntry *key { key_entry->desc_gconf_key = g_strdup (keys_list[i].description_name); key_entry->desc_editable = gconf_client_key_is_writable (client, key_entry->desc_gconf_key, NULL); - /* key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, */ - /* key_entry->desc_gconf_key, */ - /* (GConfClientNotifyFunc) &keybinding_description_changed, */ - /* key_entry, NULL, NULL); */ + key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, + key_entry->desc_gconf_key, + (GConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); } if (keys_list[i].cmd_name != NULL) { key_entry->cmd_gconf_key = g_strdup (keys_list[i].cmd_name); key_entry->cmd_editable = gconf_client_key_is_writable (client, key_entry->cmd_gconf_key, NULL); - /* key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, */ - /* key_entry->cmd_gconf_key, */ - /* (GConfClientNotifyFunc) &keybinding_command_changed, */ - /* key_entry, NULL, NULL); */ + key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, + key_entry->cmd_gconf_key, + (GConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); } gconf_client_add_dir (client, key_string, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); - /* key_entry->gconf_cnxn = gconf_client_notify_add (client, */ - /* key_string, */ - /* (GConfClientNotifyFunc) &keybinding_key_changed, */ - /* key_entry, NULL, NULL); */ + key_entry->gconf_cnxn = gconf_client_notify_add (client, + key_string, + (GConfClientNotifyFunc) &keybinding_key_changed, + key_entry, NULL, NULL); key_value = gconf_client_get_string (client, key_string, NULL); binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); @@ -285,8 +384,6 @@ append_section (GtkBuilder *builder, const gchar *title, const KeyListEntry *key gconf_entry_free (entry); - g_print ("Adding %s to section %s\n", key_entry->description, title); - g_ptr_array_add (keys_array, key_entry); } @@ -707,9 +804,6 @@ section_selection_changed (GtkTreeSelection *selection, gpointer data) GtkTreeIter new_row; KeyEntry *entry = g_ptr_array_index (keys, i); - g_print ("Adding gconf: %s, keyval = %d\n", - entry->gconf_key, entry->keyval); - gtk_list_store_append (GTK_LIST_STORE (shortcut_model), &new_row); gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row, DESCRIPTION_COLUMN, entry->description, @@ -739,6 +833,859 @@ shortcut_selection_changed (GtkTreeSelection *selection, gpointer data) gtk_widget_set_sensitive (button, can_remove); } +typedef struct +{ + GtkTreeView *tree_view; + GtkTreePath *path; + GtkTreeViewColumn *column; +} IdleData; + +static gboolean +edit_custom_shortcut (KeyEntry *key) +{ + gint result; + const gchar *text; + gboolean ret; + + gtk_entry_set_text (GTK_ENTRY (custom_shortcut_name_entry), key->description ? key->description : ""); + gtk_widget_set_sensitive (custom_shortcut_name_entry, key->desc_editable); + gtk_widget_grab_focus (custom_shortcut_name_entry); + gtk_entry_set_text (GTK_ENTRY (custom_shortcut_command_entry), key->command ? key->command : ""); + gtk_widget_set_sensitive (custom_shortcut_command_entry, key->cmd_editable); + + gtk_window_present (GTK_WINDOW (custom_shortcut_dialog)); + result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog)); + switch (result) + { + case GTK_RESPONSE_OK: + text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_name_entry)); + g_free (key->description); + key->description = g_strdup (text); + text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_command_entry)); + g_free (key->command); + key->command = g_strdup (text); + ret = TRUE; + break; + default: + ret = FALSE; + break; + } + + gtk_widget_hide (custom_shortcut_dialog); + + return ret; +} + +static gboolean +remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) +{ + GtkTreeIter parent; + GConfClient *client; + gchar *base; + KeyEntry *key; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key, + -1); + + /* not a custom shortcut */ + if (key->command == NULL) + return FALSE; + + client = gconf_client_get_default (); + + gconf_client_notify_remove (client, key->gconf_cnxn); + if (key->gconf_cnxn_desc != 0) + gconf_client_notify_remove (client, key->gconf_cnxn_desc); + if (key->gconf_cnxn_cmd != 0) + gconf_client_notify_remove (client, key->gconf_cnxn_cmd); + + base = g_path_get_dirname (key->gconf_key); + gconf_client_recursive_unset (client, base, 0, NULL); + g_free (base); + /* suggest sync now so the unset directory actually gets dropped; + * if we don't do this we may end up with 'zombie' shortcuts when + * restarting the app */ + gconf_client_suggest_sync (client, NULL); + g_object_unref (client); + + g_free (key->gconf_key); + g_free (key->description); + g_free (key->desc_gconf_key); + g_free (key->command); + g_free (key->cmd_gconf_key); + g_free (key); + + gtk_tree_model_iter_parent (model, &parent, iter); + gtk_tree_store_remove (GTK_TREE_STORE (model), iter); + if (!gtk_tree_model_iter_has_child (model, &parent)) + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent); + + return TRUE; +} + +static void +update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) +{ + KeyEntry *key; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key, + -1); + + edit_custom_shortcut (key); + if (key->command == NULL || key->command[0] == '\0') + { + remove_custom_shortcut (model, iter); + } + else + { + GConfClient *client; + + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + KEYENTRY_COLUMN, key, -1); + client = gconf_client_get_default (); + if (key->description != NULL) + gconf_client_set_string (client, key->desc_gconf_key, key->description, NULL); + else + gconf_client_unset (client, key->desc_gconf_key, NULL); + gconf_client_set_string (client, key->cmd_gconf_key, key->command, NULL); + g_object_unref (client); + } +} + +static gboolean +real_start_editing_cb (IdleData *idle_data) +{ + gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view)); + gtk_tree_view_set_cursor (idle_data->tree_view, + idle_data->path, + idle_data->column, + TRUE); + gtk_tree_path_free (idle_data->path); + g_free (idle_data); + return FALSE; +} + +static gboolean +start_editing_cb (GtkTreeView *tree_view, + GdkEventButton *event, + gpointer user_data) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + + if (event->window != gtk_tree_view_get_bin_window (tree_view)) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (tree_view, + (gint) event->x, + (gint) event->y, + &path, &column, + NULL, NULL)) + { + IdleData *idle_data; + GtkTreeModel *model; + GtkTreeIter iter; + KeyEntry *key; + + if (gtk_tree_path_get_depth (path) == 1) + { + gtk_tree_path_free (path); + return FALSE; + } + + model = gtk_tree_view_get_model (tree_view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key, + -1); + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (key->desc_editable && + column == gtk_tree_view_get_column (tree_view, 0)) + { + gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + gtk_tree_view_set_cursor (tree_view, path, + gtk_tree_view_get_column (tree_view, 0), + FALSE); + update_custom_shortcut (model, &iter); + } + else + { + idle_data = g_new (IdleData, 1); + idle_data->tree_view = tree_view; + idle_data->path = path; + idle_data->column = key->desc_editable ? column : + gtk_tree_view_get_column (tree_view, 1); + g_idle_add ((GSourceFunc) real_start_editing_cb, idle_data); + block_accels = TRUE; + } + g_signal_stop_emission_by_name (tree_view, "button_press_event"); + } + return TRUE; +} + +static void +start_editing_kb_cb (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + KeyEntry *key; + + model = gtk_tree_view_get_model (treeview); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key, + -1); + + if (key == NULL) + { + /* This is a section heading - expand or collapse */ + if (gtk_tree_view_row_expanded (treeview, path)) + gtk_tree_view_collapse_row (treeview, path); + else + gtk_tree_view_expand_row (treeview, path, FALSE); + return; + } + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (key->desc_editable && + column == gtk_tree_view_get_column (treeview, 0)) + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, path, + gtk_tree_view_get_column (treeview, 0), + FALSE); + update_custom_shortcut (model, &iter); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, + path, + gtk_tree_view_get_column (treeview, 1), + TRUE); + } +} + +static void +description_edited_callback (GtkCellRendererText *renderer, + gchar *path_string, + gchar *new_text, + gpointer user_data) +{ + GConfClient *client; + GtkTreeView *view = GTK_TREE_VIEW (user_data); + GtkTreeModel *model; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + KeyEntry *key_entry; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL || key_entry->desc_gconf_key == NULL) + return; + + client = gconf_client_get_default (); + if (!gconf_client_set_string (client, key_entry->desc_gconf_key, new_text, NULL)) + key_entry->desc_editable = FALSE; + + g_object_unref (client); +} + +static const guint forbidden_keyvals[] = { + /* Navigation keys */ + GDK_KEY_Home, + GDK_KEY_Left, + GDK_KEY_Up, + GDK_KEY_Right, + GDK_KEY_Down, + GDK_KEY_Page_Up, + GDK_KEY_Page_Down, + GDK_KEY_End, + GDK_KEY_Tab, + + /* Return */ + GDK_KEY_KP_Enter, + GDK_KEY_Return, + + GDK_KEY_space, + GDK_KEY_Mode_switch +}; + +static char* +binding_name (guint keyval, + guint keycode, + EggVirtualModifierType mask, + gboolean translate) +{ + if (keyval != 0 || keycode != 0) + return translate ? + egg_virtual_accelerator_label (keyval, keycode, mask) : + egg_virtual_accelerator_name (keyval, keycode, mask); + else + return g_strdup (translate ? _("Disabled") : ""); +} + +static gboolean +keyval_is_forbidden (guint keyval) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) { + if (keyval == forbidden_keyvals[i]) + return TRUE; + } + + return FALSE; +} + +static void +show_error (GtkWindow *parent, + GError *err) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + _("Error saving the new shortcut")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +static gboolean +cb_check_for_uniqueness (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + KeyEntry *new_key) +{ + KeyEntry *element; + + gtk_tree_model_get (new_key->model, iter, + KEYENTRY_COLUMN, &element, + -1); + + /* no conflict for : blanks, different modifiers, or ourselves */ + if (element == NULL || new_key->mask != element->mask || + !strcmp (new_key->gconf_key, element->gconf_key)) + return FALSE; + + if (new_key->keyval != 0) { + if (new_key->keyval != element->keyval) + return FALSE; + } else if (element->keyval != 0 || new_key->keycode != element->keycode) + return FALSE; + + new_key->editable = FALSE; + new_key->gconf_key = element->gconf_key; + new_key->description = element->description; + new_key->desc_gconf_key = element->desc_gconf_key; + new_key->desc_editable = element->desc_editable; + return TRUE; +} + +static void +accel_edited_callback (GtkCellRendererText *cell, + const char *path_string, + guint keyval, + EggVirtualModifierType mask, + guint keycode, + gpointer data) +{ + GConfClient *client; + GtkTreeView *view = (GtkTreeView *)data; + GtkTreeModel *model; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + KeyEntry *key_entry, tmp_key; + GError *err = NULL; + char *str; + + block_accels = FALSE; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL) + return; + + /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ + mask &= ~EGG_VIRTUAL_LOCK_MASK; + + tmp_key.model = model; + tmp_key.keyval = keyval; + tmp_key.keycode = keycode; + tmp_key.mask = mask; + tmp_key.gconf_key = key_entry->gconf_key; + tmp_key.description = NULL; + tmp_key.editable = TRUE; /* kludge to stuff in a return flag */ + + if (keyval != 0 || keycode != 0) /* any number of keys can be disabled */ + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) cb_check_for_uniqueness, + &tmp_key); + + /* Check for unmodified keys */ + if (tmp_key.mask == 0 && tmp_key.keycode != 0) + { + if ((tmp_key.keyval >= GDK_KEY_a && tmp_key.keyval <= GDK_KEY_z) + || (tmp_key.keyval >= GDK_KEY_A && tmp_key.keyval <= GDK_KEY_Z) + || (tmp_key.keyval >= GDK_KEY_0 && tmp_key.keyval <= GDK_KEY_9) + || (tmp_key.keyval >= GDK_KEY_kana_fullstop && tmp_key.keyval <= GDK_KEY_semivoicedsound) + || (tmp_key.keyval >= GDK_KEY_Arabic_comma && tmp_key.keyval <= GDK_KEY_Arabic_sukun) + || (tmp_key.keyval >= GDK_KEY_Serbian_dje && tmp_key.keyval <= GDK_KEY_Cyrillic_HARDSIGN) + || (tmp_key.keyval >= GDK_KEY_Greek_ALPHAaccent && tmp_key.keyval <= GDK_KEY_Greek_omega) + || (tmp_key.keyval >= GDK_KEY_hebrew_doublelowline && tmp_key.keyval <= GDK_KEY_hebrew_taf) + || (tmp_key.keyval >= GDK_KEY_Thai_kokai && tmp_key.keyval <= GDK_KEY_Thai_lekkao) + || (tmp_key.keyval >= GDK_KEY_Hangul && tmp_key.keyval <= GDK_KEY_Hangul_Special) + || (tmp_key.keyval >= GDK_KEY_Hangul_Kiyeog && tmp_key.keyval <= GDK_KEY_Hangul_J_YeorinHieuh) + || keyval_is_forbidden (tmp_key.keyval)) { + GtkWidget *dialog; + char *name; + + name = binding_name (keyval, keycode, mask, TRUE); + + dialog = + gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n" + "Please try with a key such as Control, Alt or Shift at the same time."), + name); + + g_free (name); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + /* set it back to its previous value. */ + egg_cell_renderer_keys_set_accelerator + (EGG_CELL_RENDERER_KEYS (cell), + key_entry->keyval, key_entry->keycode, key_entry->mask); + return; + } + } + + /* flag to see if the new accelerator was in use by something */ + if (!tmp_key.editable) + { + GtkWidget *dialog; + char *name; + int response; + + name = binding_name (keyval, keycode, mask, TRUE); + + dialog = + gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut \"%s\" is already used for\n\"%s\""), + name, tmp_key.description ? + tmp_key.description : tmp_key.gconf_key); + g_free (name); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("If you reassign the shortcut to \"%s\", the \"%s\" shortcut " + "will be disabled."), + key_entry->description ? + key_entry->description : key_entry->gconf_key, + tmp_key.description ? + tmp_key.description : tmp_key.gconf_key); + + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Reassign"), + GTK_RESPONSE_ACCEPT); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + if (response == GTK_RESPONSE_ACCEPT) + { + GConfClient *client; + + client = gconf_client_get_default (); + + gconf_client_set_string (client, + tmp_key.gconf_key, + "", &err); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + err); + g_error_free (err); + g_object_unref (client); + return; + } + + str = binding_name (keyval, keycode, mask, FALSE); + gconf_client_set_string (client, + key_entry->gconf_key, + str, &err); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + err); + g_error_free (err); + + /* reset the previous shortcut */ + gconf_client_set_string (client, + tmp_key.gconf_key, + str, NULL); + } + + g_free (str); + g_object_unref (client); + } + else + { + /* set it back to its previous value. */ + egg_cell_renderer_keys_set_accelerator (EGG_CELL_RENDERER_KEYS (cell), + key_entry->keyval, + key_entry->keycode, + key_entry->mask); + } + + return; + } + + str = binding_name (keyval, keycode, mask, FALSE); + + client = gconf_client_get_default (); + gconf_client_set_string (client, + key_entry->gconf_key, + str, + &err); + g_free (str); + g_object_unref (client); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), err); + g_error_free (err); + key_entry->editable = FALSE; + } +} + +static void +accel_cleared_callback (GtkCellRendererText *cell, + const char *path_string, + gpointer data) +{ + GConfClient *client; + GtkTreeView *view = (GtkTreeView *) data; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + KeyEntry *key_entry; + GtkTreeIter iter; + GError *err = NULL; + GtkTreeModel *model; + + block_accels = FALSE; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL) + return; + + /* Unset the key */ + client = gconf_client_get_default(); + gconf_client_set_string (client, + key_entry->gconf_key, + "", + &err); + g_object_unref (client); + + if (err != NULL) + { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + _("Error unsetting accelerator in configuration database: %s"), + err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + g_error_free (err); + key_entry->editable = FALSE; + } +} + +static void +key_entry_controlling_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + reload_sections (user_data); +} + +/* this handler is used to keep accels from activating while the user + * is assigning a new shortcut so that he won't accidentally trigger one + * of the widgets */ +static gboolean +maybe_block_accels (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + if (block_accels) + { + return gtk_window_propagate_key_event (GTK_WINDOW (widget), event); + } + return FALSE; +} + +static gchar * +find_free_gconf_key (GError **error) +{ + GConfClient *client; + + gchar *dir; + int i; + + client = gconf_client_get_default (); + + for (i = 0; i < MAX_CUSTOM_SHORTCUTS; i++) + { + dir = g_strdup_printf ("%s/custom%d", GCONF_BINDING_DIR, i); + if (!gconf_client_dir_exists (client, dir, NULL)) + break; + g_free (dir); + } + + if (i == MAX_CUSTOM_SHORTCUTS) + { + dir = NULL; + g_set_error_literal (error, + g_quark_from_string ("Keyboard Shortcuts"), + 0, + _("Too many custom shortcuts")); + } + + g_object_unref (client); + + return dir; +} + +static void +add_custom_shortcut (GtkTreeView *tree_view, + GtkTreeModel *model) +{ + KeyEntry *key_entry; + GtkTreePath *path; + gchar *dir; + GConfClient *client; + GError *error; + + error = NULL; + dir = find_free_gconf_key (&error); + if (dir == NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree_view))), error); + + g_error_free (error); + return; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->gconf_key = g_strconcat (dir, "/binding", NULL); + key_entry->editable = TRUE; + key_entry->model = model; + key_entry->desc_gconf_key = g_strconcat (dir, "/name", NULL); + key_entry->description = g_strdup (""); + key_entry->desc_editable = TRUE; + key_entry->cmd_gconf_key = g_strconcat (dir, "/action", NULL); + key_entry->command = g_strdup (""); + key_entry->cmd_editable = TRUE; + g_free (dir); + + if (edit_custom_shortcut (key_entry) && + key_entry->command && key_entry->command[0]) + { + GPtrArray *keys_array; + GtkTreeIter iter; + + keys_array = g_hash_table_lookup (kb_sections, _("Custom Shortcuts")); + if (keys_array == NULL) + { + keys_array = g_ptr_array_new (); + g_hash_table_insert (kb_sections, g_strdup (_("Custom Shortcuts")), keys_array); + } + + g_ptr_array_add (keys_array, key_entry); + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, KEYENTRY_COLUMN, key_entry, -1); + + /* store in gconf */ + client = gconf_client_get_default (); + gconf_client_set_string (client, key_entry->gconf_key, "", NULL); + gconf_client_set_string (client, key_entry->desc_gconf_key, key_entry->description, NULL); + gconf_client_set_string (client, key_entry->cmd_gconf_key, key_entry->command, NULL); + + /* add gconf watches */ + key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, + key_entry->desc_gconf_key, + (GConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); + key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, + key_entry->cmd_gconf_key, + (GConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); + key_entry->gconf_cnxn = gconf_client_notify_add (client, + key_entry->gconf_key, + (GConfClientNotifyFunc) &keybinding_key_changed, + key_entry, NULL, NULL); + + + g_object_unref (client); + + /* make the new shortcut visible */ + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_expand_to_path (tree_view, path); + gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0); + gtk_tree_path_free (path); + } + else + { + g_free (key_entry->gconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_gconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_gconf_key); + g_free (key_entry); + } +} + +static void +add_button_clicked (GtkWidget *button, + GtkBuilder *builder) +{ + GtkTreeView *treeview; + GtkTreeModel *model; + + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, + "shortcut_treeview")); + model = gtk_tree_view_get_model (treeview); + + add_custom_shortcut (treeview, model); +} + +static void +remove_button_clicked (GtkWidget *button, + GtkBuilder *builder) +{ + GtkTreeView *treeview; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, + "shortcut_treeview")); + model = gtk_tree_view_get_model (treeview); + + selection = gtk_tree_view_get_selection (treeview); + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + remove_custom_shortcut (model, &iter); + } +} + +static int +keyentry_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + KeyEntry *key_entry_a; + KeyEntry *key_entry_b; + int retval; + + key_entry_a = NULL; + gtk_tree_model_get (model, a, + KEYENTRY_COLUMN, &key_entry_a, + -1); + + key_entry_b = NULL; + gtk_tree_model_get (model, b, + KEYENTRY_COLUMN, &key_entry_b, + -1); + + if (key_entry_a && key_entry_b) + { + if ((key_entry_a->keyval || key_entry_a->keycode) && + (key_entry_b->keyval || key_entry_b->keycode)) + { + gchar *name_a, *name_b; + + name_a = binding_name (key_entry_a->keyval, + key_entry_a->keycode, + key_entry_a->mask, + TRUE); + + name_b = binding_name (key_entry_b->keyval, + key_entry_b->keycode, + key_entry_b->mask, + TRUE); + + retval = g_utf8_collate (name_a, name_b); + + g_free (name_a); + g_free (name_b); + } + else if (key_entry_a->keyval || key_entry_a->keycode) + retval = -1; + else if (key_entry_b->keyval || key_entry_b->keycode) + retval = 1; + else + retval = 0; + } + else if (key_entry_a) + retval = -1; + else if (key_entry_b) + retval = 1; + else + retval = 0; + + return retval; +} + static void setup_dialog (CcPanel *panel, GtkBuilder *builder) { @@ -764,6 +1711,7 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) model = gtk_list_store_new (1, G_TYPE_STRING); gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model)); + g_object_unref (model); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); g_signal_connect (selection, "changed", @@ -775,16 +1723,16 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) client = gconf_client_get_default (); - /* g_signal_connect (treeview, "button_press_event", */ - /* G_CALLBACK (start_editing_cb), builder); */ - /* g_signal_connect (treeview, "row-activated", */ - /* G_CALLBACK (start_editing_kb_cb), NULL); */ + g_signal_connect (treeview, "button_press_event", + G_CALLBACK (start_editing_cb), builder); + g_signal_connect (treeview, "row-activated", + G_CALLBACK (start_editing_kb_cb), NULL); renderer = gtk_cell_renderer_text_new (); - /* g_signal_connect (renderer, "edited", */ - /* G_CALLBACK (description_edited_callback), */ - /* treeview); */ + g_signal_connect (renderer, "edited", + G_CALLBACK (description_edited_callback), + treeview); column = gtk_tree_view_column_new_with_attributes (_("Action"), renderer, @@ -800,13 +1748,13 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X, NULL); - /* g_signal_connect (renderer, "accel_edited", */ - /* G_CALLBACK (accel_edited_callback), */ - /* treeview); */ + g_signal_connect (renderer, "accel_edited", + G_CALLBACK (accel_edited_callback), + treeview); - /* g_signal_connect (renderer, "accel_cleared", */ - /* G_CALLBACK (accel_cleared_callback), */ - /* treeview); */ + g_signal_connect (renderer, "accel_cleared", + G_CALLBACK (accel_cleared_callback), + treeview); column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL); gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); @@ -817,20 +1765,25 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) gconf_client_add_dir (client, GCONF_BINDING_DIR, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); gconf_client_add_dir (client, "/apps/metacity/general", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); - /* gconf_client_notify_add (client, */ - /* "/apps/metacity/general/num_workspaces", */ - /* (GConfClientNotifyFunc) key_entry_controlling_key_changed, */ - /* builder, NULL, NULL); */ + gconf_client_notify_add (client, + "/apps/metacity/general/num_workspaces", + (GConfClientNotifyFunc) key_entry_controlling_key_changed, + builder, NULL, NULL); model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model), + KEYENTRY_COLUMN, + keyentry_sort_func, + NULL, NULL); gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model)); + g_object_unref (model); /* set up the dialog */ shell = cc_panel_get_shell (CC_PANEL (panel)); widget = cc_shell_get_toplevel (shell); - /* maybe_block_accels_id = g_signal_connect (widget, "key_press_event", */ - /* G_CALLBACK (maybe_block_accels), NULL); */ + maybe_block_accels_id = g_signal_connect (widget, "key_press_event", + G_CALLBACK (maybe_block_accels), NULL); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); g_signal_connect (selection, "changed", @@ -858,10 +1811,10 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) "custom-shortcut-name-entry"); custom_shortcut_command_entry = WID (builder, "custom-shortcut-command-entry"); - /* g_signal_connect (WID (builder, "add-button"), */ - /* "clicked", G_CALLBACK (add_button_clicked), builder); */ - /* g_signal_connect (WID (builder, "remove-button"), */ - /* "clicked", G_CALLBACK (remove_button_clicked), builder); */ + g_signal_connect (WID (builder, "add-button"), + "clicked", G_CALLBACK (add_button_clicked), builder); + g_signal_connect (WID (builder, "remove-button"), + "clicked", G_CALLBACK (remove_button_clicked), builder); gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog), GTK_RESPONSE_OK); @@ -870,11 +1823,20 @@ setup_dialog (CcPanel *panel, GtkBuilder *builder) GTK_WINDOW (widget)); } +static void +on_window_manager_change (const char *wm_name, GtkBuilder *builder) +{ + reload_sections (builder); +} + void keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder) { kb_sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) free_key_array); + + wm_common_register_window_manager_change ((GFunc) on_window_manager_change, + builder); setup_dialog (panel, builder); reload_sections (builder); } @@ -882,4 +1844,18 @@ keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder) void keyboard_shortcuts_dispose (CcPanel *panel) { + if (maybe_block_accels_id != 0) + { + CcShell *shell; + GtkWidget *toplevel; + + shell = cc_panel_get_shell (CC_PANEL (panel)); + toplevel = cc_shell_get_toplevel (shell); + + g_signal_handler_disconnect (toplevel, maybe_block_accels_id); + maybe_block_accels_id = 0; + + if (kb_sections != NULL) + g_hash_table_destroy (kb_sections); + } }