From ae26a638bc05a63877ec60ab5d52e938b6eed426 Mon Sep 17 00:00:00 2001 From: Rodrigo Moya Date: Thu, 9 Dec 2010 10:42:33 +0100 Subject: [PATCH] keyboard: Display sections in a separate treeview --- panels/keyboard/gnome-keyboard-panel.c | 732 +--------------------- panels/keyboard/gnome-keyboard-panel.ui | 5 +- panels/keyboard/keyboard-shortcuts.c | 772 +++++++++++++++++++++++- 3 files changed, 773 insertions(+), 736 deletions(-) diff --git a/panels/keyboard/gnome-keyboard-panel.c b/panels/keyboard/gnome-keyboard-panel.c index e6f4d9eba..97a21532e 100644 --- a/panels/keyboard/gnome-keyboard-panel.c +++ b/panels/keyboard/gnome-keyboard-panel.c @@ -7,85 +7,21 @@ #include #include #include -#include #include #include -#include #include #include -#include "wm-common.h" #include "gnome-keyboard-panel.h" -#include "eggcellrendererkeys.h" -#define GCONF_BINDING_DIR "/desktop/gnome/keybindings" #define MAX_ELEMENTS_BEFORE_SCROLLING 10 #define MAX_CUSTOM_SHORTCUTS 1000 #define RESPONSE_ADD 0 #define RESPONSE_REMOVE 1 -typedef struct { - char *name; - /* The gettext package to use to translate the section title */ - char *package; - /* Name of the window manager the keys would apply to */ - char *wm_name; - /* an array of KeyListEntry */ - GArray *entries; -} KeyList; - -typedef enum { - COMPARISON_NONE = 0, - COMPARISON_GT, - COMPARISON_LT, - COMPARISON_EQ -} Comparison; - -typedef struct -{ - char *name; - int value; - char *key; - char *description_name; - char *cmd_name; - Comparison comparison; -} KeyListEntry; - -enum -{ - DESCRIPTION_COLUMN, - KEYENTRY_COLUMN, - N_COLUMNS -}; - -typedef struct -{ - char *gconf_key; - guint keyval; - guint keycode; - EggVirtualModifierType mask; - gboolean editable; - GtkTreeModel *model; - char *description; - char *desc_gconf_key; - gboolean desc_editable; - char *command; - char *cmd_gconf_key; - gboolean cmd_editable; - guint gconf_cnxn; - guint gconf_cnxn_desc; - guint gconf_cnxn_cmd; -} KeyEntry; - -static guint maybe_block_accels_id = 0; static gboolean block_accels = FALSE; -static GtkWidget *custom_shortcut_dialog = NULL; -static GtkWidget *custom_shortcut_name_entry = NULL; -static GtkWidget *custom_shortcut_command_entry = NULL; static GHashTable *keyb_sections = NULL; -#define WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name))) - static char* binding_name (guint keyval, guint keycode, @@ -100,91 +36,6 @@ binding_name (guint keyval, return g_strdup (translate ? _("Disabled") : ""); } -static gboolean -binding_from_string (const char *str, - guint *accelerator_key, - guint *keycode, - EggVirtualModifierType *accelerator_mods) -{ - g_return_val_if_fail (accelerator_key != NULL, FALSE); - - if (str == NULL || strcmp (str, "disabled") == 0) - { - *accelerator_key = 0; - *keycode = 0; - *accelerator_mods = 0; - return TRUE; - } - - egg_accelerator_parse_virtual (str, accelerator_key, keycode, accelerator_mods); - - if (*accelerator_key == 0) - return FALSE; - else - return TRUE; -} - -static void -accel_set_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - KeyEntry *key_entry; - - gtk_tree_model_get (model, iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - if (key_entry == NULL) - g_object_set (cell, - "visible", FALSE, - NULL); - else if (! key_entry->editable) - g_object_set (cell, - "visible", TRUE, - "editable", FALSE, - "accel_key", key_entry->keyval, - "accel_mask", key_entry->mask, - "keycode", key_entry->keycode, - "style", PANGO_STYLE_ITALIC, - NULL); - else - g_object_set (cell, - "visible", TRUE, - "editable", TRUE, - "accel_key", key_entry->keyval, - "accel_mask", key_entry->mask, - "keycode", key_entry->keycode, - "style", PANGO_STYLE_NORMAL, - NULL); -} - -static void -description_set_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - KeyEntry *key_entry; - - gtk_tree_model_get (model, iter, - KEYENTRY_COLUMN, &key_entry, - -1); - - if (key_entry != NULL) - g_object_set (cell, - "editable", FALSE, - "text", key_entry->description != NULL ? - key_entry->description : _(""), - NULL); - else - g_object_set (cell, - "editable", FALSE, NULL); -} - static gboolean keybinding_key_changed_foreach (GtkTreeModel *model, GtkTreePath *path, @@ -433,43 +284,6 @@ key_is_already_shown (GtkTreeModel *model, const KeyListEntry *entry) return data.found; } -static gboolean -should_show_key (const KeyListEntry *entry) -{ - int value; - GConfClient *client; - - if (entry->comparison == COMPARISON_NONE) - return TRUE; - - g_return_val_if_fail (entry->key != NULL, FALSE); - - client = gconf_client_get_default(); - value = gconf_client_get_int (client, entry->key, NULL); - g_object_unref (client); - - switch (entry->comparison) { - case COMPARISON_NONE: - /* For compiler warnings */ - g_assert_not_reached (); - return FALSE; - case COMPARISON_GT: - if (value > entry->value) - return TRUE; - break; - case COMPARISON_LT: - if (value < entry->value) - return TRUE; - break; - case COMPARISON_EQ: - if (value == entry->value) - return TRUE; - break; - } - - return FALSE; -} - static gboolean count_rows_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { @@ -530,14 +344,10 @@ append_keys_to_tree (GtkBuilder *builder, const gchar *title, const KeyListEntry *keys_list) { - GConfClient *client; GtkTreeIter parent_iter, iter; GtkTreeModel *model; gint i, j; - client = gconf_client_get_default (); - model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"))); - /* Try to find a section parent iter, if it already exists */ find_section (model, &iter, title); parent_iter = iter; @@ -549,121 +359,7 @@ append_keys_to_tree (GtkBuilder *builder, * then we need to scroll now */ ensure_scrollbar (builder, i - 1); - for (j = 0; keys_list[j].name != NULL; j++) - { - GConfEntry *entry; - KeyEntry *key_entry; - const gchar *key_string; - gchar *key_value; - gchar *description; - gchar *command; - - if (!should_show_key (&keys_list[j])) - continue; - - if (key_is_already_shown (model, &keys_list[j])) - continue; - - key_string = keys_list[j].name; - - entry = gconf_client_get_entry (client, - key_string, - NULL, - TRUE, - NULL); - if (entry == NULL) - { - /* We don't actually want to popup a dialog - just skip this one */ - continue; - } - - if (keys_list[j].description_name != NULL) - { - description = gconf_client_get_string (client, keys_list[j].description_name, NULL); - } - else - { - description = NULL; - - if (gconf_entry_get_schema_name (entry)) - { - GConfSchema *schema; - - schema = gconf_client_get_schema (client, - gconf_entry_get_schema_name (entry), - NULL); - if (schema != NULL) - { - description = g_strdup (gconf_schema_get_short_desc (schema)); - gconf_schema_free (schema); - } - } - } - - if (description == NULL) - { - /* Only print a warning for keys that should have a schema */ - if (keys_list[j].description_name == NULL) - g_warning ("No description for key '%s'", key_string); - } - - if (keys_list[j].cmd_name != NULL) - { - command = gconf_client_get_string (client, keys_list[j].cmd_name, NULL); - } - else - { - command = NULL; - } - - key_entry = g_new0 (KeyEntry, 1); - key_entry->gconf_key = g_strdup (key_string); - key_entry->editable = gconf_entry_get_is_writable (entry); - key_entry->model = model; - key_entry->description = description; - key_entry->command = command; - if (keys_list[j].description_name != NULL) - { - key_entry->desc_gconf_key = g_strdup (keys_list[j].description_name); - key_entry->desc_editable = gconf_client_key_is_writable (client, key_entry->desc_gconf_key, NULL); - key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, - key_entry->desc_gconf_key, - (GConfClientNotifyFunc) &keybinding_description_changed, - key_entry, NULL, NULL); - } - if (keys_list[j].cmd_name != NULL) - { - key_entry->cmd_gconf_key = g_strdup (keys_list[j].cmd_name); - key_entry->cmd_editable = gconf_client_key_is_writable (client, key_entry->cmd_gconf_key, NULL); - key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, - key_entry->cmd_gconf_key, - (GConfClientNotifyFunc) &keybinding_command_changed, - key_entry, NULL, NULL); - } - - gconf_client_add_dir (client, key_string, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); - key_entry->gconf_cnxn = gconf_client_notify_add (client, - key_string, - (GConfClientNotifyFunc) &keybinding_key_changed, - key_entry, NULL, NULL); - - key_value = gconf_client_get_string (client, key_string, NULL); - binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); - g_free (key_value); - - gconf_entry_free (entry); - ensure_scrollbar (builder, i); - - ++i; - gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter); - /* we use the DESCRIPTION_COLUMN only for the section headers */ - gtk_tree_store_set (GTK_TREE_STORE (model), &iter, - KEYENTRY_COLUMN, key_entry, - -1); - gtk_tree_view_expand_all (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"))); - } - - g_object_unref (client); +=============================================================== /* Don't show an empty section */ if (gtk_tree_model_iter_n_children (model, &parent_iter) == 0) @@ -675,318 +371,6 @@ append_keys_to_tree (GtkBuilder *builder, gtk_widget_show (WID (builder, "shortcuts_vbox")); } -static void -parse_start_tag (GMarkupParseContext *ctx, - const gchar *element_name, - const gchar **attr_names, - const gchar **attr_values, - gpointer user_data, - GError **error) -{ - KeyList *keylist = (KeyList *) user_data; - KeyListEntry key; - const char *name, *gconf_key; - int value; - Comparison comparison; - - name = NULL; - - /* The top-level element, names the section in the tree */ - if (g_str_equal (element_name, "KeyListEntries")) - { - const char *wm_name = NULL; - const char *package = NULL; - - while (*attr_names && *attr_values) - { - if (g_str_equal (*attr_names, "name")) - { - if (**attr_values) - name = *attr_values; - } else if (g_str_equal (*attr_names, "wm_name")) { - if (**attr_values) - wm_name = *attr_values; - } else if (g_str_equal (*attr_names, "package")) { - if (**attr_values) - package = *attr_values; - } - ++attr_names; - ++attr_values; - } - - if (name) - { - if (keylist->name) - g_warning ("Duplicate section name"); - g_free (keylist->name); - keylist->name = g_strdup (name); - } - if (wm_name) - { - if (keylist->wm_name) - g_warning ("Duplicate window manager name"); - g_free (keylist->wm_name); - keylist->wm_name = g_strdup (wm_name); - } - if (package) - { - if (keylist->package) - g_warning ("Duplicate gettext package name"); - g_free (keylist->package); - keylist->package = g_strdup (package); - } - return; - } - - if (!g_str_equal (element_name, "KeyListEntry") - || attr_names == NULL - || attr_values == NULL) - return; - - value = 0; - comparison = COMPARISON_NONE; - gconf_key = NULL; - - while (*attr_names && *attr_values) - { - if (g_str_equal (*attr_names, "name")) - { - /* skip if empty */ - if (**attr_values) - name = *attr_values; - } else if (g_str_equal (*attr_names, "value")) { - if (**attr_values) { - value = (int) g_ascii_strtoull (*attr_values, NULL, 0); - } - } else if (g_str_equal (*attr_names, "key")) { - if (**attr_values) { - gconf_key = *attr_values; - } - } else if (g_str_equal (*attr_names, "comparison")) { - if (**attr_values) { - if (g_str_equal (*attr_values, "gt")) { - comparison = COMPARISON_GT; - } else if (g_str_equal (*attr_values, "lt")) { - comparison = COMPARISON_LT; - } else if (g_str_equal (*attr_values, "eq")) { - comparison = COMPARISON_EQ; - } - } - } - - ++attr_names; - ++attr_values; - } - - if (name == NULL) - return; - - key.name = g_strdup (name); - key.description_name = NULL; - key.value = value; - if (gconf_key) - key.key = g_strdup (gconf_key); - else - key.key = NULL; - key.comparison = comparison; - key.cmd_name = NULL; - 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_keys_to_tree_from_file (GtkBuilder *builder, - const char *filename, - char **wm_keybindings) -{ - GMarkupParseContext *ctx; - GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL }; - KeyList *keylist; - KeyListEntry key, *keys; - GError *err = NULL; - char *buf; - const char *title; - gsize buf_len; - guint i; - - if (!g_file_get_contents (filename, &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'", filename, 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; - key.description_name = NULL; - key.key = NULL; - key.value = 0; - key.comparison = COMPARISON_NONE; - g_array_append_val (keylist->entries, key); - - keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE); - if (keylist->package) - { - bind_textdomain_codeset (keylist->package, "UTF-8"); - title = dgettext (keylist->package, keylist->name); - } else { - title = _(keylist->name); - } - - append_keys_to_tree (builder, title, keys); - - g_free (keylist->name); - g_free (keylist->package); - for (i = 0; keys[i].name != NULL; i++) - g_free (keys[i].name); - g_free (keylist); -} - -static void -append_keys_to_tree_from_gconf (GtkBuilder *builder, const gchar *gconf_path) -{ - GConfClient *client; - GSList *custom_list, *l; - GArray *entries; - KeyListEntry key; - - /* load custom shortcuts from GConf */ - entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); - - key.key = NULL; - key.value = 0; - key.comparison = COMPARISON_NONE; - - client = gconf_client_get_default (); - custom_list = gconf_client_all_dirs (client, gconf_path, NULL); - - for (l = custom_list; l != NULL; l = l->next) - { - key.name = g_strconcat (l->data, "/binding", NULL); - key.cmd_name = g_strconcat (l->data, "/action", NULL); - key.description_name = g_strconcat (l->data, "/name", NULL); - g_array_append_val (entries, key); - - g_free (l->data); - } - - g_slist_free (custom_list); - g_object_unref (client); - - if (entries->len > 0) - { - KeyListEntry *keys; - int i; - - /* Empty KeyListEntry to end the array */ - key.name = NULL; - key.description_name = NULL; - g_array_append_val (entries, key); - - keys = (KeyListEntry *) entries->data; - append_keys_to_tree (builder, _("Custom Shortcuts"), keys); - for (i = 0; i < entries->len; ++i) - { - g_free (keys[i].name); - g_free (keys[i].description_name); - } - } - - g_array_free (entries, TRUE); -} - -static void -reload_key_entries (GtkBuilder *builder) -{ - gchar **wm_keybindings; - GDir *dir; - const char *name; - GList *list, *l; - - wm_keybindings = wm_common_get_current_keybindings(); - - clear_old_model (builder); - - dir = g_dir_open (GNOMECC_KEYBINDINGS_DIR, 0, NULL); - if (!dir) - return; - - list = NULL; - for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir)) - { - if (g_str_has_suffix (name, ".xml")) - { - list = g_list_insert_sorted (list, g_strdup (name), - (GCompareFunc) g_ascii_strcasecmp); - } - } - g_dir_close (dir); - - for (l = list; l != NULL; l = l->next) - { - gchar *path; - - path = g_build_filename (GNOMECC_KEYBINDINGS_DIR, l->data, NULL); - append_keys_to_tree_from_file (builder, path, wm_keybindings); - g_free (l->data); - g_free (path); - } - g_list_free (list); - - /* Load custom shortcuts _after_ system-provided ones, - * since some of the custom shortcuts may also be listed - * in a file. Loading the custom shortcuts last makes - * such keys not show up in the custom section. - */ - append_keys_to_tree_from_gconf (builder, GCONF_BINDING_DIR); - - g_strfreev (wm_keybindings); -} - static void key_entry_controlling_key_changed (GConfClient *client, guint cnxn_id, @@ -1819,117 +1203,6 @@ remove_button_clicked (GtkWidget *button, } } -static void -setup_dialog (CcPanel *panel, GtkBuilder *builder) -{ - GConfClient *client; - GtkCellRenderer *renderer; - GtkTreeViewColumn *column; - GtkWidget *widget; - GtkTreeView *treeview; - GtkTreeSelection *selection; - GSList *allowed_keys; - CcShell *shell; - - treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, - "shortcut_treeview")); - - client = gconf_client_get_default (); - - g_signal_connect (treeview, "button_press_event", - G_CALLBACK (start_editing_cb), builder); - g_signal_connect (treeview, "row-activated", - G_CALLBACK (start_editing_kb_cb), NULL); - - renderer = gtk_cell_renderer_text_new (); - - g_signal_connect (renderer, "edited", - G_CALLBACK (description_edited_callback), - treeview); - - column = gtk_tree_view_column_new_with_attributes (_("Action"), - renderer, - "text", DESCRIPTION_COLUMN, - 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_append_column (treeview, column); - gtk_tree_view_column_set_sort_column_id (column, DESCRIPTION_COLUMN); - - renderer = (GtkCellRenderer *) g_object_new (EGG_TYPE_CELL_RENDERER_KEYS, - "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X, - NULL); - - g_signal_connect (renderer, "accel_edited", - G_CALLBACK (accel_edited_callback), - treeview); - - g_signal_connect (renderer, "accel_cleared", - G_CALLBACK (accel_cleared_callback), - treeview); - - column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL); - gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); - gtk_tree_view_column_set_resizable (column, FALSE); - - gtk_tree_view_append_column (treeview, column); - gtk_tree_view_column_set_sort_column_id (column, KEYENTRY_COLUMN); - - gconf_client_add_dir (client, GCONF_BINDING_DIR, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); - gconf_client_add_dir (client, "/apps/metacity/general", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); - gconf_client_notify_add (client, - "/apps/metacity/general/num_workspaces", - (GConfClientNotifyFunc) key_entry_controlling_key_changed, - builder, NULL, NULL); - - /* set up the dialog */ - reload_key_entries (builder); - - shell = cc_panel_get_shell (CC_PANEL (panel)); - widget = cc_shell_get_toplevel (shell); - - maybe_block_accels_id = g_signal_connect (widget, "key_press_event", - G_CALLBACK (maybe_block_accels), NULL); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); - g_signal_connect (selection, "changed", - G_CALLBACK (selection_changed), - WID (builder, "remove-button")); - - allowed_keys = gconf_client_get_list (client, - GCONF_BINDING_DIR "/allowed_keys", - GCONF_VALUE_STRING, - NULL); - if (allowed_keys != NULL) - { - g_slist_foreach (allowed_keys, (GFunc)g_free, NULL); - g_slist_free (allowed_keys); - gtk_widget_set_sensitive (WID (builder, "add-button"), - FALSE); - } - - g_object_unref (client); - - /* setup the custom shortcut dialog */ - custom_shortcut_dialog = WID (builder, - "custom-shortcut-dialog"); - custom_shortcut_name_entry = WID (builder, - "custom-shortcut-name-entry"); - custom_shortcut_command_entry = WID (builder, - "custom-shortcut-command-entry"); - g_signal_connect (WID (builder, "add-button"), - "clicked", G_CALLBACK (add_button_clicked), builder); - g_signal_connect (WID (builder, "remove-button"), - "clicked", G_CALLBACK (remove_button_clicked), builder); - - 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)); -} - static void on_window_manager_change (const char *wm_name, GtkBuilder *builder) { @@ -1942,9 +1215,6 @@ gnome_keybinding_properties_init (CcPanel *panel, GtkBuilder *builder) wm_common_register_window_manager_change ((GFunc) on_window_manager_change, builder); - keyb_sections = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, free_key_list); - setup_dialog (panel, builder); } void diff --git a/panels/keyboard/gnome-keyboard-panel.ui b/panels/keyboard/gnome-keyboard-panel.ui index e7d532248..1732920c6 100644 --- a/panels/keyboard/gnome-keyboard-panel.ui +++ b/panels/keyboard/gnome-keyboard-panel.ui @@ -552,6 +552,7 @@ True True + False @@ -565,8 +566,8 @@ True True - never - never + automatic + automatic in diff --git a/panels/keyboard/keyboard-shortcuts.c b/panels/keyboard/keyboard-shortcuts.c index 490025f8a..2496c47b2 100644 --- a/panels/keyboard/keyboard-shortcuts.c +++ b/panels/keyboard/keyboard-shortcuts.c @@ -19,25 +19,791 @@ * Rodrigo Moya */ +#include +#include +#include "eggcellrendererkeys.h" #include "keyboard-shortcuts.h" +#include "wm-common.h" -GHashTable *kb_sections = NULL; +#define GCONF_BINDING_DIR "/desktop/gnome/keybindings" +#define WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name))) + +typedef struct { + char *name; + /* The gettext package to use to translate the section title */ + char *package; + /* Name of the window manager the keys would apply to */ + char *wm_name; + /* an array of KeyListEntry */ + GArray *entries; +} KeyList; + +typedef enum { + COMPARISON_NONE = 0, + COMPARISON_GT, + COMPARISON_LT, + COMPARISON_EQ +} Comparison; + +typedef struct +{ + char *name; + int value; + char *key; + char *description_name; + char *cmd_name; + Comparison comparison; +} KeyListEntry; + +typedef struct +{ + char *gconf_key; + guint keyval; + guint keycode; + EggVirtualModifierType mask; + gboolean editable; + GtkTreeModel *model; + char *description; + char *desc_gconf_key; + gboolean desc_editable; + char *command; + char *cmd_gconf_key; + gboolean cmd_editable; + guint gconf_cnxn; + guint gconf_cnxn_desc; + guint gconf_cnxn_cmd; +} KeyEntry; + +enum +{ + DESCRIPTION_COLUMN, + KEYENTRY_COLUMN, + N_COLUMNS +}; + +static guint maybe_block_accels_id = 0; +static GtkWidget *custom_shortcut_dialog = NULL; +static GtkWidget *custom_shortcut_name_entry = NULL; +static GtkWidget *custom_shortcut_command_entry = NULL; +static GHashTable *kb_sections = NULL; static void -free_key_list (gpointer list) +free_key_array (KeyEntry **keys) { + if (keys != NULL) + { + gint i; + + for (i = 0; i < G_N_ELEMENTS (keys); i++) + { + g_free (keys[i]->gconf_key); + g_free (keys[i]->description); + g_free (keys[i]->desc_gconf_key); + g_free (keys[i]->command); + g_free (keys[i]->cmd_gconf_key); + } + + g_free (keys); + } +} + +static gboolean +should_show_key (const KeyListEntry *entry) +{ + int value; + GConfClient *client; + + if (entry->comparison == COMPARISON_NONE) + return TRUE; + + g_return_val_if_fail (entry->key != NULL, FALSE); + + client = gconf_client_get_default(); + value = gconf_client_get_int (client, entry->key, NULL); + g_object_unref (client); + + switch (entry->comparison) { + case COMPARISON_NONE: + /* For compiler warnings */ + g_assert_not_reached (); + return FALSE; + case COMPARISON_GT: + if (value > entry->value) + return TRUE; + break; + case COMPARISON_LT: + if (value < entry->value) + return TRUE; + break; + case COMPARISON_EQ: + if (value == entry->value) + return TRUE; + break; + } + + return FALSE; +} + +static gboolean +binding_from_string (const char *str, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods) +{ + g_return_val_if_fail (accelerator_key != NULL, FALSE); + + if (str == NULL || strcmp (str, "disabled") == 0) + { + *accelerator_key = 0; + *keycode = 0; + *accelerator_mods = 0; + return TRUE; + } + + egg_accelerator_parse_virtual (str, accelerator_key, keycode, accelerator_mods); + + if (*accelerator_key == 0) + return FALSE; + else + return TRUE; +} + +static void +append_section (GtkBuilder *builder, const gchar *title, const KeyListEntry *keys_list) +{ + GArray *keys_array; + GtkTreeModel *model; + GConfClient *client; + GtkTreeIter iter; + gint i; + + client = gconf_client_get_default (); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"))); + + g_print ("Appending section %s\n", title); + + /* 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, + DESCRIPTION_COLUMN, title, + -1); + + /* Add all KeyEntry's for this section */ + keys_array = g_array_new (TRUE, FALSE, sizeof (KeyEntry)); + + for (i = 0; keys_list[i].name != NULL; i++) + { + GConfEntry *entry; + const gchar *key_string; + gchar *description, *command, *key_value; + KeyEntry *key_entry; + + if (!should_show_key (&keys_list[i])) + continue; + + key_string = keys_list[i].name; + + entry = gconf_client_get_entry (client, + key_string, + NULL, + TRUE, + NULL); + if (entry == NULL) + { + /* We don't actually want to popup a dialog - just skip this one */ + continue; + } + + if (keys_list[i].description_name != NULL) + { + description = gconf_client_get_string (client, keys_list[i].description_name, NULL); + } + else + { + description = NULL; + + if (gconf_entry_get_schema_name (entry)) + { + GConfSchema *schema; + + schema = gconf_client_get_schema (client, + gconf_entry_get_schema_name (entry), + NULL); + if (schema != NULL) + { + description = g_strdup (gconf_schema_get_short_desc (schema)); + gconf_schema_free (schema); + } + } + } + + if (description == NULL) + { + /* Only print a warning for keys that should have a schema */ + if (keys_list[i].description_name == NULL) + g_warning ("No description for key '%s'", key_string); + } + + if (keys_list[i].cmd_name != NULL) + { + command = gconf_client_get_string (client, keys_list[i].cmd_name, NULL); + } + else + { + command = NULL; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->gconf_key = g_strdup (key_string); + key_entry->editable = gconf_entry_get_is_writable (entry); + key_entry->model = model; + key_entry->description = description; + key_entry->command = command; + if (keys_list[i].description_name != NULL) + { + key_entry->desc_gconf_key = g_strdup (keys_list[i].description_name); + key_entry->desc_editable = gconf_client_key_is_writable (client, key_entry->desc_gconf_key, NULL); + /* key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, */ + /* key_entry->desc_gconf_key, */ + /* (GConfClientNotifyFunc) &keybinding_description_changed, */ + /* key_entry, NULL, NULL); */ + } + if (keys_list[i].cmd_name != NULL) + { + /* key_entry->cmd_gconf_key = g_strdup (keys_list[i].cmd_name); */ + /* key_entry->cmd_editable = gconf_client_key_is_writable (client, key_entry->cmd_gconf_key, NULL); */ + /* key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, */ + /* key_entry->cmd_gconf_key, */ + /* (GConfClientNotifyFunc) &keybinding_command_changed, */ + /* key_entry, NULL, NULL); */ + } + + gconf_client_add_dir (client, key_string, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + /* key_entry->gconf_cnxn = gconf_client_notify_add (client, */ + /* key_string, */ + /* (GConfClientNotifyFunc) &keybinding_key_changed, */ + /* key_entry, NULL, NULL); */ + + key_value = gconf_client_get_string (client, key_string, NULL); + binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); + g_free (key_value); + + gconf_entry_free (entry); + + g_array_append_val (keys_array, key_entry); + } + + g_object_unref (client); + + /* Add the keys to the hash table */ + if (keys_array->len > 0) + { + g_hash_table_insert (kb_sections, g_strdup (title), keys_array->data); + } + + g_array_free (keys_array, FALSE); +} + +static void +parse_start_tag (GMarkupParseContext *ctx, + const gchar *element_name, + const gchar **attr_names, + const gchar **attr_values, + gpointer user_data, + GError **error) +{ + KeyList *keylist = (KeyList *) user_data; + KeyListEntry key; + const char *name, *gconf_key; + int value; + Comparison comparison; + + name = NULL; + + /* The top-level element, names the section in the tree */ + if (g_str_equal (element_name, "KeyListEntries")) + { + const char *wm_name = NULL; + const char *package = NULL; + + while (*attr_names && *attr_values) + { + if (g_str_equal (*attr_names, "name")) + { + if (**attr_values) + name = *attr_values; + } else if (g_str_equal (*attr_names, "wm_name")) { + if (**attr_values) + wm_name = *attr_values; + } else if (g_str_equal (*attr_names, "package")) { + if (**attr_values) + package = *attr_values; + } + ++attr_names; + ++attr_values; + } + + if (name) + { + if (keylist->name) + g_warning ("Duplicate section name"); + g_free (keylist->name); + keylist->name = g_strdup (name); + } + if (wm_name) + { + if (keylist->wm_name) + g_warning ("Duplicate window manager name"); + g_free (keylist->wm_name); + keylist->wm_name = g_strdup (wm_name); + } + if (package) + { + if (keylist->package) + g_warning ("Duplicate gettext package name"); + g_free (keylist->package); + keylist->package = g_strdup (package); + } + return; + } + + if (!g_str_equal (element_name, "KeyListEntry") + || attr_names == NULL + || attr_values == NULL) + return; + + value = 0; + comparison = COMPARISON_NONE; + gconf_key = NULL; + + while (*attr_names && *attr_values) + { + if (g_str_equal (*attr_names, "name")) + { + /* skip if empty */ + if (**attr_values) + name = *attr_values; + } else if (g_str_equal (*attr_names, "value")) { + if (**attr_values) { + value = (int) g_ascii_strtoull (*attr_values, NULL, 0); + } + } else if (g_str_equal (*attr_names, "key")) { + if (**attr_values) { + gconf_key = *attr_values; + } + } else if (g_str_equal (*attr_names, "comparison")) { + if (**attr_values) { + if (g_str_equal (*attr_values, "gt")) { + comparison = COMPARISON_GT; + } else if (g_str_equal (*attr_values, "lt")) { + comparison = COMPARISON_LT; + } else if (g_str_equal (*attr_values, "eq")) { + comparison = COMPARISON_EQ; + } + } + } + + ++attr_names; + ++attr_values; + } + + if (name == NULL) + return; + + key.name = g_strdup (name); + key.description_name = NULL; + key.value = value; + if (gconf_key) + key.key = g_strdup (gconf_key); + else + key.key = NULL; + key.comparison = comparison; + key.cmd_name = NULL; + 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, gchar **wm_keybindings) +{ + GError *err = NULL; + char *buf; + gsize buf_len; + KeyList *keylist; + KeyListEntry key, *keys; + const char *title; + 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; + key.description_name = NULL; + key.key = NULL; + key.value = 0; + key.comparison = COMPARISON_NONE; + g_array_append_val (keylist->entries, key); + + keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE); + if (keylist->package) + { + bind_textdomain_codeset (keylist->package, "UTF-8"); + title = dgettext (keylist->package, keylist->name); + } else { + title = _(keylist->name); + } + + append_section (builder, title, keys); + + g_free (keylist->name); + g_free (keylist->package); + + for (i = 0; keys[i].name != NULL; i++) + g_free (keys[i].name); + + g_free (keylist); +} + +static void +append_sections_from_gconf (GtkBuilder *builder, const gchar *gconf_path) +{ + GConfClient *client; + GSList *custom_list, *l; + GArray *entries; + KeyListEntry key; + + /* load custom shortcuts from GConf */ + entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); + + key.key = NULL; + key.value = 0; + key.comparison = COMPARISON_NONE; + + client = gconf_client_get_default (); + custom_list = gconf_client_all_dirs (client, gconf_path, NULL); + + for (l = custom_list; l != NULL; l = l->next) + { + key.name = g_strconcat (l->data, "/binding", NULL); + key.cmd_name = g_strconcat (l->data, "/action", NULL); + key.description_name = g_strconcat (l->data, "/name", NULL); + g_array_append_val (entries, key); + + g_free (l->data); + } + + g_slist_free (custom_list); + g_object_unref (client); + + if (entries->len > 0) + { + KeyListEntry *keys; + int i; + + /* Empty KeyListEntry to end the array */ + key.name = NULL; + key.description_name = NULL; + g_array_append_val (entries, key); + + keys = (KeyListEntry *) entries->data; + append_section (builder, _("Custom Shortcuts"), keys); + for (i = 0; i < entries->len; ++i) + { + g_free (keys[i].name); + g_free (keys[i].description_name); + } + } + + g_array_free (entries, TRUE); +} + +static void +reload_sections (GtkBuilder *builder) +{ + gchar **wm_keybindings; + GDir *dir; + const gchar *name; + GtkTreeModel *section_model, *shortcut_model; + + section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"))); + 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 */ + gtk_list_store_clear (GTK_LIST_STORE (section_model)); + gtk_list_store_clear (GTK_LIST_STORE (shortcut_model)); + + /* Load WM keybindings */ + wm_keybindings = wm_common_get_current_keybindings (); + + dir = g_dir_open (GNOMECC_KEYBINDINGS_DIR, 0, NULL); + if (!dir) + return; + + for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir)) + { + if (g_str_has_suffix (name, ".xml")) + { + gchar *path; + + path = g_build_filename (GNOMECC_KEYBINDINGS_DIR, name, NULL); + append_sections_from_file (builder, path, wm_keybindings); + + g_free (path); + } + } + + g_dir_close (dir); + g_strfreev (wm_keybindings); + + /* Load custom keybindings */ + append_sections_from_gconf (builder, GCONF_BINDING_DIR); +} + +static void +accel_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + KeyEntry *key_entry; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + if (key_entry == NULL) + g_object_set (cell, + "visible", FALSE, + NULL); + else if (! key_entry->editable) + g_object_set (cell, + "visible", TRUE, + "editable", FALSE, + "accel_key", key_entry->keyval, + "accel_mask", key_entry->mask, + "keycode", key_entry->keycode, + "style", PANGO_STYLE_ITALIC, + NULL); + else + g_object_set (cell, + "visible", TRUE, + "editable", TRUE, + "accel_key", key_entry->keyval, + "accel_mask", key_entry->mask, + "keycode", key_entry->keycode, + "style", PANGO_STYLE_NORMAL, + NULL); +} + +static void +description_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + KeyEntry *key_entry; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + if (key_entry != NULL) + g_object_set (cell, + "editable", FALSE, + "text", key_entry->description != NULL ? + key_entry->description : _(""), + NULL); + else + g_object_set (cell, + "editable", FALSE, NULL); } static void setup_dialog (CcPanel *panel, GtkBuilder *builder) { + GConfClient *client; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkWidget *widget; + GtkTreeView *treeview; + GtkTreeSelection *selection; + GSList *allowed_keys; + CcShell *shell; + GtkListStore *model; + + /* Setup the section treeview */ + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview")); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Section"), + renderer, + "text", DESCRIPTION_COLUMN, + NULL); + gtk_tree_view_append_column (treeview, column); + + model = gtk_list_store_new (1, G_TYPE_STRING); + gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model)); + + /* Setup the shortcut treeview */ + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, + "shortcut_treeview")); + + client = gconf_client_get_default (); + + /* g_signal_connect (treeview, "button_press_event", */ + /* G_CALLBACK (start_editing_cb), builder); */ + /* g_signal_connect (treeview, "row-activated", */ + /* G_CALLBACK (start_editing_kb_cb), NULL); */ + + renderer = gtk_cell_renderer_text_new (); + + /* g_signal_connect (renderer, "edited", */ + /* G_CALLBACK (description_edited_callback), */ + /* treeview); */ + + column = gtk_tree_view_column_new_with_attributes (_("Action"), + renderer, + "text", DESCRIPTION_COLUMN, + 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_append_column (treeview, column); + gtk_tree_view_column_set_sort_column_id (column, DESCRIPTION_COLUMN); + + renderer = (GtkCellRenderer *) g_object_new (EGG_TYPE_CELL_RENDERER_KEYS, + "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X, + NULL); + + /* g_signal_connect (renderer, "accel_edited", */ + /* G_CALLBACK (accel_edited_callback), */ + /* treeview); */ + + /* g_signal_connect (renderer, "accel_cleared", */ + /* G_CALLBACK (accel_cleared_callback), */ + /* treeview); */ + + column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), renderer, NULL); + gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL); + gtk_tree_view_column_set_resizable (column, FALSE); + + gtk_tree_view_append_column (treeview, column); + gtk_tree_view_column_set_sort_column_id (column, KEYENTRY_COLUMN); + + gconf_client_add_dir (client, GCONF_BINDING_DIR, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + gconf_client_add_dir (client, "/apps/metacity/general", GCONF_CLIENT_PRELOAD_ONELEVEL, NULL); + /* gconf_client_notify_add (client, */ + /* "/apps/metacity/general/num_workspaces", */ + /* (GConfClientNotifyFunc) key_entry_controlling_key_changed, */ + /* builder, NULL, NULL); */ + + /* set up the dialog */ + shell = cc_panel_get_shell (CC_PANEL (panel)); + widget = cc_shell_get_toplevel (shell); + + /* maybe_block_accels_id = g_signal_connect (widget, "key_press_event", */ + /* G_CALLBACK (maybe_block_accels), NULL); */ + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + /* g_signal_connect (selection, "changed", */ + /* G_CALLBACK (selection_changed), */ + /* WID (builder, "remove-button")); */ + + allowed_keys = gconf_client_get_list (client, + GCONF_BINDING_DIR "/allowed_keys", + GCONF_VALUE_STRING, + NULL); + if (allowed_keys != NULL) + { + g_slist_foreach (allowed_keys, (GFunc)g_free, NULL); + g_slist_free (allowed_keys); + gtk_widget_set_sensitive (WID (builder, "add-button"), + FALSE); + } + + g_object_unref (client); + + /* setup the custom shortcut dialog */ + custom_shortcut_dialog = WID (builder, + "custom-shortcut-dialog"); + custom_shortcut_name_entry = WID (builder, + "custom-shortcut-name-entry"); + custom_shortcut_command_entry = WID (builder, + "custom-shortcut-command-entry"); + /* g_signal_connect (WID (builder, "add-button"), */ + /* "clicked", G_CALLBACK (add_button_clicked), builder); */ + /* g_signal_connect (WID (builder, "remove-button"), */ + /* "clicked", G_CALLBACK (remove_button_clicked), builder); */ + + 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)); } void keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder) { - kb_sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_key_list); + kb_sections = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) free_key_array); setup_dialog (panel, builder); + reload_sections (builder); } void