gnome-control-center/panels/keyboard/keyboard-shortcuts.c
Marcus Karlsson fe9fe99439 keyboard: custom shortcut should require name and command
It is possible to press the Add button in the custom shortcut dialog
when the name and command fields are empty. Disable the button by
default, and only enable it when the name and command is non-empty.

https://bugzilla.gnome.org/show_bug.cgi?id=739647
2015-02-10 13:50:17 +01:00

2197 lines
66 KiB
C

/*
* Copyright (C) 2010 Intel, Inc
* Copyright (C) 2014 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Authors: Thomas Wood <thomas.wood@intel.com>
* Rodrigo Moya <rodrigo@gnome.org>
* Christophe Fergeau <cfergeau@redhat.com>
*/
#include <config.h>
#include <glib/gi18n.h>
#include "keyboard-shortcuts.h"
#include "cc-keyboard-item.h"
#include "cc-keyboard-option.h"
#include "wm-common.h"
#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif
#define BINDINGS_SCHEMA "org.gnome.settings-daemon.plugins.media-keys"
#define CUSTOM_KEYS_BASENAME "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings"
#define CUSTOM_SHORTCUTS_ID "custom"
#define WID(builder, name) (GTK_WIDGET (gtk_builder_get_object (builder, name)))
typedef struct {
/* The untranslated name, combine with ->package to translate */
char *name;
/* The group of keybindings (system or application) */
char *group;
/* The gettext package to use to translate the section title */
char *package;
/* Name of the window manager the keys would apply to */
char *wm_name;
/* The GSettings schema for the whole file, if any */
char *schema;
/* an array of KeyListEntry */
GArray *entries;
} KeyList;
typedef struct
{
CcKeyboardItemType type;
char *schema; /* GSettings schema name, if any */
char *description; /* description for GSettings types */
char *gettext_package;
char *name; /* GSettings schema path, or GSettings key name depending on type */
char *reverse_entry;
gboolean is_reversed;
gboolean hidden;
} KeyListEntry;
typedef enum
{
SHORTCUT_TYPE_KEY_ENTRY,
SHORTCUT_TYPE_XKB_OPTION,
} ShortcutType;
enum
{
DETAIL_DESCRIPTION_COLUMN,
DETAIL_KEYENTRY_COLUMN,
DETAIL_TYPE_COLUMN,
DETAIL_N_COLUMNS
};
enum
{
SECTION_DESCRIPTION_COLUMN,
SECTION_ID_COLUMN,
SECTION_GROUP_COLUMN,
SECTION_N_COLUMNS
};
static GRegex *pictures_regex = NULL;
static GSettings *binding_settings = NULL;
static GtkWidget *custom_shortcut_dialog = NULL;
static GtkWidget *custom_shortcut_name_entry = NULL;
static GtkWidget *custom_shortcut_command_entry = NULL;
static GtkWidget *custom_shortcut_ok_button = NULL;
static GHashTable *kb_system_sections = NULL;
static GHashTable *kb_apps_sections = NULL;
static GHashTable *kb_user_sections = NULL;
static gpointer wm_changed_id = NULL;
static void
free_key_array (GPtrArray *keys)
{
if (keys != NULL)
{
gint i;
for (i = 0; i < keys->len; i++)
{
CcKeyboardItem *item;
item = g_ptr_array_index (keys, i);
g_object_unref (item);
}
g_ptr_array_free (keys, TRUE);
}
}
static GHashTable *
get_hash_for_group (BindingGroupType group)
{
GHashTable *hash;
switch (group)
{
case BINDING_GROUP_SYSTEM:
hash = kb_system_sections;
break;
case BINDING_GROUP_APPS:
hash = kb_apps_sections;
break;
case BINDING_GROUP_USER:
hash = kb_user_sections;
break;
default:
hash = NULL;
}
return hash;
}
static gboolean
have_key_for_group (int group, const gchar *name)
{
GHashTableIter iter;
GPtrArray *keys;
gint i;
g_hash_table_iter_init (&iter, get_hash_for_group (group));
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&keys))
{
for (i = 0; i < keys->len; i++)
{
CcKeyboardItem *item = g_ptr_array_index (keys, i);
if (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS &&
g_strcmp0 (name, item->key) == 0)
{
return TRUE;
}
return FALSE;
}
}
return FALSE;
}
static gboolean
keybinding_key_changed_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
CcKeyboardItem *item)
{
CcKeyboardItem *tmp_item;
gtk_tree_model_get (item->model, iter,
DETAIL_KEYENTRY_COLUMN, &tmp_item,
-1);
if (item == tmp_item)
{
gtk_tree_model_row_changed (item->model, path, iter);
return TRUE;
}
return FALSE;
}
static void
item_changed (CcKeyboardItem *item,
GParamSpec *pspec,
gpointer user_data)
{
/* update the model */
gtk_tree_model_foreach (item->model, (GtkTreeModelForeachFunc) keybinding_key_changed_foreach, item);
}
static void
append_section (GtkBuilder *builder,
const gchar *title,
const gchar *id,
BindingGroupType group,
const KeyListEntry *keys_list)
{
GPtrArray *keys_array;
GtkTreeModel *sort_model;
GtkTreeModel *model, *shortcut_model;
GtkTreeIter iter;
gint i;
GHashTable *hash;
gboolean is_new;
hash = get_hash_for_group (group);
if (!hash)
return;
sort_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview")));
model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
/* Add all CcKeyboardItems for this section */
is_new = FALSE;
keys_array = g_hash_table_lookup (hash, id);
if (keys_array == NULL)
{
keys_array = g_ptr_array_new ();
is_new = TRUE;
}
GHashTable *reverse_items = g_hash_table_new (g_str_hash, g_str_equal);
for (i = 0; keys_list != NULL && keys_list[i].name != NULL; i++)
{
CcKeyboardItem *item;
gboolean ret;
if (have_key_for_group (group, keys_list[i].name))
continue;
item = cc_keyboard_item_new (keys_list[i].type);
switch (keys_list[i].type)
{
case CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH:
ret = cc_keyboard_item_load_from_gsettings_path (item, keys_list[i].name, FALSE);
break;
case CC_KEYBOARD_ITEM_TYPE_GSETTINGS:
ret = cc_keyboard_item_load_from_gsettings (item,
keys_list[i].description,
keys_list[i].schema,
keys_list[i].name);
if (ret && keys_list[i].reverse_entry != NULL)
{
CcKeyboardItem *reverse_item;
reverse_item = g_hash_table_lookup (reverse_items,
keys_list[i].reverse_entry);
if (reverse_item != NULL)
{
cc_keyboard_item_add_reverse_item (item,
reverse_item,
keys_list[i].is_reversed);
}
else
{
g_hash_table_insert (reverse_items,
keys_list[i].name,
item);
}
}
break;
default:
g_assert_not_reached ();
}
if (ret == FALSE)
{
/* We don't actually want to popup a dialog - just skip this one */
g_object_unref (item);
continue;
}
cc_keyboard_item_set_hidden (item, keys_list[i].hidden);
item->model = shortcut_model;
item->group = group;
g_signal_connect (G_OBJECT (item), "notify",
G_CALLBACK (item_changed), NULL);
g_ptr_array_add (keys_array, item);
}
g_hash_table_destroy (reverse_items);
/* Add the keys to the hash table */
if (is_new)
{
g_hash_table_insert (hash, g_strdup (id), keys_array);
/* Append the section to the left tree view */
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
SECTION_DESCRIPTION_COLUMN, title,
SECTION_ID_COLUMN, id,
SECTION_GROUP_COLUMN, group,
-1);
}
}
static char *
replace_pictures_folder (const char *description)
{
const char *path;
char *dirname;
char *ret;
if (description == NULL)
return NULL;
if (strstr (description, "$PICTURES") == NULL)
return g_strdup (description);
path = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
dirname = g_filename_display_basename (path);
ret = g_regex_replace (pictures_regex, description, -1,
0, dirname, 0, NULL);
g_free (dirname);
if (ret == NULL)
return g_strdup (description);
return ret;
}
static void
parse_start_tag (GMarkupParseContext *ctx,
const gchar *element_name,
const gchar **attr_names,
const gchar **attr_values,
gpointer user_data,
GError **error)
{
KeyList *keylist = (KeyList *) user_data;
KeyListEntry key = { 0, };
const char *name, *schema, *description, *package, *context, *orig_description, *reverse_entry;
gboolean is_reversed, hidden;
name = NULL;
schema = NULL;
package = NULL;
context = NULL;
/* The top-level element, names the section in the tree */
if (g_str_equal (element_name, "KeyListEntries"))
{
const char *wm_name = NULL;
const char *group = NULL;
while (*attr_names && *attr_values)
{
if (g_str_equal (*attr_names, "name"))
{
if (**attr_values)
name = *attr_values;
} else if (g_str_equal (*attr_names, "group")) {
if (**attr_values)
group = *attr_values;
} else if (g_str_equal (*attr_names, "wm_name")) {
if (**attr_values)
wm_name = *attr_values;
} else if (g_str_equal (*attr_names, "schema")) {
if (**attr_values)
schema = *attr_values;
} else if (g_str_equal (*attr_names, "package")) {
if (**attr_values)
package = *attr_values;
}
++attr_names;
++attr_values;
}
if (name)
{
if (keylist->name)
g_warning ("Duplicate section name");
g_free (keylist->name);
keylist->name = g_strdup (name);
}
if (wm_name)
{
if (keylist->wm_name)
g_warning ("Duplicate window manager name");
g_free (keylist->wm_name);
keylist->wm_name = g_strdup (wm_name);
}
if (package)
{
if (keylist->package)
g_warning ("Duplicate gettext package name");
g_free (keylist->package);
keylist->package = g_strdup (package);
bind_textdomain_codeset (keylist->package, "UTF-8");
}
if (group)
{
if (keylist->group)
g_warning ("Duplicate group");
g_free (keylist->group);
keylist->group = g_strdup (group);
}
if (schema)
{
if (keylist->schema)
g_warning ("Duplicate schema");
g_free (keylist->schema);
keylist->schema = g_strdup (schema);
}
return;
}
if (!g_str_equal (element_name, "KeyListEntry")
|| attr_names == NULL
|| attr_values == NULL)
return;
schema = NULL;
description = NULL;
context = NULL;
orig_description = NULL;
reverse_entry = NULL;
is_reversed = FALSE;
hidden = FALSE;
while (*attr_names && *attr_values)
{
if (g_str_equal (*attr_names, "name"))
{
/* skip if empty */
if (**attr_values)
name = *attr_values;
} else if (g_str_equal (*attr_names, "schema")) {
if (**attr_values) {
schema = *attr_values;
}
} else if (g_str_equal (*attr_names, "description")) {
if (**attr_values)
orig_description = *attr_values;
} else if (g_str_equal (*attr_names, "msgctxt")) {
if (**attr_values)
context = *attr_values;
} else if (g_str_equal (*attr_names, "reverse-entry")) {
if (**attr_values)
reverse_entry = *attr_values;
} else if (g_str_equal (*attr_names, "is-reversed")) {
if (g_str_equal (*attr_values, "true"))
is_reversed = TRUE;
} else if (g_str_equal (*attr_names, "hidden")) {
if (g_str_equal (*attr_values, "true"))
hidden = TRUE;
}
++attr_names;
++attr_values;
}
if (name == NULL)
return;
if (schema == NULL &&
keylist->schema == NULL) {
g_debug ("Ignored GConf keyboard shortcut '%s'", name);
return;
}
if (context != NULL)
description = g_dpgettext2 (keylist->package, context, orig_description);
else
description = dgettext (keylist->package, orig_description);
key.name = g_strdup (name);
key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS;
key.description = replace_pictures_folder (description);
key.gettext_package = g_strdup (keylist->package);
key.schema = schema ? g_strdup (schema) : g_strdup (keylist->schema);
key.reverse_entry = g_strdup (reverse_entry);
key.is_reversed = is_reversed;
key.hidden = hidden;
g_array_append_val (keylist->entries, key);
}
static gboolean
strv_contains (char **strv,
char *str)
{
char **p = strv;
for (p = strv; *p; p++)
if (strcmp (*p, str) == 0)
return TRUE;
return FALSE;
}
static void
append_sections_from_file (GtkBuilder *builder, const gchar *path, const char *datadir, gchar **wm_keybindings)
{
GError *err = NULL;
char *buf;
gsize buf_len;
KeyList *keylist;
KeyListEntry *keys;
KeyListEntry key = { 0, };
const char *title;
int group;
guint i;
GMarkupParseContext *ctx;
GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL };
/* Parse file */
if (!g_file_get_contents (path, &buf, &buf_len, &err))
return;
keylist = g_new0 (KeyList, 1);
keylist->entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
ctx = g_markup_parse_context_new (&parser, 0, keylist, NULL);
if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err))
{
g_warning ("Failed to parse '%s': '%s'", path, err->message);
g_error_free (err);
g_free (keylist->name);
g_free (keylist->package);
g_free (keylist->wm_name);
for (i = 0; i < keylist->entries->len; i++)
g_free (((KeyListEntry *) &(keylist->entries->data[i]))->name);
g_array_free (keylist->entries, TRUE);
g_free (keylist);
keylist = NULL;
}
g_markup_parse_context_free (ctx);
g_free (buf);
if (keylist == NULL)
return;
/* If there's no keys to add, or the settings apply to a window manager
* that's not the one we're running */
if (keylist->entries->len == 0
|| (keylist->wm_name != NULL && !strv_contains (wm_keybindings, keylist->wm_name))
|| keylist->name == NULL)
{
g_free (keylist->name);
g_free (keylist->package);
g_free (keylist->wm_name);
g_array_free (keylist->entries, TRUE);
g_free (keylist);
return;
}
/* Empty KeyListEntry to end the array */
key.name = NULL;
g_array_append_val (keylist->entries, key);
keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE);
if (keylist->package)
{
char *localedir;
localedir = g_build_filename (datadir, "locale", NULL);
bindtextdomain (keylist->package, localedir);
g_free (localedir);
title = dgettext (keylist->package, keylist->name);
} else {
title = _(keylist->name);
}
if (keylist->group && strcmp (keylist->group, "system") == 0)
group = BINDING_GROUP_SYSTEM;
else
group = BINDING_GROUP_APPS;
append_section (builder, title, keylist->name, group, keys);
g_free (keylist->name);
g_free (keylist->package);
g_free (keylist->wm_name);
g_free (keylist->schema);
g_free (keylist->group);
for (i = 0; keys[i].name != NULL; i++) {
KeyListEntry *entry = &keys[i];
g_free (entry->schema);
g_free (entry->description);
g_free (entry->gettext_package);
g_free (entry->name);
g_free (entry->reverse_entry);
}
g_free (keylist);
}
static void
append_sections_from_gsettings (GtkBuilder *builder)
{
char **custom_paths;
GArray *entries;
KeyListEntry key = { 0, };
int i;
/* load custom shortcuts from GSettings */
entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry));
custom_paths = g_settings_get_strv (binding_settings, "custom-keybindings");
for (i = 0; custom_paths[i]; i++)
{
key.name = g_strdup (custom_paths[i]);
if (!have_key_for_group (BINDING_GROUP_USER, key.name))
{
key.type = CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH;
g_array_append_val (entries, key);
}
else
g_free (key.name);
}
g_strfreev (custom_paths);
if (entries->len > 0)
{
KeyListEntry *keys;
int i;
/* Empty KeyListEntry to end the array */
key.name = NULL;
g_array_append_val (entries, key);
keys = (KeyListEntry *) entries->data;
append_section (builder, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, keys);
for (i = 0; i < entries->len; ++i)
{
g_free (keys[i].name);
}
}
else
{
append_section (builder, _("Custom Shortcuts"), CUSTOM_SHORTCUTS_ID, BINDING_GROUP_USER, NULL);
}
g_array_free (entries, TRUE);
}
static void
reload_sections (CcPanel *panel)
{
GtkBuilder *builder;
gchar **wm_keybindings;
gchar *default_wm_keybindings[] = { "Mutter", "GNOME Shell", NULL };
GDir *dir;
GtkTreeModel *sort_model;
GtkTreeModel *section_model;
GtkTreeModel *shortcut_model;
const gchar * const * data_dirs;
guint i;
GtkTreeView *section_treeview;
GtkTreeSelection *selection;
GtkTreeIter iter;
GHashTable *loaded_files;
const char *section_to_set;
builder = g_object_get_data (G_OBJECT (panel), "builder");
section_treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"));
sort_model = gtk_tree_view_get_model (section_treeview);
section_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sort_model));
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview")));
/* FIXME: get current selection and keep it after refreshing */
/* Clear previous models and hash tables */
gtk_list_store_clear (GTK_LIST_STORE (section_model));
gtk_list_store_clear (GTK_LIST_STORE (shortcut_model));
if (kb_system_sections != NULL)
g_hash_table_destroy (kb_system_sections);
kb_system_sections = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) free_key_array);
if (kb_apps_sections != NULL)
g_hash_table_destroy (kb_apps_sections);
kb_apps_sections = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) free_key_array);
if (kb_user_sections != NULL)
g_hash_table_destroy (kb_user_sections);
kb_user_sections = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) free_key_array);
/* Load WM keybindings */
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
wm_keybindings = wm_common_get_current_keybindings ();
#endif
else
wm_keybindings = g_strdupv (default_wm_keybindings);
loaded_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
data_dirs = g_get_system_data_dirs ();
for (i = 0; data_dirs[i] != NULL; i++)
{
char *dir_path;
const gchar *name;
dir_path = g_build_filename (data_dirs[i], "gnome-control-center", "keybindings", NULL);
dir = g_dir_open (dir_path, 0, NULL);
if (!dir)
{
g_free (dir_path);
continue;
}
for (name = g_dir_read_name (dir) ; name ; name = g_dir_read_name (dir))
{
gchar *path;
if (g_str_has_suffix (name, ".xml") == FALSE)
continue;
if (g_hash_table_lookup (loaded_files, name) != NULL)
{
g_debug ("Not loading %s, it was already loaded from another directory", name);
continue;
}
g_hash_table_insert (loaded_files, g_strdup (name), GINT_TO_POINTER (1));
path = g_build_filename (dir_path, name, NULL);
append_sections_from_file (builder, path, data_dirs[i], wm_keybindings);
g_free (path);
}
g_free (dir_path);
g_dir_close (dir);
}
g_hash_table_destroy (loaded_files);
g_strfreev (wm_keybindings);
/* Add a separator */
gtk_list_store_append (GTK_LIST_STORE (section_model), &iter);
gtk_list_store_set (GTK_LIST_STORE (section_model), &iter,
SECTION_DESCRIPTION_COLUMN, NULL,
SECTION_GROUP_COLUMN, BINDING_GROUP_SEPARATOR,
-1);
/* Load custom keybindings */
append_sections_from_gsettings (builder);
/* Select the first item, or the requested section, if any */
section_to_set = g_object_get_data (G_OBJECT (panel), "section-to-set");
if (section_to_set != NULL)
{
if (keyboard_shortcuts_set_section (panel, section_to_set))
{
g_object_set_data (G_OBJECT (panel), "section-to-set", NULL);
return;
}
}
gtk_tree_model_get_iter_first (sort_model, &iter);
selection = gtk_tree_view_get_selection (section_treeview);
gtk_tree_selection_select_iter (selection, &iter);
g_object_set_data (G_OBJECT (panel), "section-to-set", NULL);
}
static void
accel_set_func (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
gpointer entry;
ShortcutType type;
gtk_tree_model_get (model, iter,
DETAIL_KEYENTRY_COLUMN, &entry,
DETAIL_TYPE_COLUMN, &type,
-1);
gtk_cell_renderer_set_visible (cell, FALSE);
if (type == SHORTCUT_TYPE_XKB_OPTION &&
GTK_IS_CELL_RENDERER_COMBO (cell))
{
CcKeyboardOption *option = entry;
gtk_cell_renderer_set_visible (cell, TRUE);
g_object_set (cell,
"model", cc_keyboard_option_get_store (option),
"text", cc_keyboard_option_get_current_value_description (option),
NULL);
}
else if (type == SHORTCUT_TYPE_KEY_ENTRY &&
GTK_IS_CELL_RENDERER_TEXT (cell) &&
!GTK_IS_CELL_RENDERER_COMBO (cell) &&
entry != NULL)
{
CcKeyboardItem *item = entry;
gtk_cell_renderer_set_visible (cell, TRUE);
if (item->editable)
g_object_set (cell,
"editable", TRUE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_NORMAL,
NULL);
else
g_object_set (cell,
"editable", FALSE,
"accel-key", item->keyval,
"accel-mods", item->mask,
"keycode", item->keycode,
"style", PANGO_STYLE_ITALIC,
NULL);
}
}
static void
description_set_func (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
gchar *description;
CcKeyboardItem *item;
ShortcutType type;
gtk_tree_model_get (model, iter,
DETAIL_DESCRIPTION_COLUMN, &description,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type == SHORTCUT_TYPE_XKB_OPTION)
{
g_object_set (cell, "text", description, NULL);
}
else
{
if (item != NULL)
g_object_set (cell,
"editable", FALSE,
"text", item->description != NULL ?
item->description : _("<Unknown Action>"),
NULL);
else
g_object_set (cell,
"editable", FALSE, NULL);
}
g_free (description);
}
static void
shortcut_selection_changed (GtkTreeSelection *selection, gpointer data)
{
GtkWidget *button = data;
GtkTreeModel *model;
GtkTreeIter iter;
CcKeyboardItem *item;
gboolean can_remove;
ShortcutType type;
can_remove = FALSE;
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type == SHORTCUT_TYPE_KEY_ENTRY &&
item && item->command != NULL && item->editable)
can_remove = TRUE;
}
gtk_widget_set_sensitive (button, can_remove);
}
static void
fill_xkb_options_shortcuts (GtkTreeModel *model)
{
GList *l;
GtkTreeIter iter;
for (l = cc_keyboard_option_get_all (); l; l = l->next)
{
CcKeyboardOption *option = l->data;
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
DETAIL_DESCRIPTION_COLUMN, cc_keyboard_option_get_description (option),
DETAIL_KEYENTRY_COLUMN, option,
DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_XKB_OPTION,
-1);
}
}
static void
section_selection_changed (GtkTreeSelection *selection, gpointer data)
{
GtkTreeIter iter;
GtkTreeModel *model;
GtkBuilder *builder = GTK_BUILDER (data);
if (gtk_tree_selection_get_selected (selection, &model, &iter))
{
GPtrArray *keys;
GtkWidget *shortcut_treeview;
GtkTreeModel *shortcut_model;
gchar *id;
BindingGroupType group;
gint i;
gtk_tree_model_get (model, &iter,
SECTION_ID_COLUMN, &id,
SECTION_GROUP_COLUMN, &group, -1);
keys = g_hash_table_lookup (get_hash_for_group (group), id);
if (keys == NULL)
{
g_warning ("Can't find section %s in sections hash table.", id);
g_free (id);
return;
}
gtk_widget_set_sensitive (WID (builder, "remove-toolbutton"), FALSE);
/* Fill the shortcut treeview with the keys for the selected section */
shortcut_treeview = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut_treeview"));
shortcut_model = gtk_tree_view_get_model (GTK_TREE_VIEW (shortcut_treeview));
gtk_list_store_clear (GTK_LIST_STORE (shortcut_model));
for (i = 0; i < keys->len; i++)
{
GtkTreeIter new_row;
CcKeyboardItem *item = g_ptr_array_index (keys, i);
if (!cc_keyboard_item_is_hidden (item))
{
gtk_list_store_append (GTK_LIST_STORE (shortcut_model), &new_row);
gtk_list_store_set (GTK_LIST_STORE (shortcut_model), &new_row,
DETAIL_DESCRIPTION_COLUMN, item->description,
DETAIL_KEYENTRY_COLUMN, item,
DETAIL_TYPE_COLUMN, SHORTCUT_TYPE_KEY_ENTRY,
-1);
}
}
if (g_str_equal (id, "Typing"))
fill_xkb_options_shortcuts (shortcut_model);
g_free (id);
}
}
static gboolean
edit_custom_shortcut (CcKeyboardItem *item)
{
gint result;
gboolean ret;
GSettings *settings;
settings = g_settings_new_with_path (item->schema, item->gsettings_path);
g_settings_bind (settings, "name",
G_OBJECT (custom_shortcut_name_entry), "text",
G_SETTINGS_BIND_DEFAULT);
gtk_widget_grab_focus (custom_shortcut_name_entry);
g_settings_bind (settings, "command",
G_OBJECT (custom_shortcut_command_entry), "text",
G_SETTINGS_BIND_DEFAULT);
g_settings_delay (settings);
gtk_window_present (GTK_WINDOW (custom_shortcut_dialog));
result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog));
switch (result)
{
case GTK_RESPONSE_OK:
g_settings_apply (settings);
ret = TRUE;
break;
default:
g_settings_revert (settings);
ret = FALSE;
break;
}
g_settings_unbind (G_OBJECT (custom_shortcut_name_entry), "text");
g_settings_unbind (G_OBJECT (custom_shortcut_command_entry), "text");
g_object_unref (settings);
gtk_widget_hide (custom_shortcut_dialog);
return ret;
}
static gboolean
remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
{
CcKeyboardItem *item;
GPtrArray *keys_array;
GVariantBuilder builder;
char **settings_paths;
int i;
gtk_tree_model_get (model, iter,
DETAIL_KEYENTRY_COLUMN, &item,
-1);
/* not a custom shortcut */
g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
g_settings_delay (item->settings);
g_settings_reset (item->settings, "name");
g_settings_reset (item->settings, "command");
g_settings_reset (item->settings, "binding");
g_settings_apply (item->settings);
g_settings_sync ();
settings_paths = g_settings_get_strv (binding_settings, "custom-keybindings");
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
for (i = 0; settings_paths[i]; i++)
if (strcmp (settings_paths[i], item->gsettings_path) != 0)
g_variant_builder_add (&builder, "s", settings_paths[i]);
g_settings_set_value (binding_settings,
"custom-keybindings", g_variant_builder_end (&builder));
g_strfreev (settings_paths);
g_object_unref (item);
keys_array = g_hash_table_lookup (get_hash_for_group (BINDING_GROUP_USER), CUSTOM_SHORTCUTS_ID);
g_ptr_array_remove (keys_array, item);
gtk_list_store_remove (GTK_LIST_STORE (model), iter);
return TRUE;
}
static void
update_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter)
{
CcKeyboardItem *item;
gtk_tree_model_get (model, iter,
DETAIL_KEYENTRY_COLUMN, &item,
-1);
g_assert (item->type == CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
edit_custom_shortcut (item);
if (item->command == NULL || item->command[0] == '\0')
{
remove_custom_shortcut (model, iter);
}
else
{
gtk_list_store_set (GTK_LIST_STORE (model), iter,
DETAIL_KEYENTRY_COLUMN, item, -1);
}
}
static gboolean
start_editing_cb (GtkTreeView *tree_view,
GdkEventButton *event,
gpointer user_data)
{
GtkTreePath *path;
GtkTreeViewColumn *column;
GtkCellRenderer *cell = user_data;
if (event->window != gtk_tree_view_get_bin_window (tree_view))
return FALSE;
if (gtk_tree_view_get_path_at_pos (tree_view,
(gint) event->x,
(gint) event->y,
&path, &column,
NULL, NULL))
{
GtkTreeModel *model;
GtkTreeIter iter;
CcKeyboardItem *item;
ShortcutType type;
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type == SHORTCUT_TYPE_XKB_OPTION)
{
gtk_tree_path_free (path);
return FALSE;
}
/* if only the accel can be edited on the selected row
* always select the accel column */
if (item->desc_editable &&
column == gtk_tree_view_get_column (tree_view, 0))
{
gtk_widget_grab_focus (GTK_WIDGET (tree_view));
gtk_tree_view_set_cursor (tree_view,
path,
column,
FALSE);
update_custom_shortcut (model, &iter);
}
else
{
gtk_widget_grab_focus (GTK_WIDGET (tree_view));
gtk_tree_view_set_cursor_on_cell (tree_view,
path,
gtk_tree_view_get_column (tree_view, 1),
cell,
TRUE);
}
g_signal_stop_emission_by_name (tree_view, "button_press_event");
gtk_tree_path_free (path);
}
return TRUE;
}
static void
start_editing_kb_cb (GtkTreeView *treeview,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer user_data)
{
GtkTreeModel *model;
GtkTreeIter iter;
CcKeyboardItem *item;
ShortcutType type;
GtkCellRenderer *cell = user_data;
model = gtk_tree_view_get_model (treeview);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type == SHORTCUT_TYPE_XKB_OPTION)
return;
/* if only the accel can be edited on the selected row
* always select the accel column */
if (item->desc_editable &&
column == gtk_tree_view_get_column (treeview, 0))
{
gtk_widget_grab_focus (GTK_WIDGET (treeview));
gtk_tree_view_set_cursor (treeview,
path,
column,
FALSE);
update_custom_shortcut (model, &iter);
}
else
{
gtk_widget_grab_focus (GTK_WIDGET (treeview));
gtk_tree_view_set_cursor_on_cell (treeview,
path,
gtk_tree_view_get_column (treeview, 1),
cell,
TRUE);
}
}
static const guint forbidden_keyvals[] = {
/* Navigation keys */
GDK_KEY_Home,
GDK_KEY_Left,
GDK_KEY_Up,
GDK_KEY_Right,
GDK_KEY_Down,
GDK_KEY_Page_Up,
GDK_KEY_Page_Down,
GDK_KEY_End,
GDK_KEY_Tab,
/* Return */
GDK_KEY_KP_Enter,
GDK_KEY_Return,
GDK_KEY_Mode_switch
};
static char*
binding_name (guint keyval,
guint keycode,
GdkModifierType mask,
gboolean translate)
{
if (keyval != 0 || keycode != 0)
return translate ?
gtk_accelerator_get_label_with_keycode (NULL, keyval, keycode, mask) :
gtk_accelerator_name_with_keycode (NULL, keyval, keycode, mask);
else
return g_strdup (translate ? _("Disabled") : NULL);
}
static gboolean
keyval_is_forbidden (guint keyval)
{
guint i;
for (i = 0; i < G_N_ELEMENTS(forbidden_keyvals); i++) {
if (keyval == forbidden_keyvals[i])
return TRUE;
}
return FALSE;
}
typedef struct {
CcKeyboardItem *orig_item;
CcKeyboardItem *conflict_item;
guint new_keyval;
GdkModifierType new_mask;
guint new_keycode;
} CcUniquenessData;
static gboolean
compare_keys_for_uniqueness (CcKeyboardItem *element,
CcUniquenessData *data)
{
CcKeyboardItem *orig_item;
orig_item = data->orig_item;
/* no conflict for : blanks, different modifiers, or ourselves */
if (element == NULL || data->new_mask != element->mask ||
cc_keyboard_item_equal (orig_item, element))
return FALSE;
if (data->new_keyval != 0) {
if (data->new_keyval != element->keyval)
return FALSE;
} else if (element->keyval != 0 || data->new_keycode != element->keycode)
return FALSE;
data->conflict_item = element;
return TRUE;
}
static gboolean
cb_check_for_uniqueness (gpointer key,
GPtrArray *keys_array,
CcUniquenessData *data)
{
guint i;
for (i = 0; i < keys_array->len; i++)
{
CcKeyboardItem *item;
item = keys_array->pdata[i];
if (compare_keys_for_uniqueness (item, data))
return TRUE;
}
return FALSE;
}
static CcKeyboardItem *
search_for_conflict_item (CcKeyboardItem *item,
guint keyval,
GdkModifierType mask,
guint keycode)
{
CcUniquenessData data;
data.orig_item = item;
data.new_keyval = keyval;
data.new_mask = mask;
data.new_keycode = keycode;
data.conflict_item = NULL;
if (keyval != 0 || keycode != 0) /* any number of shortcuts can be disabled */
{
BindingGroupType i;
for (i = BINDING_GROUP_SYSTEM; i <= BINDING_GROUP_USER && data.conflict_item == NULL; i++)
{
GHashTable *table;
table = get_hash_for_group (i);
if (!table)
continue;
g_hash_table_find (table, (GHRFunc) cb_check_for_uniqueness, &data);
}
}
return data.conflict_item;
}
static gboolean
is_valid_binding (guint keyval,
GdkModifierType mask,
guint keycode)
{
if ((mask == 0 || mask == GDK_SHIFT_MASK) && keycode != 0)
{
if ((keyval >= GDK_KEY_a && keyval <= GDK_KEY_z)
|| (keyval >= GDK_KEY_A && keyval <= GDK_KEY_Z)
|| (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9)
|| (keyval >= GDK_KEY_kana_fullstop && keyval <= GDK_KEY_semivoicedsound)
|| (keyval >= GDK_KEY_Arabic_comma && keyval <= GDK_KEY_Arabic_sukun)
|| (keyval >= GDK_KEY_Serbian_dje && keyval <= GDK_KEY_Cyrillic_HARDSIGN)
|| (keyval >= GDK_KEY_Greek_ALPHAaccent && keyval <= GDK_KEY_Greek_omega)
|| (keyval >= GDK_KEY_hebrew_doublelowline && keyval <= GDK_KEY_hebrew_taf)
|| (keyval >= GDK_KEY_Thai_kokai && keyval <= GDK_KEY_Thai_lekkao)
|| (keyval >= GDK_KEY_Hangul_Kiyeog && keyval <= GDK_KEY_Hangul_J_YeorinHieuh)
|| (keyval == GDK_KEY_space && mask == 0)
|| keyval_is_forbidden (keyval)) {
return FALSE;
}
}
return TRUE;
}
static GtkResponseType
show_invalid_binding_dialog (GtkTreeView *view,
guint keyval,
GdkModifierType mask,
guint keycode)
{
GtkWidget *dialog;
char *name;
name = binding_name (keyval, keycode, mask, TRUE);
dialog =
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_CANCEL,
_("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n"
"Please try with a key such as Control, Alt or Shift at the same time."),
name);
g_free (name);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return GTK_RESPONSE_NONE;
}
static GtkResponseType
show_conflict_item_dialog (GtkTreeView *view,
CcKeyboardItem *item,
CcKeyboardItem *conflict_item,
guint keyval,
GdkModifierType mask,
guint keycode)
{
GtkWidget *dialog;
char *name;
int response;
name = binding_name (keyval, keycode, mask, TRUE);
dialog =
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_CANCEL,
_("The shortcut \"%s\" is already used for\n\"%s\""),
name, conflict_item->description);
g_free (name);
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
_("If you reassign the shortcut to \"%s\", the \"%s\" shortcut "
"will be disabled."),
item->description,
conflict_item->description);
gtk_dialog_add_button (GTK_DIALOG (dialog),
_("_Reassign"),
GTK_RESPONSE_ACCEPT);
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
GTK_RESPONSE_ACCEPT);
response = gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return response;
}
static GtkResponseType
show_reverse_item_dialog (GtkTreeView *view,
CcKeyboardItem *item,
CcKeyboardItem *reverse_item,
CcKeyboardItem *reverse_conflict_item,
guint keyval,
GdkModifierType mask,
guint keycode)
{
GtkWidget *dialog;
char *name;
int response;
name = binding_name (keyval, keycode, mask, TRUE);
/* translators:
* This is the text you get in a dialogue when an action has an associated
* "reverse" action, for example Alt+Tab going in the opposite direction to
* Alt+Shift+Tab.
*
* An example text would be:
* The "Switch to next input source" shortcut has an associated "Switch to
* previous input source" shortcut. Do you want to automatically set it to
* "Shift+Ctrl+Alt+Space"? */
dialog =
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_WARNING,
GTK_BUTTONS_CANCEL,
_("The \"%s\" shortcut has an associated \"%s\" shortcut. "
"Do you want to automatically set it to \"%s\"?"),
item->description, reverse_item->description, name);
if (reverse_conflict_item != NULL) {
/* translators:
* This is the text you get in a dialogue when you try to use a shortcut
* that was already associated with another action, for example:
* "Alt+F4" is currently associated with "Close Window", ... */
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
_("\"%s\" is currently associated with \"%s\", this shortcut will be"
" disabled if you move forward."),
name, reverse_conflict_item->description);
}
g_free (name);
gtk_dialog_add_button (GTK_DIALOG (dialog),
_("_Assign"),
GTK_RESPONSE_ACCEPT);
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
GTK_RESPONSE_ACCEPT);
response = gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
return response;
}
static void
handle_reverse_item (CcKeyboardItem *item,
CcKeyboardItem *reverse_item,
guint keyval,
GdkModifierType mask,
guint keycode,
GtkTreeView *view)
{
GtkResponseType response;
GdkModifierType reverse_mask;
reverse_mask = mask ^ GDK_SHIFT_MASK;
if (!is_valid_binding(keyval, reverse_mask, keycode))
return;
if ((reverse_item->keyval != keyval)
|| (reverse_item->keycode != keycode)
|| (reverse_item->mask != reverse_mask))
{
CcKeyboardItem *reverse_conflict_item;
char *binding_str;
reverse_conflict_item = search_for_conflict_item (reverse_item, keyval,
reverse_mask,
keycode);
response = show_reverse_item_dialog (view, item, reverse_item,
reverse_conflict_item,
keyval, reverse_mask,
keycode);
if (response == GTK_RESPONSE_ACCEPT)
{
binding_str = binding_name (keyval, keycode, reverse_mask, FALSE);
g_object_set (G_OBJECT (reverse_item), "binding", binding_str, NULL);
g_free (binding_str);
if (reverse_conflict_item != NULL)
g_object_set (G_OBJECT (reverse_conflict_item),
"binding", NULL, NULL);
}
else
{
/* The existing reverse binding may be conflicting with the binding
* we are setting. Other conflicts have already been handled in
* accel_edited_callback()
*/
CcKeyboardItem *conflict_item;
conflict_item = search_for_conflict_item (item, keyval,
mask, keycode);
if (conflict_item != NULL)
{
g_warn_if_fail (conflict_item == reverse_item);
g_object_set (G_OBJECT (conflict_item),
"binding", NULL, NULL);
}
}
}
}
static void
accel_edited_callback (GtkCellRendererText *cell,
const char *path_string,
guint keyval,
GdkModifierType mask,
guint keycode,
GtkTreeView *view)
{
GtkTreeModel *model;
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
GtkTreeIter iter;
CcKeyboardItem *item;
CcKeyboardItem *conflict_item;
CcKeyboardItem *reverse_item;
char *str;
model = gtk_tree_view_get_model (view);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
-1);
/* sanity check */
if (item == NULL)
return;
/* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */
mask &= ~GDK_LOCK_MASK;
conflict_item = search_for_conflict_item (item, keyval, mask, keycode);
/* Check for unmodified keys */
if (!is_valid_binding (keyval, mask, keycode))
{
show_invalid_binding_dialog (view, keyval, mask, keycode);
/* set it back to its previous value. */
g_object_set (G_OBJECT (cell),
"accel-key", item->keyval,
"keycode", item->keycode,
"accel-mods", item->mask,
NULL);
return;
}
reverse_item = cc_keyboard_item_get_reverse_item (item);
/* flag to see if the new accelerator was in use by something */
if ((conflict_item != NULL) && (conflict_item != reverse_item))
{
GtkResponseType response;
response = show_conflict_item_dialog (view, item, conflict_item,
keyval, mask, keycode);
if (response == GTK_RESPONSE_ACCEPT)
{
g_object_set (G_OBJECT (conflict_item), "binding", NULL, NULL);
str = binding_name (keyval, keycode, mask, FALSE);
g_object_set (G_OBJECT (item), "binding", str, NULL);
g_free (str);
if (reverse_item == NULL)
return;
}
else
{
/* set it back to its previous value. */
g_object_set (G_OBJECT (cell),
"accel-key", item->keyval,
"keycode", item->keycode,
"accel-mods", item->mask,
NULL);
return;
}
}
str = binding_name (keyval, keycode, mask, FALSE);
g_object_set (G_OBJECT (item), "binding", str, NULL);
g_free (str);
if (reverse_item != NULL)
handle_reverse_item (item, reverse_item, keyval, mask, keycode, view);
}
static void
accel_cleared_callback (GtkCellRendererText *cell,
const char *path_string,
gpointer data)
{
GtkTreeView *view = (GtkTreeView *) data;
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
CcKeyboardItem *item;
GtkTreeIter iter;
GtkTreeModel *model;
model = gtk_tree_view_get_model (view);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (model, &iter,
DETAIL_KEYENTRY_COLUMN, &item,
-1);
/* sanity check */
if (item == NULL)
return;
/* Unset the key */
g_object_set (G_OBJECT (item), "binding", NULL, NULL);
}
static gchar *
find_free_settings_path ()
{
char **used_names;
char *dir = NULL;
int i, num, n_names;
used_names = g_settings_get_strv (binding_settings, "custom-keybindings");
n_names = g_strv_length (used_names);
for (num = 0; dir == NULL; num++)
{
char *tmp;
gboolean found = FALSE;
tmp = g_strdup_printf ("%s/custom%d/", CUSTOM_KEYS_BASENAME, num);
for (i = 0; i < n_names && !found; i++)
found = strcmp (used_names[i], tmp) == 0;
if (!found)
dir = tmp;
else
g_free (tmp);
}
return dir;
}
static void
add_custom_shortcut (GtkTreeView *tree_view,
GtkTreeModel *model)
{
CcKeyboardItem *item;
GtkTreePath *path;
gchar *settings_path;
item = cc_keyboard_item_new (CC_KEYBOARD_ITEM_TYPE_GSETTINGS_PATH);
settings_path = find_free_settings_path ();
cc_keyboard_item_load_from_gsettings_path (item, settings_path, TRUE);
g_free (settings_path);
item->model = model;
if (edit_custom_shortcut (item) &&
item->command && item->command[0])
{
GPtrArray *keys_array;
GtkTreeIter iter;
GHashTable *hash;
GVariantBuilder builder;
char **settings_paths;
int i;
hash = get_hash_for_group (BINDING_GROUP_USER);
keys_array = g_hash_table_lookup (hash, CUSTOM_SHORTCUTS_ID);
if (keys_array == NULL)
{
keys_array = g_ptr_array_new ();
g_hash_table_insert (hash, g_strdup (CUSTOM_SHORTCUTS_ID), keys_array);
}
g_ptr_array_add (keys_array, item);
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter, DETAIL_KEYENTRY_COLUMN, item, -1);
settings_paths = g_settings_get_strv (binding_settings, "custom-keybindings");
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
for (i = 0; settings_paths[i]; i++)
g_variant_builder_add (&builder, "s", settings_paths[i]);
g_variant_builder_add (&builder, "s", item->gsettings_path);
g_settings_set_value (binding_settings, "custom-keybindings",
g_variant_builder_end (&builder));
/* make the new shortcut visible */
path = gtk_tree_model_get_path (model, &iter);
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
}
else
{
g_object_unref (item);
}
}
static void
shortcut_entry_changed (GtkEntry *entry,
gpointer user_data)
{
guint16 name_length;
guint16 command_length;
name_length = gtk_entry_get_text_length (custom_shortcut_name_entry);
command_length = gtk_entry_get_text_length (custom_shortcut_command_entry);
gtk_widget_set_sensitive (custom_shortcut_ok_button,
name_length > 0 && command_length > 0);
}
static void
add_button_clicked (GtkWidget *button,
GtkBuilder *builder)
{
GtkTreeView *treeview;
GtkTreeModel *model;
GtkTreeModel *section_model;
GtkTreeIter iter;
gboolean found, cont;
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
"shortcut_treeview"));
model = gtk_tree_view_get_model (treeview);
/* Select the Custom Shortcuts section
* before adding the shortcut itself */
section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (WID (builder, "section_treeview")));
cont = gtk_tree_model_get_iter_first (section_model, &iter);
found = FALSE;
while (cont)
{
BindingGroupType group;
gtk_tree_model_get (section_model, &iter,
SECTION_GROUP_COLUMN, &group,
-1);
if (group == BINDING_GROUP_USER)
{
found = TRUE;
break;
}
cont = gtk_tree_model_iter_next (section_model, &iter);
}
if (found)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID (builder, "section_treeview")));
gtk_tree_selection_select_iter (selection, &iter);
}
/* And add the shortcut */
add_custom_shortcut (treeview, model);
}
static void
remove_button_clicked (GtkWidget *button,
GtkBuilder *builder)
{
GtkTreeView *treeview;
GtkTreeModel *model;
GtkTreeSelection *selection;
GtkTreeIter iter;
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
"shortcut_treeview"));
model = gtk_tree_view_get_model (treeview);
selection = gtk_tree_view_get_selection (treeview);
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
{
remove_custom_shortcut (model, &iter);
}
}
static int
section_sort_item (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
gpointer data)
{
char *a_desc;
int a_group;
char *b_desc;
int b_group;
int ret;
gtk_tree_model_get (model, a,
SECTION_DESCRIPTION_COLUMN, &a_desc,
SECTION_GROUP_COLUMN, &a_group,
-1);
gtk_tree_model_get (model, b,
SECTION_DESCRIPTION_COLUMN, &b_desc,
SECTION_GROUP_COLUMN, &b_group,
-1);
if (a_group == b_group && a_desc && b_desc)
ret = g_utf8_collate (a_desc, b_desc);
else
ret = a_group - b_group;
g_free (a_desc);
g_free (b_desc);
return ret;
}
static gboolean
sections_separator_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
BindingGroupType type;
gtk_tree_model_get (model, iter, SECTION_GROUP_COLUMN, &type, -1);
return type == BINDING_GROUP_SEPARATOR;
}
static void
xkb_options_combo_changed (GtkCellRendererCombo *combo,
gchar *model_path,
GtkTreeIter *model_iter,
gpointer data)
{
GtkTreeView *shortcut_treeview;
GtkTreeModel *shortcut_model;
GtkTreeIter shortcut_iter;
GtkTreeSelection *selection;
CcKeyboardOption *option;
ShortcutType type;
GtkBuilder *builder = data;
shortcut_treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"));
selection = gtk_tree_view_get_selection (shortcut_treeview);
if (!gtk_tree_selection_get_selected (selection, &shortcut_model, &shortcut_iter))
return;
gtk_tree_model_get (shortcut_model, &shortcut_iter,
DETAIL_KEYENTRY_COLUMN, &option,
DETAIL_TYPE_COLUMN, &type,
-1);
if (type != SHORTCUT_TYPE_XKB_OPTION)
return;
cc_keyboard_option_set_selection (option, model_iter);
}
static gboolean
poke_xkb_option_row (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
gpointer option)
{
gpointer item;
gtk_tree_model_get (model, iter,
DETAIL_KEYENTRY_COLUMN, &item,
-1);
if (item != option)
return FALSE;
gtk_tree_model_row_changed (model, path, iter);
return TRUE;
}
static void
xkb_option_changed (CcKeyboardOption *option,
gpointer data)
{
GtkTreeModel *model = data;
gtk_tree_model_foreach (model, poke_xkb_option_row, option);
}
static void
setup_keyboard_options (GtkListStore *store)
{
GList *l;
for (l = cc_keyboard_option_get_all (); l; l = l->next)
g_signal_connect (l->data, "changed",
G_CALLBACK (xkb_option_changed), store);
}
static void
setup_dialog (CcPanel *panel, GtkBuilder *builder)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
GtkWidget *widget;
GtkTreeView *treeview;
GtkTreeSelection *selection;
GList *focus_chain;
CcShell *shell;
GtkListStore *model;
GtkTreeModelSort *sort_model;
GtkStyleContext *context;
gtk_widget_set_size_request (GTK_WIDGET (panel), -1, 400);
/* Setup the section treeview */
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, "section_treeview"));
gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (treeview),
sections_separator_func,
panel,
NULL);
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes (_("Section"),
renderer,
"text", SECTION_DESCRIPTION_COLUMN,
NULL);
g_object_set (renderer,
"width-chars", 20,
"ellipsize", PANGO_ELLIPSIZE_END,
NULL);
gtk_tree_view_append_column (treeview, column);
model = gtk_list_store_new (SECTION_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
sort_model = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (model)));
gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (sort_model));
g_object_unref (model);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort_model),
SECTION_DESCRIPTION_COLUMN,
section_sort_item,
panel,
NULL);
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort_model),
SECTION_DESCRIPTION_COLUMN,
GTK_SORT_ASCENDING);
g_object_unref (sort_model);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
g_signal_connect (selection, "changed",
G_CALLBACK (section_selection_changed), builder);
section_selection_changed (selection, builder);
/* Setup the shortcut treeview */
treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder,
"shortcut_treeview"));
binding_settings = g_settings_new (BINDINGS_SCHEMA);
renderer = gtk_cell_renderer_text_new ();
g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL);
gtk_tree_view_column_set_cell_data_func (column, renderer, description_set_func, NULL, NULL);
gtk_tree_view_column_set_resizable (column, FALSE);
gtk_tree_view_column_set_expand (column, TRUE);
gtk_tree_view_append_column (treeview, column);
renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_ACCEL,
"accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
NULL);
g_signal_connect (treeview, "button_press_event",
G_CALLBACK (start_editing_cb), renderer);
g_signal_connect (treeview, "row-activated",
G_CALLBACK (start_editing_kb_cb), renderer);
g_signal_connect (renderer, "accel_edited",
G_CALLBACK (accel_edited_callback),
treeview);
g_signal_connect (renderer, "accel_cleared",
G_CALLBACK (accel_cleared_callback),
treeview);
column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL);
gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL);
gtk_tree_view_column_set_resizable (column, FALSE);
gtk_tree_view_column_set_expand (column, FALSE);
renderer = (GtkCellRenderer *) g_object_new (GTK_TYPE_CELL_RENDERER_COMBO,
"has-entry", FALSE,
"text-column", XKB_OPTION_DESCRIPTION_COLUMN,
"editable", TRUE,
"ellipsize", PANGO_ELLIPSIZE_END,
"width-chars", 25,
NULL);
g_signal_connect (renderer, "changed",
G_CALLBACK (xkb_options_combo_changed), builder);
gtk_tree_view_column_pack_end (column, renderer, FALSE);
gtk_tree_view_column_set_cell_data_func (column, renderer, accel_set_func, NULL, NULL);
gtk_tree_view_append_column (treeview, column);
model = gtk_list_store_new (DETAIL_N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT);
gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (model));
g_object_unref (model);
setup_keyboard_options (model);
widget = GTK_WIDGET (gtk_builder_get_object (builder, "actions_swindow"));
context = gtk_widget_get_style_context (widget);
gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
widget = GTK_WIDGET (gtk_builder_get_object (builder, "shortcut-toolbar"));
context = gtk_widget_get_style_context (widget);
gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
/* set up the focus chain */
focus_chain = g_list_append (NULL, WID (builder, "sections_swindow"));
focus_chain = g_list_append (focus_chain, WID (builder, "actions_swindow"));
focus_chain = g_list_append (focus_chain, WID (builder, "shortcut-toolbar"));
widget = GTK_WIDGET (gtk_builder_get_object (builder, "shortcuts_grid"));
gtk_container_set_focus_chain (GTK_CONTAINER (widget), focus_chain);
g_list_free (focus_chain);
/* set up the dialog */
shell = cc_panel_get_shell (CC_PANEL (panel));
widget = cc_shell_get_toplevel (shell);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
g_signal_connect (selection, "changed",
G_CALLBACK (shortcut_selection_changed),
WID (builder, "remove-toolbutton"));
/* setup the custom shortcut dialog */
custom_shortcut_dialog = WID (builder,
"custom-shortcut-dialog");
custom_shortcut_ok_button = WID (builder, "custom-shortcut-ok-button");
custom_shortcut_name_entry = WID (builder,
"custom-shortcut-name-entry");
g_signal_connect (custom_shortcut_name_entry, "changed",
G_CALLBACK (shortcut_entry_changed), NULL);
custom_shortcut_command_entry = WID (builder,
"custom-shortcut-command-entry");
g_signal_connect (custom_shortcut_command_entry, "changed",
G_CALLBACK (shortcut_entry_changed), NULL);
g_signal_connect (WID (builder, "add-toolbutton"),
"clicked", G_CALLBACK (add_button_clicked), builder);
g_signal_connect (WID (builder, "remove-toolbutton"),
"clicked", G_CALLBACK (remove_button_clicked), builder);
gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog),
GTK_RESPONSE_OK);
gtk_window_set_transient_for (GTK_WINDOW (custom_shortcut_dialog),
GTK_WINDOW (widget));
gtk_window_set_resizable (GTK_WINDOW (custom_shortcut_dialog), FALSE);
}
static void
on_window_manager_change (const char *wm_name, CcPanel *panel)
{
reload_sections (panel);
}
void
keyboard_shortcuts_init (CcPanel *panel, GtkBuilder *builder)
{
g_object_set_data (G_OBJECT (panel), "builder", builder);
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
wm_changed_id = wm_common_register_window_manager_change ((GFunc) on_window_manager_change,
panel);
#endif
pictures_regex = g_regex_new ("\\$PICTURES", 0, 0, NULL);
setup_dialog (panel, builder);
reload_sections (panel);
}
gboolean
keyboard_shortcuts_set_section (CcPanel *panel, const char *section)
{
GtkBuilder *builder;
GtkTreeModel *section_model;
GtkTreeIter iter;
gboolean found, cont;
builder = g_object_get_data (G_OBJECT (panel), "builder");
if (builder == NULL)
{
/* Remember the section name to be set later */
g_object_set_data_full (G_OBJECT (panel), "section-to-set", g_strdup (section), g_free);
return TRUE;
}
section_model = gtk_tree_view_get_model (GTK_TREE_VIEW (WID (builder, "section_treeview")));
cont = gtk_tree_model_get_iter_first (section_model, &iter);
found = FALSE;
while (cont)
{
char *id;
gtk_tree_model_get (section_model, &iter,
SECTION_ID_COLUMN, &id,
-1);
if (g_strcmp0 (id, section) == 0)
{
found = TRUE;
g_free (id);
break;
}
g_free (id);
cont = gtk_tree_model_iter_next (section_model, &iter);
}
if (found)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (WID (builder, "section_treeview")));
gtk_tree_selection_select_iter (selection, &iter);
}
else
{
g_warning ("Could not find section '%s' to switch to.", section);
}
return found;
}
void
keyboard_shortcuts_dispose (CcPanel *panel)
{
if (kb_system_sections != NULL)
{
g_hash_table_destroy (kb_system_sections);
kb_system_sections = NULL;
}
if (kb_apps_sections != NULL)
{
g_hash_table_destroy (kb_apps_sections);
kb_apps_sections = NULL;
}
if (kb_user_sections != NULL)
{
g_hash_table_destroy (kb_user_sections);
kb_user_sections = NULL;
}
if (pictures_regex != NULL)
{
g_regex_unref (pictures_regex);
pictures_regex = NULL;
}
g_clear_object (&binding_settings);
g_clear_pointer (&custom_shortcut_dialog, gtk_widget_destroy);
if (wm_changed_id)
{
wm_common_unregister_window_manager_change (wm_changed_id);
wm_changed_id = NULL;
}
cc_keyboard_option_clear_all ();
}