diff --git a/panels/keyboard/cc-keyboard-item.h b/panels/keyboard/cc-keyboard-item.h index 5d181876b..8792ba0af 100644 --- a/panels/keyboard/cc-keyboard-item.h +++ b/panels/keyboard/cc-keyboard-item.h @@ -21,6 +21,7 @@ #define __CC_KEYBOARD_ITEM_H #include +#include G_BEGIN_DECLS diff --git a/panels/keyboard/cc-keyboard-panel.c b/panels/keyboard/cc-keyboard-panel.c index 38fe4ba6d..992cfc8af 100644 --- a/panels/keyboard/cc-keyboard-panel.c +++ b/panels/keyboard/cc-keyboard-panel.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2010 Intel, Inc + * Copyright (C) 2016 Endless, Inc * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,21 +16,61 @@ * along with this program; if not, see . * * Author: Thomas Wood + * Georges Basile Stavracas Neto * */ +#include + +#include "cc-keyboard-item.h" +#include "cc-keyboard-option.h" #include "cc-keyboard-panel.h" #include "cc-keyboard-resources.h" #include "keyboard-shortcuts.h" +#include "wm-common.h" + +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys" +#define CUSTOM_SHORTCUTS_ID "custom" struct _CcKeyboardPanel { CcPanel parent; - GtkBuilder *builder; + /* Treeviews */ + GtkWidget *section_treeview; + GtkWidget *shortcut_treeview; + + /* Toolbar widgets */ + GtkWidget *add_toolbutton; + GtkWidget *remove_toolbutton; + GtkWidget *shortcut_toolbar; + + /* Custom shortcut dialog */ + GtkWidget *custom_shortcut_command_entry; + GtkWidget *custom_shortcut_dialog; + GtkWidget *custom_shortcut_name_entry; + GtkWidget *custom_shortcut_ok_button; + + GHashTable *kb_system_sections; + GHashTable *kb_apps_sections; + GHashTable *kb_user_sections; + + GSettings *binding_settings; + + GRegex *pictures_regex; + + gpointer wm_changed_id; + gchar *section_to_set; }; +static gboolean cc_keyboard_panel_set_section (CcKeyboardPanel *self, + const char *section); + CC_PANEL_REGISTER (CcKeyboardPanel, cc_keyboard_panel) enum { @@ -37,35 +78,1686 @@ enum { PROP_PARAMETERS }; -enum { - TYPING_PAGE, - SHORTCUTS_PAGE -}; +static GHashTable* +get_hash_for_group (CcKeyboardPanel *self, + BindingGroupType group) +{ + GHashTable *hash; + + switch (group) + { + case BINDING_GROUP_SYSTEM: + hash = self->kb_system_sections; + break; + case BINDING_GROUP_APPS: + hash = self->kb_apps_sections; + break; + case BINDING_GROUP_USER: + hash = self->kb_user_sections; + break; + default: + hash = NULL; + } + + return hash; +} + +static gboolean +have_key_for_group (CcKeyboardPanel *self, + int group, + const gchar *name) +{ + GHashTableIter iter; + GPtrArray *keys; + gint i; + + g_hash_table_iter_init (&iter, get_hash_for_group (self, group)); + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &keys)) + { + for (i = 0; i < keys->len; i++) + { + CcKeyboardItem *item = g_ptr_array_index (keys, i); + + if (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS && + g_strcmp0 (name, item->key) == 0) + { + return TRUE; + } + + return FALSE; + } + } + + return FALSE; +} static void -cc_keyboard_panel_set_page (CcKeyboardPanel *panel, - const gchar *page, - const gchar *section) +free_key_array (GPtrArray *keys) { - GtkWidget *notebook; - gint page_num; + if (keys != NULL) + { + gint i; - if (g_strcmp0 (page, "typing") == 0) - page_num = TYPING_PAGE; - else if (g_strcmp0 (page, "shortcuts") == 0) - page_num = SHORTCUTS_PAGE; - else { - g_warning ("Could not switch to non-existent page '%s'", page); + for (i = 0; i < keys->len; i++) + { + CcKeyboardItem *item; + + item = g_ptr_array_index (keys, i); + + g_object_unref (item); + } + + g_ptr_array_free (keys, TRUE); + } +} + +static char* +binding_name (guint keyval, + guint keycode, + GdkModifierType mask, + gboolean translate) +{ + if (keyval != 0 || keycode != 0) + { + return translate ? gtk_accelerator_get_label_with_keycode (NULL, keyval, keycode, mask) : + gtk_accelerator_name_with_keycode (NULL, keyval, keycode, mask); + } + else + { + return g_strdup (translate ? _("Disabled") : NULL); + } +} + + +static gboolean +keybinding_key_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CcKeyboardItem *item) +{ + CcKeyboardItem *tmp_item; + + gtk_tree_model_get (item->model, + iter, + DETAIL_KEYENTRY_COLUMN, &tmp_item, + -1); + + if (item == tmp_item) + { + gtk_tree_model_row_changed (item->model, path, iter); + return TRUE; + } + + return FALSE; +} + +static void +item_changed (CcKeyboardItem *item, + GParamSpec *pspec, + gpointer user_data) +{ + /* update the model */ + gtk_tree_model_foreach (item->model, + (GtkTreeModelForeachFunc) keybinding_key_changed_foreach, + item); +} + +static void +append_section (CcKeyboardPanel *self, + const gchar *title, + const gchar *id, + BindingGroupType group, + const KeyListEntry *keys_list) +{ + GtkTreeModel *sort_model; + GtkTreeModel *model, *shortcut_model; + GtkTreeIter iter; + GHashTable *reverse_items; + GHashTable *hash; + GPtrArray *keys_array; + gboolean is_new; + gint i; + + hash = get_hash_for_group (self, group); + + if (!hash) return; - } - notebook = GTK_WIDGET (gtk_builder_get_object (panel->builder, "keyboard_notebook")); - gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num); + sort_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->section_treeview)); + model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model)); - if (page_num == SHORTCUTS_PAGE && - section != NULL) { - keyboard_shortcuts_set_section (CC_PANEL (panel), section); - } + shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->shortcut_treeview)); + + /* Add all CcKeyboardItems for this section */ + is_new = FALSE; + keys_array = g_hash_table_lookup (hash, id); + if (keys_array == NULL) + { + keys_array = g_ptr_array_new (); + is_new = TRUE; + } + + reverse_items = g_hash_table_new (g_str_hash, g_str_equal); + + for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++) + { + CcKeyboardItem *item; + gboolean ret; + + if (have_key_for_group (self, group, keys_list[i].name)) + continue; + + item = cc_keyboard_item_new (keys_list[i].type); + switch (keys_list[i].type) + { + case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH: + ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE); + break; + + case CC_KEYBOARD_ITEM_TYPE_GSETTINGS: + ret = cc_keyboard_item_load_from_gsettings (item, + keys_list[i].description, + keys_list[i].schema, + keys_list[i].name); + if (ret && keys_list[i].reverse_entry != NULL) + { + CcKeyboardItem *reverse_item; + reverse_item = g_hash_table_lookup (reverse_items, + keys_list[i].reverse_entry); + if (reverse_item != NULL) + { + cc_keyboard_item_add_reverse_item (item, + reverse_item, + keys_list[i].is_reversed); + } + else + { + g_hash_table_insert (reverse_items, + keys_list[i].name, + item); + } + } + break; + + default: + g_assert_not_reached (); + } + + if (ret == FALSE) + { + /* We don't actually want to popup a dialog - just skip this one */ + g_object_unref (item); + continue; + } + + cc_keyboard_item_set_hidden (item, keys_list[i].hidden); + item->model = shortcut_model; + + g_signal_connect (G_OBJECT (item), + "notify", + G_CALLBACK (item_changed), + NULL); + + g_ptr_array_add (keys_array, item); + } + + g_hash_table_destroy (reverse_items); + + /* Add the keys to the hash table */ + if (is_new) + { + g_hash_table_insert (hash, g_strdup (id), keys_array); + + /* Append the section to the left tree view */ + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + SECTION_DESCRIPTION_COLUMN, title, + SECTION_ID_COLUMN, id, + SECTION_GROUP_COLUMN, group, + -1); + } +} + +static void +append_sections_from_file (CcKeyboardPanel *self, + const gchar *path, + const char *datadir, + gchar **wm_keybindings) +{ + KeyList *keylist; + KeyListEntry *keys; + KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 }; + const char *title; + int group; + guint i; + + keylist = parse_keylist_from_file (path); + + if (keylist == NULL) + return; + +#define const_strv(s) ((const gchar* const*) s) + + /* If there's no keys to add, or the settings apply to a window manager + * that's not the one we're running */ + if (keylist->entries->len == 0 || + (keylist->wm_name != NULL && !g_strv_contains (const_strv (wm_keybindings), keylist->wm_name)) || + keylist->name == NULL) + { + g_free (keylist->name); + g_free (keylist->package); + g_free (keylist->wm_name); + g_array_free (keylist->entries, TRUE); + g_free (keylist); + return; + } + +#undef const_strv + + /* Empty KeyListEntry to end the array */ + key.name = NULL; + g_array_append_val (keylist->entries, key); + + keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE); + if (keylist->package) + { + char *localedir; + + localedir = g_build_filename (datadir, "locale", NULL); + bindtextdomain (keylist->package, localedir); + g_free (localedir); + + title = dgettext (keylist->package, keylist->name); + } else { + title = _(keylist->name); + } + if (keylist->group && strcmp (keylist->group, "system") == 0) + group = BINDING_GROUP_SYSTEM; + else + group = BINDING_GROUP_APPS; + + append_section (self, title, keylist->name, group, keys); + + g_free (keylist->name); + g_free (keylist->package); + g_free (keylist->wm_name); + g_free (keylist->schema); + g_free (keylist->group); + + for (i = 0; keys[i].name != NULL; i++) + { + KeyListEntry *entry = &keys[i]; + g_free (entry->schema); + g_free (entry->description); + g_free (entry->name); + g_free (entry->reverse_entry); + } + + g_free (keylist); + g_free (keys); +} + +static void +append_sections_from_gsettings (CcKeyboardPanel *self) +{ + char **custom_paths; + GArray *entries; + KeyListEntry key = { 0, 0, 0, 0, 0, 0, 0 }; + int i; + + /* load custom shortcuts from GSettings */ + entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); + + custom_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings"); + for (i = 0; custom_paths[i]; i++) + { + key.name = g_strdup (custom_paths[i]); + if (!have_key_for_group (self, BINDING_GROUP_USER, key.name)) + { + key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH; + g_array_append_val (entries, key); + } + else + g_free (key.name); + } + g_strfreev (custom_paths); + + if (entries->len > 0) + { + KeyListEntry *keys; + int i; + + /* Empty KeyListEntry to end the array */ + key.name = NULL; + g_array_append_val (entries, key); + + keys = (KeyListEntry *) entries->data; + append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys); + for (i = 0; i < entries->len; ++i) + { + g_free (keys[i].name); + } + } + else + { + append_section (self, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL); + } + + g_array_free (entries, TRUE); +} + +static void +reload_sections (CcKeyboardPanel *self) +{ + GtkTreeSelection *selection; + GtkTreeModel *shortcut_model; + GtkTreeModel *section_model; + GtkTreeModel *sort_model; + GtkTreeView *section_treeview; + GtkTreeIter iter; + GHashTable *loaded_files; + GDir *dir; + gchar *default_wm_keybindings[] = { "Mutter", "GNOME Shell", NULL }; + gchar **wm_keybindings; + const gchar * const * data_dirs; + guint i; + + section_treeview = GTK_TREE_VIEW (self->section_treeview); + sort_model = gtk_tree_view_get_model (section_treeview); + section_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model)); + + shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->shortcut_treeview)); + /* FIXME: get current selection and keep it after refreshing */ + + /* Clear previous models and hash tables */ + gtk_list_store_clear (GTK_LIST_STORE (section_model)); + gtk_list_store_clear (GTK_LIST_STORE (shortcut_model)); + + g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy); + self->kb_system_sections = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) free_key_array); + + g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy); + self->kb_apps_sections = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) free_key_array); + + g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy); + self->kb_user_sections = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) free_key_array); + + /* Load WM keybindings */ +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) + wm_keybindings = wm_common_get_current_keybindings (); + else +#endif + wm_keybindings = g_strdupv (default_wm_keybindings); + + loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + data_dirs = g_get_system_data_dirs (); + for (i = 0; data_dirs[i] != NULL; i++) + { + char *dir_path; + const gchar *name; + + dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL); + + dir = g_dir_open (dir_path, 0, NULL); + if (!dir) + { + g_free (dir_path); + continue; + } + + for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir)) + { + gchar *path; + + if (g_str_has_suffix (name, ".xml") == FALSE) + continue; + + if (g_hash_table_lookup (loaded_files, name) != NULL) + { + g_debug ("Not loading %s, it was already loaded from another directory", name); + continue; + } + + g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1)); + path = g_build_filename (dir_path, name, NULL); + append_sections_from_file (self, path, data_dirs[i], wm_keybindings); + g_free (path); + } + g_free (dir_path); + g_dir_close (dir); + } + + g_hash_table_destroy (loaded_files); + g_strfreev (wm_keybindings); + + /* Add a separator */ + gtk_list_store_append (GTK_LIST_STORE (section_model), &iter); + gtk_list_store_set (GTK_LIST_STORE (section_model), &iter, + SECTION_DESCRIPTION_COLUMN, NULL, + SECTION_GROUP_COLUMN, BINDING_GROUP_SEPARATOR, + -1); + + /* Load custom keybindings */ + append_sections_from_gsettings (self); + + /* Select the first item, or the requested section, if any */ + if (self->section_to_set != NULL) + { + if (cc_keyboard_panel_set_section (self, self->section_to_set)) + { + g_clear_pointer (&self->section_to_set, g_free); + return; + } + } + g_assert (gtk_tree_model_get_iter_first (sort_model, &iter)); + selection = gtk_tree_view_get_selection (section_treeview); + gtk_tree_selection_select_iter (selection, &iter); +} + +static gboolean +sections_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + BindingGroupType type; + + gtk_tree_model_get (model, iter, SECTION_GROUP_COLUMN, &type, -1); + + return type == BINDING_GROUP_SEPARATOR; +} + +static int +section_sort_item (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer data) +{ + char *a_desc; + int a_group; + char *b_desc; + int b_group; + int ret; + + gtk_tree_model_get (model, a, + SECTION_DESCRIPTION_COLUMN, &a_desc, + SECTION_GROUP_COLUMN, &a_group, + -1); + gtk_tree_model_get (model, b, + SECTION_DESCRIPTION_COLUMN, &b_desc, + SECTION_GROUP_COLUMN, &b_group, + -1); + + if (a_group == b_group && a_desc && b_desc) + ret = g_utf8_collate (a_desc, b_desc); + else + ret = a_group - b_group; + + g_free (a_desc); + g_free (b_desc); + + return ret; +} + +static void +section_selection_changed (GtkTreeSelection *selection, + CcKeyboardPanel *self) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + BindingGroupType group; + GtkTreeModel *shortcut_model; + GPtrArray *keys; + gchar *id; + gint i; + + gtk_tree_model_get (model, &iter, + SECTION_ID_COLUMN, &id, + SECTION_GROUP_COLUMN, &group, -1); + + keys = g_hash_table_lookup (get_hash_for_group (self, group), id); + if (keys == NULL) + { + g_warning ("Can't find section %s in sections hash table.", id); + g_free (id); + return; + } + + gtk_widget_set_sensitive (self->remove_toolbutton, FALSE); + + /* Fill the shortcut treeview with the keys for the selected section */ + shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->shortcut_treeview)); + gtk_list_store_clear (GTK_LIST_STORE (shortcut_model)); + + for (i = 0; i < keys->len; i++) + { + GtkTreeIter new_row; + CcKeyboardItem *item = g_ptr_array_index (keys, i); + + if (!cc_keyboard_item_is_hidden (item)) + { + gtk_list_store_append (GTK_LIST_STORE (shortcut_model), &new_row); + gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row, + DETAIL_DESCRIPTION_COLUMN, item->description, + DETAIL_KEYENTRY_COLUMN, item, + DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_KEY_ENTRY, + -1); + } + } + + if (g_str_equal (id, "Typing")) + fill_xkb_options_shortcuts (shortcut_model); + + g_free (id); + } +} + +static void +description_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *description; + CcKeyboardItem *item; + ShortcutType type; + + gtk_tree_model_get (model, iter, + DETAIL_DESCRIPTION_COLUMN, &description, + DETAIL_KEYENTRY_COLUMN, &item, + DETAIL_TYPE_COLUMN, &type, + -1); + + if (type == SHORTCUT_TYPE_XKB_OPTION) + { + g_object_set (cell, "text", description, NULL); + } + else + { + if (item != NULL) + g_object_set (cell, + "editable", FALSE, + "text", item->description != NULL ? + item->description : _(""), + NULL); + else + g_object_set (cell, + "editable", FALSE, NULL); + } + + g_free (description); +} + + +static void +accel_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gpointer entry; + ShortcutType type; + + gtk_tree_model_get (model, iter, + DETAIL_KEYENTRY_COLUMN, &entry, + DETAIL_TYPE_COLUMN, &type, + -1); + + gtk_cell_renderer_set_visible (cell, FALSE); + + if (type == SHORTCUT_TYPE_XKB_OPTION && + GTK_IS_CELL_RENDERER_COMBO (cell)) + { + CcKeyboardOption *option = entry; + + gtk_cell_renderer_set_visible (cell, TRUE); + g_object_set (cell, + "model", cc_keyboard_option_get_store (option), + "text", cc_keyboard_option_get_current_value_description (option), + NULL); + } + else if (type == SHORTCUT_TYPE_KEY_ENTRY && + GTK_IS_CELL_RENDERER_TEXT (cell) && + !GTK_IS_CELL_RENDERER_COMBO (cell) && + entry != NULL) + { + CcKeyboardItem *item = entry; + + gtk_cell_renderer_set_visible (cell, TRUE); + + if (item->editable) + g_object_set (cell, + "editable", TRUE, + "accel-key", item->keyval, + "accel-mods", item->mask, + "keycode", item->keycode, + "style", PANGO_STYLE_NORMAL, + NULL); + else + g_object_set (cell, + "editable", FALSE, + "accel-key", item->keyval, + "accel-mods", item->mask, + "keycode", item->keycode, + "style", PANGO_STYLE_ITALIC, + NULL); + } +} + +static void +shortcut_selection_changed (GtkTreeSelection *selection, + GtkWidget *button) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gboolean can_remove; + + can_remove = FALSE; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + CcKeyboardItem *item; + ShortcutType type; + + gtk_tree_model_get (model, &iter, + DETAIL_KEYENTRY_COLUMN, &item, + DETAIL_TYPE_COLUMN, &type, + -1); + + if (type == SHORTCUT_TYPE_KEY_ENTRY && + item && + item->command != NULL && + item->editable) + { + can_remove = TRUE; + } + } + + gtk_widget_set_sensitive (button, can_remove); +} + + +static gboolean +edit_custom_shortcut (CcKeyboardPanel *self, + CcKeyboardItem *item) +{ + gint result; + gboolean ret; + GSettings *settings; + + settings = g_settings_new_with_path (item->schema, item->gsettings_path); + + g_settings_bind (settings, "name", + G_OBJECT (self->custom_shortcut_name_entry), "text", + G_SETTINGS_BIND_DEFAULT); + gtk_widget_grab_focus (self->custom_shortcut_name_entry); + + g_settings_bind (settings, "command", + G_OBJECT (self->custom_shortcut_command_entry), "text", + G_SETTINGS_BIND_DEFAULT); + + g_settings_delay (settings); + + gtk_widget_set_sensitive (self->custom_shortcut_name_entry, item->desc_editable); + gtk_widget_set_sensitive (self->custom_shortcut_command_entry, item->cmd_editable); + gtk_window_present (GTK_WINDOW (self->custom_shortcut_dialog)); + + result = gtk_dialog_run (GTK_DIALOG (self->custom_shortcut_dialog)); + switch (result) + { + case GTK_RESPONSE_OK: + g_settings_apply (settings); + ret = TRUE; + break; + default: + g_settings_revert (settings); + ret = FALSE; + break; + } + + g_settings_unbind (G_OBJECT (self->custom_shortcut_name_entry), "text"); + g_settings_unbind (G_OBJECT (self->custom_shortcut_command_entry), "text"); + + gtk_widget_hide (self->custom_shortcut_dialog); + + g_object_unref (settings); + + return ret; +} + +static gboolean +remove_custom_shortcut (CcKeyboardPanel *self, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + CcKeyboardItem *item; + GPtrArray *keys_array; + GVariantBuilder builder; + char **settings_paths; + int i; + + gtk_tree_model_get (model, iter, + DETAIL_KEYENTRY_COLUMN, &item, + -1); + + /* not a custom shortcut */ + g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); + + g_settings_delay (item->settings); + g_settings_reset (item->settings, "name"); + g_settings_reset (item->settings, "command"); + g_settings_reset (item->settings, "binding"); + g_settings_apply (item->settings); + g_settings_sync (); + + settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings"); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + + for (i = 0; settings_paths[i]; i++) + if (strcmp (settings_paths[i], item->gsettings_path) != 0) + g_variant_builder_add (&builder, "s", settings_paths[i]); + + g_settings_set_value (self->binding_settings, + "custom-keybindings", + g_variant_builder_end (&builder)); + + g_strfreev (settings_paths); + g_object_unref (item); + + keys_array = g_hash_table_lookup (get_hash_for_group (self, BINDING_GROUP_USER), CUSTOM_SHORTCUTS_ID); + g_ptr_array_remove (keys_array, item); + + gtk_list_store_remove (GTK_LIST_STORE (model), iter); + + return TRUE; +} + +static void +add_custom_shortcut (CcKeyboardPanel *self, + GtkTreeView *tree_view, + GtkTreeModel *model) +{ + CcKeyboardItem *item; + GtkTreePath *path; + gchar *settings_path; + + item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); + + settings_path = find_free_settings_path (self->binding_settings); + cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE); + g_free (settings_path); + + item->model = model; + + if (edit_custom_shortcut (self, item) && item->command && item->command[0]) + { + GPtrArray *keys_array; + GtkTreeIter iter; + GHashTable *hash; + GVariantBuilder builder; + char **settings_paths; + int i; + + hash = get_hash_for_group (self, BINDING_GROUP_USER); + keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID); + if (keys_array == NULL) + { + keys_array = g_ptr_array_new (); + g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array); + } + + g_ptr_array_add (keys_array, item); + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, DETAIL_KEYENTRY_COLUMN, item, -1); + + settings_paths = g_settings_get_strv (self->binding_settings, "custom-keybindings"); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); + for (i = 0; settings_paths[i]; i++) + g_variant_builder_add (&builder, "s", settings_paths[i]); + g_variant_builder_add (&builder, "s", item->gsettings_path); + g_settings_set_value (self->binding_settings, "custom-keybindings", + g_variant_builder_end (&builder)); + + /* 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_object_unref (item); + } +} + +static void +update_custom_shortcut (CcKeyboardPanel *self, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + CcKeyboardItem *item; + + gtk_tree_model_get (model, iter, + DETAIL_KEYENTRY_COLUMN, &item, + -1); + + g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); + + edit_custom_shortcut (self, item); + + if (item->command == NULL || item->command[0] == '\0') + { + remove_custom_shortcut (self, model, iter); + } + else + { + gtk_list_store_set (GTK_LIST_STORE (model), iter, + DETAIL_KEYENTRY_COLUMN, item, -1); + } +} + +static gboolean +start_editing_cb (GtkTreeView *treeview, + GdkEventButton *event, + gpointer user_data) +{ + CcKeyboardPanel *self; + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkCellRenderer *cell = user_data; + + if (event->window != gtk_tree_view_get_bin_window (treeview)) + return FALSE; + + self = CC_KEYBOARD_PANEL (gtk_widget_get_ancestor (GTK_WIDGET (treeview), CC_TYPE_KEYBOARD_PANEL)); + + if (gtk_tree_view_get_path_at_pos (treeview, + (gint) event->x, + (gint) event->y, + &path, + &column, + NULL, + NULL)) + { + GtkTreeModel *model; + GtkTreeIter iter; + CcKeyboardItem *item; + ShortcutType type; + + model = gtk_tree_view_get_model (treeview); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + DETAIL_KEYENTRY_COLUMN, &item, + DETAIL_TYPE_COLUMN, &type, + -1); + + if (type == SHORTCUT_TYPE_XKB_OPTION) + { + gtk_tree_path_free (path); + return FALSE; + } + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (item->desc_editable && + column == gtk_tree_view_get_column (treeview, 0)) + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, + path, + column, + FALSE); + + update_custom_shortcut (self, model, &iter); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor_on_cell (treeview, + path, + gtk_tree_view_get_column (treeview, 1), + cell, + TRUE); + } + + g_signal_stop_emission_by_name (treeview, "button_press_event"); + gtk_tree_path_free (path); + } + + return TRUE; +} + +static void +start_editing_kb_cb (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + CcKeyboardPanel *self; + GtkTreeModel *model; + GtkTreeIter iter; + CcKeyboardItem *item; + ShortcutType type; + GtkCellRenderer *cell = user_data; + + model = gtk_tree_view_get_model (treeview); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + DETAIL_KEYENTRY_COLUMN, &item, + DETAIL_TYPE_COLUMN, &type, + -1); + + if (type == SHORTCUT_TYPE_XKB_OPTION) + return; + + self = CC_KEYBOARD_PANEL (gtk_widget_get_ancestor (GTK_WIDGET (treeview), CC_TYPE_KEYBOARD_PANEL)); + + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (item->desc_editable && + column == gtk_tree_view_get_column (treeview, 0)) + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, + path, + column, + FALSE); + update_custom_shortcut (self, model, &iter); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor_on_cell (treeview, + path, + gtk_tree_view_get_column (treeview, 1), + cell, + TRUE); + } +} + +static gboolean +compare_keys_for_uniqueness (CcKeyboardItem *element, + CcUniquenessData *data) +{ + CcKeyboardItem *orig_item; + + orig_item = data->orig_item; + + /* no conflict for : blanks, different modifiers, or ourselves */ + if (element == NULL || + data->new_mask != element->mask || + cc_keyboard_item_equal (orig_item, element)) + { + return FALSE; + } + + if (data->new_keyval != 0) + { + if (data->new_keyval != element->keyval) + return FALSE; + } + else if (element->keyval != 0 || data->new_keycode != element->keycode) + { + return FALSE; + } + + data->conflict_item = element; + + return TRUE; + +} + +static gboolean +cb_check_for_uniqueness (gpointer key, + GPtrArray *keys_array, + CcUniquenessData *data) +{ + guint i; + + for (i = 0; i < keys_array->len; i++) + { + CcKeyboardItem *item; + + item = keys_array->pdata[i]; + if (compare_keys_for_uniqueness (item, data)) + return TRUE; + } + return FALSE; +} + + +static CcKeyboardItem * +search_for_conflict_item (CcKeyboardPanel *self, + CcKeyboardItem *item, + guint keyval, + GdkModifierType mask, + guint keycode) +{ + CcUniquenessData data; + + data.orig_item = item; + data.new_keyval = keyval; + data.new_mask = mask; + data.new_keycode = keycode; + data.conflict_item = NULL; + + if (keyval != 0 || keycode != 0) /* any number of shortcuts can be disabled */ + { + BindingGroupType i; + + for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && data.conflict_item == NULL; i++) + { + GHashTable *table; + + table = get_hash_for_group (self, i); + if (!table) + continue; + g_hash_table_find (table, (GHRFunc) cb_check_for_uniqueness, &data); + } + } + + return data.conflict_item; +} + +static GtkResponseType +show_invalid_binding_dialog (CcKeyboardPanel *self, + guint keyval, + GdkModifierType mask, + guint keycode) +{ + GtkWidget *dialog; + char *name; + + name = binding_name (keyval, keycode, mask, TRUE); + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + 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); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + g_free (name); + + return GTK_RESPONSE_NONE; +} + +static GtkResponseType +show_conflict_item_dialog (CcKeyboardPanel *self, + CcKeyboardItem *item, + CcKeyboardItem *conflict_item, + guint keyval, + GdkModifierType mask, + guint keycode) +{ + 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 (self))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut “%s” is already used for\n“%s”"), + name, + conflict_item->description); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("If you reassign the shortcut to “%s”, the “%s” shortcut " + "will be disabled."), + item->description, + conflict_item->description); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Reassign"), GTK_RESPONSE_ACCEPT); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + + g_free (name); + + return response; +} + + +static GtkResponseType +show_reverse_item_dialog (CcKeyboardPanel *self, + CcKeyboardItem *item, + CcKeyboardItem *reverse_item, + CcKeyboardItem *reverse_conflict_item, + guint keyval, + GdkModifierType mask, + guint keycode) +{ + GtkWidget *dialog; + char *name; + int response; + + name = binding_name (keyval, keycode, mask, TRUE); + + /* translators: + * This is the text you get in a dialogue when an action has an associated + * "reverse" action, for example Alt+Tab going in the opposite direction to + * Alt+Shift+Tab. + * + * An example text would be: + * The "Switch to next input source" shortcut has an associated "Switch to + * previous input source" shortcut. Do you want to automatically set it to + * "Shift+Ctrl+Alt+Space"? */ + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The “%s” shortcut has an associated “%s” shortcut. " + "Do you want to automatically set it to “%s”?"), + item->description, + reverse_item->description, + name); + + if (reverse_conflict_item != NULL) + { + /* translators: + * This is the text you get in a dialogue when you try to use a shortcut + * that was already associated with another action, for example: + * "Alt+F4" is currently associated with "Close Window", ... */ + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("“%s” is currently associated with “%s”, this shortcut will be" + " disabled if you move forward."), + name, + reverse_conflict_item->description); + } + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Assign"), GTK_RESPONSE_ACCEPT); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + g_free (name); + + return response; +} + +static void +handle_reverse_item (CcKeyboardItem *item, + CcKeyboardItem *reverse_item, + guint keyval, + GdkModifierType mask, + guint keycode, + CcKeyboardPanel *self) +{ + GtkResponseType response; + GdkModifierType reverse_mask; + + reverse_mask = mask ^ GDK_SHIFT_MASK; + + if (!is_valid_binding (keyval, reverse_mask, keycode)) + return; + + if (reverse_item->keyval != keyval || + reverse_item->keycode != keycode || + reverse_item->mask != reverse_mask) + { + CcKeyboardItem *reverse_conflict_item; + char *binding_str; + + reverse_conflict_item = search_for_conflict_item (self, + reverse_item, + keyval, + reverse_mask, + keycode); + + response = show_reverse_item_dialog (self, + item, + reverse_item, + reverse_conflict_item, + keyval, reverse_mask, + keycode); + if (response == GTK_RESPONSE_ACCEPT) + { + binding_str = binding_name (keyval, keycode, reverse_mask, FALSE); + + g_object_set (G_OBJECT (reverse_item), "binding", binding_str, NULL); + g_free (binding_str); + + if (reverse_conflict_item != NULL) + g_object_set (G_OBJECT (reverse_conflict_item), "binding", NULL, NULL); + } + else + { + /* The existing reverse binding may be conflicting with the binding + * we are setting. Other conflicts have already been handled in + * accel_edited_callback() + */ + CcKeyboardItem *conflict_item; + + conflict_item = search_for_conflict_item (self, item, keyval, mask, keycode); + + if (conflict_item != NULL) + { + g_warn_if_fail (conflict_item == reverse_item); + g_object_set (G_OBJECT (conflict_item), "binding", NULL, NULL); + } + } + } +} + +static void +accel_edited_callback (GtkCellRendererText *cell, + const char *path_string, + guint keyval, + GdkModifierType mask, + guint keycode, + CcKeyboardPanel *self) +{ + GtkTreeModel *model; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + CcKeyboardItem *item; + CcKeyboardItem *conflict_item; + CcKeyboardItem *reverse_item; + char *str; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->shortcut_treeview)); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + DETAIL_KEYENTRY_COLUMN, &item, + -1); + + /* sanity check */ + if (item == NULL) + return; + + /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ + mask &= ~GDK_LOCK_MASK; + + conflict_item = search_for_conflict_item (self, item, keyval, mask, keycode); + + /* Check for unmodified keys */ + if (!is_valid_binding (keyval, mask, keycode)) + { + show_invalid_binding_dialog (self, keyval, mask, keycode); + + /* set it back to its previous value. */ + g_object_set (G_OBJECT (cell), + "accel-key", item->keyval, + "keycode", item->keycode, + "accel-mods", item->mask, + NULL); + return; + } + + reverse_item = cc_keyboard_item_get_reverse_item (item); + + /* flag to see if the new accelerator was in use by something */ + if ((conflict_item != NULL) && (conflict_item != reverse_item)) + { + GtkResponseType response; + + response = show_conflict_item_dialog (self, + item, + conflict_item, + keyval, + mask, + keycode); + + if (response == GTK_RESPONSE_ACCEPT) + { + g_object_set (G_OBJECT (conflict_item), "binding", NULL, NULL); + + str = binding_name (keyval, keycode, mask, FALSE); + g_object_set (G_OBJECT (item), "binding", str, NULL); + + g_free (str); + if (reverse_item == NULL) + return; + } + else + { + /* set it back to its previous value. */ + g_object_set (G_OBJECT (cell), + "accel-key", item->keyval, + "keycode", item->keycode, + "accel-mods", item->mask, + NULL); + return; + } + + } + + str = binding_name (keyval, keycode, mask, FALSE); + g_object_set (G_OBJECT (item), "binding", str, NULL); + + if (reverse_item != NULL) + handle_reverse_item (item, reverse_item, keyval, mask, keycode, self); + + g_free (str); +} + +static void +accel_cleared_callback (GtkCellRendererText *cell, + const char *path_string, + gpointer data) +{ + GtkTreeView *view = (GtkTreeView *) data; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + CcKeyboardItem *item; + GtkTreeIter iter; + GtkTreeModel *model; + + 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, + DETAIL_KEYENTRY_COLUMN, &item, + -1); + + /* sanity check */ + if (item == NULL) + return; + + /* Unset the key */ + g_object_set (G_OBJECT (item), "binding", NULL, NULL); +} + +static void +shortcut_entry_changed (GtkEntry *entry, + CcKeyboardPanel *self) +{ + guint16 name_length; + guint16 command_length; + + name_length = gtk_entry_get_text_length (GTK_ENTRY (self->custom_shortcut_name_entry)); + command_length = gtk_entry_get_text_length (GTK_ENTRY (self->custom_shortcut_command_entry)); + + gtk_widget_set_sensitive (self->custom_shortcut_ok_button, name_length > 0 && command_length > 0); +} + +static void +add_button_clicked (GtkWidget *button, + CcKeyboardPanel *self) +{ + GtkTreeView *treeview; + GtkTreeModel *model; + GtkTreeModel *section_model; + GtkTreeIter iter; + gboolean found, cont; + + treeview = GTK_TREE_VIEW (self->shortcut_treeview); + model = gtk_tree_view_get_model (treeview); + + /* Select the Custom Shortcuts section + * before adding the shortcut itself */ + section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->section_treeview)); + cont = gtk_tree_model_get_iter_first (section_model, &iter); + found = FALSE; + + while (cont) + { + BindingGroupType group; + + gtk_tree_model_get (section_model, &iter, + SECTION_GROUP_COLUMN, &group, + -1); + + if (group == BINDING_GROUP_USER) + { + found = TRUE; + break; + } + cont = gtk_tree_model_iter_next (section_model, &iter); + } + if (found) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->section_treeview)); + gtk_tree_selection_select_iter (selection, &iter); + } + + /* And add the shortcut */ + add_custom_shortcut (self, treeview, model); +} + +static void +remove_button_clicked (GtkWidget *button, + CcKeyboardPanel *self) +{ + GtkTreeView *treeview; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + treeview = GTK_TREE_VIEW (self->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 (self, model, &iter); + } +} + +static void +xkb_options_combo_changed (GtkCellRendererCombo *combo, + gchar *model_path, + GtkTreeIter *model_iter, + CcKeyboardPanel *self) +{ + GtkTreeView *shortcut_treeview; + GtkTreeModel *shortcut_model; + GtkTreeIter shortcut_iter; + GtkTreeSelection *selection; + CcKeyboardOption *option; + ShortcutType type; + + shortcut_treeview = GTK_TREE_VIEW (self->shortcut_treeview); + selection = gtk_tree_view_get_selection (shortcut_treeview); + + if (!gtk_tree_selection_get_selected (selection, &shortcut_model, &shortcut_iter)) + return; + + gtk_tree_model_get (shortcut_model, &shortcut_iter, + DETAIL_KEYENTRY_COLUMN, &option, + DETAIL_TYPE_COLUMN, &type, + -1); + + if (type != SHORTCUT_TYPE_XKB_OPTION) + return; + + cc_keyboard_option_set_selection (option, model_iter); +} + +static void +setup_tree_views (CcKeyboardPanel *self) +{ + GtkTreeViewColumn *column; + GtkTreeModelSort *sort_model; + GtkCellRenderer *renderer; + GtkListStore *model; + GtkWidget *widget; + CcShell *shell; + GList *focus_chain; + + /* Setup the section treeview */ + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (self->section_treeview), + sections_separator_func, + self, + NULL); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Section"), + renderer, + "text", SECTION_DESCRIPTION_COLUMN, + NULL); + g_object_set (renderer, + "width-chars", 20, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (self->section_treeview), column); + + model = gtk_list_store_new (SECTION_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); + sort_model = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model))); + gtk_tree_view_set_model (GTK_TREE_VIEW (self->section_treeview), GTK_TREE_MODEL (sort_model)); + g_object_unref (model); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model), + SECTION_DESCRIPTION_COLUMN, + section_sort_item, + self, + NULL); + + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model), + SECTION_DESCRIPTION_COLUMN, + GTK_SORT_ASCENDING); + g_object_unref (sort_model); + + section_selection_changed (gtk_tree_view_get_selection (GTK_TREE_VIEW (self->section_treeview)), + self); + + /* Setup the shortcut treeview */ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); + gtk_tree_view_column_set_cell_data_func (column, renderer, description_set_func, NULL, NULL); + gtk_tree_view_column_set_resizable (column, FALSE); + gtk_tree_view_column_set_expand (column, TRUE); + + gtk_tree_view_append_column (GTK_TREE_VIEW (self->shortcut_treeview), column); + + renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL, + "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER, + NULL); + + g_signal_connect (self->shortcut_treeview, + "button_press_event", + G_CALLBACK (start_editing_cb), + renderer); + g_signal_connect (self->shortcut_treeview, + "row-activated", + G_CALLBACK (start_editing_kb_cb), + renderer); + + g_signal_connect (renderer, + "accel_edited", + G_CALLBACK (accel_edited_callback), + self); + g_signal_connect (renderer, + "accel_cleared", + G_CALLBACK (accel_cleared_callback), + self->shortcut_treeview); + + column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); + gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); + gtk_tree_view_column_set_resizable (column, FALSE); + gtk_tree_view_column_set_expand (column, FALSE); + + renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_COMBO, + "has-entry", FALSE, + "text-column", XKB_OPTION_DESCRIPTION_COLUMN, + "editable", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, + "width-chars", 25, + NULL); + g_signal_connect (renderer, + "changed", + G_CALLBACK (xkb_options_combo_changed), + self); + + gtk_tree_view_column_pack_end (column, renderer, FALSE); + + gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (self->shortcut_treeview), column); + + model = gtk_list_store_new (DETAIL_N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); + gtk_tree_view_set_model (GTK_TREE_VIEW (self->shortcut_treeview), GTK_TREE_MODEL (model)); + g_object_unref (model); + + setup_keyboard_options (model); + + /* set up the focus chain */ + focus_chain = g_list_append (NULL, self->section_treeview); + focus_chain = g_list_append (focus_chain, self->shortcut_treeview); + focus_chain = g_list_append (focus_chain, self->shortcut_toolbar); + + gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain); + g_list_free (focus_chain); + + /* set up the dialog */ + shell = cc_panel_get_shell (CC_PANEL (self)); + widget = cc_shell_get_toplevel (shell); + + gtk_dialog_set_default_response (GTK_DIALOG (self->custom_shortcut_dialog), GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (self->custom_shortcut_dialog), GTK_WINDOW (widget)); +} + +static gboolean +cc_keyboard_panel_set_section (CcKeyboardPanel *self, + const char *section) +{ + GtkTreeModel *section_model; + GtkTreeIter iter; + gboolean found, cont; + + section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->section_treeview)); + cont = gtk_tree_model_get_iter_first (section_model, &iter); + found = FALSE; + while (cont) + { + char *id; + + gtk_tree_model_get (section_model, &iter, + SECTION_ID_COLUMN, &id, + -1); + + if (g_strcmp0 (id, section) == 0) + { + found = TRUE; + g_free (id); + break; + } + g_free (id); + cont = gtk_tree_model_iter_next (section_model, &iter); + } + if (found) + { + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->section_treeview)); + gtk_tree_selection_select_iter (selection, &iter); + } + else + { + g_warning ("Could not find section '%s' to switch to.", section); + } + + return found; } static void @@ -74,123 +1766,105 @@ cc_keyboard_panel_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - CcKeyboardPanel *panel = CC_KEYBOARD_PANEL (object); - switch (property_id) { - case PROP_PARAMETERS: { - GVariant *parameters, *v; - const gchar *page, *section; - - parameters = g_value_get_variant (value); - if (!parameters) - break; - page = section = NULL; - switch (g_variant_n_children (parameters)) - { - case 2: - g_variant_get_child (parameters, 1, "v", &v); - section = g_variant_get_string (v, NULL); - g_variant_unref (v); - /* fall-through */ - case 1: - g_variant_get_child (parameters, 0, "v", &v); - page = g_variant_get_string (v, NULL); - g_variant_unref (v); - cc_keyboard_panel_set_page (panel, page, section); - /* fall-through */ - case 0: - break; - default: - g_warning ("Unexpected parameters found, ignore request"); - } + case PROP_PARAMETERS: break; - } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } -static GObject * -cc_keyboard_panel_constructor (GType gtype, - guint n_properties, - GObjectConstructParam *properties) -{ - GObject *obj; - CcKeyboardPanel *self; - GtkWidget *widget; - - obj = G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructor (gtype, n_properties, properties); - - self = CC_KEYBOARD_PANEL (obj); - - keyboard_shortcuts_init (CC_PANEL (self), self->builder); - - widget = (GtkWidget *) gtk_builder_get_object (self->builder, "shortcuts_page"); - - gtk_container_add (GTK_CONTAINER (self), widget); - - return obj; -} - static const char * cc_keyboard_panel_get_help_uri (CcPanel *panel) { return "help:gnome-help/keyboard"; } -static void -cc_keyboard_panel_dispose (GObject *object) -{ - keyboard_shortcuts_dispose (CC_PANEL (object)); - - G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->dispose (object); -} - static void cc_keyboard_panel_finalize (GObject *object) { - CcKeyboardPanel *panel = CC_KEYBOARD_PANEL (object); + CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object); - if (panel->builder) - g_object_unref (panel->builder); + g_clear_pointer (&self->kb_system_sections, g_hash_table_destroy); + g_clear_pointer (&self->kb_apps_sections, g_hash_table_destroy); + g_clear_pointer (&self->kb_user_sections, g_hash_table_destroy); + g_clear_pointer (&self->pictures_regex, g_regex_unref); + g_clear_pointer (&self->wm_changed_id, wm_common_unregister_window_manager_change); + + g_clear_object (&self->custom_shortcut_dialog); + g_clear_object (&self->binding_settings); + + cc_keyboard_option_clear_all (); G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->finalize (object); } +static void +on_window_manager_change (const char *wm_name, + CcKeyboardPanel *self) +{ + reload_sections (self); +} + +static void +cc_keyboard_panel_constructed (GObject *object) +{ + CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object); + + G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructed (object); + +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) + self->wm_changed_id = wm_common_register_window_manager_change ((GFunc) on_window_manager_change, + self); +#endif + + setup_tree_views (self); + reload_sections (self); +} + static void cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); CcPanelClass *panel_class = CC_PANEL_CLASS (klass); panel_class->get_help_uri = cc_keyboard_panel_get_help_uri; - object_class->constructor = cc_keyboard_panel_constructor; object_class->set_property = cc_keyboard_panel_set_property; - object_class->dispose = cc_keyboard_panel_dispose; object_class->finalize = cc_keyboard_panel_finalize; + object_class->constructed = cc_keyboard_panel_constructed; g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/gnome-keyboard-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, add_toolbutton); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, custom_shortcut_command_entry); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, custom_shortcut_dialog); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, custom_shortcut_name_entry); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, custom_shortcut_ok_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, remove_toolbutton); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, section_treeview); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, shortcut_toolbar); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, shortcut_treeview); + + gtk_widget_class_bind_template_callback (widget_class, add_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, section_selection_changed); + gtk_widget_class_bind_template_callback (widget_class, shortcut_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, shortcut_selection_changed); } static void cc_keyboard_panel_init (CcKeyboardPanel *self) { - GError *error = NULL; - g_resources_register (cc_keyboard_get_resource ()); - self->builder = gtk_builder_new (); + gtk_widget_init_template (GTK_WIDGET (self)); - if (gtk_builder_add_from_resource (self->builder, - "/org/gnome/control-center/keyboard/gnome-keyboard-panel.ui", - &error) == 0) - { - g_warning ("Could not load UI: %s", error->message); - g_clear_error (&error); - g_object_unref (self->builder); - self->builder = NULL; - } + self->binding_settings = g_settings_new (BINDINGS_SCHEMA); } diff --git a/panels/keyboard/cc-keyboard-panel.h b/panels/keyboard/cc-keyboard-panel.h index 2276c3fd3..859a80786 100644 --- a/panels/keyboard/cc-keyboard-panel.h +++ b/panels/keyboard/cc-keyboard-panel.h @@ -23,6 +23,7 @@ #define _CC_KEYBOARD_PANEL_H #include +#include G_BEGIN_DECLS diff --git a/panels/keyboard/gnome-keyboard-panel.ui b/panels/keyboard/gnome-keyboard-panel.ui index dd9e50ad5..cd453dbd1 100644 --- a/panels/keyboard/gnome-keyboard-panel.ui +++ b/panels/keyboard/gnome-keyboard-panel.ui @@ -8,10 +8,11 @@ 200 200 - + False dialog 1 + False True @@ -19,7 +20,7 @@ Custom Shortcut False - + _Cancel True True @@ -36,7 +37,7 @@ - + _Add True True @@ -81,7 +82,7 @@ 0 _Name: True - custom-shortcut-name-entry + custom_shortcut_name_entry 0 @@ -95,7 +96,7 @@ 0 C_ommand: True - custom-shortcut-command-entry + custom_shortcut_command_entry 0 @@ -103,12 +104,13 @@ - + True True True True + 1 @@ -116,12 +118,13 @@ - + True True True True + 1 @@ -145,154 +148,152 @@ - custom-shortcut-cancel-button - custom-shortcut-ok-button + custom_shortcut_cancel_button + custom_shortcut_ok_button - + diff --git a/panels/keyboard/keyboard-shortcuts.c b/panels/keyboard/keyboard-shortcuts.c index 3a6ffd096..5b59eba90 100644 --- a/panels/keyboard/keyboard-shortcuts.c +++ b/panels/keyboard/keyboard-shortcuts.c @@ -28,238 +28,12 @@ #include "cc-keyboard-option.h" #include "wm-common.h" -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys" -#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings" -#define CUSTOM_SHORTCUTS_ID "custom" -#define WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name))) - -static GRegex *pictures_regex = NULL; -static GSettings *binding_settings = NULL; -static GtkWidget *custom_shortcut_dialog = NULL; -static GtkWidget *custom_shortcut_name_entry = NULL; -static GtkWidget *custom_shortcut_command_entry = NULL; -static GtkWidget *custom_shortcut_ok_button = NULL; -static GHashTable *kb_system_sections = NULL; -static GHashTable *kb_apps_sections = NULL; -static GHashTable *kb_user_sections = NULL; -static gpointer wm_changed_id = NULL; - -static void -free_key_array (GPtrArray *keys) -{ - if (keys != NULL) - { - gint i; - - for (i = 0; i < keys->len; i++) - { - CcKeyboardItem *item; - - item = g_ptr_array_index (keys, i); - - g_object_unref (item); - } - - g_ptr_array_free (keys, TRUE); - } -} - -static GHashTable * -get_hash_for_group (BindingGroupType group) -{ - GHashTable *hash; - - switch (group) - { - case BINDING_GROUP_SYSTEM: - hash = kb_system_sections; - break; - case BINDING_GROUP_APPS: - hash = kb_apps_sections; - break; - case BINDING_GROUP_USER: - hash = kb_user_sections; - break; - default: - hash = NULL; - } - return hash; -} - -static gboolean -have_key_for_group (int group, const gchar *name) -{ - GHashTableIter iter; - GPtrArray *keys; - gint i; - - g_hash_table_iter_init (&iter, get_hash_for_group (group)); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&keys)) - { - for (i = 0; i < keys->len; i++) - { - CcKeyboardItem *item = g_ptr_array_index (keys, i); - - if (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS && - g_strcmp0 (name, item->key) == 0) - { - return TRUE; - } - - return FALSE; - } - } - - return FALSE; -} - -static gboolean -keybinding_key_changed_foreach (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - CcKeyboardItem *item) -{ - CcKeyboardItem *tmp_item; - - gtk_tree_model_get (item->model, iter, - DETAIL_KEYENTRY_COLUMN, &tmp_item, - -1); - - if (item == tmp_item) - { - gtk_tree_model_row_changed (item->model, path, iter); - return TRUE; - } - return FALSE; -} - -static void -item_changed (CcKeyboardItem *item, - GParamSpec *pspec, - gpointer user_data) -{ - /* update the model */ - gtk_tree_model_foreach (item->model, (GtkTreeModelForeachFunc) keybinding_key_changed_foreach, item); -} - - -static void -append_section (GtkBuilder *builder, - const gchar *title, - const gchar *id, - BindingGroupType group, - const KeyListEntry *keys_list) -{ - GPtrArray *keys_array; - GtkTreeModel *sort_model; - GtkTreeModel *model, *shortcut_model; - GtkTreeIter iter; - gint i; - GHashTable *hash; - gboolean is_new; - - hash = get_hash_for_group (group); - if (!hash) - return; - - sort_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"))); - model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model)); - - shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"))); - - /* Add all CcKeyboardItems for this section */ - is_new = FALSE; - keys_array = g_hash_table_lookup (hash, id); - if (keys_array == NULL) - { - keys_array = g_ptr_array_new (); - is_new = TRUE; - } - - GHashTable *reverse_items = g_hash_table_new (g_str_hash, g_str_equal); - - for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++) - { - CcKeyboardItem *item; - gboolean ret; - - if (have_key_for_group (group, keys_list[i].name)) - continue; - - item = cc_keyboard_item_new (keys_list[i].type); - switch (keys_list[i].type) - { - case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH: - ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE); - break; - case CC_KEYBOARD_ITEM_TYPE_GSETTINGS: - ret = cc_keyboard_item_load_from_gsettings (item, - keys_list[i].description, - keys_list[i].schema, - keys_list[i].name); - if (ret && keys_list[i].reverse_entry != NULL) - { - CcKeyboardItem *reverse_item; - reverse_item = g_hash_table_lookup (reverse_items, - keys_list[i].reverse_entry); - if (reverse_item != NULL) - { - cc_keyboard_item_add_reverse_item (item, - reverse_item, - keys_list[i].is_reversed); - } - else - { - g_hash_table_insert (reverse_items, - keys_list[i].name, - item); - } - } - break; - default: - g_assert_not_reached (); - } - - if (ret == FALSE) - { - /* We don't actually want to popup a dialog - just skip this one */ - g_object_unref (item); - continue; - } - - cc_keyboard_item_set_hidden (item, keys_list[i].hidden); - item->model = shortcut_model; - - g_signal_connect (G_OBJECT (item), "notify", - G_CALLBACK (item_changed), NULL); - - g_ptr_array_add (keys_array, item); - } - - g_hash_table_destroy (reverse_items); - - /* Add the keys to the hash table */ - if (is_new) - { - g_hash_table_insert (hash, g_strdup (id), keys_array); - - /* Append the section to the left tree view */ - gtk_list_store_append (GTK_LIST_STORE (model), &iter); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, - SECTION_DESCRIPTION_COLUMN, title, - SECTION_ID_COLUMN, id, - SECTION_GROUP_COLUMN, group, - -1); - } -} +#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings" static char * replace_pictures_folder (const char *description) { + GRegex *pictures_regex; const char *path; char *dirname; char *ret; @@ -270,11 +44,15 @@ replace_pictures_folder (const char *description) if (strstr (description, "$PICTURES") == NULL) return g_strdup (description); + pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL); path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); dirname = g_filename_display_basename (path); ret = g_regex_replace (pictures_regex, description, -1, 0, dirname, 0, NULL); + + g_regex_unref (pictures_regex); g_free (dirname); + if (ret == NULL) return g_strdup (description); @@ -436,412 +214,7 @@ parse_start_tag (GMarkupParseContext *ctx, g_array_append_val (keylist->entries, key); } -static gboolean -strv_contains (char **strv, - char *str) -{ - char **p = strv; - for (p = strv; *p; p++) - if (strcmp (*p, str) == 0) - return TRUE; - - return FALSE; -} - -static void -append_sections_from_file (GtkBuilder *builder, const gchar *path, const char *datadir, gchar **wm_keybindings) -{ - GError *err = NULL; - char *buf; - gsize buf_len; - KeyList *keylist; - KeyListEntry *keys; - KeyListEntry key = { 0, }; - const char *title; - int group; - guint i; - GMarkupParseContext *ctx; - GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL }; - - /* Parse file */ - if (!g_file_get_contents (path, &buf, &buf_len, &err)) - return; - - keylist = g_new0 (KeyList, 1); - keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); - ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL); - - if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) - { - g_warning ("Failed to parse '%s': '%s'", path, err->message); - g_error_free (err); - g_free (keylist->name); - g_free (keylist->package); - g_free (keylist->wm_name); - for (i = 0; i < keylist->entries->len; i++) - g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name); - g_array_free (keylist->entries, TRUE); - g_free (keylist); - keylist = NULL; - } - g_markup_parse_context_free (ctx); - g_free (buf); - - if (keylist == NULL) - return; - - /* If there's no keys to add, or the settings apply to a window manager - * that's not the one we're running */ - if (keylist->entries->len == 0 - || (keylist->wm_name != NULL && !strv_contains (wm_keybindings, keylist->wm_name)) - || keylist->name == NULL) - { - g_free (keylist->name); - g_free (keylist->package); - g_free (keylist->wm_name); - g_array_free (keylist->entries, TRUE); - g_free (keylist); - return; - } - - /* Empty KeyListEntry to end the array */ - key.name = NULL; - g_array_append_val (keylist->entries, key); - - keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE); - if (keylist->package) - { - char *localedir; - - localedir = g_build_filename (datadir, "locale", NULL); - bindtextdomain (keylist->package, localedir); - g_free (localedir); - - title = dgettext (keylist->package, keylist->name); - } else { - title = _(keylist->name); - } - if (keylist->group && strcmp (keylist->group, "system") == 0) - group = BINDING_GROUP_SYSTEM; - else - group = BINDING_GROUP_APPS; - - append_section (builder, title, keylist->name, group, keys); - - g_free (keylist->name); - g_free (keylist->package); - g_free (keylist->wm_name); - g_free (keylist->schema); - g_free (keylist->group); - - for (i = 0; keys[i].name != NULL; i++) { - KeyListEntry *entry = &keys[i]; - g_free (entry->schema); - g_free (entry->description); - g_free (entry->name); - g_free (entry->reverse_entry); - } - g_free (keys); - - g_free (keylist); -} - -static void -append_sections_from_gsettings (GtkBuilder *builder) -{ - char **custom_paths; - GArray *entries; - KeyListEntry key = { 0, }; - int i; - - /* load custom shortcuts from GSettings */ - entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); - - custom_paths = g_settings_get_strv (binding_settings, "custom-keybindings"); - for (i = 0; custom_paths[i]; i++) - { - key.name = g_strdup (custom_paths[i]); - if (!have_key_for_group (BINDING_GROUP_USER, key.name)) - { - key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH; - g_array_append_val (entries, key); - } - else - g_free (key.name); - } - g_strfreev (custom_paths); - - if (entries->len > 0) - { - KeyListEntry *keys; - int i; - - /* Empty KeyListEntry to end the array */ - key.name = NULL; - g_array_append_val (entries, key); - - keys = (KeyListEntry *) entries->data; - append_section (builder, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys); - for (i = 0; i < entries->len; ++i) - { - g_free (keys[i].name); - } - } - else - { - append_section (builder, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL); - } - - g_array_free (entries, TRUE); -} - -static void -reload_sections (CcPanel *panel) -{ - GtkBuilder *builder; - gchar **wm_keybindings; - gchar *default_wm_keybindings[] = { "Mutter", "GNOME Shell", NULL }; - GDir *dir; - GtkTreeModel *sort_model; - GtkTreeModel *section_model; - GtkTreeModel *shortcut_model; - const gchar * const * data_dirs; - guint i; - GtkTreeView *section_treeview; - GtkTreeSelection *selection; - GtkTreeIter iter; - GHashTable *loaded_files; - const char *section_to_set; - - builder = g_object_get_data (G_OBJECT (panel), "builder"); - - section_treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview")); - sort_model = gtk_tree_view_get_model (section_treeview); - section_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model)); - - shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"))); - /* FIXME: get current selection and keep it after refreshing */ - - /* Clear previous models and hash tables */ - gtk_list_store_clear (GTK_LIST_STORE (section_model)); - gtk_list_store_clear (GTK_LIST_STORE (shortcut_model)); - if (kb_system_sections != NULL) - g_hash_table_destroy (kb_system_sections); - kb_system_sections = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) free_key_array); - - if (kb_apps_sections != NULL) - g_hash_table_destroy (kb_apps_sections); - kb_apps_sections = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) free_key_array); - - if (kb_user_sections != NULL) - g_hash_table_destroy (kb_user_sections); - kb_user_sections = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) free_key_array); - - /* Load WM keybindings */ -#ifdef GDK_WINDOWING_X11 - if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) - wm_keybindings = wm_common_get_current_keybindings (); -#endif - else - wm_keybindings = g_strdupv (default_wm_keybindings); - - loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - - data_dirs = g_get_system_data_dirs (); - for (i = 0; data_dirs[i] != NULL; i++) - { - char *dir_path; - const gchar *name; - - dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL); - - dir = g_dir_open (dir_path, 0, NULL); - if (!dir) - { - g_free (dir_path); - continue; - } - - for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir)) - { - gchar *path; - - if (g_str_has_suffix (name, ".xml") == FALSE) - continue; - - if (g_hash_table_lookup (loaded_files, name) != NULL) - { - g_debug ("Not loading %s, it was already loaded from another directory", name); - continue; - } - - g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1)); - path = g_build_filename (dir_path, name, NULL); - append_sections_from_file (builder, path, data_dirs[i], wm_keybindings); - g_free (path); - } - g_free (dir_path); - g_dir_close (dir); - } - - g_hash_table_destroy (loaded_files); - g_strfreev (wm_keybindings); - - /* Add a separator */ - gtk_list_store_append (GTK_LIST_STORE (section_model), &iter); - gtk_list_store_set (GTK_LIST_STORE (section_model), &iter, - SECTION_DESCRIPTION_COLUMN, NULL, - SECTION_GROUP_COLUMN, BINDING_GROUP_SEPARATOR, - -1); - - /* Load custom keybindings */ - append_sections_from_gsettings (builder); - - /* Select the first item, or the requested section, if any */ - section_to_set = g_object_get_data (G_OBJECT (panel), "section-to-set"); - if (section_to_set != NULL) - { - if (keyboard_shortcuts_set_section (panel, section_to_set)) - { - g_object_set_data (G_OBJECT (panel), "section-to-set", NULL); - return; - } - } - g_assert (gtk_tree_model_get_iter_first (sort_model, &iter)); - selection = gtk_tree_view_get_selection (section_treeview); - gtk_tree_selection_select_iter (selection, &iter); - - g_object_set_data (G_OBJECT (panel), "section-to-set", NULL); -} - -static void -accel_set_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - gpointer entry; - ShortcutType type; - - gtk_tree_model_get (model, iter, - DETAIL_KEYENTRY_COLUMN, &entry, - DETAIL_TYPE_COLUMN, &type, - -1); - - gtk_cell_renderer_set_visible (cell, FALSE); - - if (type == SHORTCUT_TYPE_XKB_OPTION && - GTK_IS_CELL_RENDERER_COMBO (cell)) - { - CcKeyboardOption *option = entry; - - gtk_cell_renderer_set_visible (cell, TRUE); - g_object_set (cell, - "model", cc_keyboard_option_get_store (option), - "text", cc_keyboard_option_get_current_value_description (option), - NULL); - } - else if (type == SHORTCUT_TYPE_KEY_ENTRY && - GTK_IS_CELL_RENDERER_TEXT (cell) && - !GTK_IS_CELL_RENDERER_COMBO (cell) && - entry != NULL) - { - CcKeyboardItem *item = entry; - - gtk_cell_renderer_set_visible (cell, TRUE); - - if (item->editable) - g_object_set (cell, - "editable", TRUE, - "accel-key", item->keyval, - "accel-mods", item->mask, - "keycode", item->keycode, - "style", PANGO_STYLE_NORMAL, - NULL); - else - g_object_set (cell, - "editable", FALSE, - "accel-key", item->keyval, - "accel-mods", item->mask, - "keycode", item->keycode, - "style", PANGO_STYLE_ITALIC, - NULL); - } -} - -static void -description_set_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - gchar *description; - CcKeyboardItem *item; - ShortcutType type; - - gtk_tree_model_get (model, iter, - DETAIL_DESCRIPTION_COLUMN, &description, - DETAIL_KEYENTRY_COLUMN, &item, - DETAIL_TYPE_COLUMN, &type, - -1); - - if (type == SHORTCUT_TYPE_XKB_OPTION) - { - g_object_set (cell, "text", description, NULL); - } - else - { - if (item != NULL) - g_object_set (cell, - "editable", FALSE, - "text", item->description != NULL ? - item->description : _(""), - NULL); - else - g_object_set (cell, - "editable", FALSE, NULL); - } - - g_free (description); -} - -static void -shortcut_selection_changed (GtkTreeSelection *selection, gpointer data) -{ - GtkWidget *button = data; - GtkTreeModel *model; - GtkTreeIter iter; - CcKeyboardItem *item; - gboolean can_remove; - ShortcutType type; - - can_remove = FALSE; - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - { - gtk_tree_model_get (model, &iter, - DETAIL_KEYENTRY_COLUMN, &item, - DETAIL_TYPE_COLUMN, &type, - -1); - if (type == SHORTCUT_TYPE_KEY_ENTRY && - item && item->command != NULL && item->editable) - can_remove = TRUE; - } - - gtk_widget_set_sensitive (button, can_remove); -} - -static void +void fill_xkb_options_shortcuts (GtkTreeModel *model) { GList *l; @@ -860,284 +233,6 @@ fill_xkb_options_shortcuts (GtkTreeModel *model) } } -static void -section_selection_changed (GtkTreeSelection *selection, gpointer data) -{ - GtkTreeIter iter; - GtkTreeModel *model; - GtkBuilder *builder = GTK_BUILDER (data); - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - { - GPtrArray *keys; - GtkWidget *shortcut_treeview; - GtkTreeModel *shortcut_model; - gchar *id; - BindingGroupType group; - gint i; - - gtk_tree_model_get (model, &iter, - SECTION_ID_COLUMN, &id, - SECTION_GROUP_COLUMN, &group, -1); - - keys = g_hash_table_lookup (get_hash_for_group (group), id); - if (keys == NULL) - { - g_warning ("Can't find section %s in sections hash table.", id); - g_free (id); - return; - } - - gtk_widget_set_sensitive (WID (builder, "remove-toolbutton"), FALSE); - - /* Fill the shortcut treeview with the keys for the selected section */ - shortcut_treeview = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut_treeview")); - shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (shortcut_treeview)); - gtk_list_store_clear (GTK_LIST_STORE (shortcut_model)); - - for (i = 0; i < keys->len; i++) - { - GtkTreeIter new_row; - CcKeyboardItem *item = g_ptr_array_index (keys, i); - - if (!cc_keyboard_item_is_hidden (item)) - { - gtk_list_store_append (GTK_LIST_STORE (shortcut_model), &new_row); - gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row, - DETAIL_DESCRIPTION_COLUMN, item->description, - DETAIL_KEYENTRY_COLUMN, item, - DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_KEY_ENTRY, - -1); - } - } - - if (g_str_equal (id, "Typing")) - fill_xkb_options_shortcuts (shortcut_model); - - g_free (id); - } -} - -static gboolean -edit_custom_shortcut (CcKeyboardItem *item) -{ - gint result; - gboolean ret; - GSettings *settings; - - settings = g_settings_new_with_path (item->schema, item->gsettings_path); - - g_settings_bind (settings, "name", - G_OBJECT (custom_shortcut_name_entry), "text", - G_SETTINGS_BIND_DEFAULT); - gtk_widget_grab_focus (custom_shortcut_name_entry); - - g_settings_bind (settings, "command", - G_OBJECT (custom_shortcut_command_entry), "text", - G_SETTINGS_BIND_DEFAULT); - - g_settings_delay (settings); - - gtk_widget_set_sensitive (custom_shortcut_name_entry, - item->desc_editable); - gtk_widget_set_sensitive (custom_shortcut_command_entry, - item->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: - g_settings_apply (settings); - ret = TRUE; - break; - default: - g_settings_revert (settings); - ret = FALSE; - break; - } - - g_settings_unbind (G_OBJECT (custom_shortcut_name_entry), "text"); - g_settings_unbind (G_OBJECT (custom_shortcut_command_entry), "text"); - - g_object_unref (settings); - - gtk_widget_hide (custom_shortcut_dialog); - - return ret; -} - -static gboolean -remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) -{ - CcKeyboardItem *item; - GPtrArray *keys_array; - GVariantBuilder builder; - char **settings_paths; - int i; - - gtk_tree_model_get (model, iter, - DETAIL_KEYENTRY_COLUMN, &item, - -1); - - /* not a custom shortcut */ - g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); - - g_settings_delay (item->settings); - g_settings_reset (item->settings, "name"); - g_settings_reset (item->settings, "command"); - g_settings_reset (item->settings, "binding"); - g_settings_apply (item->settings); - g_settings_sync (); - - settings_paths = g_settings_get_strv (binding_settings, "custom-keybindings"); - g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); - for (i = 0; settings_paths[i]; i++) - if (strcmp (settings_paths[i], item->gsettings_path) != 0) - g_variant_builder_add (&builder, "s", settings_paths[i]); - g_settings_set_value (binding_settings, - "custom-keybindings", g_variant_builder_end (&builder)); - g_strfreev (settings_paths); - g_object_unref (item); - - keys_array = g_hash_table_lookup (get_hash_for_group (BINDING_GROUP_USER), CUSTOM_SHORTCUTS_ID); - g_ptr_array_remove (keys_array, item); - - gtk_list_store_remove (GTK_LIST_STORE (model), iter); - - return TRUE; -} - -static void -update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) -{ - CcKeyboardItem *item; - - gtk_tree_model_get (model, iter, - DETAIL_KEYENTRY_COLUMN, &item, - -1); - - g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); - - edit_custom_shortcut (item); - if (item->command == NULL || item->command[0] == '\0') - { - remove_custom_shortcut (model, iter); - } - else - { - gtk_list_store_set (GTK_LIST_STORE (model), iter, - DETAIL_KEYENTRY_COLUMN, item, -1); - } -} - -static gboolean -start_editing_cb (GtkTreeView *tree_view, - GdkEventButton *event, - gpointer user_data) -{ - GtkTreePath *path; - GtkTreeViewColumn *column; - GtkCellRenderer *cell = user_data; - - 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)) - { - GtkTreeModel *model; - GtkTreeIter iter; - CcKeyboardItem *item; - ShortcutType type; - - model = gtk_tree_view_get_model (tree_view); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, - DETAIL_KEYENTRY_COLUMN, &item, - DETAIL_TYPE_COLUMN, &type, - -1); - - if (type == SHORTCUT_TYPE_XKB_OPTION) - { - gtk_tree_path_free (path); - return FALSE; - } - - /* if only the accel can be edited on the selected row - * always select the accel column */ - if (item->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, - column, - FALSE); - update_custom_shortcut (model, &iter); - } - else - { - gtk_widget_grab_focus (GTK_WIDGET (tree_view)); - gtk_tree_view_set_cursor_on_cell (tree_view, - path, - gtk_tree_view_get_column (tree_view, 1), - cell, - TRUE); - } - g_signal_stop_emission_by_name (tree_view, "button_press_event"); - gtk_tree_path_free (path); - } - return TRUE; -} - -static void -start_editing_kb_cb (GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data) -{ - GtkTreeModel *model; - GtkTreeIter iter; - CcKeyboardItem *item; - ShortcutType type; - GtkCellRenderer *cell = user_data; - - model = gtk_tree_view_get_model (treeview); - gtk_tree_model_get_iter (model, &iter, path); - gtk_tree_model_get (model, &iter, - DETAIL_KEYENTRY_COLUMN, &item, - DETAIL_TYPE_COLUMN, &type, - -1); - - if (type == SHORTCUT_TYPE_XKB_OPTION) - return; - - /* if only the accel can be edited on the selected row - * always select the accel column */ - if (item->desc_editable && - column == gtk_tree_view_get_column (treeview, 0)) - { - gtk_widget_grab_focus (GTK_WIDGET (treeview)); - gtk_tree_view_set_cursor (treeview, - path, - column, - FALSE); - update_custom_shortcut (model, &iter); - } - else - { - gtk_widget_grab_focus (GTK_WIDGET (treeview)); - gtk_tree_view_set_cursor_on_cell (treeview, - path, - gtk_tree_view_get_column (treeview, 1), - cell, - TRUE); - } -} - static const guint forbidden_keyvals[] = { /* Navigation keys */ GDK_KEY_Home, @@ -1157,20 +252,6 @@ static const guint forbidden_keyvals[] = { GDK_KEY_Mode_switch }; -static char* -binding_name (guint keyval, - guint keycode, - GdkModifierType mask, - gboolean translate) -{ - if (keyval != 0 || keycode != 0) - return translate ? - gtk_accelerator_get_label_with_keycode (NULL, keyval, keycode, mask) : - gtk_accelerator_name_with_keycode (NULL, keyval, keycode, mask); - else - return g_strdup (translate ? _("Disabled") : NULL); -} - static gboolean keyval_is_forbidden (guint keyval) { @@ -1184,81 +265,7 @@ keyval_is_forbidden (guint keyval) return FALSE; } -static gboolean -compare_keys_for_uniqueness (CcKeyboardItem *element, - CcUniquenessData *data) -{ - CcKeyboardItem *orig_item; - - orig_item = data->orig_item; - - /* no conflict for : blanks, different modifiers, or ourselves */ - if (element == NULL || data->new_mask != element->mask || - cc_keyboard_item_equal (orig_item, element)) - return FALSE; - - if (data->new_keyval != 0) { - if (data->new_keyval != element->keyval) - return FALSE; - } else if (element->keyval != 0 || data->new_keycode != element->keycode) - return FALSE; - - data->conflict_item = element; - - return TRUE; -} - -static gboolean -cb_check_for_uniqueness (gpointer key, - GPtrArray *keys_array, - CcUniquenessData *data) -{ - guint i; - - for (i = 0; i < keys_array->len; i++) - { - CcKeyboardItem *item; - - item = keys_array->pdata[i]; - if (compare_keys_for_uniqueness (item, data)) - return TRUE; - } - return FALSE; -} - -static CcKeyboardItem * -search_for_conflict_item (CcKeyboardItem *item, - guint keyval, - GdkModifierType mask, - guint keycode) -{ - CcUniquenessData data; - - data.orig_item = item; - data.new_keyval = keyval; - data.new_mask = mask; - data.new_keycode = keycode; - data.conflict_item = NULL; - - if (keyval != 0 || keycode != 0) /* any number of shortcuts can be disabled */ - { - BindingGroupType i; - - for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && data.conflict_item == NULL; i++) - { - GHashTable *table; - - table = get_hash_for_group (i); - if (!table) - continue; - g_hash_table_find (table, (GHRFunc) cb_check_for_uniqueness, &data); - } - } - - return data.conflict_item; -} - -static gboolean +gboolean is_valid_binding (guint keyval, GdkModifierType mask, guint keycode) @@ -1283,316 +290,14 @@ is_valid_binding (guint keyval, return TRUE; } -static GtkResponseType -show_invalid_binding_dialog (GtkTreeView *view, - guint keyval, - GdkModifierType mask, - guint keycode) -{ - 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); - - return GTK_RESPONSE_NONE; -} - -static GtkResponseType -show_conflict_item_dialog (GtkTreeView *view, - CcKeyboardItem *item, - CcKeyboardItem *conflict_item, - guint keyval, - GdkModifierType mask, - guint keycode) -{ - 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, conflict_item->description); - 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."), - item->description, - conflict_item->description); - - 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); - - return response; -} - -static GtkResponseType -show_reverse_item_dialog (GtkTreeView *view, - CcKeyboardItem *item, - CcKeyboardItem *reverse_item, - CcKeyboardItem *reverse_conflict_item, - guint keyval, - GdkModifierType mask, - guint keycode) -{ - GtkWidget *dialog; - char *name; - int response; - - name = binding_name (keyval, keycode, mask, TRUE); - - /* translators: - * This is the text you get in a dialogue when an action has an associated - * "reverse" action, for example Alt+Tab going in the opposite direction to - * Alt+Shift+Tab. - * - * An example text would be: - * The "Switch to next input source" shortcut has an associated "Switch to - * previous input source" shortcut. Do you want to automatically set it to - * "Shift+Ctrl+Alt+Space"? */ - 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 \"%s\" shortcut has an associated \"%s\" shortcut. " - "Do you want to automatically set it to \"%s\"?"), - item->description, reverse_item->description, name); - - if (reverse_conflict_item != NULL) { - /* translators: - * This is the text you get in a dialogue when you try to use a shortcut - * that was already associated with another action, for example: - * "Alt+F4" is currently associated with "Close Window", ... */ - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - _("\"%s\" is currently associated with \"%s\", this shortcut will be" - " disabled if you move forward."), - name, reverse_conflict_item->description); - } - g_free (name); - - gtk_dialog_add_button (GTK_DIALOG (dialog), - _("_Assign"), - 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); - - return response; -} - -static void -handle_reverse_item (CcKeyboardItem *item, - CcKeyboardItem *reverse_item, - guint keyval, - GdkModifierType mask, - guint keycode, - GtkTreeView *view) -{ - GtkResponseType response; - GdkModifierType reverse_mask; - - reverse_mask = mask ^ GDK_SHIFT_MASK; - - if (!is_valid_binding(keyval, reverse_mask, keycode)) - return; - - if ((reverse_item->keyval != keyval) - || (reverse_item->keycode != keycode) - || (reverse_item->mask != reverse_mask)) - { - CcKeyboardItem *reverse_conflict_item; - char *binding_str; - - reverse_conflict_item = search_for_conflict_item (reverse_item, keyval, - reverse_mask, - keycode); - - response = show_reverse_item_dialog (view, item, reverse_item, - reverse_conflict_item, - keyval, reverse_mask, - keycode); - if (response == GTK_RESPONSE_ACCEPT) - { - binding_str = binding_name (keyval, keycode, reverse_mask, FALSE); - g_object_set (G_OBJECT (reverse_item), "binding", binding_str, NULL); - g_free (binding_str); - - if (reverse_conflict_item != NULL) - g_object_set (G_OBJECT (reverse_conflict_item), - "binding", NULL, NULL); - } - else - { - /* The existing reverse binding may be conflicting with the binding - * we are setting. Other conflicts have already been handled in - * accel_edited_callback() - */ - CcKeyboardItem *conflict_item; - conflict_item = search_for_conflict_item (item, keyval, - mask, keycode); - if (conflict_item != NULL) - { - g_warn_if_fail (conflict_item == reverse_item); - g_object_set (G_OBJECT (conflict_item), - "binding", NULL, NULL); - } - } - } -} - -static void -accel_edited_callback (GtkCellRendererText *cell, - const char *path_string, - guint keyval, - GdkModifierType mask, - guint keycode, - GtkTreeView *view) -{ - GtkTreeModel *model; - GtkTreePath *path = gtk_tree_path_new_from_string (path_string); - GtkTreeIter iter; - CcKeyboardItem *item; - CcKeyboardItem *conflict_item; - CcKeyboardItem *reverse_item; - char *str; - - 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, - DETAIL_KEYENTRY_COLUMN, &item, - -1); - - /* sanity check */ - if (item == NULL) - return; - - /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ - mask &= ~GDK_LOCK_MASK; - - conflict_item = search_for_conflict_item (item, keyval, mask, keycode); - - /* Check for unmodified keys */ - if (!is_valid_binding (keyval, mask, keycode)) - { - show_invalid_binding_dialog (view, keyval, mask, keycode); - - /* set it back to its previous value. */ - g_object_set (G_OBJECT (cell), - "accel-key", item->keyval, - "keycode", item->keycode, - "accel-mods", item->mask, - NULL); - return; - } - - reverse_item = cc_keyboard_item_get_reverse_item (item); - - /* flag to see if the new accelerator was in use by something */ - if ((conflict_item != NULL) && (conflict_item != reverse_item)) - { - GtkResponseType response; - - response = show_conflict_item_dialog (view, item, conflict_item, - keyval, mask, keycode); - - if (response == GTK_RESPONSE_ACCEPT) - { - g_object_set (G_OBJECT (conflict_item), "binding", NULL, NULL); - - str = binding_name (keyval, keycode, mask, FALSE); - g_object_set (G_OBJECT (item), "binding", str, NULL); - - g_free (str); - if (reverse_item == NULL) - return; - } - else - { - /* set it back to its previous value. */ - g_object_set (G_OBJECT (cell), - "accel-key", item->keyval, - "keycode", item->keycode, - "accel-mods", item->mask, - NULL); - return; - } - - } - - str = binding_name (keyval, keycode, mask, FALSE); - g_object_set (G_OBJECT (item), "binding", str, NULL); - - g_free (str); - - if (reverse_item != NULL) - handle_reverse_item (item, reverse_item, keyval, mask, keycode, view); -} - -static void -accel_cleared_callback (GtkCellRendererText *cell, - const char *path_string, - gpointer data) -{ - GtkTreeView *view = (GtkTreeView *) data; - GtkTreePath *path = gtk_tree_path_new_from_string (path_string); - CcKeyboardItem *item; - GtkTreeIter iter; - GtkTreeModel *model; - - 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, - DETAIL_KEYENTRY_COLUMN, &item, - -1); - - /* sanity check */ - if (item == NULL) - return; - - /* Unset the key */ - g_object_set (G_OBJECT (item), "binding", NULL, NULL); -} - -static gchar * -find_free_settings_path () +gchar* +find_free_settings_path (GSettings *settings) { char **used_names; char *dir = NULL; int i, num, n_names; - used_names = g_settings_get_strv (binding_settings, "custom-keybindings"); + used_names = g_settings_get_strv (settings, "custom-keybindings"); n_names = g_strv_length (used_names); for (num = 0; dir == NULL; num++) @@ -1614,219 +319,6 @@ find_free_settings_path () return dir; } -static void -add_custom_shortcut (GtkTreeView *tree_view, - GtkTreeModel *model) -{ - CcKeyboardItem *item; - GtkTreePath *path; - gchar *settings_path; - - item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); - - settings_path = find_free_settings_path (); - cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE); - g_free (settings_path); - - item->model = model; - - if (edit_custom_shortcut (item) && - item->command && item->command[0]) - { - GPtrArray *keys_array; - GtkTreeIter iter; - GHashTable *hash; - GVariantBuilder builder; - char **settings_paths; - int i; - - hash = get_hash_for_group (BINDING_GROUP_USER); - keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID); - if (keys_array == NULL) - { - keys_array = g_ptr_array_new (); - g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array); - } - - g_ptr_array_add (keys_array, item); - - gtk_list_store_append (GTK_LIST_STORE (model), &iter); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, DETAIL_KEYENTRY_COLUMN, item, -1); - - settings_paths = g_settings_get_strv (binding_settings, "custom-keybindings"); - g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); - for (i = 0; settings_paths[i]; i++) - g_variant_builder_add (&builder, "s", settings_paths[i]); - g_variant_builder_add (&builder, "s", item->gsettings_path); - g_settings_set_value (binding_settings, "custom-keybindings", - g_variant_builder_end (&builder)); - - /* 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_object_unref (item); - } -} - -static void -shortcut_entry_changed (GtkEntry *entry, - gpointer user_data) -{ - guint16 name_length; - guint16 command_length; - - name_length = gtk_entry_get_text_length (GTK_ENTRY (custom_shortcut_name_entry)); - command_length = gtk_entry_get_text_length (GTK_ENTRY (custom_shortcut_command_entry)); - - gtk_widget_set_sensitive (custom_shortcut_ok_button, - name_length > 0 && command_length > 0); -} - -static void -add_button_clicked (GtkWidget *button, - GtkBuilder *builder) -{ - GtkTreeView *treeview; - GtkTreeModel *model; - GtkTreeModel *section_model; - GtkTreeIter iter; - gboolean found, cont; - - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - model = gtk_tree_view_get_model (treeview); - - /* Select the Custom Shortcuts section - * before adding the shortcut itself */ - section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (WID (builder, "section_treeview"))); - cont = gtk_tree_model_get_iter_first (section_model, &iter); - found = FALSE; - while (cont) - { - BindingGroupType group; - - gtk_tree_model_get (section_model, &iter, - SECTION_GROUP_COLUMN, &group, - -1); - - if (group == BINDING_GROUP_USER) - { - found = TRUE; - break; - } - cont = gtk_tree_model_iter_next (section_model, &iter); - } - if (found) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID (builder, "section_treeview"))); - gtk_tree_selection_select_iter (selection, &iter); - } - - /* And add the shortcut */ - 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 -section_sort_item (GtkTreeModel *model, - GtkTreeIter *a, - GtkTreeIter *b, - gpointer data) -{ - char *a_desc; - int a_group; - char *b_desc; - int b_group; - int ret; - - gtk_tree_model_get (model, a, - SECTION_DESCRIPTION_COLUMN, &a_desc, - SECTION_GROUP_COLUMN, &a_group, - -1); - gtk_tree_model_get (model, b, - SECTION_DESCRIPTION_COLUMN, &b_desc, - SECTION_GROUP_COLUMN, &b_group, - -1); - - if (a_group == b_group && a_desc && b_desc) - ret = g_utf8_collate (a_desc, b_desc); - else - ret = a_group - b_group; - - g_free (a_desc); - g_free (b_desc); - - return ret; -} - -static gboolean -sections_separator_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - BindingGroupType type; - - gtk_tree_model_get (model, iter, SECTION_GROUP_COLUMN, &type, -1); - - return type == BINDING_GROUP_SEPARATOR; -} - -static void -xkb_options_combo_changed (GtkCellRendererCombo *combo, - gchar *model_path, - GtkTreeIter *model_iter, - gpointer data) -{ - GtkTreeView *shortcut_treeview; - GtkTreeModel *shortcut_model; - GtkTreeIter shortcut_iter; - GtkTreeSelection *selection; - CcKeyboardOption *option; - ShortcutType type; - GtkBuilder *builder = data; - - shortcut_treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")); - selection = gtk_tree_view_get_selection (shortcut_treeview); - if (!gtk_tree_selection_get_selected (selection, &shortcut_model, &shortcut_iter)) - return; - - gtk_tree_model_get (shortcut_model, &shortcut_iter, - DETAIL_KEYENTRY_COLUMN, &option, - DETAIL_TYPE_COLUMN, &type, - -1); - - if (type != SHORTCUT_TYPE_XKB_OPTION) - return; - - cc_keyboard_option_set_selection (option, model_iter); -} - static gboolean poke_xkb_option_row (GtkTreeModel *model, GtkTreePath *path, @@ -1855,7 +347,7 @@ xkb_option_changed (CcKeyboardOption *option, gtk_tree_model_foreach (model, poke_xkb_option_row, option); } -static void +void setup_keyboard_options (GtkListStore *store) { GList *l; @@ -1865,278 +357,43 @@ setup_keyboard_options (GtkListStore *store) G_CALLBACK (xkb_option_changed), store); } -static void -setup_dialog (CcPanel *panel, GtkBuilder *builder) +KeyList* +parse_keylist_from_file (const gchar *path) { - GtkCellRenderer *renderer; - GtkTreeViewColumn *column; - GtkWidget *widget; - GtkTreeView *treeview; - GtkTreeSelection *selection; - GList *focus_chain; - CcShell *shell; - GtkListStore *model; - GtkTreeModelSort *sort_model; - GtkStyleContext *context; + KeyList *keylist; + GError *err = NULL; + char *buf; + gsize buf_len; + guint i; - gtk_widget_set_size_request (GTK_WIDGET (panel), -1, 400); + GMarkupParseContext *ctx; + GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL }; - /* Setup the section treeview */ - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview")); - gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (treeview), - sections_separator_func, - panel, - NULL); + /* Parse file */ + if (!g_file_get_contents (path, &buf, &buf_len, &err)) + return NULL; - renderer = gtk_cell_renderer_text_new (); - column = gtk_tree_view_column_new_with_attributes (_("Section"), - renderer, - "text", SECTION_DESCRIPTION_COLUMN, - NULL); - g_object_set (renderer, - "width-chars", 20, - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); + keylist = g_new0 (KeyList, 1); + keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); + ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL); - gtk_tree_view_append_column (treeview, column); + if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) + { + g_warning ("Failed to parse '%s': '%s'", path, err->message); + g_error_free (err); + g_free (keylist->name); + g_free (keylist->package); + g_free (keylist->wm_name); - model = gtk_list_store_new (SECTION_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); - sort_model = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model))); - gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (sort_model)); - g_object_unref (model); + for (i = 0; i < keylist->entries->len; i++) + g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model), - SECTION_DESCRIPTION_COLUMN, - section_sort_item, - panel, - NULL); + g_array_free (keylist->entries, TRUE); + g_free (keylist); + keylist = NULL; + } + g_markup_parse_context_free (ctx); + g_free (buf); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model), - SECTION_DESCRIPTION_COLUMN, - GTK_SORT_ASCENDING); - g_object_unref (sort_model); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); - - gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); - - g_signal_connect (selection, "changed", - G_CALLBACK (section_selection_changed), builder); - section_selection_changed (selection, builder); - - /* Setup the shortcut treeview */ - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - - binding_settings = g_settings_new (BINDINGS_SCHEMA); - - renderer = gtk_cell_renderer_text_new (); - g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); - - column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); - gtk_tree_view_column_set_cell_data_func (column, renderer, description_set_func, NULL, NULL); - gtk_tree_view_column_set_resizable (column, FALSE); - gtk_tree_view_column_set_expand (column, TRUE); - - gtk_tree_view_append_column (treeview, column); - - renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL, - "accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER, - NULL); - - g_signal_connect (treeview, "button_press_event", - G_CALLBACK (start_editing_cb), renderer); - g_signal_connect (treeview, "row-activated", - G_CALLBACK (start_editing_kb_cb), renderer); - - g_signal_connect (renderer, "accel_edited", - G_CALLBACK (accel_edited_callback), - treeview); - g_signal_connect (renderer, "accel_cleared", - G_CALLBACK (accel_cleared_callback), - treeview); - - column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); - gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); - gtk_tree_view_column_set_resizable (column, FALSE); - gtk_tree_view_column_set_expand (column, FALSE); - - renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_COMBO, - "has-entry", FALSE, - "text-column", XKB_OPTION_DESCRIPTION_COLUMN, - "editable", TRUE, - "ellipsize", PANGO_ELLIPSIZE_END, - "width-chars", 25, - NULL); - g_signal_connect (renderer, "changed", - G_CALLBACK (xkb_options_combo_changed), builder); - - gtk_tree_view_column_pack_end (column, renderer, FALSE); - - gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); - - gtk_tree_view_append_column (treeview, column); - - model = gtk_list_store_new (DETAIL_N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); - gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model)); - g_object_unref (model); - - setup_keyboard_options (model); - - widget = GTK_WIDGET (gtk_builder_get_object (builder, "actions_swindow")); - context = gtk_widget_get_style_context (widget); - gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM); - widget = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut-toolbar")); - context = gtk_widget_get_style_context (widget); - gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP); - - /* set up the focus chain */ - focus_chain = g_list_append (NULL, WID (builder, "sections_swindow")); - focus_chain = g_list_append (focus_chain, WID (builder, "actions_swindow")); - focus_chain = g_list_append (focus_chain, WID (builder, "shortcut-toolbar")); - - widget = GTK_WIDGET (gtk_builder_get_object (builder, "shortcuts_grid")); - gtk_container_set_focus_chain (GTK_CONTAINER (widget), focus_chain); - g_list_free (focus_chain); - - /* set up the dialog */ - shell = cc_panel_get_shell (CC_PANEL (panel)); - widget = cc_shell_get_toplevel (shell); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); - g_signal_connect (selection, "changed", - G_CALLBACK (shortcut_selection_changed), - WID (builder, "remove-toolbutton")); - - /* setup the custom shortcut dialog */ - custom_shortcut_dialog = WID (builder, - "custom-shortcut-dialog"); - custom_shortcut_ok_button = WID (builder, "custom-shortcut-ok-button"); - custom_shortcut_name_entry = WID (builder, - "custom-shortcut-name-entry"); - g_signal_connect (custom_shortcut_name_entry, "changed", - G_CALLBACK (shortcut_entry_changed), NULL); - custom_shortcut_command_entry = WID (builder, - "custom-shortcut-command-entry"); - g_signal_connect (custom_shortcut_command_entry, "changed", - G_CALLBACK (shortcut_entry_changed), NULL); - g_signal_connect (WID (builder, "add-toolbutton"), - "clicked", G_CALLBACK (add_button_clicked), builder); - g_signal_connect (WID (builder, "remove-toolbutton"), - "clicked", G_CALLBACK (remove_button_clicked), builder); - - gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog), - GTK_RESPONSE_OK); - - gtk_window_set_transient_for (GTK_WINDOW (custom_shortcut_dialog), - GTK_WINDOW (widget)); - - gtk_window_set_resizable (GTK_WINDOW (custom_shortcut_dialog), FALSE); -} - -static void -on_window_manager_change (const char *wm_name, CcPanel *panel) -{ - reload_sections (panel); -} - -void -keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder) -{ - g_object_set_data (G_OBJECT (panel), "builder", builder); -#ifdef GDK_WINDOWING_X11 - if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) - wm_changed_id = wm_common_register_window_manager_change ((GFunc) on_window_manager_change, - panel); -#endif - pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL); - setup_dialog (panel, builder); - reload_sections (panel); -} - -gboolean -keyboard_shortcuts_set_section (CcPanel *panel, const char *section) -{ - GtkBuilder *builder; - GtkTreeModel *section_model; - GtkTreeIter iter; - gboolean found, cont; - - builder = g_object_get_data (G_OBJECT (panel), "builder"); - if (builder == NULL) - { - /* Remember the section name to be set later */ - g_object_set_data_full (G_OBJECT (panel), "section-to-set", g_strdup (section), g_free); - return TRUE; - } - section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (WID (builder, "section_treeview"))); - cont = gtk_tree_model_get_iter_first (section_model, &iter); - found = FALSE; - while (cont) - { - char *id; - - gtk_tree_model_get (section_model, &iter, - SECTION_ID_COLUMN, &id, - -1); - - if (g_strcmp0 (id, section) == 0) - { - found = TRUE; - g_free (id); - break; - } - g_free (id); - cont = gtk_tree_model_iter_next (section_model, &iter); - } - if (found) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID (builder, "section_treeview"))); - gtk_tree_selection_select_iter (selection, &iter); - } - else - { - g_warning ("Could not find section '%s' to switch to.", section); - } - - return found; -} - -void -keyboard_shortcuts_dispose (CcPanel *panel) -{ - if (kb_system_sections != NULL) - { - g_hash_table_destroy (kb_system_sections); - kb_system_sections = NULL; - } - if (kb_apps_sections != NULL) - { - g_hash_table_destroy (kb_apps_sections); - kb_apps_sections = NULL; - } - if (kb_user_sections != NULL) - { - g_hash_table_destroy (kb_user_sections); - kb_user_sections = NULL; - } - if (pictures_regex != NULL) - { - g_regex_unref (pictures_regex); - pictures_regex = NULL; - } - - g_clear_object (&binding_settings); - - g_clear_pointer (&custom_shortcut_dialog, gtk_widget_destroy); - - if (wm_changed_id) - { - wm_common_unregister_window_manager_change (wm_changed_id); - wm_changed_id = NULL; - } - - cc_keyboard_option_clear_all (); + return keylist; } diff --git a/panels/keyboard/keyboard-shortcuts.h b/panels/keyboard/keyboard-shortcuts.h index 02564b167..4631b3499 100644 --- a/panels/keyboard/keyboard-shortcuts.h +++ b/panels/keyboard/keyboard-shortcuts.h @@ -79,6 +79,14 @@ enum SECTION_N_COLUMNS }; -void keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder); -gboolean keyboard_shortcuts_set_section (CcPanel *panel, const char *section); -void keyboard_shortcuts_dispose (CcPanel *panel); +gchar* find_free_settings_path (GSettings *settings); + +void fill_xkb_options_shortcuts (GtkTreeModel *model); + +void setup_keyboard_options (GtkListStore *store); + +gboolean is_valid_binding (guint keyval, + GdkModifierType mask, + guint keycode); + +KeyList* parse_keylist_from_file (const gchar *path);