Ultimately it would be good to get rid of the use of `g_object_set_data`/`g_object_get_data` here, but this works for now. Fixes https://gitlab.gnome.org/GNOME/gnome-control-center/-/issues/1373.
877 lines
28 KiB
C
877 lines
28 KiB
C
/* 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 <config.h>
|
|
#include <glib/gi18n.h>
|
|
#include <adwaita.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 "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;
|
|
GtkWidget *back_button;
|
|
GtkListBoxRow *custom_shortcut_add_row;
|
|
guint custom_shortcut_count;
|
|
GtkWidget *empty_custom_shortcuts_placeholder;
|
|
GtkWidget *empty_search_placeholder;
|
|
GtkHeaderBar *headerbar;
|
|
GtkStack *header_stack;
|
|
GtkWidget *reset_all_button;
|
|
GtkWidget *section_box;
|
|
GtkSearchEntry *search_entry;
|
|
GtkListBox *section_listbox;
|
|
GtkListBoxRow *section_row;
|
|
GtkWidget *shortcut_box;
|
|
GtkListBox *shortcut_listbox;
|
|
GtkStack *stack;
|
|
|
|
CcKeyboardManager *manager;
|
|
GtkWidget *shortcut_editor;
|
|
GHashTable *sections;
|
|
};
|
|
|
|
G_DEFINE_TYPE (CcKeyboardShortcutDialog, cc_keyboard_shortcut_dialog, GTK_TYPE_DIALOG)
|
|
static gboolean
|
|
is_matched_shortcut_present (GtkListBox *listbox,
|
|
gpointer user_data);
|
|
|
|
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, *row;
|
|
|
|
icon = gtk_image_new_from_icon_name ("go-next-symbolic");
|
|
gtk_widget_add_css_class (icon, "dim-label");
|
|
|
|
modified_label = gtk_label_new (NULL);
|
|
gtk_widget_add_css_class (modified_label, "dim-label");
|
|
|
|
row = adw_action_row_new ();
|
|
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
|
|
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _(section_title));
|
|
//TODO gtk_container_add (GTK_CONTAINER (row), modified_label);
|
|
//TODO gtk_container_add (GTK_CONTAINER (row), icon);
|
|
|
|
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_list_box_append (self->section_listbox, row);
|
|
|
|
return GTK_LIST_BOX_ROW (row);
|
|
}
|
|
|
|
static void
|
|
set_custom_shortcut_placeholder_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, self->shortcut_box);
|
|
}
|
|
}
|
|
|
|
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_placeholder_visibility (self);
|
|
}
|
|
|
|
gtk_list_box_append (self->shortcut_listbox, row);
|
|
}
|
|
|
|
static void
|
|
remove_item (CcKeyboardShortcutDialog *self,
|
|
CcKeyboardItem *item)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
|
|
child;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
ShortcutRowData *row_data;
|
|
|
|
if (!GTK_IS_LIST_BOX_ROW (child))
|
|
continue;
|
|
|
|
row_data = g_object_get_data (G_OBJECT (child), "data");
|
|
|
|
if (row_data && row_data->item == item)
|
|
{
|
|
if (strcmp (row_data->section_id, "custom") == 0)
|
|
{
|
|
self->custom_shortcut_count--;
|
|
set_custom_shortcut_placeholder_visibility (self);
|
|
}
|
|
|
|
gtk_list_box_remove (self->shortcut_listbox, child);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_modified_counts (CcKeyboardShortcutDialog *self)
|
|
{
|
|
SectionRowData *section_data;
|
|
ShortcutRowData *shortcut_data;
|
|
g_autofree gchar *modified_text = NULL;
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->section_listbox));
|
|
child;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!GTK_IS_LIST_BOX_ROW (child))
|
|
continue;
|
|
|
|
section_data = g_object_get_data (G_OBJECT (child), "data");
|
|
section_data->modified_count = 0;
|
|
}
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
|
|
child;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!GTK_IS_LIST_BOX_ROW (child))
|
|
continue;
|
|
|
|
if (GTK_LIST_BOX_ROW (child) == self->custom_shortcut_add_row)
|
|
continue;
|
|
shortcut_data = g_object_get_data (G_OBJECT (child), "data");
|
|
if (!cc_keyboard_item_is_value_default (shortcut_data->item))
|
|
shortcut_data->section_data->modified_count++;
|
|
}
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->section_listbox));
|
|
child;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!GTK_IS_LIST_BOX_ROW (child))
|
|
continue;
|
|
|
|
section_data = g_object_get_data (G_OBJECT (child), "data");
|
|
if (section_data->modified_count > 0)
|
|
{
|
|
modified_text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
|
|
"%d modified",
|
|
"%d modified",
|
|
section_data->modified_count),
|
|
section_data->modified_count);
|
|
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, self->section_box);
|
|
gtk_window_set_title (GTK_WINDOW (self), _("Keyboard Shortcuts"));
|
|
gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
|
|
gtk_stack_set_visible_child (self->header_stack, self->reset_all_button);
|
|
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);
|
|
}
|
|
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, self->shortcut_box);
|
|
|
|
gtk_window_set_title (GTK_WINDOW (self), title);
|
|
set_custom_shortcut_placeholder_visibility (self);
|
|
gtk_stack_set_visible_child (self->header_stack, self->back_button);
|
|
gtk_widget_set_visible (GTK_WIDGET (self->search_entry), self->section_row == NULL);
|
|
|
|
}
|
|
|
|
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
|
|
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;
|
|
|
|
if (row == self->custom_shortcut_add_row)
|
|
{
|
|
add_custom_shortcut_clicked_cb (self);
|
|
return;
|
|
}
|
|
|
|
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
|
|
back_button_clicked_cb (CcKeyboardShortcutDialog *self)
|
|
{
|
|
show_section_list (self);
|
|
}
|
|
|
|
static void
|
|
reset_shortcut (CcKeyboardShortcutDialog *self,
|
|
GtkWidget *row)
|
|
{
|
|
ShortcutRowData *data;
|
|
|
|
if (row == GTK_WIDGET (self->custom_shortcut_add_row))
|
|
return;
|
|
|
|
data = g_object_get_data (G_OBJECT (row), "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
|
|
on_reset_all_dialog_response_cb (GtkDialog *dialog,
|
|
gint response,
|
|
CcKeyboardShortcutDialog *self)
|
|
{
|
|
|
|
if (response == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self->shortcut_listbox));
|
|
child;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (!GTK_IS_LIST_BOX_ROW (child))
|
|
continue;
|
|
if (GTK_LIST_BOX_ROW (child) == self->custom_shortcut_add_row)
|
|
continue;
|
|
reset_shortcut (self, child);
|
|
}
|
|
}
|
|
|
|
gtk_window_destroy (GTK_WINDOW (dialog));
|
|
update_modified_counts (self);
|
|
}
|
|
|
|
static void
|
|
reset_all_clicked_cb (CcKeyboardShortcutDialog *self)
|
|
{
|
|
GtkWidget *dialog, *button;
|
|
|
|
dialog = gtk_message_dialog_new (GTK_WINDOW (self),
|
|
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_widget_add_css_class (button, "destructive-action");
|
|
|
|
g_signal_connect (dialog, "response", G_CALLBACK (on_reset_all_dialog_response_cb), self);
|
|
|
|
gtk_window_present (GTK_WINDOW (dialog));
|
|
}
|
|
|
|
static void
|
|
search_entry_cb (CcKeyboardShortcutDialog *self)
|
|
{
|
|
gboolean is_shortcut = is_matched_shortcut_present (self->shortcut_listbox, self);
|
|
const gchar *search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_entry));
|
|
|
|
if (!is_shortcut)
|
|
gtk_stack_set_visible_child (self->stack, self->empty_search_placeholder);
|
|
else if (g_utf8_strlen (search_text, -1) == 0 && self->section_row == NULL)
|
|
show_section_list (self);
|
|
else if (gtk_stack_get_visible_child (self->stack) != self->shortcut_box)
|
|
show_shortcut_list (self);
|
|
else
|
|
gtk_list_box_invalidate_filter (self->shortcut_listbox);
|
|
}
|
|
|
|
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 ("gtk40", "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)
|
|
{
|
|
CcKeyboardShortcutDialog *self = user_data;
|
|
ShortcutRowData *a_data, *b_data;
|
|
gint retval;
|
|
|
|
if (a == self->custom_shortcut_add_row)
|
|
return 1;
|
|
else if (b == self->custom_shortcut_add_row)
|
|
return -1;
|
|
|
|
a_data = g_object_get_data (G_OBJECT (a), "data");
|
|
b_data = g_object_get_data (G_OBJECT (b), "data");
|
|
|
|
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;
|
|
gboolean is_custom_shortcuts = FALSE;
|
|
const gchar *search_text;
|
|
|
|
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);
|
|
|
|
data = g_object_get_data (G_OBJECT (row), "data");
|
|
if (data && strcmp (data->section_id, section_data->section_id) != 0)
|
|
return FALSE;
|
|
}
|
|
|
|
if (row == self->custom_shortcut_add_row)
|
|
return is_custom_shortcuts;
|
|
|
|
search_text = gtk_editable_get_text (GTK_EDITABLE (self->search_entry));
|
|
if (g_utf8_strlen (search_text, -1) == 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 (search_text);
|
|
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 gboolean
|
|
is_matched_shortcut_present (GtkListBox* listbox,
|
|
gpointer user_data)
|
|
{
|
|
for (gint i = 0; ; i++)
|
|
{
|
|
GtkListBoxRow *current = gtk_list_box_get_row_at_index (listbox, i);
|
|
if (!current)
|
|
return FALSE;
|
|
if (shortcut_filter_function (current, user_data))
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
shortcut_header_function (GtkListBoxRow *row,
|
|
GtkListBoxRow *before,
|
|
gpointer user_data)
|
|
{
|
|
CcKeyboardShortcutDialog *self = user_data;
|
|
gboolean add_header;
|
|
ShortcutRowData *data, *before_data;
|
|
|
|
data = g_object_get_data (G_OBJECT (row), "data");
|
|
|
|
if (row == self->custom_shortcut_add_row)
|
|
{
|
|
|
|
add_header = FALSE;
|
|
}
|
|
else if (before && before != self->custom_shortcut_add_row)
|
|
{
|
|
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);
|
|
if (!before)
|
|
gtk_widget_set_margin_top (box, 6);
|
|
|
|
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_box_append (GTK_BOX (box), label);
|
|
|
|
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
|
|
gtk_box_append (GTK_BOX (box), separator);
|
|
|
|
gtk_list_box_row_set_header (row, box);
|
|
}
|
|
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_finalize (GObject *object)
|
|
{
|
|
CcKeyboardShortcutDialog *self = CC_KEYBOARD_SHORTCUT_DIALOG (object);
|
|
|
|
g_clear_object (&self->manager);
|
|
g_clear_pointer (&self->sections, g_hash_table_destroy);
|
|
g_clear_pointer ((GtkWindow**)&self->shortcut_editor, gtk_window_destroy);
|
|
}
|
|
|
|
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;
|
|
object_class->finalize = cc_keyboard_shortcut_dialog_finalize;
|
|
|
|
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_button);
|
|
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, custom_shortcut_add_row);
|
|
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, header_stack);
|
|
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, reset_all_button);
|
|
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_box);
|
|
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_listbox);
|
|
gtk_widget_class_bind_template_child (widget_class, CcKeyboardShortcutDialog, shortcut_box);
|
|
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, 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));
|
|
|
|
gtk_search_entry_set_key_capture_widget (self->search_entry, 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_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);
|
|
}
|