diff options
Diffstat (limited to 'panels/keyboard/cc-keyboard-shortcut-editor.c')
-rw-r--r-- | panels/keyboard/cc-keyboard-shortcut-editor.c | 985 |
1 files changed, 985 insertions, 0 deletions
diff --git a/panels/keyboard/cc-keyboard-shortcut-editor.c b/panels/keyboard/cc-keyboard-shortcut-editor.c new file mode 100644 index 0000000..66cf024 --- /dev/null +++ b/panels/keyboard/cc-keyboard-shortcut-editor.c @@ -0,0 +1,985 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + * Authors: Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + */ + +#include <glib-object.h> +#include <glib/gi18n.h> + +#include "cc-keyboard-shortcut-editor.h" +#include "keyboard-shortcuts.h" + +struct _CcKeyboardShortcutEditor +{ + GtkDialog parent; + + GtkButton *add_button; + GtkButton *cancel_button; + GtkButton *change_custom_shortcut_button; + GtkEntry *command_entry; + GtkGrid *custom_grid; + GtkShortcutLabel *custom_shortcut_accel_label; + GtkStack *custom_shortcut_stack; + GtkBox *edit_box; + GtkHeaderBar *headerbar; + GtkEntry *name_entry; + GtkLabel *new_shortcut_conflict_label; + GtkButton *remove_button; + GtkButton *replace_button; + GtkButton *reset_button; + GtkButton *reset_custom_button; + GtkButton *set_button; + GtkShortcutLabel *shortcut_accel_label; + GtkLabel *shortcut_conflict_label; + GtkBox *standard_box; + GtkStack *stack; + GtkLabel *top_info_label; + + CcShortcutEditorMode mode; + + CcKeyboardManager *manager; + CcKeyboardItem *item; + GBinding *reset_item_binding; + + CcKeyboardItem *collision_item; + + /* Custom shortcuts */ + gboolean system_shortcuts_inhibited; + guint grab_idle_id; + + CcKeyCombo *custom_combo; + gboolean custom_is_modifier; + gboolean edited : 1; +}; + +static void command_entry_changed_cb (CcKeyboardShortcutEditor *self); +static void name_entry_changed_cb (CcKeyboardShortcutEditor *self); +static void set_button_clicked_cb (CcKeyboardShortcutEditor *self); + +G_DEFINE_TYPE (CcKeyboardShortcutEditor, cc_keyboard_shortcut_editor, GTK_TYPE_DIALOG) + +enum +{ + PROP_0, + PROP_KEYBOARD_ITEM, + PROP_MANAGER, + N_PROPS +}; + +typedef enum +{ + HEADER_MODE_NONE, + HEADER_MODE_ADD, + HEADER_MODE_SET, + HEADER_MODE_REPLACE, + HEADER_MODE_CUSTOM_CANCEL, + HEADER_MODE_CUSTOM_EDIT +} HeaderMode; + +typedef enum +{ + PAGE_CUSTOM, + PAGE_EDIT, + PAGE_STANDARD, +} ShortcutEditorPage; + +static GParamSpec *properties [N_PROPS] = { NULL, }; + +/* Getter and setter for ShortcutEditorPage */ +static ShortcutEditorPage +get_shortcut_editor_page (CcKeyboardShortcutEditor *self) +{ + if (gtk_stack_get_visible_child (self->stack) == GTK_WIDGET (self->edit_box)) + return PAGE_EDIT; + + if (gtk_stack_get_visible_child (self->stack) == GTK_WIDGET (self->custom_grid)) + return PAGE_CUSTOM; + + return PAGE_STANDARD; +} + +static void +set_shortcut_editor_page (CcKeyboardShortcutEditor *self, + ShortcutEditorPage page) +{ + switch (page) + { + case PAGE_CUSTOM: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->custom_grid)); + break; + + case PAGE_EDIT: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->edit_box)); + break; + + case PAGE_STANDARD: + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->standard_box)); + break; + + default: + g_assert_not_reached (); + } + + gtk_widget_set_visible (GTK_WIDGET (self->top_info_label), page != PAGE_CUSTOM); +} + +static void +apply_custom_item_fields (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item) +{ + /* Only setup the binding when it was actually edited */ + if (self->edited) + { + CcKeyCombo *combo = self->custom_combo; + + cc_keyboard_item_disable (item); + + if (combo->keycode != 0 || combo->keyval != 0 || combo->mask != 0) + cc_keyboard_item_add_key_combo (item, combo); + } + + /* Set the keyboard shortcut name and command for custom entries */ + if (cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH) + { + g_settings_set_string (cc_keyboard_item_get_settings (item), + "name", + gtk_editable_get_text (GTK_EDITABLE (self->name_entry))); + g_settings_set_string (cc_keyboard_item_get_settings (item), + "command", + gtk_editable_get_text (GTK_EDITABLE (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_editable_set_text (GTK_EDITABLE (self->name_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (self->command_entry), ""); + + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->custom_shortcut_accel_label), ""); + gtk_label_set_label (self->new_shortcut_conflict_label, ""); + gtk_label_set_label (self->shortcut_conflict_label, ""); + + memset (self->custom_combo, 0, sizeof (CcKeyCombo)); + self->custom_is_modifier = TRUE; + self->edited = FALSE; + + self->collision_item = NULL; + + 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 void +cancel_editing (CcKeyboardShortcutEditor *self) +{ + cc_keyboard_shortcut_editor_set_item (self, NULL); + clear_custom_entries (self); + + gtk_widget_hide (GTK_WIDGET (self)); +} + +static gboolean +is_custom_shortcut (CcKeyboardShortcutEditor *self) { + return self->item == NULL || cc_keyboard_item_get_item_type (self->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH; +} + +static void +inhibit_system_shortcuts (CcKeyboardShortcutEditor *self) +{ + GtkNative *native; + GdkSurface *surface; + + if (self->system_shortcuts_inhibited) + return; + + native = gtk_widget_get_native (GTK_WIDGET (self)); + surface = gtk_native_get_surface (native); + + if (GDK_IS_TOPLEVEL (surface)) + { + gdk_toplevel_inhibit_system_shortcuts (GDK_TOPLEVEL (surface), NULL); + self->system_shortcuts_inhibited = TRUE; + } +} + +static void +uninhibit_system_shortcuts (CcKeyboardShortcutEditor *self) +{ + GtkNative *native; + GdkSurface *surface; + + if (!self->system_shortcuts_inhibited) + return; + + native = gtk_widget_get_native (GTK_WIDGET (self)); + surface = gtk_native_get_surface (native); + + if (GDK_IS_TOPLEVEL (surface)) + { + gdk_toplevel_restore_system_shortcuts (GDK_TOPLEVEL (surface)); + self->system_shortcuts_inhibited = FALSE; + } +} + +static void +update_shortcut (CcKeyboardShortcutEditor *self) +{ + if (!self->item) + return; + + /* Setup the binding */ + apply_custom_item_fields (self, self->item); + + /* Eventually disable the conflict shortcut */ + if (self->collision_item) + cc_keyboard_item_disable (self->collision_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 +set_header_mode (CcKeyboardShortcutEditor *self, + HeaderMode mode) +{ + gtk_header_bar_set_show_title_buttons (self->headerbar, mode == HEADER_MODE_CUSTOM_EDIT); + + gtk_widget_set_visible (GTK_WIDGET (self->add_button), mode == HEADER_MODE_ADD); + gtk_widget_set_visible (GTK_WIDGET (self->cancel_button), mode != HEADER_MODE_NONE && + mode != HEADER_MODE_CUSTOM_EDIT); + gtk_widget_set_visible (GTK_WIDGET (self->replace_button), mode == HEADER_MODE_REPLACE); + gtk_widget_set_visible (GTK_WIDGET (self->set_button), mode == HEADER_MODE_SET); + gtk_widget_set_visible (GTK_WIDGET (self->remove_button), mode == HEADER_MODE_CUSTOM_EDIT); + + /* By setting the default response, the action button gets the 'suggested-action' applied */ + switch (mode) + { + case HEADER_MODE_SET: + gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_APPLY); + break; + + case HEADER_MODE_REPLACE: + gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT); + break; + + case HEADER_MODE_ADD: + gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_OK); + break; + + default: + gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_NONE); + } +} + +static void +setup_custom_shortcut (CcKeyboardShortcutEditor *self) +{ + GtkShortcutLabel *shortcut_label; + CcKeyboardItem *collision_item; + HeaderMode mode; + gboolean is_custom, is_accel_empty; + gboolean valid, accel_valid; + g_autofree char *accel = NULL; + + is_custom = is_custom_shortcut (self); + accel_valid = is_valid_binding (self->custom_combo) && + is_valid_accel (self->custom_combo) && + !self->custom_is_modifier; + + is_accel_empty = is_empty_binding (self->custom_combo); + + if (is_accel_empty) + accel_valid = TRUE; + valid = accel_valid; + + /* Additional checks for custom shortcuts */ + if (is_custom) + { + if (accel_valid) + { + set_shortcut_editor_page (self, PAGE_CUSTOM); + + /* We have to check if the current accelerator is empty in order to + * decide if we show the "Set Shortcut" button or the accelerator label */ + gtk_stack_set_visible_child (self->custom_shortcut_stack, + is_accel_empty ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label)); + gtk_widget_set_visible (GTK_WIDGET (self->reset_custom_button), !is_accel_empty); + } + + valid = accel_valid && + gtk_entry_get_text_length (self->name_entry) > 0 && + gtk_entry_get_text_length (self->command_entry) > 0; + } + + gtk_widget_set_sensitive (GTK_WIDGET (self->replace_button), valid); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), valid); + if (valid) + set_header_mode (self, HEADER_MODE_ADD); + else + set_header_mode (self, is_custom ? HEADER_MODE_CUSTOM_CANCEL : HEADER_MODE_NONE); + + /* Nothing else to do if the shortcut is invalid */ + if (!accel_valid) + return; + + /* Valid shortcut, show it in the standard page */ + if (!is_custom) + set_shortcut_editor_page (self, PAGE_STANDARD); + + shortcut_label = get_current_shortcut_label (self); + + collision_item = cc_keyboard_manager_get_collision (self->manager, + self->item, + self->custom_combo); + + accel = gtk_accelerator_name (self->custom_combo->keyval, self->custom_combo->mask); + + + /* Setup the accelerator label */ + gtk_shortcut_label_set_accelerator (shortcut_label, accel); + + self->edited = TRUE; + + uninhibit_system_shortcuts (self); + + /* + * Oops! Looks like the accelerator is already being used, so we + * must warn the user and let it be very clear that adding this + * shortcut will disable the other. + */ + gtk_widget_set_visible (GTK_WIDGET (self->new_shortcut_conflict_label), collision_item != NULL); + + if (collision_item) + { + GtkLabel *label; + g_autofree gchar *friendly_accelerator = NULL; + g_autofree gchar *accelerator_text = NULL; + g_autofree gchar *collision_text = NULL; + + friendly_accelerator = convert_keysym_state_to_string (self->custom_combo); + + accelerator_text = g_strdup_printf ("<b>%s</b>", friendly_accelerator); + collision_text = g_strdup_printf (_("%s is already being used for %s. If you " + "replace it, %s will be disabled"), + accelerator_text, + cc_keyboard_item_get_description (collision_item), + cc_keyboard_item_get_description (collision_item)); + + label = is_custom_shortcut (self) ? self->new_shortcut_conflict_label : self->shortcut_conflict_label; + + gtk_label_set_markup (label, collision_text); + } + + /* + * When there is a collision between the current shortcut and another shortcut, + * and we're editing an existing shortcut (rather than creating a new one), setup + * the headerbar to display "Cancel" and "Replace". Otherwise, make sure to set + * only the close button again. + */ + if (collision_item) + { + mode = HEADER_MODE_REPLACE; + } + else + { + if (self->mode == CC_SHORTCUT_EDITOR_EDIT) + mode = is_custom ? HEADER_MODE_CUSTOM_EDIT : HEADER_MODE_SET; + else + mode = is_custom ? HEADER_MODE_ADD : HEADER_MODE_SET; + } + + set_header_mode (self, mode); + + self->collision_item = collision_item; +} + +static void +add_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + CcKeyboardItem *item; + + item = cc_keyboard_manager_create_custom_shortcut (self->manager); + + /* Apply the custom shortcut setup at the new item */ + apply_custom_item_fields (self, item); + + /* Eventually disable the conflict shortcut */ + if (self->collision_item) + cc_keyboard_item_disable (self->collision_item); + + /* Cleanup everything once we're done */ + clear_custom_entries (self); + + cc_keyboard_manager_add_custom_shortcut (self->manager, item); + + gtk_widget_hide (GTK_WIDGET (self)); +} + +static void +cancel_button_clicked_cb (GtkWidget *button, + CcKeyboardShortcutEditor *self) +{ + cancel_editing (self); +} + +static void +change_custom_shortcut_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + inhibit_system_shortcuts (self); + set_shortcut_editor_page (self, PAGE_EDIT); + set_header_mode (self, HEADER_MODE_NONE); +} + +static void +command_entry_changed_cb (CcKeyboardShortcutEditor *self) +{ + setup_custom_shortcut (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)); + + cc_keyboard_manager_remove_custom_shortcut (self->manager, self->item); +} + +static void +replace_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + if (self->mode == CC_SHORTCUT_EDITOR_CREATE) + add_button_clicked_cb (self); + else + set_button_clicked_cb (self); +} + +static void +reset_custom_clicked_cb (CcKeyboardShortcutEditor *self) +{ + if (self->item) + cc_keyboard_manager_reset_shortcut (self->manager, self->item); + + gtk_stack_set_visible_child (self->custom_shortcut_stack, GTK_WIDGET (self->change_custom_shortcut_button)); + gtk_widget_hide (GTK_WIDGET (self->reset_custom_button)); +} + +static void +reset_item_clicked_cb (CcKeyboardShortcutEditor *self) +{ + CcKeyCombo combo; + gchar *accel; + + /* Reset first, then update the shortcut */ + cc_keyboard_manager_reset_shortcut (self->manager, self->item); + + combo = cc_keyboard_item_get_primary_combo (self->item); + accel = gtk_accelerator_name (combo.keyval, combo.mask); + gtk_shortcut_label_set_accelerator (GTK_SHORTCUT_LABEL (self->shortcut_accel_label), accel); + + g_free (accel); +} + +static void +set_button_clicked_cb (CcKeyboardShortcutEditor *self) +{ + update_shortcut (self); + gtk_widget_hide (GTK_WIDGET (self)); +} + +static void +setup_keyboard_item (CcKeyboardShortcutEditor *self, + CcKeyboardItem *item) +{ + CcKeyCombo combo; + gboolean is_custom; + g_autofree gchar *accel = NULL; + g_autofree gchar *description_text = NULL; + g_autofree gchar *text = NULL; + + if (!item) { + gtk_label_set_text (self->top_info_label, _("Enter the new shortcut")); + return; + } + + combo = cc_keyboard_item_get_primary_combo (item); + is_custom = cc_keyboard_item_get_item_type (item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH; + accel = gtk_accelerator_name (combo.keyval, combo.mask); + + /* To avoid accidentally thinking we unset the current keybinding, set the values + * of the keyboard item that is being edited */ + self->custom_is_modifier = FALSE; + *self->custom_combo = combo; + + /* Headerbar */ + gtk_window_set_title (GTK_WINDOW (self), + is_custom ? _("Set Custom Shortcut") : _("Set Shortcut")); + + set_header_mode (self, is_custom ? HEADER_MODE_CUSTOM_EDIT : HEADER_MODE_NONE); + + gtk_widget_hide (GTK_WIDGET (self->add_button)); + gtk_widget_hide (GTK_WIDGET (self->cancel_button)); + gtk_widget_hide (GTK_WIDGET (self->replace_button)); + + /* Setup the top label */ + description_text = g_strdup_printf ("<b>%s</b>", cc_keyboard_item_get_description (item)); + /* TRANSLATORS: %s is replaced with a description of the keyboard shortcut */ + text = g_strdup_printf (_("Enter new shortcut to change %s."), description_text); + + gtk_label_set_markup (self->top_info_label, text); + + /* Accelerator labels */ + gtk_shortcut_label_set_accelerator (self->shortcut_accel_label, accel); + gtk_shortcut_label_set_accelerator (self->custom_shortcut_accel_label, accel); + + g_clear_pointer (&self->reset_item_binding, g_binding_unbind); + self->reset_item_binding = g_object_bind_property (item, + "is-value-default", + self->reset_button, + "visible", + G_BINDING_DEFAULT | G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE); + + /* Setup the custom entries */ + if (is_custom) + { + gboolean is_accel_empty; + + 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_editable_set_text (GTK_EDITABLE (self->name_entry), cc_keyboard_item_get_description (item)); + gtk_widget_set_sensitive (GTK_WIDGET (self->name_entry), cc_keyboard_item_get_desc_editable (item)); + + /* Command entry */ + gtk_editable_set_text (GTK_EDITABLE (self->command_entry), cc_keyboard_item_get_command (item)); + gtk_widget_set_sensitive (GTK_WIDGET (self->command_entry), cc_keyboard_item_get_cmd_editable (item)); + + /* If there is no accelerator set for this custom shortcut, show the "Set Shortcut" button. */ + is_accel_empty = !accel || accel[0] == '\0'; + + gtk_stack_set_visible_child (self->custom_shortcut_stack, + is_accel_empty ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label)); + + gtk_widget_set_visible (GTK_WIDGET (self->reset_custom_button), !is_accel_empty); + + 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); + + uninhibit_system_shortcuts (self); + } + + /* Show the appropriate view */ + set_shortcut_editor_page (self, is_custom ? PAGE_CUSTOM : PAGE_EDIT); +} + +static void +cc_keyboard_shortcut_editor_finalize (GObject *object) +{ + CcKeyboardShortcutEditor *self = (CcKeyboardShortcutEditor *)object; + + g_clear_object (&self->item); + g_clear_object (&self->manager); + + g_clear_pointer (&self->custom_combo, g_free); + + 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_MANAGER: + g_value_set_object (value, self->manager); + 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_MANAGER: + g_set_object (&self->manager, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +on_key_pressed_cb (GtkEventControllerKey *key_controller, + guint keyval, + guint keycode, + GdkModifierType state, + CcKeyboardShortcutEditor *self) +{ + GdkModifierType real_mask; + GdkEvent *event; + gboolean editing; + gboolean is_modifier; + guint keyval_lower; + + /* Being in the "change-shortcut" page is the only check we must + * perform to decide if we're editing a shortcut. */ + editing = get_shortcut_editor_page (self) == PAGE_EDIT; + + if (!editing) + return GDK_EVENT_PROPAGATE; + + normalize_keyval_and_mask (keycode, state, + gtk_event_controller_key_get_group (key_controller), + &keyval_lower, &real_mask); + + event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (key_controller)); + is_modifier = gdk_key_event_is_modifier (event); + + /* A single Escape press cancels the editing */ + if (!is_modifier && real_mask == 0 && keyval_lower == GDK_KEY_Escape) + { + self->edited = FALSE; + + uninhibit_system_shortcuts (self); + cancel_editing (self); + + return GDK_EVENT_STOP; + } + + /* Backspace disables the current shortcut */ + if (!is_modifier && real_mask == 0 && keyval_lower == GDK_KEY_BackSpace) + { + self->edited = TRUE; + self->custom_is_modifier = FALSE; + memset (self->custom_combo, 0, sizeof (CcKeyCombo)); + + 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), ""); + + uninhibit_system_shortcuts (self); + + self->edited = FALSE; + + setup_custom_shortcut (self); + + return GDK_EVENT_STOP; + } + + self->custom_is_modifier = is_modifier; + self->custom_combo->keycode = keycode; + self->custom_combo->keyval = keyval_lower; + self->custom_combo->mask = real_mask; + + /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ + self->custom_combo->mask &= ~GDK_LOCK_MASK; + + 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 gboolean +grab_idle (gpointer data) +{ + CcKeyboardShortcutEditor *self = data; + + if (self->item && cc_keyboard_item_get_item_type (self->item) != CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH) + inhibit_system_shortcuts (self); + + self->grab_idle_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +cc_keyboard_shortcut_editor_show (GtkWidget *widget) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (widget); + + /* Map before grabbing, so that the window is visible */ + GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->show (widget); + + self->grab_idle_id = g_timeout_add (100, grab_idle, self); +} + +static void +cc_keyboard_shortcut_editor_unrealize (GtkWidget *widget) +{ + CcKeyboardShortcutEditor *self = CC_KEYBOARD_SHORTCUT_EDITOR (widget); + + if (self->grab_idle_id) { + g_source_remove (self->grab_idle_id); + self->grab_idle_id = 0; + } + + uninhibit_system_shortcuts (self); + + GTK_WIDGET_CLASS (cc_keyboard_shortcut_editor_parent_class)->unrealize (widget); +} + +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->show = cc_keyboard_shortcut_editor_show; + widget_class->unrealize = cc_keyboard_shortcut_editor_unrealize; + + 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_MANAGER] = g_param_spec_object ("manager", + "Keyboard manager", + "The keyboard manager", + CC_TYPE_KEYBOARD_MANAGER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-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, change_custom_shortcut_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, command_entry); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_grid); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_shortcut_accel_label); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, custom_shortcut_stack); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, edit_box); + 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, new_shortcut_conflict_label); + 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, reset_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, reset_custom_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, set_button); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_accel_label); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, shortcut_conflict_label); + gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutEditor, standard_box); + 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, change_custom_shortcut_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, command_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, name_entry_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_key_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, replace_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, reset_custom_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, reset_item_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, set_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; + self->custom_combo = g_new0 (CcKeyCombo, 1); + + gtk_widget_set_direction (GTK_WIDGET (self->custom_shortcut_accel_label), GTK_TEXT_DIR_LTR); + gtk_widget_set_direction (GTK_WIDGET (self->shortcut_accel_label), GTK_TEXT_DIR_LTR); +} + +/** + * cc_keyboard_shortcut_editor_new: + * + * Creates a new #CcKeyboardShortcutEditor. + * + * Returns: (transfer full): a newly created #CcKeyboardShortcutEditor. + */ +GtkWidget* +cc_keyboard_shortcut_editor_new (CcKeyboardManager *manager) +{ + return g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_EDITOR, + "manager", manager, + "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)); + + setup_keyboard_item (self, item); + + if (!g_set_object (&self->item, item)) + return; + + 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) +{ + gboolean is_create_mode; + + g_return_if_fail (CC_IS_KEYBOARD_SHORTCUT_EDITOR (self)); + + self->mode = mode; + is_create_mode = mode == CC_SHORTCUT_EDITOR_CREATE; + + gtk_widget_set_visible (GTK_WIDGET (self->new_shortcut_conflict_label), is_create_mode); + gtk_stack_set_visible_child (self->custom_shortcut_stack, + is_create_mode ? GTK_WIDGET (self->change_custom_shortcut_button) : GTK_WIDGET (self->custom_shortcut_accel_label)); + + if (mode == CC_SHORTCUT_EDITOR_CREATE) + { + /* Cleanup whatever was set before */ + clear_custom_entries (self); + + set_header_mode (self, HEADER_MODE_ADD); + set_shortcut_editor_page (self, PAGE_CUSTOM); + gtk_window_set_title (GTK_WINDOW (self), _("Add Custom Shortcut")); + + gtk_widget_set_sensitive (GTK_WIDGET (self->command_entry), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->name_entry), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + + gtk_widget_hide (GTK_WIDGET (self->reset_custom_button)); + } +} |