keyboard: Move keyboard shortcuts configuration to a dialog window
This commit is contained in:
parent
ac30be8c6d
commit
238327e0ae
9 changed files with 1201 additions and 637 deletions
|
@ -1,6 +1,8 @@
|
|||
/*
|
||||
/* cc-keyboard-panel.c
|
||||
*
|
||||
* Copyright (C) 2010 Intel, Inc
|
||||
* Copyright (C) 2016 Endless, Inc
|
||||
* Copyright (C) 2020 System76, 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
|
||||
|
@ -17,59 +19,30 @@
|
|||
*
|
||||
* Author: Thomas Wood <thomas.wood@intel.com>
|
||||
* Georges Basile Stavracas Neto <gbsneto@gnome.org>
|
||||
* Ian Douglas Scott <idscott@system76.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "cc-keyboard-shortcut-row.h"
|
||||
#include "cc-keyboard-item.h"
|
||||
#include "cc-keyboard-manager.h"
|
||||
#include "cc-keyboard-panel.h"
|
||||
#include "cc-keyboard-resources.h"
|
||||
#include "cc-keyboard-shortcut-editor.h"
|
||||
#include "cc-keyboard-shortcut-dialog.h"
|
||||
#include "cc-xkb-modifier-dialog.h"
|
||||
|
||||
#include "keyboard-shortcuts.h"
|
||||
|
||||
#include "cc-util.h"
|
||||
|
||||
#define SHORTCUT_DELIMITERS "+ "
|
||||
|
||||
typedef struct {
|
||||
CcKeyboardItem *item;
|
||||
gchar *section_title;
|
||||
gchar *section_id;
|
||||
} RowData;
|
||||
|
||||
struct _CcKeyboardPanel
|
||||
{
|
||||
CcPanel parent_instance;
|
||||
|
||||
/* Search */
|
||||
GtkWidget *empty_search_placeholder;
|
||||
GtkWidget *reset_button;
|
||||
GtkWidget *search_bar;
|
||||
GtkWidget *search_button;
|
||||
GtkWidget *search_entry;
|
||||
guint search_bar_handler_id;
|
||||
|
||||
/* Shortcuts */
|
||||
GtkWidget *shortcuts_listbox;
|
||||
GtkListBoxRow *add_shortcut_row;
|
||||
GtkSizeGroup *accelerator_sizegroup;
|
||||
|
||||
/* Alternate characters key */
|
||||
CcXkbModifierDialog *xkb_modifier_dialog;
|
||||
GSettings *input_source_settings;
|
||||
GtkWidget *value_alternate_chars;
|
||||
|
||||
/* Custom shortcut dialog */
|
||||
GtkWidget *shortcut_editor;
|
||||
|
||||
GRegex *pictures_regex;
|
||||
|
||||
CcKeyboardManager *manager;
|
||||
GtkListBoxRow *common_shortcuts_row;
|
||||
};
|
||||
|
||||
CC_PANEL_REGISTER (CcKeyboardPanel, cc_keyboard_panel)
|
||||
|
@ -79,11 +52,6 @@ enum {
|
|||
PROP_PARAMETERS
|
||||
};
|
||||
|
||||
static const gchar* custom_css =
|
||||
"button.reset-shortcut-button {"
|
||||
" padding: 0;"
|
||||
"}";
|
||||
|
||||
static const XkbModifier LV3_MODIFIER = {
|
||||
"lv3:",
|
||||
N_("Alternate Characters Key"),
|
||||
|
@ -100,409 +68,6 @@ static const XkbModifier LV3_MODIFIER = {
|
|||
"lv3:ralt_switch",
|
||||
};
|
||||
|
||||
/* RowData functions */
|
||||
static RowData *
|
||||
row_data_new (CcKeyboardItem *item,
|
||||
const gchar *section_id,
|
||||
const gchar *section_title)
|
||||
{
|
||||
RowData *data;
|
||||
|
||||
data = g_new0 (RowData, 1);
|
||||
data->item = g_object_ref (item);
|
||||
data->section_id = g_strdup (section_id);
|
||||
data->section_title = g_strdup (section_title);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
row_data_free (RowData *data)
|
||||
{
|
||||
g_object_unref (data->item);
|
||||
g_free (data->section_id);
|
||||
g_free (data->section_title);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
reset_all_shortcuts_cb (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardPanel *self;
|
||||
RowData *data;
|
||||
|
||||
self = user_data;
|
||||
|
||||
if (widget == (GtkWidget *) self->add_shortcut_row)
|
||||
return;
|
||||
|
||||
data = g_object_get_data (G_OBJECT (widget), "data");
|
||||
|
||||
/* Don't reset custom shortcuts */
|
||||
if (cc_keyboard_item_get_item_type (data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
|
||||
return;
|
||||
|
||||
/* cc_keyboard_manager_reset_shortcut() already resets conflicting shortcuts,
|
||||
* so no other check is needed here. */
|
||||
cc_keyboard_manager_reset_shortcut (self->manager, data->item);
|
||||
}
|
||||
|
||||
static void
|
||||
reset_all_clicked_cb (CcKeyboardPanel *self)
|
||||
{
|
||||
GtkWidget *dialog, *toplevel, *button;
|
||||
guint response;
|
||||
|
||||
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (toplevel),
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_WARNING,
|
||||
GTK_BUTTONS_NONE,
|
||||
_("Reset All Shortcuts?"));
|
||||
|
||||
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
|
||||
_("Resetting the shortcuts may affect your custom shortcuts. "
|
||||
"This cannot be undone."));
|
||||
|
||||
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
|
||||
_("Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("Reset All"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
|
||||
|
||||
/* Make the "Reset All" button destructive */
|
||||
button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (button), "destructive-action");
|
||||
|
||||
/* Reset shortcuts if accepted */
|
||||
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
gtk_container_foreach (GTK_CONTAINER (self->shortcuts_listbox),
|
||||
reset_all_shortcuts_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
gtk_widget_destroy (dialog);
|
||||
}
|
||||
|
||||
static void
|
||||
add_item (CcKeyboardPanel *self,
|
||||
CcKeyboardItem *item,
|
||||
const gchar *section_id,
|
||||
const gchar *section_title)
|
||||
{
|
||||
GtkWidget *row;
|
||||
|
||||
row = GTK_WIDGET(cc_keyboard_shortcut_row_new(item,
|
||||
self->manager,
|
||||
CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor),
|
||||
self->accelerator_sizegroup));
|
||||
g_object_set_data_full (G_OBJECT (row),
|
||||
"data",
|
||||
row_data_new (item, section_id, section_title),
|
||||
(GDestroyNotify) row_data_free);
|
||||
gtk_container_add (GTK_CONTAINER (self->shortcuts_listbox), row);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_item (CcKeyboardPanel *self,
|
||||
CcKeyboardItem *item)
|
||||
{
|
||||
GList *children, *l;
|
||||
|
||||
children = gtk_container_get_children (GTK_CONTAINER (self->shortcuts_listbox));
|
||||
|
||||
for (l = children; l != NULL; l = l->next)
|
||||
{
|
||||
RowData *row_data;
|
||||
|
||||
row_data = g_object_get_data (l->data, "data");
|
||||
|
||||
if (row_data->item == item)
|
||||
{
|
||||
gtk_container_remove (GTK_CONTAINER (self->shortcuts_listbox), l->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free (children);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
strv_contains_prefix_or_match (gchar **strv,
|
||||
const gchar *prefix)
|
||||
{
|
||||
guint i;
|
||||
|
||||
const struct {
|
||||
const gchar *key;
|
||||
const gchar *untranslated;
|
||||
const gchar *synonym;
|
||||
} key_aliases[] =
|
||||
{
|
||||
{ "ctrl", "Ctrl", "ctrl" },
|
||||
{ "win", "Super", "super" },
|
||||
{ "option", NULL, "alt" },
|
||||
{ "command", NULL, "super" },
|
||||
{ "apple", NULL, "super" },
|
||||
};
|
||||
|
||||
for (i = 0; strv[i]; i++)
|
||||
{
|
||||
if (g_str_has_prefix (strv[i], prefix))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (key_aliases); i++)
|
||||
{
|
||||
g_autofree gchar *alias = NULL;
|
||||
const gchar *synonym;
|
||||
|
||||
if (!g_str_has_prefix (key_aliases[i].key, prefix))
|
||||
continue;
|
||||
|
||||
if (key_aliases[i].untranslated)
|
||||
{
|
||||
const gchar *translated_label;
|
||||
|
||||
/* Steal GTK+'s translation */
|
||||
translated_label = g_dpgettext2 ("gtk30", "keyboard label", key_aliases[i].untranslated);
|
||||
alias = g_utf8_strdown (translated_label, -1);
|
||||
}
|
||||
|
||||
synonym = key_aliases[i].synonym;
|
||||
|
||||
/* If a translation or synonym of the key is in the accelerator, and we typed
|
||||
* the key, also consider that a prefix */
|
||||
if ((alias && g_strv_contains ((const gchar * const *) strv, alias)) ||
|
||||
(synonym && g_strv_contains ((const gchar * const *) strv, synonym)))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
search_match_shortcut (CcKeyboardItem *item,
|
||||
const gchar *search)
|
||||
{
|
||||
GStrv shortcut_tokens, search_tokens;
|
||||
g_autofree gchar *normalized_accel = NULL;
|
||||
g_autofree gchar *accel = NULL;
|
||||
gboolean match;
|
||||
guint i;
|
||||
GList *key_combos, *l;
|
||||
CcKeyCombo *combo;
|
||||
|
||||
key_combos = cc_keyboard_item_get_key_combos (item);
|
||||
for (l = key_combos; l != NULL; l = l->next)
|
||||
{
|
||||
combo = l->data;
|
||||
|
||||
if (is_empty_binding (combo))
|
||||
continue;
|
||||
|
||||
match = TRUE;
|
||||
accel = convert_keysym_state_to_string (combo);
|
||||
normalized_accel = cc_util_normalize_casefold_and_unaccent (accel);
|
||||
|
||||
shortcut_tokens = g_strsplit_set (normalized_accel, SHORTCUT_DELIMITERS, -1);
|
||||
search_tokens = g_strsplit_set (search, SHORTCUT_DELIMITERS, -1);
|
||||
|
||||
for (i = 0; search_tokens[i] != NULL; i++)
|
||||
{
|
||||
const gchar *token;
|
||||
|
||||
/* Strip leading and trailing whitespaces */
|
||||
token = g_strstrip (search_tokens[i]);
|
||||
|
||||
if (g_utf8_strlen (token, -1) == 0)
|
||||
continue;
|
||||
|
||||
match = match && strv_contains_prefix_or_match (shortcut_tokens, token);
|
||||
|
||||
if (!match)
|
||||
break;
|
||||
}
|
||||
|
||||
g_strfreev (shortcut_tokens);
|
||||
g_strfreev (search_tokens);
|
||||
|
||||
if (match)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gint
|
||||
sort_function (GtkListBoxRow *a,
|
||||
GtkListBoxRow *b,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardPanel *self;
|
||||
RowData *a_data, *b_data;
|
||||
gint retval;
|
||||
|
||||
self = user_data;
|
||||
|
||||
if (a == self->add_shortcut_row)
|
||||
return 1;
|
||||
|
||||
if (b == self->add_shortcut_row)
|
||||
return -1;
|
||||
|
||||
a_data = g_object_get_data (G_OBJECT (a), "data");
|
||||
b_data = g_object_get_data (G_OBJECT (b), "data");
|
||||
|
||||
/* Put custom shortcuts below everything else */
|
||||
if (cc_keyboard_item_get_item_type (a_data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
|
||||
return 1;
|
||||
else if (cc_keyboard_item_get_item_type (b_data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
|
||||
return -1;
|
||||
|
||||
retval = g_strcmp0 (a_data->section_title, b_data->section_title);
|
||||
|
||||
if (retval != 0)
|
||||
return retval;
|
||||
|
||||
return g_strcmp0 (cc_keyboard_item_get_description (a_data->item), cc_keyboard_item_get_description (b_data->item));
|
||||
}
|
||||
|
||||
static void
|
||||
header_function (GtkListBoxRow *row,
|
||||
GtkListBoxRow *before,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardPanel *self;
|
||||
gboolean add_header;
|
||||
RowData *data;
|
||||
|
||||
self = user_data;
|
||||
add_header = FALSE;
|
||||
|
||||
/* The + row always has a separator */
|
||||
if (row == self->add_shortcut_row)
|
||||
{
|
||||
GtkWidget *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_widget_show (separator);
|
||||
|
||||
gtk_list_box_row_set_header (row, separator);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
data = g_object_get_data (G_OBJECT (row), "data");
|
||||
|
||||
if (before)
|
||||
{
|
||||
RowData *before_data = g_object_get_data (G_OBJECT (before), "data");
|
||||
|
||||
if (before_data)
|
||||
add_header = g_strcmp0 (before_data->section_id, data->section_id) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
add_header = TRUE;
|
||||
}
|
||||
|
||||
if (add_header)
|
||||
{
|
||||
GtkWidget *box, *label, *separator;
|
||||
g_autofree gchar *markup = NULL;
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
|
||||
gtk_widget_show (box);
|
||||
gtk_widget_set_margin_top (box, before ? 18 : 6);
|
||||
|
||||
markup = g_strdup_printf ("<b>%s</b>", _(data->section_title));
|
||||
label = gtk_label_new (NULL);
|
||||
gtk_label_set_markup (GTK_LABEL (label), markup);
|
||||
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
|
||||
gtk_widget_set_margin_start (label, 6);
|
||||
gtk_widget_show (label);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
|
||||
gtk_container_add (GTK_CONTAINER (box), label);
|
||||
|
||||
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_widget_show (separator);
|
||||
gtk_container_add (GTK_CONTAINER (box), separator);
|
||||
|
||||
gtk_list_box_row_set_header (row, box);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_list_box_row_set_header (row, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
filter_function (GtkListBoxRow *row,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardPanel *self = user_data;
|
||||
CcKeyboardItem *item;
|
||||
RowData *data;
|
||||
gboolean retval;
|
||||
g_autofree gchar *search = NULL;
|
||||
g_autofree gchar *name = NULL;
|
||||
g_auto(GStrv) terms = NULL;
|
||||
guint i;
|
||||
|
||||
if (gtk_entry_get_text_length (GTK_ENTRY (self->search_entry)) == 0)
|
||||
return TRUE;
|
||||
|
||||
/* When searching, the '+' row is always hidden */
|
||||
if (row == self->add_shortcut_row)
|
||||
return FALSE;
|
||||
|
||||
data = g_object_get_data (G_OBJECT (row), "data");
|
||||
item = data->item;
|
||||
name = cc_util_normalize_casefold_and_unaccent (cc_keyboard_item_get_description (item));
|
||||
search = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (self->search_entry)));
|
||||
terms = g_strsplit (search, " ", -1);
|
||||
|
||||
for (i = 0; terms && terms[i]; i++)
|
||||
{
|
||||
retval = strstr (name, terms[i]) || search_match_shortcut (item, terms[i]);
|
||||
if (!retval)
|
||||
break;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
shortcut_row_activated (GtkWidget *button,
|
||||
GtkListBoxRow *row,
|
||||
CcKeyboardPanel *self)
|
||||
{
|
||||
CcKeyboardShortcutEditor *editor;
|
||||
|
||||
editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);
|
||||
|
||||
if (row != self->add_shortcut_row)
|
||||
{
|
||||
RowData *data = g_object_get_data (G_OBJECT (row), "data");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
gtk_widget_show (self->shortcut_editor);
|
||||
}
|
||||
|
||||
static void
|
||||
alternate_chars_activated (GtkWidget *button,
|
||||
GtkListBoxRow *row,
|
||||
|
@ -516,6 +81,24 @@ alternate_chars_activated (GtkWidget *button,
|
|||
gtk_widget_show (GTK_WIDGET (self->xkb_modifier_dialog));
|
||||
}
|
||||
|
||||
static void
|
||||
keyboard_shortcuts_activated (GtkWidget *button,
|
||||
GtkListBoxRow *row,
|
||||
CcKeyboardPanel *self)
|
||||
{
|
||||
GtkWindow *window;
|
||||
GtkWidget *shortcut_dialog;
|
||||
|
||||
if (row == self->common_shortcuts_row)
|
||||
{
|
||||
window = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))));
|
||||
|
||||
shortcut_dialog = cc_keyboard_shortcut_dialog_new ();
|
||||
gtk_window_set_transient_for (GTK_WINDOW (shortcut_dialog), window);
|
||||
gtk_widget_show (GTK_WIDGET (shortcut_dialog));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_panel_set_property (GObject *object,
|
||||
guint property_id,
|
||||
|
@ -542,45 +125,12 @@ static void
|
|||
cc_keyboard_panel_finalize (GObject *object)
|
||||
{
|
||||
CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object);
|
||||
GtkWidget *window;
|
||||
|
||||
g_clear_pointer (&self->pictures_regex, g_regex_unref);
|
||||
g_clear_object (&self->accelerator_sizegroup);
|
||||
g_clear_object (&self->input_source_settings);
|
||||
|
||||
if (self->search_bar_handler_id != 0)
|
||||
{
|
||||
window = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)));
|
||||
g_signal_handler_disconnect (window, self->search_bar_handler_id);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_panel_constructed (GObject *object)
|
||||
{
|
||||
CcKeyboardPanel *self = CC_KEYBOARD_PANEL (object);
|
||||
GtkWindow *toplevel;
|
||||
CcShell *shell;
|
||||
|
||||
G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructed (object);
|
||||
|
||||
/* Setup the dialog's transient parent */
|
||||
shell = cc_panel_get_shell (CC_PANEL (self));
|
||||
toplevel = GTK_WINDOW (cc_shell_get_toplevel (shell));
|
||||
gtk_window_set_transient_for (GTK_WINDOW (self->shortcut_editor), toplevel);
|
||||
|
||||
cc_shell_embed_widget_in_header (shell, self->reset_button, GTK_POS_LEFT);
|
||||
cc_shell_embed_widget_in_header (shell, self->search_button, GTK_POS_RIGHT);
|
||||
|
||||
self->search_bar_handler_id =
|
||||
g_signal_connect_swapped (toplevel,
|
||||
"key-press-event",
|
||||
G_CALLBACK (gtk_search_bar_handle_event),
|
||||
self->search_bar);
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass)
|
||||
{
|
||||
|
@ -592,45 +142,25 @@ cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass)
|
|||
|
||||
object_class->set_property = cc_keyboard_panel_set_property;
|
||||
object_class->finalize = cc_keyboard_panel_finalize;
|
||||
object_class->constructed = cc_keyboard_panel_constructed;
|
||||
|
||||
g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters");
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-keyboard-panel.ui");
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, add_shortcut_row);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, empty_search_placeholder);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, reset_button);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_bar);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_button);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, search_entry);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, shortcuts_listbox);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, value_alternate_chars);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardPanel, common_shortcuts_row);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, reset_all_clicked_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, shortcut_row_activated);
|
||||
gtk_widget_class_bind_template_callback (widget_class, alternate_chars_activated);
|
||||
gtk_widget_class_bind_template_callback (widget_class, keyboard_shortcuts_activated);
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_panel_init (CcKeyboardPanel *self)
|
||||
{
|
||||
GtkCssProvider *provider;
|
||||
|
||||
g_resources_register (cc_keyboard_get_resource ());
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
/* Custom CSS */
|
||||
provider = gtk_css_provider_new ();
|
||||
gtk_css_provider_load_from_data (provider, custom_css, -1, NULL);
|
||||
|
||||
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
||||
GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
|
||||
|
||||
g_object_unref (provider);
|
||||
|
||||
/* Alternate characters key */
|
||||
self->input_source_settings = g_settings_new ("org.gnome.desktop.input-sources");
|
||||
g_settings_bind_with_mapping (self->input_source_settings,
|
||||
|
@ -644,46 +174,4 @@ cc_keyboard_panel_init (CcKeyboardPanel *self)
|
|||
NULL);
|
||||
|
||||
self->xkb_modifier_dialog = cc_xkb_modifier_dialog_new (self->input_source_settings, &LV3_MODIFIER);
|
||||
|
||||
/* Shortcut manager */
|
||||
self->manager = cc_keyboard_manager_new ();
|
||||
|
||||
/* Shortcut editor dialog */
|
||||
self->shortcut_editor = cc_keyboard_shortcut_editor_new (self->manager);
|
||||
|
||||
/* Use a sizegroup to make the accelerator labels the same width */
|
||||
self->accelerator_sizegroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
||||
|
||||
g_signal_connect_object (self->manager,
|
||||
"shortcut-added",
|
||||
G_CALLBACK (add_item),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (self->manager,
|
||||
"shortcut-removed",
|
||||
G_CALLBACK (remove_item),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
cc_keyboard_manager_load_shortcuts (self->manager);
|
||||
|
||||
/* Setup the shortcuts shortcuts_listbox */
|
||||
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->shortcuts_listbox),
|
||||
sort_function,
|
||||
self,
|
||||
NULL);
|
||||
|
||||
gtk_list_box_set_header_func (GTK_LIST_BOX (self->shortcuts_listbox),
|
||||
header_function,
|
||||
self,
|
||||
NULL);
|
||||
|
||||
gtk_list_box_set_filter_func (GTK_LIST_BOX (self->shortcuts_listbox),
|
||||
filter_function,
|
||||
self,
|
||||
NULL);
|
||||
|
||||
gtk_list_box_set_placeholder (GTK_LIST_BOX (self->shortcuts_listbox), self->empty_search_placeholder);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,4 @@ G_BEGIN_DECLS
|
|||
#define CC_TYPE_KEYBOARD_PANEL (cc_keyboard_panel_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (CcKeyboardPanel, cc_keyboard_panel, CC, KEYBOARD_PANEL, CcPanel)
|
||||
|
||||
CcKeyboardItem* cc_keyboard_panel_create_custom_item (CcKeyboardPanel *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -12,26 +12,11 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="expand">True</property>
|
||||
<signal name="key-press-event" handler="gtk_search_bar_handle_event" object="search_bar" swapped="yes" />
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="search_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="search_mode_enabled" bind-source="search_button" bind-property="active" bind-flags="bidirectional" />
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="width_chars">30</property>
|
||||
<signal name="notify::text" handler="gtk_list_box_invalidate_filter" object="shortcuts_listbox" swapped="yes" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
|
@ -53,6 +38,17 @@
|
|||
<property name="margin_right">18</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="halign">center</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Type Special Characters</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
|
@ -68,7 +64,6 @@
|
|||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="use-underline">true</property>
|
||||
<property name="title" translatable="yes">Alternate Characters Key</property>
|
||||
<property name="subtitle" translatable="yes">Hold down and type to enter different characters</property>
|
||||
<property name="activatable">True</property>
|
||||
|
@ -94,34 +89,41 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">Keyboard Shortcuts</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="shortcuts_listbox">
|
||||
<object class="GtkListBox" id="keyboard_shortcuts_listbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<property name="width-request">250</property>
|
||||
<signal name="row-activated" handler="shortcut_row_activated" object="CcKeyboardPanel" swapped="no" />
|
||||
<signal name="row-activated" handler="keyboard_shortcuts_activated" object="CcKeyboardPanel" swapped="no" />
|
||||
<child>
|
||||
<object class="GtkListBoxRow" id="add_shortcut_row">
|
||||
<object class="HdyActionRow" id="common_shortcuts_row">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="title" translatable="yes">Customize Shortcuts</property>
|
||||
<property name="activatable">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="border_width">6</property>
|
||||
<child type="center">
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="icon_name">go-next-symbolic</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -137,69 +139,4 @@
|
|||
</object>
|
||||
</child>
|
||||
</template>
|
||||
|
||||
<!-- Header widgets -->
|
||||
<object class="GtkToggleButton" id="search_button">
|
||||
<property name="visible">True</property>
|
||||
<style>
|
||||
<class name="image-button" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="icon_name">system-search-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkButton" id="reset_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Reset All…</property>
|
||||
<property name="tooltip-text" translatable="yes">Reset all shortcuts to their default keybindings</property>
|
||||
<signal name="clicked" handler="reset_all_clicked_cb" object="CcKeyboardPanel" swapped="yes" />
|
||||
</object>
|
||||
|
||||
<object class="GtkBox" id="empty_search_placeholder">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="border_width">18</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="pixel_size">80</property>
|
||||
<property name="icon_name">edit-find-symbolic</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">No keyboard shortcut found</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
<attribute name="scale" value="1.44"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Try a different search</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
847
panels/keyboard/cc-keyboard-shortcut-dialog.c
Normal file
847
panels/keyboard/cc-keyboard-shortcut-dialog.c
Normal file
|
@ -0,0 +1,847 @@
|
|||
/* cc-keyboard-shortcut-dialog.c
|
||||
*
|
||||
* Copyright (C) 2010 Intel, Inc
|
||||
* Copyright (C) 2016 Endless, Inc
|
||||
* Copyright (C) 2020 System76, 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/>.
|
||||
*
|
||||
* Author: Thomas Wood <thomas.wood@intel.com>
|
||||
* Georges Basile Stavracas Neto <gbsneto@gnome.org>
|
||||
* Ian Douglas Scott <idscott@system76.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#define HANDY_USE_UNSTABLE_API
|
||||
#include <handy.h>
|
||||
|
||||
#include "cc-keyboard-shortcut-dialog.h"
|
||||
#include "cc-keyboard-item.h"
|
||||
#include "cc-keyboard-manager.h"
|
||||
#include "cc-keyboard-shortcut-editor.h"
|
||||
#include "cc-keyboard-shortcut-row.h"
|
||||
#include "cc-list-row.h"
|
||||
#include "cc-util.h"
|
||||
#include "list-box-helper.h"
|
||||
#include "keyboard-shortcuts.h"
|
||||
|
||||
#define SHORTCUT_DELIMITERS "+ "
|
||||
|
||||
typedef struct {
|
||||
gchar *section_title;
|
||||
gchar *section_id;
|
||||
guint modified_count;
|
||||
GtkLabel *modified_label;
|
||||
} SectionRowData;
|
||||
|
||||
typedef struct {
|
||||
CcKeyboardItem *item;
|
||||
gchar *section_title;
|
||||
gchar *section_id;
|
||||
SectionRowData *section_data;
|
||||
} ShortcutRowData;
|
||||
|
||||
struct _CcKeyboardShortcutDialog
|
||||
{
|
||||
GtkDialog parent_instance;
|
||||
|
||||
GtkSizeGroup *accelerator_sizegroup;
|
||||
GtkRevealer *back_revealer;
|
||||
GtkWidget *custom_shortcut_add_box;
|
||||
guint custom_shortcut_count;
|
||||
GtkWidget *empty_custom_shortcuts_placeholder;
|
||||
GtkWidget *empty_search_placeholder;
|
||||
GtkHeaderBar *headerbar;
|
||||
GtkRevealer *reset_all_revealer;
|
||||
GtkSearchEntry *search_entry;
|
||||
GtkListBox *section_listbox;
|
||||
GtkListBoxRow *section_row;
|
||||
GtkScrolledWindow *section_scrolled_window;
|
||||
GtkListBox *shortcut_listbox;
|
||||
GtkScrolledWindow *shortcut_scrolled_window;
|
||||
GtkStack *stack;
|
||||
|
||||
CcKeyboardManager *manager;
|
||||
GtkWidget *shortcut_editor;
|
||||
GHashTable *sections;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (CcKeyboardShortcutDialog, cc_keyboard_shortcut_dialog, GTK_TYPE_DIALOG)
|
||||
|
||||
static SectionRowData*
|
||||
section_row_data_new (const gchar *section_id,
|
||||
const gchar *section_title,
|
||||
GtkLabel *modified_label)
|
||||
{
|
||||
SectionRowData *data;
|
||||
|
||||
data = g_new0 (SectionRowData, 1);
|
||||
data->section_id = g_strdup (section_id);
|
||||
data->section_title = g_strdup (section_title);
|
||||
data->modified_count = 0;
|
||||
data->modified_label = modified_label;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
section_row_data_free (SectionRowData *data)
|
||||
{
|
||||
g_free (data->section_id);
|
||||
g_free (data->section_title);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static ShortcutRowData*
|
||||
shortcut_row_data_new (CcKeyboardItem *item,
|
||||
const gchar *section_id,
|
||||
const gchar *section_title,
|
||||
SectionRowData *section_data)
|
||||
{
|
||||
ShortcutRowData *data;
|
||||
|
||||
data = g_new0 (ShortcutRowData, 1);
|
||||
data->item = g_object_ref (item);
|
||||
data->section_id = g_strdup (section_id);
|
||||
data->section_title = g_strdup (section_title);
|
||||
data->section_data = section_data;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
shortcut_row_data_free (ShortcutRowData *data)
|
||||
{
|
||||
g_object_unref (data->item);
|
||||
g_free (data->section_id);
|
||||
g_free (data->section_title);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static GtkListBoxRow*
|
||||
add_section (CcKeyboardShortcutDialog *self,
|
||||
const gchar *section_id,
|
||||
const gchar *section_title)
|
||||
{
|
||||
GtkWidget *icon, *modified_label, *label, *box;
|
||||
GtkListBoxRow *row;
|
||||
|
||||
icon = g_object_new (GTK_TYPE_IMAGE,
|
||||
"visible", 1,
|
||||
"icon_name", "go-next-symbolic",
|
||||
NULL);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (icon), "dim-label");
|
||||
|
||||
modified_label = g_object_new (GTK_TYPE_LABEL,
|
||||
"visible", 1,
|
||||
NULL);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (modified_label), "dim-label");
|
||||
|
||||
label = g_object_new (GTK_TYPE_LABEL,
|
||||
"visible", 1,
|
||||
"label", _(section_title),
|
||||
NULL);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (label), "row-label");
|
||||
|
||||
box = g_object_new (GTK_TYPE_BOX,
|
||||
"visible", 1,
|
||||
"spacing", 8,
|
||||
"margin_left", 12,
|
||||
"margin_right", 12,
|
||||
"margin_top", 8,
|
||||
"margin_bottom", 8,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (box), label);
|
||||
gtk_box_pack_end (GTK_BOX (box), icon, FALSE, FALSE, 0);
|
||||
gtk_box_pack_end (GTK_BOX (box), modified_label, FALSE, FALSE, 0);
|
||||
|
||||
row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
|
||||
"visible", 1,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (row), box);
|
||||
|
||||
g_object_set_data_full (G_OBJECT (row),
|
||||
"data",
|
||||
section_row_data_new (section_id, section_title, GTK_LABEL (modified_label)),
|
||||
(GDestroyNotify)section_row_data_free);
|
||||
|
||||
g_hash_table_insert (self->sections, g_strdup (section_id), row);
|
||||
gtk_container_add (GTK_CONTAINER (self->section_listbox), GTK_WIDGET (row));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
static void
|
||||
set_custom_shortcut_add_box_visibility (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
SectionRowData *section_data;
|
||||
gboolean is_custom_shortcuts = FALSE;
|
||||
|
||||
if (self->section_row != NULL)
|
||||
{
|
||||
section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
|
||||
is_custom_shortcuts = (strcmp (section_data->section_id, "custom") == 0);
|
||||
|
||||
gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE);
|
||||
if (is_custom_shortcuts && (self->custom_shortcut_count == 0))
|
||||
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_custom_shortcuts_placeholder));
|
||||
else
|
||||
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->shortcut_scrolled_window));
|
||||
}
|
||||
|
||||
gtk_widget_set_visible (self->custom_shortcut_add_box, is_custom_shortcuts);
|
||||
}
|
||||
|
||||
static void
|
||||
add_item (CcKeyboardShortcutDialog *self,
|
||||
CcKeyboardItem *item,
|
||||
const gchar *section_id,
|
||||
const gchar *section_title)
|
||||
{
|
||||
GtkWidget *row;
|
||||
GtkListBoxRow *section_row;
|
||||
SectionRowData *section_data;
|
||||
|
||||
section_row = g_hash_table_lookup (self->sections, section_id);
|
||||
if (section_row == NULL)
|
||||
section_row = add_section (self, section_id, section_title);
|
||||
|
||||
section_data = g_object_get_data (G_OBJECT (section_row), "data");
|
||||
|
||||
row = GTK_WIDGET (cc_keyboard_shortcut_row_new (item,
|
||||
self->manager,
|
||||
CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor),
|
||||
self->accelerator_sizegroup));
|
||||
|
||||
g_object_set_data_full (G_OBJECT (row),
|
||||
"data",
|
||||
shortcut_row_data_new (item, section_id, section_title, section_data),
|
||||
(GDestroyNotify)shortcut_row_data_free);
|
||||
|
||||
if (strcmp (section_id, "custom") == 0)
|
||||
{
|
||||
self->custom_shortcut_count++;
|
||||
set_custom_shortcut_add_box_visibility (self);
|
||||
}
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (self->shortcut_listbox), row);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_item (CcKeyboardShortcutDialog *self,
|
||||
CcKeyboardItem *item)
|
||||
{
|
||||
g_autoptr(GList) children;
|
||||
|
||||
children = gtk_container_get_children (GTK_CONTAINER (self->shortcut_listbox));
|
||||
|
||||
for (GList *l = children; l != NULL; l = l->next)
|
||||
{
|
||||
ShortcutRowData *row_data;
|
||||
|
||||
row_data = g_object_get_data (l->data, "data");
|
||||
|
||||
if (row_data->item == item)
|
||||
{
|
||||
if (strcmp (row_data->section_id, "custom") == 0)
|
||||
{
|
||||
self->custom_shortcut_count--;
|
||||
set_custom_shortcut_add_box_visibility (self);
|
||||
}
|
||||
|
||||
gtk_container_remove (GTK_CONTAINER (self->shortcut_listbox), l->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_modified_counts (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
g_autoptr(GList) sections = NULL, shortcuts = NULL;
|
||||
SectionRowData *section_data;
|
||||
ShortcutRowData *shortcut_data;
|
||||
g_autofree gchar *modified_text = NULL;
|
||||
|
||||
sections = gtk_container_get_children (GTK_CONTAINER (self->section_listbox));
|
||||
shortcuts = gtk_container_get_children (GTK_CONTAINER (self->shortcut_listbox));
|
||||
|
||||
for (GList *l = sections; l != NULL; l = l->next)
|
||||
{
|
||||
section_data = g_object_get_data (G_OBJECT (l->data), "data");
|
||||
section_data->modified_count = 0;
|
||||
}
|
||||
|
||||
for (GList *l = shortcuts; l != NULL; l = l->next)
|
||||
{
|
||||
shortcut_data = g_object_get_data (G_OBJECT (l->data), "data");
|
||||
if (!cc_keyboard_item_is_value_default (shortcut_data->item))
|
||||
shortcut_data->section_data->modified_count++;
|
||||
}
|
||||
|
||||
for (GList *l = sections; l != NULL; l = l->next)
|
||||
{
|
||||
section_data = g_object_get_data (G_OBJECT (l->data), "data");
|
||||
if (section_data->modified_count > 0)
|
||||
{
|
||||
modified_text = g_strdup_printf ("%d %s", section_data->modified_count, _("modified"));
|
||||
gtk_label_set_text (section_data->modified_label, modified_text);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_label_set_text (section_data->modified_label, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_section_list (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
if (self->section_row != NULL)
|
||||
gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
|
||||
else
|
||||
gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_NONE);
|
||||
self->section_row = NULL;
|
||||
|
||||
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->section_scrolled_window));
|
||||
gtk_header_bar_set_title (self->headerbar, _("Keyboard Shortcuts"));
|
||||
gtk_entry_set_text(GTK_ENTRY (self->search_entry), "");
|
||||
gtk_revealer_set_reveal_child (self->reset_all_revealer, TRUE);
|
||||
gtk_revealer_set_reveal_child (self->back_revealer, FALSE);
|
||||
gtk_widget_set_visible (GTK_WIDGET (self->search_entry), TRUE);
|
||||
|
||||
update_modified_counts (self);
|
||||
}
|
||||
|
||||
static void
|
||||
show_shortcut_list (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
SectionRowData *section_data;
|
||||
gchar *title;
|
||||
gboolean is_custom_shortcuts = FALSE;
|
||||
|
||||
title = _("Keyboard Shortcuts");
|
||||
gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_NONE);
|
||||
if (self->section_row != NULL)
|
||||
{
|
||||
section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
|
||||
title = _(section_data->section_title);
|
||||
is_custom_shortcuts = (strcmp (section_data->section_id, "custom") == 0);
|
||||
gtk_stack_set_transition_type (self->stack, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
|
||||
}
|
||||
|
||||
if (is_custom_shortcuts)
|
||||
gtk_list_box_set_placeholder (self->shortcut_listbox, NULL);
|
||||
else
|
||||
gtk_list_box_set_placeholder (self->shortcut_listbox, self->empty_search_placeholder);
|
||||
|
||||
gtk_list_box_invalidate_filter (self->shortcut_listbox);
|
||||
|
||||
if (is_custom_shortcuts && (self->custom_shortcut_count == 0))
|
||||
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_custom_shortcuts_placeholder));
|
||||
else
|
||||
gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->shortcut_scrolled_window));
|
||||
|
||||
gtk_header_bar_set_title (self->headerbar, title);
|
||||
set_custom_shortcut_add_box_visibility (self);
|
||||
gtk_revealer_set_reveal_child (self->reset_all_revealer, FALSE);
|
||||
gtk_revealer_set_reveal_child (self->back_revealer, TRUE);
|
||||
gtk_widget_set_visible (GTK_WIDGET (self->search_entry), self->section_row == NULL);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
section_row_activated (GtkWidget *button,
|
||||
GtkListBoxRow *row,
|
||||
CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
self->section_row = row;
|
||||
show_shortcut_list (self);
|
||||
}
|
||||
|
||||
static void
|
||||
shortcut_row_activated (GtkWidget *button,
|
||||
GtkListBoxRow *row,
|
||||
CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
CcKeyboardShortcutEditor *editor;
|
||||
|
||||
editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);
|
||||
|
||||
ShortcutRowData *data = g_object_get_data (G_OBJECT (row), "data");
|
||||
|
||||
cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_EDIT);
|
||||
cc_keyboard_shortcut_editor_set_item (editor, data->item);
|
||||
|
||||
gtk_widget_show (self->shortcut_editor);
|
||||
}
|
||||
|
||||
static void
|
||||
add_custom_shortcut_clicked_cb (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
CcKeyboardShortcutEditor *editor;
|
||||
|
||||
editor = CC_KEYBOARD_SHORTCUT_EDITOR (self->shortcut_editor);
|
||||
|
||||
cc_keyboard_shortcut_editor_set_mode (editor, CC_SHORTCUT_EDITOR_CREATE);
|
||||
cc_keyboard_shortcut_editor_set_item (editor, NULL);
|
||||
|
||||
gtk_widget_show (self->shortcut_editor);
|
||||
}
|
||||
|
||||
static void
|
||||
back_button_clicked_cb (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
show_section_list (self);
|
||||
}
|
||||
|
||||
static void
|
||||
reset_all_shortcuts_cb (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardShortcutDialog *self;
|
||||
ShortcutRowData *data;
|
||||
|
||||
self = user_data;
|
||||
|
||||
data = g_object_get_data (G_OBJECT (widget), "data");
|
||||
|
||||
/* Don't reset custom shortcuts */
|
||||
if (cc_keyboard_item_get_item_type (data->item) == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH)
|
||||
return;
|
||||
|
||||
/* cc_keyboard_manager_reset_shortcut() already resets conflicting shortcuts,
|
||||
* so no other check is needed here. */
|
||||
cc_keyboard_manager_reset_shortcut (self->manager, data->item);
|
||||
}
|
||||
|
||||
static void
|
||||
reset_all_clicked_cb (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
GtkWidget *dialog, *toplevel, *button;
|
||||
guint response;
|
||||
|
||||
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (toplevel),
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_USE_HEADER_BAR | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_WARNING,
|
||||
GTK_BUTTONS_NONE,
|
||||
_("Reset All Shortcuts?"));
|
||||
|
||||
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
|
||||
_("Resetting the shortcuts may affect your custom shortcuts. "
|
||||
"This cannot be undone."));
|
||||
|
||||
gtk_dialog_add_buttons (GTK_DIALOG (dialog),
|
||||
_("Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("Reset All"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
|
||||
|
||||
/* Make the "Reset All" button destructive */
|
||||
button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
||||
gtk_style_context_add_class (gtk_widget_get_style_context (button), "destructive-action");
|
||||
|
||||
/* Reset shortcuts if accepted */
|
||||
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
|
||||
if (response == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
gtk_container_foreach (GTK_CONTAINER (self->shortcut_listbox),
|
||||
reset_all_shortcuts_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
update_modified_counts (self);
|
||||
}
|
||||
|
||||
static void
|
||||
search_entry_cb (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
if (gtk_entry_get_text_length (GTK_ENTRY (self->search_entry)) == 0 && self->section_row == NULL)
|
||||
show_section_list (self);
|
||||
else if (gtk_stack_get_visible_child (self->stack) != GTK_WIDGET (self->shortcut_scrolled_window))
|
||||
show_shortcut_list (self);
|
||||
else
|
||||
gtk_list_box_invalidate_filter (self->shortcut_listbox);
|
||||
}
|
||||
|
||||
static void
|
||||
key_press_cb (CcKeyboardShortcutDialog *self, GdkEvent *event)
|
||||
{
|
||||
if (gtk_widget_get_visible (GTK_WIDGET (self->search_entry)))
|
||||
gtk_search_entry_handle_event (self->search_entry, event);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
strv_contains_prefix_or_match (gchar **strv,
|
||||
const gchar *prefix)
|
||||
{
|
||||
const struct {
|
||||
const gchar *key;
|
||||
const gchar *untranslated;
|
||||
const gchar *synonym;
|
||||
} key_aliases[] =
|
||||
{
|
||||
{ "ctrl", "Ctrl", "ctrl" },
|
||||
{ "win", "Super", "super" },
|
||||
{ "option", NULL, "alt" },
|
||||
{ "command", NULL, "super" },
|
||||
{ "apple", NULL, "super" },
|
||||
};
|
||||
|
||||
for (guint i = 0; strv[i]; i++)
|
||||
{
|
||||
if (g_str_has_prefix (strv[i], prefix))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (key_aliases); i++)
|
||||
{
|
||||
g_autofree gchar *alias = NULL;
|
||||
const gchar *synonym;
|
||||
|
||||
if (!g_str_has_prefix (key_aliases[i].key, prefix))
|
||||
continue;
|
||||
|
||||
if (key_aliases[i].untranslated)
|
||||
{
|
||||
const gchar *translated_label;
|
||||
|
||||
/* Steal GTK+'s translation */
|
||||
translated_label = g_dpgettext2 ("gtk30", "keyboard label", key_aliases[i].untranslated);
|
||||
alias = g_utf8_strdown (translated_label, -1);
|
||||
}
|
||||
|
||||
synonym = key_aliases[i].synonym;
|
||||
|
||||
/* If a translation or synonym of the key is in the accelerator, and we typed
|
||||
* the key, also consider that a prefix */
|
||||
if ((alias && g_strv_contains ((const gchar * const *) strv, alias)) ||
|
||||
(synonym && g_strv_contains ((const gchar * const *) strv, synonym)))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
search_match_shortcut (CcKeyboardItem *item,
|
||||
const gchar *search)
|
||||
{
|
||||
g_auto(GStrv) shortcut_tokens = NULL, search_tokens = NULL;
|
||||
g_autofree gchar *normalized_accel = NULL;
|
||||
g_autofree gchar *accel = NULL;
|
||||
gboolean match;
|
||||
GList *key_combos;
|
||||
CcKeyCombo *combo;
|
||||
|
||||
key_combos = cc_keyboard_item_get_key_combos (item);
|
||||
for (GList *l = key_combos; l != NULL; l = l->next)
|
||||
{
|
||||
combo = l->data;
|
||||
|
||||
if (is_empty_binding (combo))
|
||||
continue;
|
||||
|
||||
match = TRUE;
|
||||
accel = convert_keysym_state_to_string (combo);
|
||||
normalized_accel = cc_util_normalize_casefold_and_unaccent (accel);
|
||||
|
||||
shortcut_tokens = g_strsplit_set (normalized_accel, SHORTCUT_DELIMITERS, -1);
|
||||
search_tokens = g_strsplit_set (search, SHORTCUT_DELIMITERS, -1);
|
||||
|
||||
for (guint i = 0; search_tokens[i] != NULL; i++)
|
||||
{
|
||||
const gchar *token;
|
||||
|
||||
/* Strip leading and trailing whitespaces */
|
||||
token = g_strstrip (search_tokens[i]);
|
||||
|
||||
if (g_utf8_strlen (token, -1) == 0)
|
||||
continue;
|
||||
|
||||
match = match && strv_contains_prefix_or_match (shortcut_tokens, token);
|
||||
|
||||
if (!match)
|
||||
break;
|
||||
}
|
||||
|
||||
if (match)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gint
|
||||
section_sort_function (GtkListBoxRow *a,
|
||||
GtkListBoxRow *b,
|
||||
gpointer user_data)
|
||||
{
|
||||
SectionRowData *a_data, *b_data;
|
||||
|
||||
a_data = g_object_get_data (G_OBJECT (a), "data");
|
||||
b_data = g_object_get_data (G_OBJECT (b), "data");
|
||||
|
||||
/* Put custom shortcuts below everything else */
|
||||
if (g_strcmp0 (a_data->section_id, "custom") == 0)
|
||||
return 1;
|
||||
|
||||
return g_strcmp0 (a_data->section_title, b_data->section_title);
|
||||
}
|
||||
|
||||
static gint
|
||||
shortcut_sort_function (GtkListBoxRow *a,
|
||||
GtkListBoxRow *b,
|
||||
gpointer user_data)
|
||||
{
|
||||
ShortcutRowData *a_data, *b_data;
|
||||
gint retval;
|
||||
|
||||
a_data = g_object_get_data (G_OBJECT (a), "data");
|
||||
b_data = g_object_get_data (G_OBJECT (b), "data");
|
||||
|
||||
/* Put custom shortcuts below everything else */
|
||||
if (g_strcmp0 (a_data->section_id, "custom") == 0)
|
||||
return 1;
|
||||
|
||||
retval = g_strcmp0 (a_data->section_title, b_data->section_title);
|
||||
|
||||
if (retval != 0)
|
||||
return retval;
|
||||
|
||||
return g_strcmp0 (cc_keyboard_item_get_description (a_data->item), cc_keyboard_item_get_description (b_data->item));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
shortcut_filter_function (GtkListBoxRow *row,
|
||||
gpointer userdata)
|
||||
{
|
||||
CcKeyboardShortcutDialog *self = userdata;
|
||||
SectionRowData *section_data;
|
||||
ShortcutRowData *data;
|
||||
CcKeyboardItem *item;
|
||||
gboolean retval;
|
||||
g_autofree gchar *search = NULL;
|
||||
g_autofree gchar *name = NULL;
|
||||
g_auto(GStrv) terms = NULL;
|
||||
|
||||
if (self->section_row != NULL)
|
||||
{
|
||||
section_data = g_object_get_data (G_OBJECT (self->section_row), "data");
|
||||
data = g_object_get_data (G_OBJECT (row), "data");
|
||||
if (strcmp (data->section_id, section_data->section_id) != 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (gtk_entry_get_text_length (GTK_ENTRY (self->search_entry)) == 0)
|
||||
return TRUE;
|
||||
|
||||
data = g_object_get_data (G_OBJECT (row), "data");
|
||||
item = data->item;
|
||||
name = cc_util_normalize_casefold_and_unaccent (cc_keyboard_item_get_description (item));
|
||||
search = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (self->search_entry)));
|
||||
terms = g_strsplit (search, " ", -1);
|
||||
|
||||
for (guint i = 0; terms && terms[i]; i++)
|
||||
{
|
||||
retval = strstr (name, terms[i]) || search_match_shortcut (item, terms[i]);
|
||||
if (!retval)
|
||||
break;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void
|
||||
shortcut_header_function (GtkListBoxRow *row,
|
||||
GtkListBoxRow *before,
|
||||
gpointer user_data)
|
||||
{
|
||||
CcKeyboardShortcutDialog *self;
|
||||
gboolean add_header;
|
||||
ShortcutRowData *data, *before_data;
|
||||
|
||||
data = g_object_get_data (G_OBJECT (row), "data");
|
||||
|
||||
self = user_data;
|
||||
add_header = FALSE;
|
||||
|
||||
if (before)
|
||||
{
|
||||
before_data = g_object_get_data (G_OBJECT (before), "data");
|
||||
add_header = g_strcmp0 (before_data->section_id, data->section_id) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
add_header = TRUE;
|
||||
}
|
||||
|
||||
if (self->section_row != NULL)
|
||||
add_header = FALSE;
|
||||
|
||||
if (add_header)
|
||||
{
|
||||
GtkWidget *box, *label, *separator;
|
||||
g_autofree gchar *markup = NULL;
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
|
||||
gtk_widget_show (box);
|
||||
if (!before)
|
||||
gtk_widget_set_margin_top (box, 6);
|
||||
|
||||
if (before)
|
||||
{
|
||||
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_widget_show (separator);
|
||||
gtk_container_add (GTK_CONTAINER (box), separator);
|
||||
}
|
||||
|
||||
markup = g_strdup_printf ("<b>%s</b>", _(data->section_title));
|
||||
label = g_object_new (GTK_TYPE_LABEL,
|
||||
"label", markup,
|
||||
"use-markup", TRUE,
|
||||
"xalign", 0.0,
|
||||
"margin-start", 6,
|
||||
NULL);
|
||||
gtk_widget_show (label);
|
||||
gtk_container_add (GTK_CONTAINER (box), label);
|
||||
|
||||
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_widget_show (separator);
|
||||
gtk_container_add (GTK_CONTAINER (box), separator);
|
||||
|
||||
gtk_list_box_row_set_header (row, box);
|
||||
}
|
||||
else if (before)
|
||||
{
|
||||
GtkWidget *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_widget_show (separator);
|
||||
|
||||
gtk_list_box_row_set_header (row, separator);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_list_box_row_set_header (row, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_shortcut_dialog_constructed (GObject *object)
|
||||
{
|
||||
CcKeyboardShortcutDialog *self = CC_KEYBOARD_SHORTCUT_DIALOG (object);
|
||||
|
||||
G_OBJECT_CLASS (cc_keyboard_shortcut_dialog_parent_class)->constructed (object);
|
||||
|
||||
/* Setup the dialog's transient parent */
|
||||
gtk_window_set_transient_for (GTK_WINDOW (self->shortcut_editor), GTK_WINDOW (self));
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_shortcut_dialog_class_init (CcKeyboardShortcutDialogClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->constructed = cc_keyboard_shortcut_dialog_constructed;
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/keyboard/cc-keyboard-shortcut-dialog.ui");
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, accelerator_sizegroup);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, back_revealer);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, custom_shortcut_add_box);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, empty_custom_shortcuts_placeholder);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, empty_search_placeholder);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, headerbar);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, reset_all_revealer);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, search_entry);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, section_listbox);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, section_scrolled_window);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_listbox);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_scrolled_window);
|
||||
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, stack);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, add_custom_shortcut_clicked_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, key_press_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, reset_all_clicked_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, search_entry_cb);
|
||||
gtk_widget_class_bind_template_callback (widget_class, section_row_activated);
|
||||
gtk_widget_class_bind_template_callback (widget_class, shortcut_row_activated);
|
||||
}
|
||||
|
||||
static void
|
||||
cc_keyboard_shortcut_dialog_init (CcKeyboardShortcutDialog *self)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
self->manager = cc_keyboard_manager_new ();
|
||||
|
||||
self->shortcut_editor = cc_keyboard_shortcut_editor_new (self->manager);
|
||||
|
||||
self->sections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
self->section_row = NULL;
|
||||
|
||||
g_signal_connect_object (self->manager,
|
||||
"shortcut-added",
|
||||
G_CALLBACK (add_item),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (self->manager,
|
||||
"shortcut-removed",
|
||||
G_CALLBACK (remove_item),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
add_section(self, "custom", "Custom Shortcuts");
|
||||
cc_keyboard_manager_load_shortcuts (self->manager);
|
||||
|
||||
gtk_list_box_set_header_func (self->section_listbox, cc_list_box_update_header_func, NULL, NULL);
|
||||
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->section_listbox),
|
||||
section_sort_function,
|
||||
self,
|
||||
NULL);
|
||||
|
||||
gtk_list_box_set_filter_func (self->shortcut_listbox,
|
||||
shortcut_filter_function,
|
||||
self,
|
||||
NULL);
|
||||
gtk_list_box_set_header_func (self->shortcut_listbox,
|
||||
shortcut_header_function,
|
||||
self,
|
||||
NULL);
|
||||
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->shortcut_listbox),
|
||||
shortcut_sort_function,
|
||||
self,
|
||||
NULL);
|
||||
|
||||
show_section_list (self);
|
||||
}
|
||||
|
||||
GtkWidget*
|
||||
cc_keyboard_shortcut_dialog_new (void)
|
||||
{
|
||||
return g_object_new (CC_TYPE_KEYBOARD_SHORTCUT_DIALOG,
|
||||
"use-header-bar", 1,
|
||||
NULL);
|
||||
}
|
35
panels/keyboard/cc-keyboard-shortcut-dialog.h
Normal file
35
panels/keyboard/cc-keyboard-shortcut-dialog.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* cc-keyboard-shortcut-dialog.h
|
||||
*
|
||||
* Copyright (C) 2020 System76, 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/>.
|
||||
*
|
||||
* Author: Ian Douglas Scott <idscott@system76.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CC_TYPE_KEYBOARD_SHORTCUT_DIALOG (cc_keyboard_shortcut_dialog_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CcKeyboardShortcutDialog, cc_keyboard_shortcut_dialog, CC, KEYBOARD_SHORTCUT_DIALOG, GtkDialog)
|
||||
|
||||
GtkWidget* cc_keyboard_shortcut_dialog_new (void);
|
||||
|
||||
G_END_DECLS
|
257
panels/keyboard/cc-keyboard-shortcut-dialog.ui
Normal file
257
panels/keyboard/cc-keyboard-shortcut-dialog.ui
Normal file
|
@ -0,0 +1,257 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.0 -->
|
||||
<template class="CcKeyboardShortcutDialog" parent="GtkDialog">
|
||||
<property name="modal">True</property>
|
||||
<signal name="key-press-event" handler="key_press_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="width_chars">30</property>
|
||||
<property name="halign">center</property>
|
||||
<signal name="notify::text" handler="search_entry_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="section_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="propagate_natural_width">True</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
<property name="max_content_height">350</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="border_width">12</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="section_listbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<property name="border_width">12</property>
|
||||
<signal name="row-activated" handler="section_row_activated" object="CcKeyboardShortcutDialog" swapped="no" />
|
||||
<style>
|
||||
<class name="frame" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="shortcut_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="propagate_natural_width">True</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
<property name="max_content_height">350</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="border_width">12</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="custom_shortcut_add_box">
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="frame" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Add Custom Shortcuts</property>
|
||||
<property name="subtitle" translatable="yes">Set up custom shortcuts for launching apps, running scripts, and more.</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes">Add Shortcut</property>
|
||||
<signal name="clicked" handler="add_custom_shortcut_clicked_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox" id="shortcut_listbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="frame" />
|
||||
</style>
|
||||
<signal name="row-activated" handler="shortcut_row_activated" object="CcKeyboardShortcutDialog" swapped="no" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="empty_custom_shortcuts_placeholder">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="border_width">18</property>
|
||||
<property name="spacing">18</property>
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="background"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="icon-name">input-keyboard-symbolic</property>
|
||||
<property name="pixel-size">128</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Add Custom Shortcuts</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold" />
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Set up custom shortcuts for launching apps, running scripts, and more.</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">Add Shortcut</property>
|
||||
<style>
|
||||
<class name="suggested-action" />
|
||||
</style>
|
||||
<signal name="clicked" handler="add_custom_shortcut_clicked_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="headerbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="back_revealer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="back">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="use-underline">True</property>
|
||||
<signal name="clicked" handler="back_button_clicked_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
<style>
|
||||
<class name="image-button"/>
|
||||
</style>
|
||||
<child internal-child="accessible">
|
||||
<object class="AtkObject" id="a11y-back">
|
||||
<property name="accessible-name" translatable="yes">Back</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="back_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">go-previous-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="reset_all_revealer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="transition-type">crossfade</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="reset_all_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Reset All…</property>
|
||||
<property name="tooltip-text" translatable="yes">Reset all shortcuts to their default keybindings</property>
|
||||
<signal name="clicked" handler="reset_all_clicked_cb" object="CcKeyboardShortcutDialog" swapped="yes" />
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="GtkBox" id="empty_search_placeholder">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="border_width">18</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="pixel_size">80</property>
|
||||
<property name="icon_name">edit-find-symbolic</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">No keyboard shortcut found</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
<attribute name="scale" value="1.44"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Try a different search</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkSizeGroup" id="accelerator_sizegroup" />
|
||||
</interface>
|
|
@ -1,5 +1,5 @@
|
|||
[Desktop Entry]
|
||||
Name=Keyboard Shortcuts
|
||||
Name=Keyboard
|
||||
Comment=View and change keyboard shortcuts and set your typing preferences
|
||||
Exec=gnome-control-center keyboard
|
||||
# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<file preprocess="xml-stripblanks">enter-keyboard-shortcut.svg</file>
|
||||
<file preprocess="xml-stripblanks">cc-xkb-modifier-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">cc-keyboard-shortcut-row.ui</file>
|
||||
<file preprocess="xml-stripblanks">cc-keyboard-shortcut-dialog.ui</file>
|
||||
<file preprocess="xml-stripblanks">cc-keyboard-panel.ui</file>
|
||||
<file preprocess="xml-stripblanks">cc-keyboard-shortcut-editor.ui</file>
|
||||
</gresource>
|
||||
|
|
|
@ -58,6 +58,7 @@ endforeach
|
|||
sources = files(
|
||||
'cc-xkb-modifier-dialog.c',
|
||||
'cc-keyboard-shortcut-row.c',
|
||||
'cc-keyboard-shortcut-dialog.c',
|
||||
'cc-keyboard-panel.c',
|
||||
'cc-keyboard-item.c',
|
||||
'cc-keyboard-manager.c',
|
||||
|
|
Loading…
Add table
Reference in a new issue