From a0a155884ef66ca8830f1f6fbf2ca8f549239a48 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 8 Jul 2016 00:35:38 -0300 Subject: [PATCH] keyboard: introduce CcKeyboardShortcutEditor The current CcKeyboardPanel used to manage keyboard editing through GtkCellRendererAccel, which was replaced when we moved to use a GtkListBox. Because of that, the ability to edit shortcuts is now missing. Re-add shortcut editing capabilities through a new dialog, which is done according to the latest mockups available. https://bugzilla.gnome.org/show_bug.cgi?id=769063 --- panels/keyboard/Makefile.am | 2 + panels/keyboard/cc-keyboard-panel.c | 904 +++--------------- panels/keyboard/cc-keyboard-panel.h | 2 + panels/keyboard/cc-keyboard-shortcut-editor.c | 716 ++++++++++++++ panels/keyboard/cc-keyboard-shortcut-editor.h | 56 ++ panels/keyboard/gnome-keyboard-panel.ui | 144 --- panels/keyboard/keyboard.gresource.xml | 1 + panels/keyboard/shortcut-editor.ui | 262 +++++ po/POTFILES.in | 2 + 9 files changed, 1160 insertions(+), 929 deletions(-) create mode 100644 panels/keyboard/cc-keyboard-shortcut-editor.c create mode 100644 panels/keyboard/cc-keyboard-shortcut-editor.h create mode 100644 panels/keyboard/shortcut-editor.ui diff --git a/panels/keyboard/Makefile.am b/panels/keyboard/Makefile.am index 9d4d772ec..fdd6ff177 100644 --- a/panels/keyboard/Makefile.am +++ b/panels/keyboard/Makefile.am @@ -15,6 +15,8 @@ libkeyboard_la_SOURCES = \ cc-keyboard-item.h \ cc-keyboard-option.c \ cc-keyboard-option.h \ + cc-keyboard-shortcut-editor.c \ + cc-keyboard-shortcut-editor.h \ wm-common.c \ wm-common.h \ keyboard-shortcuts.c \ diff --git a/panels/keyboard/cc-keyboard-panel.c b/panels/keyboard/cc-keyboard-panel.c index 64602fc15..4af9530c7 100644 --- a/panels/keyboard/cc-keyboard-panel.c +++ b/panels/keyboard/cc-keyboard-panel.c @@ -26,6 +26,7 @@ #include "cc-keyboard-option.h" #include "cc-keyboard-panel.h" #include "cc-keyboard-resources.h" +#include "cc-keyboard-shortcut-editor.h" #include "keyboard-shortcuts.h" #include "wm-common.h" @@ -56,10 +57,7 @@ struct _CcKeyboardPanel GtkSizeGroup *accelerator_sizegroup; /* Custom shortcut dialog */ - GtkWidget *custom_shortcut_command_entry; - GtkWidget *custom_shortcut_dialog; - GtkWidget *custom_shortcut_name_entry; - GtkWidget *custom_shortcut_ok_button; + GtkWidget *shortcut_editor; GHashTable *kb_system_sections; GHashTable *kb_apps_sections; @@ -606,7 +604,6 @@ append_sections_from_file (CcKeyboardPanel *self, 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); @@ -855,100 +852,6 @@ add_shortcuts (CcKeyboardPanel *self) } } -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) @@ -983,69 +886,41 @@ shortcut_selection_changed (GtkTreeSelection *selection, static gboolean -edit_custom_shortcut (CcKeyboardPanel *self, - CcKeyboardItem *item) +remove_custom_shortcut (CcKeyboardShortcutEditor *editor, + CcKeyboardItem *item, + CcKeyboardPanel *self) { - 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; + GtkTreeModel *model; + GtkTreeIter iter; GPtrArray *keys_array; GVariantBuilder builder; + gboolean valid; char **settings_paths; int i; - gtk_tree_model_get (model, iter, - DETAIL_KEYENTRY_COLUMN, &item, - -1); + model = GTK_TREE_MODEL (self->shortcuts_model); + valid = gtk_tree_model_get_iter_first (model, &iter); + + /* Search for the iter */ + while (valid) + { + CcKeyboardItem *current_item; + + gtk_tree_model_get (model, &iter, + DETAIL_KEYENTRY_COLUMN, ¤t_item, + -1); + + if (current_item == item) + break; + + valid = gtk_tree_model_iter_next (model, &iter); + + g_clear_object (¤t_item); + } + + if (!valid) + g_error ("Tried to remove a non-existant shortcut"); - /* not a custom shortcut */ g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH); remove_item (self, item); @@ -1074,617 +949,50 @@ remove_custom_shortcut (CcKeyboardPanel *self, 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); + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); return TRUE; } static void -add_custom_shortcut (CcKeyboardPanel *self) +add_custom_shortcut (CcKeyboardShortcutEditor *editor, + CcKeyboardItem *item, + CcKeyboardPanel *self) { - 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 = GTK_TREE_MODEL (self->shortcuts_model); - item->group = BINDING_GROUP_USER; - - 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 (self->shortcuts_model, &iter); - gtk_list_store_set (self->shortcuts_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 (GTK_TREE_MODEL (self->shortcuts_model), &iter); - gtk_tree_path_free (path); - - add_item (self, item, CUSTOM_SHORTCUTS_ID, _("Custom Shortcuts")); - } - 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; + GPtrArray *keys_array; GtkTreeIter iter; - CcKeyboardItem *item; - ShortcutType type; - GtkCellRenderer *cell = user_data; + GHashTable *hash; + GVariantBuilder builder; + char **settings_paths; + int i; - 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)) + hash = get_hash_for_group (self, BINDING_GROUP_USER); + keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID); + if (keys_array == NULL) { - 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; + keys_array = g_ptr_array_new (); + g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array); } - 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; - } + g_ptr_array_add (keys_array, item); - data->conflict_item = element; + gtk_list_store_append (self->shortcuts_model, &iter); + gtk_list_store_set (self->shortcuts_model, &iter, DETAIL_KEYENTRY_COLUMN, item, -1); - return TRUE; + 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)); -} - -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_MODEL (self->shortcuts_model); - gtk_tree_model_get_iter (model, &iter, path); + /* make the new shortcut visible */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (self->shortcuts_model), &iter); 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); + add_item (self, item, CUSTOM_SHORTCUTS_ID, _("Custom Shortcuts")); } static void @@ -1692,38 +1000,29 @@ shortcut_row_activated (GtkWidget *button, GtkListBoxRow *row, CcKeyboardPanel *self) { - if (row == self->add_shortcut_row) - add_custom_shortcut (self); -} + CcKeyboardShortcutEditor *editor; -static void -xkb_options_combo_changed (GtkCellRendererCombo *combo, - gchar *model_path, - GtkTreeIter *model_iter, - CcKeyboardPanel *self) -{ - GtkTreeModel *shortcut_model; - GtkTreeIter shortcut_iter; - CcKeyboardOption *option; - ShortcutType type; + editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor); - gtk_tree_model_get (shortcut_model, &shortcut_iter, - DETAIL_KEYENTRY_COLUMN, &option, - DETAIL_TYPE_COLUMN, &type, - -1); + if (row != self->add_shortcut_row) + { + RowData *data = g_object_get_data (G_OBJECT (row), "data"); - if (type != SHORTCUT_TYPE_XKB_OPTION) - return; + cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_EDIT); + cc_keyboard_shortcut_editor_set_item (editor, data->item); + } + else + { + cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_CREATE); + cc_keyboard_shortcut_editor_set_item (editor, NULL); + } - cc_keyboard_option_set_selection (option, model_iter); + gtk_widget_show (self->shortcut_editor); } static void setup_tree_views (CcKeyboardPanel *self) { - GtkWidget *widget; - CcShell *shell; - /* Setup the section treeview */ self->sections_store = gtk_list_store_new (SECTION_N_COLUMNS, G_TYPE_STRING, @@ -1747,13 +1046,6 @@ setup_tree_views (CcKeyboardPanel *self) G_TYPE_INT); setup_keyboard_options (self->shortcuts_model); - - /* 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 void @@ -1790,7 +1082,6 @@ cc_keyboard_panel_finalize (GObject *object) g_clear_pointer (&self->wm_changed_id, wm_common_unregister_window_manager_change); g_clear_object (&self->accelerator_sizegroup); - g_clear_object (&self->custom_shortcut_dialog); g_clear_object (&self->binding_settings); g_clear_object (&self->shortcuts_model); g_clear_object (&self->sections_store); @@ -1812,9 +1103,14 @@ static void cc_keyboard_panel_constructed (GObject *object) { CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object); + GtkWindow *toplevel; G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructed (object); + /* Setup the dialog's transient parent */ + toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)))); + gtk_window_set_transient_for (GTK_WINDOW (self->shortcut_editor), toplevel); + #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, @@ -1845,13 +1141,8 @@ cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass) 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_shortcut_row); - 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, listbox); - gtk_widget_class_bind_template_callback (widget_class, shortcut_entry_changed); gtk_widget_class_bind_template_callback (widget_class, shortcut_row_activated); gtk_widget_class_bind_template_callback (widget_class, shortcut_selection_changed); } @@ -1868,6 +1159,20 @@ cc_keyboard_panel_init (CcKeyboardPanel *self) /* Use a sizegroup to make the accelerator labels the same width */ self->accelerator_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + /* Shortcut editor dialog */ + self->shortcut_editor = cc_keyboard_shortcut_editor_new (self); + + g_signal_connect (self->shortcut_editor, + "add-custom-shortcut", + G_CALLBACK (add_custom_shortcut), + self); + + g_signal_connect (self->shortcut_editor, + "remove-custom-shortcut", + G_CALLBACK (remove_custom_shortcut), + self); + + /* Setup the shortcuts listbox */ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->listbox), sort_function, self, @@ -1878,3 +1183,32 @@ cc_keyboard_panel_init (CcKeyboardPanel *self) self, NULL); } + +/** + * cc_keyboard_panel_create_custom_item: + * @self: a #CcKeyboardPanel + * + * Creates a new temporary keyboard shortcut. + * + * Returns: (transfer full): a #CcKeyboardItem + */ +CcKeyboardItem* +cc_keyboard_panel_create_custom_item (CcKeyboardPanel *self) +{ + CcKeyboardItem *item; + gchar *settings_path; + + g_return_val_if_fail (CC_IS_KEYBOARD_PANEL (self), NULL); + + 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 = GTK_TREE_MODEL (self->shortcuts_model); + item->group = BINDING_GROUP_USER; + + return item; +} + diff --git a/panels/keyboard/cc-keyboard-panel.h b/panels/keyboard/cc-keyboard-panel.h index 859a80786..cbf8e0d34 100644 --- a/panels/keyboard/cc-keyboard-panel.h +++ b/panels/keyboard/cc-keyboard-panel.h @@ -31,6 +31,8 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (CcKeyboardPanel, cc_keyboard_panel, CC, KEYBOARD_PANEL, CcPanel) +CcKeyboardItem* cc_keyboard_panel_create_custom_item (CcKeyboardPanel *self); + G_END_DECLS #endif /* _CC_KEYBOARD_PANEL_H */ diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.c b/panels/keyboard/cc-keyboard-shortcut-editor.c new file mode 100644 index 000000000..04f28f984 --- /dev/null +++ b/panels/keyboard/cc-keyboard-shortcut-editor.c @@ -0,0 +1,716 @@ +/* cc-keyboard-shortcut-editor.h + * + * 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 + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: Georges Basile Stavracas Neto + */ + +#include +#include + +#include "cc-keyboard-shortcut-editor.h" +#include "keyboard-shortcuts.h" + +struct _CcKeyboardShortcutEditor +{ + GtkDialog parent; + + GtkWidget *add_button; + GtkWidget *cancel_button; + GtkWidget *command_entry; + GtkWidget *custom_shortcut_accel_label; + GtkWidget *edit_button; + GtkWidget *headerbar; + GtkWidget *name_entry; + GtkWidget *remove_button; + GtkWidget *replace_button; + GtkWidget *shortcut_accel_label; + GtkWidget *stack; + GtkWidget *top_info_label; + + CcShortcutEditorMode mode; + + GdkDevice *grab_device; + + CcKeyboardPanel *panel; + CcKeyboardItem *item; + + /* Custom shortcuts */ + GdkDevice *grab_pointer; + + guint custom_keycode; + guint custom_keyval; + GdkModifierType custom_mask; + gboolean custom_is_modifier; + gboolean edited : 1; +}; + +static void command_entry_changed_cb (CcKeyboardShortcutEditor *self); +static void name_entry_changed_cb (CcKeyboardShortcutEditor *self); + +G_DEFINE_TYPE (CcKeyboardShortcutEditor, cc_keyboard_shortcut_editor, GTK_TYPE_DIALOG) + +enum +{ + PROP_0, + PROP_KEYBOARD_ITEM, + PROP_PANEL, + N_PROPS +}; + +enum +{ + ADD_CUSTOM_SHORTCUT, + REMOVE_CUSTOM_SHORTCUT, + LAST_SIGNAL +}; + +static GParamSpec *properties [N_PROPS] = { NULL, }; +static guint signals[LAST_SIGNAL] = { 0, }; + +static void +apply_custom_item_fields (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item) +{ + /* Only setup the binding when it was actually edited */ + if (self->edited) + { + gchar *binding; + + item->keycode = self->custom_keycode; + item->keyval = self->custom_keyval; + item->mask = self->custom_mask; + + if (item->keycode == 0 && item->keyval == 0 && item->mask == 0) + binding = g_strdup (""); + else + binding = gtk_accelerator_name_with_keycode (NULL, + item->keyval, + item->keycode, + item->mask); + + g_object_set (G_OBJECT (item), "binding", binding, NULL); + + g_free (binding); + } + + /* Set the keyboard shortcut name and command for custom entries */ + if (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH) + { + g_settings_set_string (item->settings, "name", gtk_entry_get_text (GTK_ENTRY (self->name_entry))); + g_settings_set_string (item->settings, "command", gtk_entry_get_text (GTK_ENTRY (self->command_entry))); + } +} + +static void +clear_custom_entries (CcKeyboardShortcutEditor *self) +{ + g_signal_handlers_block_by_func (self->command_entry, command_entry_changed_cb, self); + g_signal_handlers_block_by_func (self->name_entry, name_entry_changed_cb, self); + + gtk_entry_set_text (GTK_ENTRY (self->name_entry), ""); + gtk_entry_set_text (GTK_ENTRY (self->command_entry), ""); + + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), ""); + + self->custom_keycode = 0; + self->custom_keyval = 0; + self->custom_mask = 0; + self->custom_is_modifier = TRUE; + self->edited = FALSE; + + g_signal_handlers_unblock_by_func (self->command_entry, command_entry_changed_cb, self); + g_signal_handlers_unblock_by_func (self->name_entry, name_entry_changed_cb, self); +} + +static gboolean +is_custom_shortcut (CcKeyboardShortcutEditor *self) +{ + return g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (self->stack)), "custom"); +} + +static void +grab_seat (CcKeyboardShortcutEditor *self, + GdkEvent *event) +{ + GdkGrabStatus status; + GdkDevice *pointer; + GdkDevice *device; + GdkWindow *window; + + if (!event) + event = gtk_get_current_event (); + + device = gdk_event_get_device (event); + window = gtk_widget_get_window (GTK_WIDGET (self)); + + if (!device || !window) + return; + + if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) + pointer = gdk_device_get_associated_device (device); + else + pointer = device; + + status = gdk_seat_grab (gdk_device_get_seat (pointer), + window, + GDK_SEAT_CAPABILITY_ALL, + FALSE, + NULL, + event, + NULL, + NULL); + + if (status != GDK_GRAB_SUCCESS) + return; + + self->grab_pointer = pointer; + + gtk_grab_add (GTK_WIDGET (self)); +} + +static void +release_grab (CcKeyboardShortcutEditor *self) +{ + if (self->grab_pointer) + { + gdk_seat_ungrab (gdk_device_get_seat (self->grab_pointer)); + self->grab_pointer = NULL; + + gtk_grab_remove (GTK_WIDGET (self)); + } +} + +static void +update_shortcut (CcKeyboardShortcutEditor *self) +{ + if (!self->item) + return; + + /* Setup the binding */ + apply_custom_item_fields (self, self->item); + + /* Cleanup whatever was set before */ + clear_custom_entries (self); + + cc_keyboard_shortcut_editor_set_item (self, NULL); +} + +static GtkShortcutLabel* +get_current_shortcut_label (CcKeyboardShortcutEditor *self) +{ + if (is_custom_shortcut (self)) + return GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label); + + return GTK_SHORTCUT_LABEL (self->shortcut_accel_label); +} + +static void +setup_custom_shortcut (CcKeyboardShortcutEditor *self) +{ + GtkShortcutLabel *shortcut_label; + gboolean valid; + gchar *accel; + + valid = is_valid_binding (self->custom_keyval, self->custom_mask, self->custom_keycode) && + gtk_accelerator_valid (self->custom_keyval, self->custom_mask) && + !self->custom_is_modifier; + + /* Additional checks for custom shortcuts */ + if (is_custom_shortcut (self)) + { + valid = valid && + gtk_entry_get_text_length (GTK_ENTRY (self->name_entry)) > 0 && + gtk_entry_get_text_length (GTK_ENTRY (self->command_entry)) > 0; + } + + gtk_widget_set_sensitive (self->add_button, valid); + + if (!valid) + return; + + shortcut_label = get_current_shortcut_label (self); + accel = gtk_accelerator_name (self->custom_keyval, self->custom_mask); + + /* Setup the accelerator label */ + gtk_shortcut_label_set_accelerator (shortcut_label, accel); + + /* + * When the user finishes typing the new shortcut, it gets immediately + * applied and the toggle button gets inactive. + */ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->edit_button), FALSE); + + self->edited = TRUE; + + release_grab (self); + + g_free (accel); +} + +static void +add_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + CcKeyboardItem *item; + + item = cc_keyboard_panel_create_custom_item (self->panel); + + /* Apply the custom shortcut setup at the new item */ + apply_custom_item_fields (self, item); + + /* Cleanup everything once we're done */ + clear_custom_entries (self); + + g_signal_emit (self, signals[ADD_CUSTOM_SHORTCUT], 0, item); + + gtk_widget_hide (GTK_WIDGET (self)); +} + +static void +cancel_button_clicked_cb (GtkWidget *button, + CcKeyboardShortcutEditor *self) +{ + cc_keyboard_shortcut_editor_set_item (self, NULL); + clear_custom_entries (self); + + gtk_widget_hide (GTK_WIDGET (self)); +} + +static void +command_entry_changed_cb (CcKeyboardShortcutEditor *self) +{ + setup_custom_shortcut (self); +} + +static void +edit_custom_shortcut_button_toggled_cb (CcKeyboardShortcutEditor *self, + GParamSpec *pspec, + GtkToggleButton *button) +{ + if (gtk_toggle_button_get_active (button)) + grab_seat (self, NULL); + else + release_grab (self); +} + +static void +name_entry_changed_cb (CcKeyboardShortcutEditor *self) +{ + setup_custom_shortcut (self); +} + +static void +remove_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + gtk_widget_hide (GTK_WIDGET (self)); + + g_signal_emit (self, signals[REMOVE_CUSTOM_SHORTCUT], 0, self->item); +} + +static void +setup_keyboard_item (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item) +{ + gboolean is_custom; + gchar *accel; + gchar *text; + + if (!item) + return; + + is_custom = item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH; + accel = gtk_accelerator_name (item->keyval, item->mask); + + /* Headerbar */ + gtk_header_bar_set_title (GTK_HEADER_BAR (self->headerbar), item->description); + gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->headerbar), TRUE); + + gtk_widget_hide (self->add_button); + gtk_widget_hide (self->cancel_button); + gtk_widget_hide (self->replace_button); + + /* Setup the top label */ + text = g_strdup_printf (_("Keyboard shortcut for %s. Enter new shortcut to change."), item->description); + + gtk_label_set_markup (GTK_LABEL (self->top_info_label), text); + + /* Accelerator labels */ + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->shortcut_accel_label), accel); + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), accel); + + /* Setup the custom entries */ + if (is_custom) + { + g_signal_handlers_block_by_func (self->command_entry, command_entry_changed_cb, self); + g_signal_handlers_block_by_func (self->name_entry, name_entry_changed_cb, self); + + /* Name entry */ + gtk_entry_set_text (GTK_ENTRY (self->name_entry), item->description); + gtk_widget_set_sensitive (self->name_entry, item->desc_editable); + + /* Command entry */ + gtk_entry_set_text (GTK_ENTRY (self->command_entry), item->command); + gtk_widget_set_sensitive (self->command_entry, item->cmd_editable); + + gtk_widget_show (self->remove_button); + + g_signal_handlers_unblock_by_func (self->command_entry, command_entry_changed_cb, self); + g_signal_handlers_unblock_by_func (self->name_entry, name_entry_changed_cb, self); + } + + g_free (accel); + g_free (text); + + /* Show the apropriate view */ + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), is_custom ? "custom" : "edit"); +} + +static void +cc_keyboard_shortcut_editor_finalize (GObject *object) +{ + CcKeyboardShortcutEditor *self = (CcKeyboardShortcutEditor *)object; + + g_clear_object (&self->item); + + G_OBJECT_CLASS (cc_keyboard_shortcut_editor_parent_class)->finalize (object); +} + +static void +cc_keyboard_shortcut_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (object); + + switch (prop_id) + { + case PROP_KEYBOARD_ITEM: + g_value_set_object (value, self->item); + break; + + case PROP_PANEL: + g_value_set_pointer (value, self->panel); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_keyboard_shortcut_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (object); + + switch (prop_id) + { + case PROP_KEYBOARD_ITEM: + cc_keyboard_shortcut_editor_set_item (self, g_value_get_object (value)); + break; + + case PROP_PANEL: + self->panel = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +cc_keyboard_shortcut_editor_key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + CcKeyboardShortcutEditor *self; + GdkModifierType real_mask; + gboolean editing; + + self = CC_KEYBOARD_SHORTCUT_EDITOR (widget); + + editing = !g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (self->stack)), "custom") || + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->edit_button)); + + if (!editing) + return GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->key_press_event (widget, event); + + real_mask = event->state & gtk_accelerator_get_default_mod_mask (); + + /* A single Escape press cancels the editing */ + if (!event->is_modifier && real_mask == 0 && event->keyval == GDK_KEY_Escape) + { + self->edited = FALSE; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->edit_button), FALSE); + release_grab (self); + + return GDK_EVENT_STOP; + } + + /* Backspace disables the current shortcut */ + if (!event->is_modifier && real_mask == 0 && event->keyval == GDK_KEY_BackSpace) + { + self->edited = TRUE; + self->custom_keycode = 0; + self->custom_keyval = 0; + self->custom_mask = 0; + + if (self->item) + apply_custom_item_fields (self, self->item); + + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), ""); + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->shortcut_accel_label), ""); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->edit_button), FALSE); + release_grab (self); + + self->edited = FALSE; + + return GDK_EVENT_STOP; + } + + self->custom_is_modifier = event->is_modifier; + self->custom_keycode = event->hardware_keycode; + self->custom_keyval = event->keyval; + self->custom_mask = real_mask; + + /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ + self->custom_mask &= ~GDK_LOCK_MASK; + + if (!self->grab_pointer) + grab_seat (self, (GdkEvent*) event); + + setup_custom_shortcut (self); + + return GDK_EVENT_STOP; +} + +static void +cc_keyboard_shortcut_editor_close (GtkDialog *dialog) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (dialog); + + if (self->mode == CC_SHORTCUT_EDITOR_EDIT) + update_shortcut (self); + + GTK_DIALOG_CLASS (cc_keyboard_shortcut_editor_parent_class)->close (dialog); +} + +static void +cc_keyboard_shortcut_editor_response (GtkDialog *dialog, + gint response_id) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (dialog); + + if (response_id == GTK_RESPONSE_DELETE_EVENT && + self->mode == CC_SHORTCUT_EDITOR_EDIT) + { + update_shortcut (self); + } +} + +static void +cc_keyboard_shortcut_editor_class_init (CcKeyboardShortcutEditorClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_keyboard_shortcut_editor_finalize; + object_class->get_property = cc_keyboard_shortcut_editor_get_property; + object_class->set_property = cc_keyboard_shortcut_editor_set_property; + + widget_class->key_press_event = cc_keyboard_shortcut_editor_key_press_event; + + dialog_class->close = cc_keyboard_shortcut_editor_close; + dialog_class->response = cc_keyboard_shortcut_editor_response; + + /** + * CcKeyboardShortcutEditor:keyboard-item: + * + * The current keyboard shortcut being edited. + */ + properties[PROP_KEYBOARD_ITEM] = g_param_spec_object ("keyboard-item", + "Keyboard item", + "The keyboard item being edited", + CC_TYPE_KEYBOARD_ITEM, + G_PARAM_READWRITE); + + /** + * CcKeyboardShortcutEditor:panel: + * + * The current keyboard panel. + */ + properties[PROP_PANEL] = g_param_spec_pointer ("panel", + "Keyboard panel", + "The keyboard panel being edited", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + /** + * CcKeyboardShortcutEditor:add-custom-shortcut: + * + * Emited when the user asks to add a custom shortcut. + */ + signals[ADD_CUSTOM_SHORTCUT] = g_signal_new ("add-custom-shortcut", + CC_TYPE_KEYBOARD_SHORTCUT_EDITOR, + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, + CC_TYPE_KEYBOARD_ITEM); + + /** + * CcKeyboardShortcutEditor:remove-custom-shortcut: + * + * Emited when the user asks to remove a custom shortcut. + */ + signals[REMOVE_CUSTOM_SHORTCUT] = g_signal_new ("remove-custom-shortcut", + CC_TYPE_KEYBOARD_SHORTCUT_EDITOR, + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, + CC_TYPE_KEYBOARD_ITEM); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/shortcut-editor.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, add_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, cancel_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, command_entry); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_shortcut_accel_label); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, edit_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, headerbar); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, name_entry); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, remove_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, replace_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_accel_label); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, stack); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, top_info_label); + + gtk_widget_class_bind_template_callback (widget_class, add_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, command_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, edit_custom_shortcut_button_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, name_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked_cb); +} + +static void +cc_keyboard_shortcut_editor_init (CcKeyboardShortcutEditor *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->mode = CC_SHORTCUT_EDITOR_EDIT; + self->custom_is_modifier = TRUE; +} + +/** + * cc_keyboard_shortcut_editor_new: + * + * Creates a new #CcKeyboardShortcutEditor. + * + * Returns: (transfer full): a newly created #CcKeyboardShortcutEditor. + */ +GtkWidget* +cc_keyboard_shortcut_editor_new (CcKeyboardPanel *panel) +{ + return g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_EDITOR, + "panel", panel, + "use-header-bar", 1, + NULL); +} + +/** + * cc_keyboard_shortcut_editor_get_item: + * @self: a #CcKeyboardShortcutEditor + * + * Retrieves the current keyboard shortcut being edited. + * + * Returns: (transfer none)(nullable): a #CcKeyboardItem + */ +CcKeyboardItem* +cc_keyboard_shortcut_editor_get_item (CcKeyboardShortcutEditor *self) +{ + g_return_val_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self), NULL); + + return self->item; +} + +/** + * cc_keyboard_shortcut_editor_set_item: + * @self: a #CcKeyboardShortcutEditor + * @item: a #CcKeyboardItem + * + * Sets the current keyboard shortcut to be edited. + */ +void +cc_keyboard_shortcut_editor_set_item (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item) +{ + g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self)); + + if (!g_set_object (&self->item, item)) + return; + + setup_keyboard_item (self, item); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEYBOARD_ITEM]); +} + +CcShortcutEditorMode +cc_keyboard_shortcut_editor_get_mode (CcKeyboardShortcutEditor *self) +{ + g_return_val_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self), 0); + + return self->mode; +} + +void +cc_keyboard_shortcut_editor_set_mode (CcKeyboardShortcutEditor *self, + CcShortcutEditorMode mode) +{ + g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self)); + + if (self->mode == mode) + return; + + self->mode = mode; + + if (mode == CC_SHORTCUT_EDITOR_CREATE) + { + /* Cleanup whatever was set before */ + clear_custom_entries (self); + + /* The 'Add' button is only sensitive when the shortcut is valid */ + gtk_widget_set_sensitive (self->add_button, FALSE); + + gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->headerbar), FALSE); + gtk_header_bar_set_title (GTK_HEADER_BAR (self->headerbar), _("Add Custom Shortcut")); + + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "custom"); + + gtk_widget_show (self->add_button); + gtk_widget_show (self->cancel_button); + + gtk_widget_hide (self->remove_button); + gtk_widget_hide (self->replace_button); + } +} diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.h b/panels/keyboard/cc-keyboard-shortcut-editor.h new file mode 100644 index 000000000..c0fe67550 --- /dev/null +++ b/panels/keyboard/cc-keyboard-shortcut-editor.h @@ -0,0 +1,56 @@ +/* cc-keyboard-shortcut-editor.h + * + * 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 + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: Georges Basile Stavracas Neto + */ + +#ifndef CC_KEYBOARD_SHORTCUT_EDITOR_H +#define CC_KEYBOARD_SHORTCUT_EDITOR_H + +#include + +#include "cc-keyboard-item.h" +#include "cc-keyboard-panel.h" + +G_BEGIN_DECLS + +#define CC_TYPE_KEYBOARD_SHORTCUT_EDITOR (cc_keyboard_shortcut_editor_get_type()) + +typedef enum +{ + CC_SHORTCUT_EDITOR_CREATE, + CC_SHORTCUT_EDITOR_EDIT +} CcShortcutEditorMode; + +G_DECLARE_FINAL_TYPE (CcKeyboardShortcutEditor, cc_keyboard_shortcut_editor, CC, KEYBOARD_SHORTCUT_EDITOR, GtkDialog) + +GtkWidget* cc_keyboard_shortcut_editor_new (CcKeyboardPanel *panel); + +CcKeyboardItem* cc_keyboard_shortcut_editor_get_item (CcKeyboardShortcutEditor *self); + +void cc_keyboard_shortcut_editor_set_item (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item); + +CcShortcutEditorMode cc_keyboard_shortcut_editor_get_mode (CcKeyboardShortcutEditor *self); + +void cc_keyboard_shortcut_editor_set_mode (CcKeyboardShortcutEditor *self, + CcShortcutEditorMode mode); + +G_END_DECLS + +#endif /* CC_KEYBOARD_SHORTCUT_EDITOR_H */ + diff --git a/panels/keyboard/gnome-keyboard-panel.ui b/panels/keyboard/gnome-keyboard-panel.ui index 2706a76b8..cb0bdb130 100644 --- a/panels/keyboard/gnome-keyboard-panel.ui +++ b/panels/keyboard/gnome-keyboard-panel.ui @@ -8,150 +8,6 @@ 200 200 - - False - dialog - 1 - False - - - True - False - Custom Shortcut - False - - - _Cancel - True - True - False - False - True - center - - - - start - - - - - _Add - True - True - True - False - False - True - center - False - - - - end - - - - - - - True - False - vertical - - - True - False - 5 - 6 - - - True - False - 6 - 6 - - - True - False - 0 - _Name: - True - custom_shortcut_name_entry - - - 0 - 0 - - - - - True - False - 0 - C_ommand: - True - custom_shortcut_command_entry - - - 0 - 1 - - - - - True - True - True - - True - - - - 1 - 0 - - - - - True - True - True - - True - - - - 1 - 1 - - - - - True - True - 0 - - - - - False - True - 1 - - - - - - custom_shortcut_cancel_button - custom_shortcut_ok_button - -