diff options
author | William Jon McCann <jmccann@redhat.com> | 2010-01-17 01:27:26 -0500 |
---|---|---|
committer | William Jon McCann <jmccann@redhat.com> | 2010-01-17 01:27:26 -0500 |
commit | 0c605ffbfc8e40bdff4c867719e3bf1a7355f3be (patch) | |
tree | ae8c6c6556656405c58617aa5908c5cff2cf5614 /capplets/keyboard | |
parent | de38485c5fb296b7af601abfd29d12f01776461e (diff) | |
download | gnome-control-center-0c605ffbfc8e40bdff4c867719e3bf1a7355f3be.tar.gz |
[keyboard] Add the keybindings capplet as a page
It makes a lot more sense here instead of having two keyboard
related panels.
Diffstat (limited to 'capplets/keyboard')
-rw-r--r-- | capplets/keyboard/00-multimedia-key.xml.in | 35 | ||||
-rw-r--r-- | capplets/keyboard/01-desktop-key.xml.in | 29 | ||||
-rw-r--r-- | capplets/keyboard/Makefile.am | 26 | ||||
-rw-r--r-- | capplets/keyboard/cc-keyboard-page.c | 255 | ||||
-rw-r--r-- | capplets/keyboard/cc-keyboard-page.h | 55 | ||||
-rw-r--r-- | capplets/keyboard/cc-keyboard-panel.c | 14 | ||||
-rw-r--r-- | capplets/keyboard/cc-shortcuts-page.c | 2041 | ||||
-rw-r--r-- | capplets/keyboard/cc-shortcuts-page.h | 55 | ||||
-rw-r--r-- | capplets/keyboard/eggaccelerators.c | 632 | ||||
-rw-r--r-- | capplets/keyboard/eggaccelerators.h | 95 | ||||
-rw-r--r-- | capplets/keyboard/eggcellrendererkeys.c | 693 | ||||
-rw-r--r-- | capplets/keyboard/eggcellrendererkeys.h | 89 | ||||
-rw-r--r-- | capplets/keyboard/gnome-keybindings.pc.in | 10 | ||||
-rw-r--r-- | capplets/keyboard/gnome-keyboard-properties-dialog.ui | 241 |
14 files changed, 4268 insertions, 2 deletions
diff --git a/capplets/keyboard/00-multimedia-key.xml.in b/capplets/keyboard/00-multimedia-key.xml.in new file mode 100644 index 000000000..b3d16819d --- /dev/null +++ b/capplets/keyboard/00-multimedia-key.xml.in @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<KeyListEntries _name="Sound"> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/volume_mute" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/volume_down" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/volume_up" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/media" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/play" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/pause" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/stop" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/previous" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/next" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/eject" /> + +</KeyListEntries> + diff --git a/capplets/keyboard/01-desktop-key.xml.in b/capplets/keyboard/01-desktop-key.xml.in new file mode 100644 index 000000000..2c9753743 --- /dev/null +++ b/capplets/keyboard/01-desktop-key.xml.in @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<KeyListEntries _name="Desktop"> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/help" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/calculator" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/email" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/www" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/power" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/screensaver" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/home" /> + + <KeyListEntry + name="/apps/gnome_settings_daemon/keybindings/search" /> + +</KeyListEntries> + diff --git a/capplets/keyboard/Makefile.am b/capplets/keyboard/Makefile.am index 7232a9c06..ff4bb689a 100644 --- a/capplets/keyboard/Makefile.am +++ b/capplets/keyboard/Makefile.am @@ -8,6 +8,7 @@ INCLUDES = \ $(LIBGNOMEKBDUI_CFLAGS) \ -DGNOMELOCALEDIR="\"$(datadir)/locale\"" \ -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \ + -DGNOMECC_KEYBINDINGS_DIR="\"$(pkgdatadir)/keybindings\"" \ -DGNOMECC_UI_DIR="\"$(uidir)\"" bin_PROGRAMS = gnome-keyboard-properties @@ -17,6 +18,12 @@ ccmodules_LTLIBRARIES = libkeyboard.la libkeyboard_la_SOURCES = \ keyboard-module.c \ + eggcellrendererkeys.c \ + eggcellrendererkeys.h \ + eggaccelerators.c \ + eggaccelerators.h \ + cc-shortcuts-page.h \ + cc-shortcuts-page.c \ cc-keyboard-page.h \ cc-keyboard-page.c \ cc-keyboard-panel.h \ @@ -62,7 +69,22 @@ desktopdir = $(datadir)/applications Desktop_in_files = keyboard.desktop.in desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop) -CLEANFILES = $(GNOMECC_CAPPLETS_CLEANFILES) $(Desktop_in_files) $(desktop_DATA) -EXTRA_DIST = $(ui_DATA) +@INTLTOOL_XML_NOMERGE_RULE@ + +xmldir = $(pkgdatadir)/keybindings +xml_in_files = 00-multimedia-key.xml.in 01-desktop-key.xml.in +xml_DATA = $(xml_in_files:.xml.in=.xml) + +pkgconfigdir = $(datadir)/pkgconfig +pkgconfig_DATA = gnome-keybindings.pc + +CLEANFILES = \ + $(GNOMECC_CAPPLETS_CLEANFILES) \ + $(Desktop_in_files) \ + $(desktop_DATA) \ + $(xml_DATA) \ + $(NULL) + +EXTRA_DIST = $(ui_DATA) $(xml_in_files) gnome-keybindings.pc.in -include $(top_srcdir)/git.mk diff --git a/capplets/keyboard/cc-keyboard-page.c b/capplets/keyboard/cc-keyboard-page.c new file mode 100644 index 000000000..6cf164233 --- /dev/null +++ b/capplets/keyboard/cc-keyboard-page.c @@ -0,0 +1,255 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <glib/gi18n-lib.h> +#include <gconf/gconf-client.h> + +#include "gconf-property-editor.h" + +#include "cc-keyboard-page.h" + +#define CC_KEYBOARD_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_KEYBOARD_PAGE, CcKeyboardPagePrivate)) + +#define WID(s) GTK_WIDGET (gtk_builder_get_object (builder, s)) + +struct CcKeyboardPagePrivate +{ + gpointer dummy; +}; + +enum { + PROP_0, +}; + +static void cc_keyboard_page_class_init (CcKeyboardPageClass *klass); +static void cc_keyboard_page_init (CcKeyboardPage *keyboard_page); +static void cc_keyboard_page_finalize (GObject *object); + +G_DEFINE_TYPE (CcKeyboardPage, cc_keyboard_page, CC_TYPE_PAGE) + +static void +cc_keyboard_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_keyboard_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static GConfValue * +blink_from_widget (GConfPropertyEditor *peditor, + const GConfValue *value) +{ + GConfValue *new_value; + + new_value = gconf_value_new (GCONF_VALUE_INT); + gconf_value_set_int (new_value, + 2600 - gconf_value_get_int (value)); + + return new_value; +} + +static GConfValue * +blink_to_widget (GConfPropertyEditor *peditor, + const GConfValue *value) +{ + GConfValue *new_value; + int current_rate; + + current_rate = gconf_value_get_int (value); + new_value = gconf_value_new (GCONF_VALUE_INT); + gconf_value_set_int (new_value, + CLAMP (2600 - current_rate, 100, 2500)); + + return new_value; +} + +static void +setup_page (CcKeyboardPage *page) +{ + GtkBuilder *builder; + GtkWidget *widget; + GError *error; + GtkSizeGroup *size_group; + GObject *peditor; + GConfChangeSet *changeset; + + changeset = NULL; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + GNOMECC_UI_DIR + "/gnome-keyboard-properties-dialog.ui", + &error); + if (error != NULL) { + g_error (_("Could not load user interface file: %s"), + error->message); + g_error_free (error); + return; + } + + + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (size_group, WID ("repeat_slow_label")); + gtk_size_group_add_widget (size_group, WID ("delay_short_label")); + gtk_size_group_add_widget (size_group, WID ("blink_slow_label")); + g_object_unref (G_OBJECT (size_group)); + + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (size_group, WID ("repeat_fast_label")); + gtk_size_group_add_widget (size_group, WID ("delay_long_label")); + gtk_size_group_add_widget (size_group, WID ("blink_fast_label")); + g_object_unref (G_OBJECT (size_group)); + + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (size_group, WID ("repeat_delay_scale")); + gtk_size_group_add_widget (size_group, WID ("repeat_speed_scale")); + gtk_size_group_add_widget (size_group, WID ("cursor_blink_time_scale")); + g_object_unref (G_OBJECT (size_group)); + + + peditor = gconf_peditor_new_boolean (changeset, + "/desktop/gnome/peripherals/keyboard/repeat", + WID ("repeat_toggle"), + NULL); + gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor), + WID ("repeat_table")); + + gconf_peditor_new_numeric_range (changeset, + "/desktop/gnome/peripherals/keyboard/delay", + WID ("repeat_delay_scale"), + NULL); + + gconf_peditor_new_numeric_range (changeset, + "/desktop/gnome/peripherals/keyboard/rate", + WID ("repeat_speed_scale"), + NULL); + + peditor = gconf_peditor_new_boolean (changeset, + "/desktop/gnome/interface/cursor_blink", + WID ("cursor_toggle"), + NULL); + gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor), + WID ("cursor_hbox")); + gconf_peditor_new_numeric_range (changeset, + "/desktop/gnome/interface/cursor_blink_time", + WID ("cursor_blink_time_scale"), + "conv-to-widget-cb", + blink_to_widget, + "conv-from-widget-cb", + blink_from_widget, NULL); + + widget = WID ("general_vbox"); + gtk_widget_reparent (widget, GTK_WIDGET (page)); + gtk_widget_show (widget); +} + +static GObject * +cc_keyboard_page_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + CcKeyboardPage *keyboard_page; + + keyboard_page = CC_KEYBOARD_PAGE (G_OBJECT_CLASS (cc_keyboard_page_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_object_set (keyboard_page, + "display-name", _("Keyboard"), + "id", "general", + NULL); + + setup_page (keyboard_page); + + return G_OBJECT (keyboard_page); +} + +static void +cc_keyboard_page_class_init (CcKeyboardPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_keyboard_page_get_property; + object_class->set_property = cc_keyboard_page_set_property; + object_class->constructor = cc_keyboard_page_constructor; + object_class->finalize = cc_keyboard_page_finalize; + + g_type_class_add_private (klass, sizeof (CcKeyboardPagePrivate)); +} + +static void +cc_keyboard_page_init (CcKeyboardPage *page) +{ + page->priv = CC_KEYBOARD_PAGE_GET_PRIVATE (page); +} + +static void +cc_keyboard_page_finalize (GObject *object) +{ + CcKeyboardPage *page; + + g_return_if_fail (object != NULL); + g_return_if_fail (CC_IS_KEYBOARD_PAGE (object)); + + page = CC_KEYBOARD_PAGE (object); + + g_return_if_fail (page->priv != NULL); + + + G_OBJECT_CLASS (cc_keyboard_page_parent_class)->finalize (object); +} + +CcPage * +cc_keyboard_page_new (void) +{ + GObject *object; + + object = g_object_new (CC_TYPE_KEYBOARD_PAGE, NULL); + + return CC_PAGE (object); +} diff --git a/capplets/keyboard/cc-keyboard-page.h b/capplets/keyboard/cc-keyboard-page.h new file mode 100644 index 000000000..7b81f963d --- /dev/null +++ b/capplets/keyboard/cc-keyboard-page.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __CC_KEYBOARD_PAGE_H +#define __CC_KEYBOARD_PAGE_H + +#include <gtk/gtk.h> +#include "cc-page.h" + +G_BEGIN_DECLS + +#define CC_TYPE_KEYBOARD_PAGE (cc_keyboard_page_get_type ()) +#define CC_KEYBOARD_PAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_KEYBOARD_PAGE, CcKeyboardPage)) +#define CC_KEYBOARD_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_KEYBOARD_PAGE, CcKeyboardPageClass)) +#define CC_IS_KEYBOARD_PAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_KEYBOARD_PAGE)) +#define CC_IS_KEYBOARD_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_KEYBOARD_PAGE)) +#define CC_KEYBOARD_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_KEYBOARD_PAGE, CcKeyboardPageClass)) + +typedef struct CcKeyboardPagePrivate CcKeyboardPagePrivate; + +typedef struct +{ + CcPage parent; + CcKeyboardPagePrivate *priv; +} CcKeyboardPage; + +typedef struct +{ + CcPageClass parent_class; +} CcKeyboardPageClass; + +GType cc_keyboard_page_get_type (void); + +CcPage * cc_keyboard_page_new (void); + +G_END_DECLS + +#endif /* __CC_KEYBOARD_PAGE_H */ diff --git a/capplets/keyboard/cc-keyboard-panel.c b/capplets/keyboard/cc-keyboard-panel.c index bc8fc0519..7f3008450 100644 --- a/capplets/keyboard/cc-keyboard-panel.c +++ b/capplets/keyboard/cc-keyboard-panel.c @@ -31,6 +31,7 @@ #include "cc-keyboard-panel.h" #include "cc-keyboard-page.h" +#include "cc-shortcuts-page.h" #define CC_KEYBOARD_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_KEYBOARD_PANEL, CcKeyboardPanelPrivate)) @@ -40,6 +41,7 @@ struct CcKeyboardPanelPrivate { GtkWidget *notebook; CcPage *keyboard_page; + CcPage *shortcuts_page; }; enum { @@ -98,6 +100,18 @@ setup_panel (CcKeyboardPanel *panel) GTK_WIDGET (panel->priv->keyboard_page), label); gtk_widget_show (GTK_WIDGET (panel->priv->keyboard_page)); + + + panel->priv->shortcuts_page = cc_shortcuts_page_new (); + g_object_get (panel->priv->shortcuts_page, + "display-name", &display_name, + NULL); + label = gtk_label_new (display_name); + g_free (display_name); + gtk_notebook_append_page (GTK_NOTEBOOK (panel->priv->notebook), + GTK_WIDGET (panel->priv->shortcuts_page), + label); + gtk_widget_show (GTK_WIDGET (panel->priv->shortcuts_page)); } static GObject * diff --git a/capplets/keyboard/cc-shortcuts-page.c b/capplets/keyboard/cc-shortcuts-page.c new file mode 100644 index 000000000..bf4943f9a --- /dev/null +++ b/capplets/keyboard/cc-shortcuts-page.c @@ -0,0 +1,2041 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n-lib.h> +#include <gconf/gconf-client.h> + +#include "eggcellrendererkeys.h" +#include "wm-common.h" + +#include "cc-shortcuts-page.h" + +#define CC_SHORTCUTS_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_SHORTCUTS_PAGE, CcShortcutsPagePrivate)) + +#define WID(s) GTK_WIDGET (gtk_builder_get_object (builder, s)) + +#define GCONF_BINDING_DIR "/desktop/gnome/keybindings" +#define MAX_ELEMENTS_BEFORE_SCROLLING 10 +#define MAX_CUSTOM_SHORTCUTS 1000 +#define RESPONSE_ADD 0 +#define RESPONSE_REMOVE 1 + +struct CcShortcutsPagePrivate +{ + GtkWidget *custom_shortcut_dialog; + GtkWidget *custom_shortcut_name_entry; + GtkWidget *custom_shortcut_command_entry; + GtkWidget *remove_button; + GtkWidget *actions_scrolled_window; + GtkWidget *tree_view; + GtkTreeModel *model; + + gboolean block_accels; +}; + +enum { + PROP_0, +}; + +static void cc_shortcuts_page_class_init (CcShortcutsPageClass *klass); +static void cc_shortcuts_page_init (CcShortcutsPage *shortcuts_page); +static void cc_shortcuts_page_finalize (GObject *object); + +G_DEFINE_TYPE (CcShortcutsPage, cc_shortcuts_page, CC_TYPE_PAGE) + +typedef struct +{ + char *name; + /* 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; + /* an array of KeyListEntry */ + GArray *entries; +} KeyList; + +typedef enum { + COMPARISON_NONE = 0, + COMPARISON_GT, + COMPARISON_LT, + COMPARISON_EQ +} Comparison; + +typedef struct +{ + char *name; + int value; + char *key; + char *description_name; + char *cmd_name; + Comparison comparison; +} KeyListEntry; + +enum { + DESCRIPTION_COLUMN, + KEYENTRY_COLUMN, + N_COLUMNS +}; + +typedef struct +{ + char *gconf_key; + guint keyval; + guint keycode; + EggVirtualModifierType mask; + gboolean editable; + GtkTreeModel *model; + char *description; + char *desc_gconf_key; + gboolean desc_editable; + char *command; + char *cmd_gconf_key; + gboolean cmd_editable; + guint gconf_cnxn; + guint gconf_cnxn_desc; + guint gconf_cnxn_cmd; +} KeyEntry; + +static char * +binding_name (guint keyval, + guint keycode, + EggVirtualModifierType mask, + gboolean translate) +{ + if (keyval != 0 || keycode != 0) + return translate ? + egg_virtual_accelerator_label (keyval, keycode, mask) : + egg_virtual_accelerator_name (keyval, keycode, mask); + else + return g_strdup (translate ? _("Disabled") : ""); +} + +static gboolean +binding_from_string (const char *str, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods) +{ + g_return_val_if_fail (accelerator_key != NULL, FALSE); + + if (str == NULL || strcmp (str, "disabled") == 0) { + *accelerator_key = 0; + *keycode = 0; + *accelerator_mods = 0; + return TRUE; + } + + egg_accelerator_parse_virtual (str, accelerator_key, keycode, accelerator_mods); + + if (*accelerator_key == 0) + return FALSE; + else + return TRUE; +} + +static void +accel_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + KeyEntry *key_entry; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + if (key_entry == NULL) + g_object_set (cell, + "visible", FALSE, + NULL); + else if (! key_entry->editable) + g_object_set (cell, + "visible", TRUE, + "editable", FALSE, + "accel_key", key_entry->keyval, + "accel_mask", key_entry->mask, + "keycode", key_entry->keycode, + "style", PANGO_STYLE_ITALIC, + NULL); + else + g_object_set (cell, + "visible", TRUE, + "editable", TRUE, + "accel_key", key_entry->keyval, + "accel_mask", key_entry->mask, + "keycode", key_entry->keycode, + "style", PANGO_STYLE_NORMAL, + NULL); +} + +static void +description_set_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + KeyEntry *key_entry; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + if (key_entry != NULL) + g_object_set (cell, + "editable", FALSE, + "text", key_entry->description != NULL ? + key_entry->description : _("<Unknown Action>"), + NULL); + else + g_object_set (cell, + "editable", FALSE, NULL); +} + +static gboolean +keybinding_key_changed_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + KeyEntry *key_entry; + KeyEntry *tmp_key_entry; + + key_entry = (KeyEntry *)user_data; + gtk_tree_model_get (key_entry->model, iter, + KEYENTRY_COLUMN, &tmp_key_entry, + -1); + + if (key_entry == tmp_key_entry) { + gtk_tree_model_row_changed (key_entry->model, path, iter); + return TRUE; + } + return FALSE; +} + +static void +keybinding_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); + key_entry->editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, keybinding_key_changed_foreach, key_entry); +} + +static void +keybinding_description_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + g_free (key_entry->description); + key_entry->description = key_value ? g_strdup (key_value) : NULL; + key_entry->desc_editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, + keybinding_key_changed_foreach, + key_entry); +} + +static void +keybinding_command_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? gconf_value_get_string (entry->value) : NULL; + + g_free (key_entry->command); + key_entry->command = key_value ? g_strdup (key_value) : NULL; + key_entry->cmd_editable = gconf_entry_get_is_writable (entry); + + /* update the model */ + gtk_tree_model_foreach (key_entry->model, + keybinding_key_changed_foreach, + key_entry); +} + +static int +keyentry_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer user_data) +{ + KeyEntry *key_entry_a; + KeyEntry *key_entry_b; + int retval; + + key_entry_a = NULL; + gtk_tree_model_get (model, a, + KEYENTRY_COLUMN, &key_entry_a, + -1); + + key_entry_b = NULL; + gtk_tree_model_get (model, b, + KEYENTRY_COLUMN, &key_entry_b, + -1); + + if (key_entry_a && key_entry_b) { + if ((key_entry_a->keyval || key_entry_a->keycode) && + (key_entry_b->keyval || key_entry_b->keycode)) { + gchar *name_a, *name_b; + + name_a = binding_name (key_entry_a->keyval, + key_entry_a->keycode, + key_entry_a->mask, + TRUE); + + name_b = binding_name (key_entry_b->keyval, + key_entry_b->keycode, + key_entry_b->mask, + TRUE); + + retval = g_utf8_collate (name_a, name_b); + + g_free (name_a); + g_free (name_b); + } + else if (key_entry_a->keyval || key_entry_a->keycode) + retval = -1; + else if (key_entry_b->keyval || key_entry_b->keycode) + retval = 1; + else + retval = 0; + } + else if (key_entry_a) + retval = -1; + else if (key_entry_b) + retval = 1; + else + retval = 0; + + return retval; +} + +static void +clear_old_model (CcShortcutsPage *page) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (page->priv->tree_view)); + + if (model == NULL) { + /* create a new model */ + model = (GtkTreeModel *) gtk_tree_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER); + + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model), + KEYENTRY_COLUMN, + keyentry_sort_func, + NULL, NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (page->priv->tree_view), model); + + g_object_unref (model); + } else { + /* clear the existing model */ + GConfClient *client; + gboolean valid; + GtkTreeIter iter; + KeyEntry *key_entry; + + client = gconf_client_get_default (); + + /* we need the schema name below; + * cached values do not have that set, though */ + gconf_client_clear_cache (client); + + for (valid = gtk_tree_model_get_iter_first (model, &iter); + valid; + valid = gtk_tree_model_iter_next (model, &iter)) { + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + if (key_entry != NULL) { + gconf_client_remove_dir (client, key_entry->gconf_key, NULL); + gconf_client_notify_remove (client, key_entry->gconf_cnxn); + if (key_entry->gconf_cnxn_desc != 0) + gconf_client_notify_remove (client, key_entry->gconf_cnxn_desc); + if (key_entry->gconf_cnxn_cmd != 0) + gconf_client_notify_remove (client, key_entry->gconf_cnxn_cmd); + g_free (key_entry->gconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_gconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_gconf_key); + g_free (key_entry); + } + } + + gtk_tree_store_clear (GTK_TREE_STORE (model)); + g_object_unref (client); + } + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (page->priv->actions_scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + gtk_widget_set_size_request (page->priv->actions_scrolled_window, -1, -1); +} + +typedef struct { + const char *key; + gboolean found; +} KeyMatchData; + +static gboolean +key_match (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + KeyMatchData *match_data = data; + KeyEntry *element; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &element, + -1); + + if (element && g_strcmp0 (element->gconf_key, match_data->key) == 0) { + match_data->found = TRUE; + return TRUE; + } + + return FALSE; +} + +static gboolean +key_is_already_shown (GtkTreeModel *model, + const KeyListEntry *entry) +{ + KeyMatchData data; + + data.key = entry->name; + data.found = FALSE; + gtk_tree_model_foreach (model, key_match, &data); + + return data.found; +} + +static gboolean +should_show_key (const KeyListEntry *entry) +{ + int value; + GConfClient *client; + + if (entry->comparison == COMPARISON_NONE) + return TRUE; + + g_return_val_if_fail (entry->key != NULL, FALSE); + + client = gconf_client_get_default(); + value = gconf_client_get_int (client, entry->key, NULL); + g_object_unref (client); + + switch (entry->comparison) { + case COMPARISON_NONE: + /* For compiler warnings */ + g_assert_not_reached (); + return FALSE; + case COMPARISON_GT: + if (value > entry->value) + return TRUE; + break; + case COMPARISON_LT: + if (value < entry->value) + return TRUE; + break; + case COMPARISON_EQ: + if (value == entry->value) + return TRUE; + break; + } + + return FALSE; +} + +static gboolean +count_rows_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gint *rows = data; + + (*rows)++; + + return FALSE; +} + +static void +ensure_scrollbar (CcShortcutsPage *page, + int i) +{ + + if (i == MAX_ELEMENTS_BEFORE_SCROLLING) { + GtkRequisition rectangle; + + gtk_widget_ensure_style (page->priv->tree_view); + gtk_widget_size_request (page->priv->tree_view, &rectangle); + gtk_widget_set_size_request (page->priv->tree_view, -1, rectangle.height); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (page->priv->actions_scrolled_window), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + } +} + +static void +find_section (GtkTreeModel *model, + GtkTreeIter *iter, + const char *title) +{ + gboolean success; + + success = gtk_tree_model_get_iter_first (model, iter); + while (success) { + char *description = NULL; + + gtk_tree_model_get (model, iter, + DESCRIPTION_COLUMN, &description, + -1); + + if (g_strcmp0 (description, title) == 0) + return; + success = gtk_tree_model_iter_next (model, iter); + } + + gtk_tree_store_append (GTK_TREE_STORE (model), iter, NULL); + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + DESCRIPTION_COLUMN, title, + -1); +} + +static void +append_keys_to_tree (CcShortcutsPage *page, + const gchar *title, + const KeyListEntry *keys_list) +{ + GConfClient *client; + GtkTreeIter parent_iter, iter; + GtkTreeModel *model; + gint i, j; + + client = gconf_client_get_default (); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (page->priv->tree_view)); + + /* Try to find a section parent iter, if it already exists */ + find_section (model, &iter, title); + parent_iter = iter; + + i = 0; + gtk_tree_model_foreach (model, count_rows_foreach, &i); + + /* If the header we just added is the MAX_ELEMENTS_BEFORE_SCROLLING th, + * then we need to scroll now */ + ensure_scrollbar (page, i - 1); + + for (j = 0; keys_list[j].name != NULL; j++) { + GConfEntry *entry; + KeyEntry *key_entry; + const gchar *key_string; + gchar *key_value; + gchar *description; + gchar *command; + + if (!should_show_key (&keys_list[j])) + continue; + + if (key_is_already_shown (model, &keys_list[j])) + continue; + + key_string = keys_list[j].name; + + entry = gconf_client_get_entry (client, + key_string, + NULL, + TRUE, + NULL); + if (entry == NULL) { + /* We don't actually want to popup a dialog - just skip this one */ + continue; + } + + if (keys_list[j].description_name != NULL) { + description = gconf_client_get_string (client, + keys_list[j].description_name, + NULL); + } else { + description = NULL; + + if (gconf_entry_get_schema_name (entry)) { + GConfSchema *schema; + + schema = gconf_client_get_schema (client, + gconf_entry_get_schema_name (entry), + NULL); + if (schema != NULL) { + description = g_strdup (gconf_schema_get_short_desc (schema)); + gconf_schema_free (schema); + } + } + } + + if (description == NULL) { + /* Only print a warning for keys that should have a schema */ + if (keys_list[j].description_name == NULL) + g_warning ("No description for key '%s'", key_string); + } + + if (keys_list[j].cmd_name != NULL) { + command = gconf_client_get_string (client, + keys_list[j].cmd_name, + NULL); + } else { + command = NULL; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->gconf_key = g_strdup (key_string); + key_entry->editable = gconf_entry_get_is_writable (entry); + key_entry->model = model; + key_entry->description = description; + key_entry->command = command; + + if (keys_list[j].description_name != NULL) { + key_entry->desc_gconf_key = g_strdup (keys_list[j].description_name); + key_entry->desc_editable = gconf_client_key_is_writable (client, key_entry->desc_gconf_key, NULL); + key_entry->gconf_cnxn_desc = gconf_client_notify_add (client, + key_entry->desc_gconf_key, + (GConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); + } + + if (keys_list[j].cmd_name != NULL) { + key_entry->cmd_gconf_key = g_strdup (keys_list[j].cmd_name); + key_entry->cmd_editable = gconf_client_key_is_writable (client, key_entry->cmd_gconf_key, NULL); + key_entry->gconf_cnxn_cmd = gconf_client_notify_add (client, + key_entry->cmd_gconf_key, + (GConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); + } + + gconf_client_add_dir (client, + key_string, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + key_entry->gconf_cnxn = gconf_client_notify_add (client, + key_string, + (GConfClientNotifyFunc) &keybinding_key_changed, + key_entry, + NULL, + NULL); + + key_value = gconf_client_get_string (client, key_string, NULL); + binding_from_string (key_value, + &key_entry->keyval, + &key_entry->keycode, + &key_entry->mask); + g_free (key_value); + + gconf_entry_free (entry); + ensure_scrollbar (page, i); + + ++i; + gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter); + /* we use the DESCRIPTION_COLUMN only for the section headers */ + gtk_tree_store_set (GTK_TREE_STORE (model), &iter, + KEYENTRY_COLUMN, key_entry, + -1); + gtk_tree_view_expand_all (GTK_TREE_VIEW (page->priv->tree_view)); + } + + g_object_unref (client); + + /* Don't show an empty section */ + if (gtk_tree_model_iter_n_children (model, &parent_iter) == 0) + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent_iter); + +#if 0 + if (i == 0) + gtk_widget_hide (WID ("shortcuts_vbox")); + else + gtk_widget_show (WID ("shortcuts_vbox")); +#endif +} + +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; + const char *name, *gconf_key; + int value; + Comparison comparison; + + name = 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 *package = 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, "wm_name")) { + if (**attr_values) + wm_name = *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); + } + return; + } + + if (!g_str_equal (element_name, "KeyListEntry") + || attr_names == NULL + || attr_values == NULL) + return; + + value = 0; + comparison = COMPARISON_NONE; + gconf_key = NULL; + + 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, "value")) { + if (**attr_values) { + value = (int) g_ascii_strtoull (*attr_values, NULL, 0); + } + } else if (g_str_equal (*attr_names, "key")) { + if (**attr_values) { + gconf_key = *attr_values; + } + } else if (g_str_equal (*attr_names, "comparison")) { + if (**attr_values) { + if (g_str_equal (*attr_values, "gt")) { + comparison = COMPARISON_GT; + } else if (g_str_equal (*attr_values, "lt")) { + comparison = COMPARISON_LT; + } else if (g_str_equal (*attr_values, "eq")) { + comparison = COMPARISON_EQ; + } + } + } + + ++attr_names; + ++attr_values; + } + + if (name == NULL) + return; + + key.name = g_strdup (name); + key.description_name = NULL; + key.value = value; + if (gconf_key) + key.key = g_strdup (gconf_key); + else + key.key = NULL; + key.comparison = comparison; + key.cmd_name = NULL; + 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_keys_to_tree_from_file (CcShortcutsPage *page, + const char *filename, + char **wm_keybindings) +{ + GMarkupParseContext *ctx; + GMarkupParser parser = { parse_start_tag, NULL, NULL, NULL, NULL }; + KeyList *keylist; + KeyListEntry key, *keys; + GError *err = NULL; + char *buf; + const char *title; + gsize buf_len; + guint i; + + if (!g_file_get_contents (filename, &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'", filename, 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; + key.description_name = NULL; + key.key = NULL; + key.value = 0; + key.comparison = COMPARISON_NONE; + g_array_append_val (keylist->entries, key); + + keys = (KeyListEntry *) g_array_free (keylist->entries, FALSE); + if (keylist->package) { + bind_textdomain_codeset (keylist->package, "UTF-8"); + title = dgettext (keylist->package, keylist->name); + } else { + title = _(keylist->name); + } + + append_keys_to_tree (page, title, keys); + + g_free (keylist->name); + g_free (keylist->package); + for (i = 0; keys[i].name != NULL; i++) + g_free (keys[i].name); + g_free (keylist); +} + +static void +append_keys_to_tree_from_gconf (CcShortcutsPage *page, + const gchar *gconf_path) +{ + GConfClient *client; + GSList *custom_list, *l; + GArray *entries; + KeyListEntry key; + + /* load custom shortcuts from GConf */ + entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); + + key.key = NULL; + key.value = 0; + key.comparison = COMPARISON_NONE; + + client = gconf_client_get_default (); + custom_list = gconf_client_all_dirs (client, gconf_path, NULL); + + for (l = custom_list; l != NULL; l = l->next) { + key.name = g_strconcat (l->data, "/binding", NULL); + key.cmd_name = g_strconcat (l->data, "/action", NULL); + key.description_name = g_strconcat (l->data, "/name", NULL); + g_array_append_val (entries, key); + + g_free (l->data); + } + + g_slist_free (custom_list); + g_object_unref (client); + + if (entries->len > 0) { + KeyListEntry *keys; + int i; + + /* Empty KeyListEntry to end the array */ + key.name = NULL; + key.description_name = NULL; + g_array_append_val (entries, key); + + keys = (KeyListEntry *) entries->data; + append_keys_to_tree (page, _("Custom Shortcuts"), keys); + for (i = 0; i < entries->len; ++i) { + g_free (keys[i].name); + g_free (keys[i].description_name); + } + } + + g_array_free (entries, TRUE); +} + +static void +reload_key_entries (CcShortcutsPage *page) +{ + gchar **wm_keybindings; + GDir *dir; + const char *name; + GList *list, *l; + + wm_keybindings = wm_common_get_current_keybindings (); + + clear_old_model (page); + + dir = g_dir_open (GNOMECC_KEYBINDINGS_DIR, 0, NULL); + if (!dir) + return; + + list = NULL; + for (name = g_dir_read_name (dir); name; name = g_dir_read_name (dir)) { + if (g_str_has_suffix (name, ".xml")) { + list = g_list_insert_sorted (list, + g_strdup (name), + (GCompareFunc) g_ascii_strcasecmp); + } + } + g_dir_close (dir); + + for (l = list; l != NULL; l = l->next) { + gchar *path; + + path = g_build_filename (GNOMECC_KEYBINDINGS_DIR, l->data, NULL); + append_keys_to_tree_from_file (page, path, wm_keybindings); + g_free (l->data); + g_free (path); + } + g_list_free (list); + + /* Load custom shortcuts _after_ system-provided ones, + * since some of the custom shortcuts may also be listed + * in a file. Loading the custom shortcuts last makes + * such keys not show up in the custom section. + */ + append_keys_to_tree_from_gconf (page, GCONF_BINDING_DIR); + + g_strfreev (wm_keybindings); +} + +static void +key_entry_controlling_key_changed (GConfClient *client, + guint cnxn_id, + GConfEntry *entry, + CcShortcutsPage *page) +{ + reload_key_entries (page); +} + +static gboolean +cb_check_for_uniqueness (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + KeyEntry *new_key) +{ + KeyEntry *element; + + gtk_tree_model_get (new_key->model, iter, + KEYENTRY_COLUMN, &element, + -1); + + /* no conflict for : blanks, different modifiers, or ourselves */ + if (element == NULL || new_key->mask != element->mask || + !strcmp (new_key->gconf_key, element->gconf_key)) + return FALSE; + + if (new_key->keyval != 0) { + if (new_key->keyval != element->keyval) + return FALSE; + } else if (element->keyval != 0 || new_key->keycode != element->keycode) + return FALSE; + + new_key->editable = FALSE; + new_key->gconf_key = element->gconf_key; + new_key->description = element->description; + new_key->desc_gconf_key = element->desc_gconf_key; + new_key->desc_editable = element->desc_editable; + return TRUE; +} + +static const guint forbidden_keyvals[] = { + /* Navigation keys */ + GDK_Home, + GDK_Left, + GDK_Up, + GDK_Right, + GDK_Down, + GDK_Page_Up, + GDK_Page_Down, + GDK_End, + GDK_Tab, + + /* Return */ + GDK_KP_Enter, + GDK_Return, + + GDK_space, + GDK_Mode_switch +}; + +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; +} + +static void +show_error (GtkWindow *parent, + GError *err) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_DESTROY_WITH_PARENT + | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + _("Error saving the new shortcut")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +static void +on_accel_edited (GtkCellRendererText *cell, + const char *path_string, + guint keyval, + EggVirtualModifierType mask, + guint keycode, + CcShortcutsPage *page) +{ + GConfClient *client; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + KeyEntry *key_entry, tmp_key; + GError *err; + char *str; + + err = NULL; + + path = gtk_tree_path_new_from_string (path_string); + page->priv->block_accels = FALSE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (page->priv->tree_view)); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL) + return; + + /* CapsLock isn't supported as a keybinding modifier, so keep it from confusing us */ + mask &= ~EGG_VIRTUAL_LOCK_MASK; + + tmp_key.model = model; + tmp_key.keyval = keyval; + tmp_key.keycode = keycode; + tmp_key.mask = mask; + tmp_key.gconf_key = key_entry->gconf_key; + tmp_key.description = NULL; + tmp_key.editable = TRUE; /* kludge to stuff in a return flag */ + + if (keyval != 0 || keycode != 0) /* any number of keys can be disabled */ + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) cb_check_for_uniqueness, + &tmp_key); + + /* Check for unmodified keys */ + if (tmp_key.mask == 0 && tmp_key.keycode != 0) { + if ((tmp_key.keyval >= GDK_a && tmp_key.keyval <= GDK_z) + || (tmp_key.keyval >= GDK_A && tmp_key.keyval <= GDK_Z) + || (tmp_key.keyval >= GDK_0 && tmp_key.keyval <= GDK_9) + || (tmp_key.keyval >= GDK_kana_fullstop && tmp_key.keyval <= GDK_semivoicedsound) + || (tmp_key.keyval >= GDK_Arabic_comma && tmp_key.keyval <= GDK_Arabic_sukun) + || (tmp_key.keyval >= GDK_Serbian_dje && tmp_key.keyval <= GDK_Cyrillic_HARDSIGN) + || (tmp_key.keyval >= GDK_Greek_ALPHAaccent && tmp_key.keyval <= GDK_Greek_omega) + || (tmp_key.keyval >= GDK_hebrew_doublelowline && tmp_key.keyval <= GDK_hebrew_taf) + || (tmp_key.keyval >= GDK_Thai_kokai && tmp_key.keyval <= GDK_Thai_lekkao) + || (tmp_key.keyval >= GDK_Hangul && tmp_key.keyval <= GDK_Hangul_Special) + || (tmp_key.keyval >= GDK_Hangul_Kiyeog && tmp_key.keyval <= GDK_Hangul_J_YeorinHieuh) + || keyval_is_forbidden (tmp_key.keyval)) { + GtkWidget *dialog; + char *name; + + name = binding_name (keyval, keycode, mask, TRUE); + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + 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); + + /* set it back to its previous value. */ + egg_cell_renderer_keys_set_accelerator (EGG_CELL_RENDERER_KEYS (cell), + key_entry->keyval, + key_entry->keycode, + key_entry->mask); + return; + } + } + + /* flag to see if the new accelerator was in use by something */ + if (!tmp_key.editable) { + 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 (page))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut \"%s\" is already used for\n\"%s\""), + name, + tmp_key.description ? + tmp_key.description : tmp_key.gconf_key); + 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."), + key_entry->description ? + key_entry->description : key_entry->gconf_key, + tmp_key.description ? + tmp_key.description : tmp_key.gconf_key); + + 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); + + if (response == GTK_RESPONSE_ACCEPT) { + GConfClient *client; + + client = gconf_client_get_default (); + + gconf_client_set_string (client, + tmp_key.gconf_key, + "", &err); + + if (err != NULL) { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + err); + g_error_free (err); + g_object_unref (client); + return; + } + + str = binding_name (keyval, keycode, mask, FALSE); + gconf_client_set_string (client, + key_entry->gconf_key, + str, &err); + + if (err != NULL) { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + err); + g_error_free (err); + + /* reset the previous shortcut */ + gconf_client_set_string (client, + tmp_key.gconf_key, + str, NULL); + } + + g_free (str); + g_object_unref (client); + } else { + /* set it back to its previous value. */ + egg_cell_renderer_keys_set_accelerator (EGG_CELL_RENDERER_KEYS (cell), + key_entry->keyval, + key_entry->keycode, + key_entry->mask); + } + + return; + } + + str = binding_name (keyval, keycode, mask, FALSE); + + client = gconf_client_get_default (); + gconf_client_set_string (client, + key_entry->gconf_key, + str, + &err); + g_free (str); + g_object_unref (client); + + if (err != NULL) { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), err); + g_error_free (err); + key_entry->editable = FALSE; + } +} + +static void +on_accel_cleared (GtkCellRendererText *cell, + const char *path_string, + CcShortcutsPage *page) +{ + GConfClient *client; + GtkTreePath *path; + KeyEntry *key_entry; + GtkTreeIter iter; + GError *err = NULL; + GtkTreeModel *model; + + path = gtk_tree_path_new_from_string (path_string); + page->priv->block_accels = FALSE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (page->priv->tree_view)); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL) + return; + + /* Unset the key */ + client = gconf_client_get_default(); + gconf_client_set_string (client, + key_entry->gconf_key, + "", + &err); + g_object_unref (client); + + if (err != NULL) { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_OK, + _("Error unsetting accelerator in configuration database: %s"), + err->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); + g_error_free (err); + key_entry->editable = FALSE; + } +} + +static void +on_description_edited (GtkCellRendererText *renderer, + gchar *path_string, + gchar *new_text, + CcShortcutsPage *page) +{ + GConfClient *client; + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + KeyEntry *key_entry; + + path = gtk_tree_path_new_from_string (path_string); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (page->priv->tree_view)); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL || key_entry->desc_gconf_key == NULL) + return; + + client = gconf_client_get_default (); + if (!gconf_client_set_string (client, key_entry->desc_gconf_key, new_text, NULL)) + key_entry->desc_editable = FALSE; + + g_object_unref (client); +} + + +typedef struct +{ + GtkTreeView *tree_view; + GtkTreePath *path; + GtkTreeViewColumn *column; +} IdleData; + +static gboolean +real_start_editing_cb (IdleData *idle_data) +{ + gtk_widget_grab_focus (GTK_WIDGET (idle_data->tree_view)); + gtk_tree_view_set_cursor (idle_data->tree_view, + idle_data->path, + idle_data->column, + TRUE); + gtk_tree_path_free (idle_data->path); + g_free (idle_data); + return FALSE; +} + +static gboolean +edit_custom_shortcut (CcShortcutsPage *page, + KeyEntry *key) +{ + gint result; + const gchar *text; + gboolean ret; + GtkWidget *widget; + + gtk_entry_set_text (GTK_ENTRY (page->priv->custom_shortcut_name_entry), + key->description ? key->description : ""); + gtk_widget_set_sensitive (page->priv->custom_shortcut_name_entry, + key->desc_editable); + + gtk_widget_grab_focus (page->priv->custom_shortcut_name_entry); + gtk_entry_set_text (GTK_ENTRY (page->priv->custom_shortcut_command_entry), + key->command ? key->command : ""); + gtk_widget_set_sensitive (page->priv->custom_shortcut_command_entry, + key->cmd_editable); + + widget = gtk_widget_get_toplevel (GTK_WIDGET (page)); + if (GTK_WIDGET_TOPLEVEL (widget)) { + gtk_window_set_transient_for (GTK_WINDOW (page->priv->custom_shortcut_dialog), + GTK_WINDOW (widget)); + } + + gtk_window_present (GTK_WINDOW (page->priv->custom_shortcut_dialog)); + result = gtk_dialog_run (GTK_DIALOG (page->priv->custom_shortcut_dialog)); + + switch (result) { + case GTK_RESPONSE_OK: + text = gtk_entry_get_text (GTK_ENTRY (page->priv->custom_shortcut_name_entry)); + g_free (key->description); + key->description = g_strdup (text); + text = gtk_entry_get_text (GTK_ENTRY (page->priv->custom_shortcut_command_entry)); + g_free (key->command); + key->command = g_strdup (text); + ret = TRUE; + break; + default: + ret = FALSE; + break; + } + + gtk_widget_hide (page->priv->custom_shortcut_dialog); + + return ret; +} + +static gboolean +remove_custom_shortcut (GtkTreeModel *model, + GtkTreeIter *iter) +{ + GtkTreeIter parent; + GConfClient *client; + gchar *base; + KeyEntry *key; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key, + -1); + + /* not a custom shortcut */ + if (key->command == NULL) + return FALSE; + + client = gconf_client_get_default (); + + gconf_client_notify_remove (client, key->gconf_cnxn); + if (key->gconf_cnxn_desc != 0) + gconf_client_notify_remove (client, key->gconf_cnxn_desc); + if (key->gconf_cnxn_cmd != 0) + gconf_client_notify_remove (client, key->gconf_cnxn_cmd); + + base = g_path_get_dirname (key->gconf_key); + gconf_client_recursive_unset (client, base, 0, NULL); + g_free (base); + + /* suggest sync now so the unset directory actually gets dropped; + * if we don't do this we may end up with 'zombie' shortcuts when + * restarting the app */ + gconf_client_suggest_sync (client, NULL); + g_object_unref (client); + + g_free (key->gconf_key); + g_free (key->description); + g_free (key->desc_gconf_key); + g_free (key->command); + g_free (key->cmd_gconf_key); + g_free (key); + + gtk_tree_model_iter_parent (model, &parent, iter); + gtk_tree_store_remove (GTK_TREE_STORE (model), iter); + if (!gtk_tree_model_iter_has_child (model, &parent)) + gtk_tree_store_remove (GTK_TREE_STORE (model), &parent); + + return TRUE; +} + +static void +update_custom_shortcut (CcShortcutsPage *page, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + KeyEntry *key; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key, + -1); + + edit_custom_shortcut (page, key); + if (key->command == NULL || key->command[0] == '\0') { + remove_custom_shortcut (model, iter); + } else { + GConfClient *client; + + gtk_tree_store_set (GTK_TREE_STORE (model), + iter, + KEYENTRY_COLUMN, key, + -1); + client = gconf_client_get_default (); + if (key->description != NULL) + gconf_client_set_string (client, key->desc_gconf_key, key->description, NULL); + else + gconf_client_unset (client, key->desc_gconf_key, NULL); + gconf_client_set_string (client, key->cmd_gconf_key, key->command, NULL); + g_object_unref (client); + } +} + +static gchar * +find_free_gconf_key (GError **error) +{ + GConfClient *client; + + gchar *dir; + int i; + + client = gconf_client_get_default (); + + for (i = 0; i < MAX_CUSTOM_SHORTCUTS; i++) { + dir = g_strdup_printf ("%s/custom%d", GCONF_BINDING_DIR, i); + if (!gconf_client_dir_exists (client, dir, NULL)) + break; + g_free (dir); + } + + if (i == MAX_CUSTOM_SHORTCUTS) { + dir = NULL; + g_set_error_literal (error, + g_quark_from_string ("Keyboard Shortcuts"), + 0, + _("Too many custom shortcuts")); + } + + g_object_unref (client); + + return dir; +} + +static void +add_custom_shortcut (CcShortcutsPage *page) +{ + KeyEntry *key_entry; + GtkTreeIter iter; + GtkTreeIter parent_iter; + GtkTreePath *path; + gchar *dir; + GConfClient *client; + GError *error; + + error = NULL; + dir = find_free_gconf_key (&error); + if (dir == NULL) { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (page))), error); + + g_error_free (error); + return; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->gconf_key = g_strconcat (dir, "/binding", NULL); + key_entry->editable = TRUE; + key_entry->model = page->priv->model; + key_entry->desc_gconf_key = g_strconcat (dir, "/name", NULL); + key_entry->description = g_strdup (""); + key_entry->desc_editable = TRUE; + key_entry->cmd_gconf_key = g_strconcat (dir, "/action", NULL); + key_entry->command = g_strdup (""); + key_entry->cmd_editable = TRUE; + g_free (dir); + + if (edit_custom_shortcut (page, key_entry) + && key_entry->command + && key_entry->command[0]) { + + find_section (page->priv->model, &iter, _("Custom Shortcuts")); + parent_iter = iter; + gtk_tree_store_append (GTK_TREE_STORE (page->priv->model), &iter, &parent_iter); + gtk_tree_store_set (GTK_TREE_STORE (page->priv->model), &iter, KEYENTRY_COLUMN, key_entry, -1); + + /* store in gconf */ + client = gconf_client_get_default (); + gconf_client_set_string (client, key_entry->gconf_key, "", NULL); + gconf_client_set_string (client, key_entry->desc_gconf_key, key_entry->description, NULL); + gconf_client_set_string (client, key_entry->cmd_gconf_key, key_entry->command, NULL); + + /* add gconf watches */ + key_entry->gconf_cnxn_desc + = gconf_client_notify_add (client, + key_entry->desc_gconf_key, + (GConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); + key_entry->gconf_cnxn_cmd + = gconf_client_notify_add (client, + key_entry->cmd_gconf_key, + (GConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); + key_entry->gconf_cnxn + = gconf_client_notify_add (client, + key_entry->gconf_key, + (GConfClientNotifyFunc) &keybinding_key_changed, + key_entry, NULL, NULL); + + + g_object_unref (client); + + /* make the new shortcut visible */ + path = gtk_tree_model_get_path (page->priv->model, &iter); + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (page->priv->tree_view), path); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (page->priv->tree_view), + path, + NULL, + FALSE, + 0, + 0); + gtk_tree_path_free (path); + } else { + g_free (key_entry->gconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_gconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_gconf_key); + g_free (key_entry); + } +} + +static void +on_row_activated (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + CcShortcutsPage *page) +{ + GtkTreeModel *model; + GtkTreeIter iter; + KeyEntry *key; + + model = gtk_tree_view_get_model (treeview); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key, + -1); + + if (key == NULL) { + /* This is a section heading - expand or collapse */ + if (gtk_tree_view_row_expanded (treeview, path)) + gtk_tree_view_collapse_row (treeview, path); + else + gtk_tree_view_expand_row (treeview, path, FALSE); + return; + } + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (key->desc_editable + && column == gtk_tree_view_get_column (treeview, 0)) { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, + path, + gtk_tree_view_get_column (treeview, 0), + FALSE); + update_custom_shortcut (page, model, &iter); + } else { + gtk_widget_grab_focus (GTK_WIDGET (treeview)); + gtk_tree_view_set_cursor (treeview, + path, + gtk_tree_view_get_column (treeview, 1), + TRUE); + } +} + +static gboolean +on_button_press_event (GtkTreeView *tree_view, + GdkEventButton *event, + CcShortcutsPage *page) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + + 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)) { + IdleData *idle_data; + GtkTreeModel *model; + GtkTreeIter iter; + KeyEntry *key; + + if (gtk_tree_path_get_depth (path) == 1) { + gtk_tree_path_free (path); + return FALSE; + } + + model = gtk_tree_view_get_model (tree_view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key, + -1); + + /* if only the accel can be edited on the selected row + * always select the accel column */ + if (key->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, + gtk_tree_view_get_column (tree_view, 0), + FALSE); + update_custom_shortcut (page, model, &iter); + } else { + idle_data = g_new (IdleData, 1); + idle_data->tree_view = tree_view; + idle_data->path = path; + idle_data->column = key->desc_editable ? column : + gtk_tree_view_get_column (tree_view, 1); + g_idle_add ((GSourceFunc) real_start_editing_cb, + idle_data); + page->priv->block_accels = TRUE; + } + g_signal_stop_emission_by_name (tree_view, "button_press_event"); + } + return TRUE; +} + +/* this handler is used to keep accels from activating while the user + * is assigning a new shortcut so that he won't accidentally trigger one + * of the widgets */ +static gboolean +maybe_block_accels (GtkWidget *widget, + GdkEventKey *event, + CcShortcutsPage *page) +{ + if (page->priv->block_accels) { + return gtk_window_propagate_key_event (GTK_WINDOW (widget), event); + } + return FALSE; +} + +static void +cc_shortcuts_page_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_shortcuts_page_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_add_button_clicked (GtkWidget *widget, + CcShortcutsPage *page) +{ + add_custom_shortcut (page); +} + +static void +on_remove_button_clicked (GtkWidget *widget, + CcShortcutsPage *page) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (page->priv->tree_view)); + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + remove_custom_shortcut (page->priv->model, &iter); + } +} + +static void +on_tree_selection_changed (GtkTreeSelection *selection, + CcShortcutsPage *page) +{ + GtkTreeIter iter; + gboolean can_remove; + + can_remove = FALSE; + if (gtk_tree_selection_get_selected (selection, &page->priv->model, &iter)) { + KeyEntry *key; + + key = NULL; + gtk_tree_model_get (page->priv->model, + &iter, + KEYENTRY_COLUMN, &key, + -1); + if (key != NULL && key->command != NULL && key->editable) + can_remove = TRUE; + } + + gtk_widget_set_sensitive (page->priv->remove_button, can_remove); +} + +static void +setup_page (CcShortcutsPage *page) +{ + GtkBuilder *builder; + GtkWidget *widget; + GError *error; + GConfClient *client; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + GSList *allowed_keys; + + builder = gtk_builder_new (); + + error = NULL; + gtk_builder_add_from_file (builder, + GNOMECC_UI_DIR + "/gnome-keyboard-properties-dialog.ui", + &error); + if (error != NULL) { + g_error (_("Could not load user interface file: %s"), + error->message); + g_error_free (error); + return; + } + + client = gconf_client_get_default (); + + page->priv->actions_scrolled_window = WID ("actions_swindow"); + page->priv->remove_button = WID ("remove-button"); + g_signal_connect (page->priv->remove_button, + "clicked", + G_CALLBACK (on_remove_button_clicked), + page); + widget = WID ("add-button"); + g_signal_connect (widget, + "clicked", + G_CALLBACK (on_add_button_clicked), + page); + + page->priv->tree_view = WID ("shortcut_treeview"); + + g_signal_connect (page->priv->tree_view, + "button_press_event", + G_CALLBACK (on_button_press_event), + page); + g_signal_connect (page->priv->tree_view, + "row-activated", + G_CALLBACK (on_row_activated), + page); + + renderer = gtk_cell_renderer_text_new (); + + g_signal_connect (renderer, + "edited", + G_CALLBACK (on_description_edited), + page); + + column = gtk_tree_view_column_new_with_attributes (_("Action"), + renderer, + "text", DESCRIPTION_COLUMN, + 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_append_column (GTK_TREE_VIEW (page->priv->tree_view), column); + gtk_tree_view_column_set_sort_column_id (column, DESCRIPTION_COLUMN); + + renderer = (GtkCellRenderer *) g_object_new (EGG_TYPE_CELL_RENDERER_KEYS, + "accel_mode", EGG_CELL_RENDERER_KEYS_MODE_X, + NULL); + + g_signal_connect (renderer, + "accel_edited", + G_CALLBACK (on_accel_edited), + page); + + g_signal_connect (renderer, + "accel_cleared", + G_CALLBACK (on_accel_cleared), + page); + + column = gtk_tree_view_column_new_with_attributes (_("Shortcut"), + 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_append_column (GTK_TREE_VIEW (page->priv->tree_view), column); + gtk_tree_view_column_set_sort_column_id (column, KEYENTRY_COLUMN); + + gconf_client_add_dir (client, + GCONF_BINDING_DIR, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + gconf_client_add_dir (client, + "/apps/metacity/general", + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + gconf_client_notify_add (client, + "/apps/metacity/general/num_workspaces", + (GConfClientNotifyFunc) key_entry_controlling_key_changed, + page, + NULL, + NULL); + + reload_key_entries (page); + + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (page->priv->tree_view)); + g_signal_connect (selection, + "changed", + G_CALLBACK (on_tree_selection_changed), + page); + + allowed_keys = gconf_client_get_list (client, + GCONF_BINDING_DIR "/allowed_keys", + GCONF_VALUE_STRING, + NULL); + if (allowed_keys != NULL) { + g_slist_foreach (allowed_keys, (GFunc)g_free, NULL); + g_slist_free (allowed_keys); + gtk_widget_set_sensitive (WID ("add-button"), FALSE); + } + + g_object_unref (client); + + /* setup the custom shortcut dialog */ + page->priv->custom_shortcut_dialog = WID ("custom-shortcut-dialog"); + page->priv->custom_shortcut_name_entry = WID ("custom-shortcut-name-entry"); + page->priv->custom_shortcut_command_entry = WID ("custom-shortcut-command-entry"); + + gtk_dialog_set_default_response (GTK_DIALOG (page->priv->custom_shortcut_dialog), + GTK_RESPONSE_OK); + + g_signal_connect (page, + "key_press_event", + G_CALLBACK (maybe_block_accels), + page); + + widget = WID ("shortcuts_vbox"); + gtk_widget_reparent (widget, GTK_WIDGET (page)); + gtk_widget_show (widget); +} + +static GObject * +cc_shortcuts_page_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + CcShortcutsPage *shortcuts_page; + + shortcuts_page = CC_SHORTCUTS_PAGE (G_OBJECT_CLASS (cc_shortcuts_page_parent_class)->constructor (type, + n_construct_properties, + construct_properties)); + + g_object_set (shortcuts_page, + "display-name", _("Keyboard Shortcuts"), + "id", "shortcuts", + NULL); + + setup_page (shortcuts_page); + + return G_OBJECT (shortcuts_page); +} + +static void +cc_shortcuts_page_class_init (CcShortcutsPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = cc_shortcuts_page_get_property; + object_class->set_property = cc_shortcuts_page_set_property; + object_class->constructor = cc_shortcuts_page_constructor; + object_class->finalize = cc_shortcuts_page_finalize; + + g_type_class_add_private (klass, sizeof (CcShortcutsPagePrivate)); +} + +static void +cc_shortcuts_page_init (CcShortcutsPage *page) +{ + page->priv = CC_SHORTCUTS_PAGE_GET_PRIVATE (page); +} + +static void +cc_shortcuts_page_finalize (GObject *object) +{ + CcShortcutsPage *page; + + g_return_if_fail (object != NULL); + g_return_if_fail (CC_IS_SHORTCUTS_PAGE (object)); + + page = CC_SHORTCUTS_PAGE (object); + + g_return_if_fail (page->priv != NULL); + + + G_OBJECT_CLASS (cc_shortcuts_page_parent_class)->finalize (object); +} + +CcPage * +cc_shortcuts_page_new (void) +{ + GObject *object; + + object = g_object_new (CC_TYPE_SHORTCUTS_PAGE, NULL); + + return CC_PAGE (object); +} diff --git a/capplets/keyboard/cc-shortcuts-page.h b/capplets/keyboard/cc-shortcuts-page.h new file mode 100644 index 000000000..abfcec606 --- /dev/null +++ b/capplets/keyboard/cc-shortcuts-page.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2010 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __CC_SHORTCUTS_PAGE_H +#define __CC_SHORTCUTS_PAGE_H + +#include <gtk/gtk.h> +#include "cc-page.h" + +G_BEGIN_DECLS + +#define CC_TYPE_SHORTCUTS_PAGE (cc_shortcuts_page_get_type ()) +#define CC_SHORTCUTS_PAGE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_SHORTCUTS_PAGE, CcShortcutsPage)) +#define CC_SHORTCUTS_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_SHORTCUTS_PAGE, CcShortcutsPageClass)) +#define CC_IS_SHORTCUTS_PAGE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_SHORTCUTS_PAGE)) +#define CC_IS_SHORTCUTS_PAGE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_SHORTCUTS_PAGE)) +#define CC_SHORTCUTS_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_SHORTCUTS_PAGE, CcShortcutsPageClass)) + +typedef struct CcShortcutsPagePrivate CcShortcutsPagePrivate; + +typedef struct +{ + CcPage parent; + CcShortcutsPagePrivate *priv; +} CcShortcutsPage; + +typedef struct +{ + CcPageClass parent_class; +} CcShortcutsPageClass; + +GType cc_shortcuts_page_get_type (void); + +CcPage * cc_shortcuts_page_new (void); + +G_END_DECLS + +#endif /* __CC_SHORTCUTS_PAGE_H */ diff --git a/capplets/keyboard/eggaccelerators.c b/capplets/keyboard/eggaccelerators.c new file mode 100644 index 000000000..0728229d1 --- /dev/null +++ b/capplets/keyboard/eggaccelerators.c @@ -0,0 +1,632 @@ +/* eggaccelerators.c + * Copyright (C) 2002 Red Hat, Inc.; Copyright 1998, 2001 Tim Janik + * Developed by Havoc Pennington, Tim Janik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "eggaccelerators.h" + +#include <stdlib.h> +#include <string.h> +#include <gdk/gdkx.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +enum +{ + EGG_MODMAP_ENTRY_SHIFT = 0, + EGG_MODMAP_ENTRY_LOCK = 1, + EGG_MODMAP_ENTRY_CONTROL = 2, + EGG_MODMAP_ENTRY_MOD1 = 3, + EGG_MODMAP_ENTRY_MOD2 = 4, + EGG_MODMAP_ENTRY_MOD3 = 5, + EGG_MODMAP_ENTRY_MOD4 = 6, + EGG_MODMAP_ENTRY_MOD5 = 7, + EGG_MODMAP_ENTRY_LAST = 8 +}; + +#define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x)) + +typedef struct +{ + EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST]; + +} EggModmap; + +const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap); + +static inline gboolean +is_alt (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'a' || string[1] == 'A') && + (string[2] == 'l' || string[2] == 'L') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == '>')); +} + +static inline gboolean +is_ctl (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == '>')); +} + +static inline gboolean +is_modx (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'd' || string[3] == 'D') && + (string[4] >= '1' && string[4] <= '5') && + (string[5] == '>')); +} + +static inline gboolean +is_ctrl (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 't' || string[2] == 'T') && + (string[3] == 'r' || string[3] == 'R') && + (string[4] == 'l' || string[4] == 'L') && + (string[5] == '>')); +} + +static inline gboolean +is_shft (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'f' || string[3] == 'F') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == '>')); +} + +static inline gboolean +is_shift (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'h' || string[2] == 'H') && + (string[3] == 'i' || string[3] == 'I') && + (string[4] == 'f' || string[4] == 'F') && + (string[5] == 't' || string[5] == 'T') && + (string[6] == '>')); +} + +static inline gboolean +is_control (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'c' || string[1] == 'C') && + (string[2] == 'o' || string[2] == 'O') && + (string[3] == 'n' || string[3] == 'N') && + (string[4] == 't' || string[4] == 'T') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == 'o' || string[6] == 'O') && + (string[7] == 'l' || string[7] == 'L') && + (string[8] == '>')); +} + +static inline gboolean +is_release (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'r' || string[1] == 'R') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 'l' || string[3] == 'L') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'a' || string[5] == 'A') && + (string[6] == 's' || string[6] == 'S') && + (string[7] == 'e' || string[7] == 'E') && + (string[8] == '>')); +} + +static inline gboolean +is_meta (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'm' || string[1] == 'M') && + (string[2] == 'e' || string[2] == 'E') && + (string[3] == 't' || string[3] == 'T') && + (string[4] == 'a' || string[4] == 'A') && + (string[5] == '>')); +} + +static inline gboolean +is_super (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 's' || string[1] == 'S') && + (string[2] == 'u' || string[2] == 'U') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); +} + +static inline gboolean +is_hyper (const gchar *string) +{ + return ((string[0] == '<') && + (string[1] == 'h' || string[1] == 'H') && + (string[2] == 'y' || string[2] == 'Y') && + (string[3] == 'p' || string[3] == 'P') && + (string[4] == 'e' || string[4] == 'E') && + (string[5] == 'r' || string[5] == 'R') && + (string[6] == '>')); +} + +static inline gboolean +is_keycode (const gchar *string) +{ + return ((string[0] == '0') && + (string[1] == 'x')); +} + +/** + * egg_accelerator_parse_virtual: + * @accelerator: string representing an accelerator + * @accelerator_key: return location for accelerator keyval + * @accelerator_mods: return location for accelerator modifier mask + * + * Parses a string representing a virtual accelerator. The format + * looks like "<Control>a" or "<Shift><Alt>F1" or + * "<Release>z" (the last one is for key release). The parser + * is fairly liberal and allows lower or upper case, and also + * abbreviations such as "<Ctl>" and "<Ctrl>". + * + * If the parse fails, @accelerator_key and @accelerator_mods will + * be set to 0 (zero) and %FALSE will be returned. If the string contains + * only modifiers, @accelerator_key will be set to 0 but %TRUE will be + * returned. + * + * The virtual vs. concrete accelerator distinction is a relic of + * how the X Window System works; there are modifiers Mod2-Mod5 that + * can represent various keyboard keys (numlock, meta, hyper, etc.), + * the virtual modifier represents the keyboard key, the concrete + * modifier the actual Mod2-Mod5 bits in the key press event. + * + * Returns: %TRUE on success. + */ +gboolean +egg_accelerator_parse_virtual (const gchar *accelerator, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods) +{ + guint keyval; + GdkModifierType mods; + gint len; + gboolean bad_keyval; + + if (accelerator_key) + *accelerator_key = 0; + if (accelerator_mods) + *accelerator_mods = 0; + if (keycode) + *keycode = 0; + + g_return_val_if_fail (accelerator != NULL, FALSE); + + bad_keyval = FALSE; + + keyval = 0; + mods = 0; + len = strlen (accelerator); + while (len) + { + if (*accelerator == '<') + { + if (len >= 9 && is_release (accelerator)) + { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_RELEASE_MASK; + } + else if (len >= 9 && is_control (accelerator)) + { + accelerator += 9; + len -= 9; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 7 && is_shift (accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } + else if (len >= 6 && is_shft (accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_SHIFT_MASK; + } + else if (len >= 6 && is_ctrl (accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 6 && is_modx (accelerator)) + { + static const guint mod_vals[] = { + EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK, + EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK + }; + + len -= 6; + accelerator += 4; + mods |= mod_vals[*accelerator - '1']; + accelerator += 2; + } + else if (len >= 5 && is_ctl (accelerator)) + { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_CONTROL_MASK; + } + else if (len >= 5 && is_alt (accelerator)) + { + accelerator += 5; + len -= 5; + mods |= EGG_VIRTUAL_ALT_MASK; + } + else if (len >= 6 && is_meta (accelerator)) + { + accelerator += 6; + len -= 6; + mods |= EGG_VIRTUAL_META_MASK; + } + else if (len >= 7 && is_hyper (accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_HYPER_MASK; + } + else if (len >= 7 && is_super (accelerator)) + { + accelerator += 7; + len -= 7; + mods |= EGG_VIRTUAL_SUPER_MASK; + } + else + { + gchar last_ch; + + last_ch = *accelerator; + while (last_ch && last_ch != '>') + { + last_ch = *accelerator; + accelerator += 1; + len -= 1; + } + } + } + else + { + keyval = gdk_keyval_from_name (accelerator); + + if (keyval == 0) + { + /* If keyval is 0, then maybe it's a keycode. Check for 0x## */ + if (len >= 4 && is_keycode (accelerator)) + { + char keystring[5]; + gchar *endptr; + gint tmp_keycode; + + memcpy (keystring, accelerator, 4); + keystring [4] = '\000'; + + tmp_keycode = strtol (keystring, &endptr, 16); + + if (endptr == NULL || *endptr != '\000') + { + bad_keyval = TRUE; + } + else if (keycode != NULL) + { + *keycode = tmp_keycode; + /* 0x00 is an invalid keycode too. */ + if (*keycode == 0) + bad_keyval = TRUE; + } + } + } + else if (keycode != NULL) + { + *keycode = XKeysymToKeycode (GDK_DISPLAY(), keyval); + if (*keycode == 0) + bad_keyval = TRUE; + } + + accelerator += len; + len -= len; + } + } + + if (accelerator_key) + *accelerator_key = gdk_keyval_to_lower (keyval); + if (accelerator_mods) + *accelerator_mods = mods; + + return !bad_keyval; +} + +/** + * egg_virtual_accelerator_name: + * @accelerator_key: accelerator keyval + * @accelerator_mods: accelerator modifier mask + * @returns: a newly-allocated accelerator name + * + * Converts an accelerator keyval and modifier mask + * into a string parseable by egg_accelerator_parse_virtual(). + * For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK, + * this function returns "<Control>q". + * + * The caller of this function must free the returned string. + */ +gchar* +egg_virtual_accelerator_name (guint accelerator_key, + guint keycode, + EggVirtualModifierType accelerator_mods) +{ + gchar *gtk_name; + GdkModifierType gdkmods = 0; + + egg_keymap_resolve_virtual_modifiers (NULL, accelerator_mods, &gdkmods); + gtk_name = gtk_accelerator_name (accelerator_key, gdkmods); + + if (!accelerator_key) + { + gchar *name; + name = g_strdup_printf ("%s0x%02x", gtk_name, keycode); + g_free (gtk_name); + return name; + } + + return gtk_name; +} + +/** + * egg_virtual_accelerator_label: + * @accelerator_key: accelerator keyval + * @accelerator_mods: accelerator modifier mask + * @returns: a newly-allocated accelerator label + * + * Converts an accelerator keyval and modifier mask + * into a (possibly translated) string that can be displayed to + * a user. + * For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK, + * and you use a German locale, this function returns "Strg+Q". + * + * The caller of this function must free the returned string. + */ +gchar* +egg_virtual_accelerator_label (guint accelerator_key, + guint keycode, + EggVirtualModifierType accelerator_mods) +{ + gchar *gtk_label; + GdkModifierType gdkmods = 0; + + egg_keymap_resolve_virtual_modifiers (NULL, accelerator_mods, &gdkmods); + gtk_label = gtk_accelerator_get_label (accelerator_key, gdkmods); + + if (!accelerator_key) + { + gchar *label; + label = g_strdup_printf ("%s0x%02x", gtk_label, keycode); + g_free (gtk_label); + return label; + } + + return gtk_label; +} + +void +egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, + EggVirtualModifierType virtual_mods, + GdkModifierType *concrete_mods) +{ + GdkModifierType concrete; + int i; + const EggModmap *modmap; + + g_return_if_fail (concrete_mods != NULL); + g_return_if_fail (keymap == NULL || GDK_IS_KEYMAP (keymap)); + + modmap = egg_keymap_get_modmap (keymap); + + /* Not so sure about this algorithm. */ + + concrete = 0; + for (i = 0; i < EGG_MODMAP_ENTRY_LAST; ++i) + { + if (modmap->mapping[i] & virtual_mods) + concrete |= MODMAP_ENTRY_TO_MODIFIER (i); + } + + *concrete_mods = concrete; +} + +void +egg_keymap_virtualize_modifiers (GdkKeymap *keymap, + GdkModifierType concrete_mods, + EggVirtualModifierType *virtual_mods) +{ + GdkModifierType virtual; + int i; + const EggModmap *modmap; + + g_return_if_fail (virtual_mods != NULL); + g_return_if_fail (keymap == NULL || GDK_IS_KEYMAP (keymap)); + + modmap = egg_keymap_get_modmap (keymap); + + /* Not so sure about this algorithm. */ + + virtual = 0; + for (i = 0; i < EGG_MODMAP_ENTRY_LAST; ++i) + { + if (MODMAP_ENTRY_TO_MODIFIER (i) & concrete_mods) + { + EggVirtualModifierType cleaned; + + cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK | + EGG_VIRTUAL_MOD3_MASK | + EGG_VIRTUAL_MOD4_MASK | + EGG_VIRTUAL_MOD5_MASK); + + if (cleaned != 0) + { + virtual |= cleaned; + } + else + { + /* Rather than dropping mod2->mod5 if not bound, + * go ahead and use the concrete names + */ + virtual |= modmap->mapping[i]; + } + } + } + + *virtual_mods = virtual; +} + +static void +reload_modmap (GdkKeymap *keymap, + EggModmap *modmap) +{ + XModifierKeymap *xmodmap; + int map_size; + int i; + + /* FIXME multihead */ + xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ()); + + memset (modmap->mapping, 0, sizeof (modmap->mapping)); + + /* there are 8 modifiers in the order shift, shift lock, + * control, mod1-5 with up to max_keypermod bindings each + */ + map_size = 8 * xmodmap->max_keypermod; + for (i = 3 * xmodmap->max_keypermod; i < map_size; ++i) + { + /* get the key code at this point in the map, + * see if its keysym is one we're interested in + */ + int keycode = xmodmap->modifiermap[i]; + GdkKeymapKey *keys; + guint *keyvals; + int n_entries; + int j; + EggVirtualModifierType mask; + + keys = NULL; + keyvals = NULL; + n_entries = 0; + + gdk_keymap_get_entries_for_keycode (keymap, + keycode, + &keys, &keyvals, &n_entries); + + mask = 0; + for (j = 0; j < n_entries; ++j) + { + if (keyvals[j] == GDK_Num_Lock) + mask |= EGG_VIRTUAL_NUM_LOCK_MASK; + else if (keyvals[j] == GDK_Scroll_Lock) + mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK; + else if (keyvals[j] == GDK_Meta_L || + keyvals[j] == GDK_Meta_R) + mask |= EGG_VIRTUAL_META_MASK; + else if (keyvals[j] == GDK_Hyper_L || + keyvals[j] == GDK_Hyper_R) + mask |= EGG_VIRTUAL_HYPER_MASK; + else if (keyvals[j] == GDK_Super_L || + keyvals[j] == GDK_Super_R) + mask |= EGG_VIRTUAL_SUPER_MASK; + else if (keyvals[j] == GDK_Mode_switch) + mask |= EGG_VIRTUAL_MODE_SWITCH_MASK; + } + + /* Mod1Mask is 1 << 3 for example, i.e. the + * fourth modifier, i / keyspermod is the modifier + * index + */ + modmap->mapping[i/xmodmap->max_keypermod] |= mask; + + g_free (keyvals); + g_free (keys); + } + + /* Add in the not-really-virtual fixed entries */ + modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_LOCK] |= EGG_VIRTUAL_LOCK_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_MOD1] |= EGG_VIRTUAL_ALT_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_MOD2] |= EGG_VIRTUAL_MOD2_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK; + modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK; + + XFreeModifiermap (xmodmap); +} + +const EggModmap* +egg_keymap_get_modmap (GdkKeymap *keymap) +{ + EggModmap *modmap; + + if (keymap == NULL) + keymap = gdk_keymap_get_default (); + + /* This is all a hack, much simpler when we can just + * modify GDK directly. + */ + + modmap = g_object_get_data (G_OBJECT (keymap), "egg-modmap"); + + if (modmap == NULL) + { + modmap = g_new0 (EggModmap, 1); + + /* FIXME modify keymap change events with an event filter + * and force a reload if we get one + */ + + reload_modmap (keymap, modmap); + + g_object_set_data_full (G_OBJECT (keymap), + "egg-modmap", + modmap, + g_free); + } + + g_assert (modmap != NULL); + + return modmap; +} diff --git a/capplets/keyboard/eggaccelerators.h b/capplets/keyboard/eggaccelerators.h new file mode 100644 index 000000000..9e22fc8da --- /dev/null +++ b/capplets/keyboard/eggaccelerators.h @@ -0,0 +1,95 @@ +/* eggaccelerators.h + * Copyright (C) 2002 Red Hat, Inc. + * Developed by Havoc Pennington + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_ACCELERATORS_H__ +#define __EGG_ACCELERATORS_H__ + +#include <gtk/gtk.h> +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +/* Where a value is also in GdkModifierType we coincide, + * otherwise we don't overlap. + */ +typedef enum +{ + EGG_VIRTUAL_SHIFT_MASK = 1 << 0, + EGG_VIRTUAL_LOCK_MASK = 1 << 1, + EGG_VIRTUAL_CONTROL_MASK = 1 << 2, + + EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */ + + EGG_VIRTUAL_MOD2_MASK = 1 << 4, + EGG_VIRTUAL_MOD3_MASK = 1 << 5, + EGG_VIRTUAL_MOD4_MASK = 1 << 6, + EGG_VIRTUAL_MOD5_MASK = 1 << 7, + +#if 0 + GDK_BUTTON1_MASK = 1 << 8, + GDK_BUTTON2_MASK = 1 << 9, + GDK_BUTTON3_MASK = 1 << 10, + GDK_BUTTON4_MASK = 1 << 11, + GDK_BUTTON5_MASK = 1 << 12, + /* 13, 14 are used by Xkb for the keyboard group */ +#endif + + EGG_VIRTUAL_MODE_SWITCH_MASK = 1 << 23, + EGG_VIRTUAL_NUM_LOCK_MASK = 1 << 24, + EGG_VIRTUAL_SCROLL_LOCK_MASK = 1 << 25, + + /* Also in GdkModifierType */ + EGG_VIRTUAL_SUPER_MASK = 1 << 26, + EGG_VIRTUAL_HYPER_MASK = 1 << 27, + EGG_VIRTUAL_META_MASK = 1 << 28, + + /* Also in GdkModifierType */ + EGG_VIRTUAL_RELEASE_MASK = 1 << 30, + + /* 28-31 24-27 20-23 16-19 12-15 8-11 4-7 0-3 + * 5 f 8 0 0 0 f f + */ + EGG_VIRTUAL_MODIFIER_MASK = 0x5f8000ff + +} EggVirtualModifierType; + +gboolean egg_accelerator_parse_virtual (const gchar *accelerator, + guint *accelerator_key, + guint *keycode, + EggVirtualModifierType *accelerator_mods); +void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap, + EggVirtualModifierType virtual_mods, + GdkModifierType *concrete_mods); +void egg_keymap_virtualize_modifiers (GdkKeymap *keymap, + GdkModifierType concrete_mods, + EggVirtualModifierType *virtual_mods); + +gchar* egg_virtual_accelerator_name (guint accelerator_key, + guint keycode, + EggVirtualModifierType accelerator_mods); + +gchar* egg_virtual_accelerator_label (guint accelerator_key, + guint keycode, + EggVirtualModifierType accelerator_mods); + +G_END_DECLS + + +#endif /* __EGG_ACCELERATORS_H__ */ diff --git a/capplets/keyboard/eggcellrendererkeys.c b/capplets/keyboard/eggcellrendererkeys.c new file mode 100644 index 000000000..279789d7f --- /dev/null +++ b/capplets/keyboard/eggcellrendererkeys.c @@ -0,0 +1,693 @@ +#include <config.h> +#include <libintl.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <gdk/gdkkeysyms.h> +#include "eggcellrendererkeys.h" +#include "eggaccelerators.h" + +#ifndef EGG_COMPILATION +#ifndef _ +#define _(x) dgettext (GETTEXT_PACKAGE, x) +#define N_(x) x +#endif +#else +#define _(x) x +#define N_(x) x +#endif + +#define EGG_CELL_RENDERER_TEXT_PATH "egg-cell-renderer-text" + +#define TOOLTIP_TEXT _("New shortcut...") + +static void egg_cell_renderer_keys_finalize (GObject *object); +static void egg_cell_renderer_keys_init (EggCellRendererKeys *cell_keys); +static void egg_cell_renderer_keys_class_init (EggCellRendererKeysClass *cell_keys_class); +static GtkCellEditable *egg_cell_renderer_keys_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + + +static void egg_cell_renderer_keys_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void egg_cell_renderer_keys_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void egg_cell_renderer_keys_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); + + +enum { + PROP_0, + + PROP_ACCEL_KEY, + PROP_ACCEL_MASK, + PROP_KEYCODE, + PROP_ACCEL_MODE +}; + +static GtkCellRendererTextClass *parent_class = NULL; + +GType +egg_cell_renderer_keys_get_type (void) +{ + static GType cell_keys_type = 0; + + if (!cell_keys_type) + { + static const GTypeInfo cell_keys_info = + { + sizeof (EggCellRendererKeysClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc)egg_cell_renderer_keys_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (EggCellRendererKeys), + 0, /* n_preallocs */ + (GInstanceInitFunc) egg_cell_renderer_keys_init + }; + + cell_keys_type = g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT, "EggCellRendererKeys", &cell_keys_info, 0); + } + + return cell_keys_type; +} + +static void +egg_cell_renderer_keys_init (EggCellRendererKeys *cell_keys) +{ + cell_keys->accel_mode = EGG_CELL_RENDERER_KEYS_MODE_GTK; +} + +/* FIXME setup stuff to generate this */ +/* VOID:STRING,UINT,FLAGS,UINT */ +static void +marshal_VOID__STRING_UINT_FLAGS_UINT (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_UINT_FLAGS_UINT) (gpointer data1, + const char *arg_1, + guint arg_2, + int arg_3, + guint arg_4, + gpointer data2); + register GMarshalFunc_VOID__STRING_UINT_FLAGS_UINT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 5); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + + callback = (GMarshalFunc_VOID__STRING_UINT_FLAGS_UINT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + g_value_get_string (param_values + 1), + g_value_get_uint (param_values + 2), + g_value_get_flags (param_values + 3), + g_value_get_uint (param_values + 4), + data2); +} + +static void +egg_cell_renderer_keys_class_init (EggCellRendererKeysClass *cell_keys_class) +{ + GObjectClass *object_class; + GtkCellRendererClass *cell_renderer_class; + + object_class = G_OBJECT_CLASS (cell_keys_class); + cell_renderer_class = GTK_CELL_RENDERER_CLASS (cell_keys_class); + parent_class = g_type_class_peek_parent (object_class); + + GTK_CELL_RENDERER_CLASS (cell_keys_class)->start_editing = egg_cell_renderer_keys_start_editing; + + object_class->set_property = egg_cell_renderer_keys_set_property; + object_class->get_property = egg_cell_renderer_keys_get_property; + cell_renderer_class->get_size = egg_cell_renderer_keys_get_size; + + object_class->finalize = egg_cell_renderer_keys_finalize; + + /* FIXME if this gets moved to a real library, rename the properties + * to match whatever the GTK convention is + */ + + g_object_class_install_property (object_class, + PROP_ACCEL_KEY, + g_param_spec_uint ("accel_key", + _("Accelerator key"), + _("Accelerator key"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_ACCEL_MASK, + g_param_spec_flags ("accel_mask", + _("Accelerator modifiers"), + _("Accelerator modifiers"), + GDK_TYPE_MODIFIER_TYPE, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property (object_class, + PROP_KEYCODE, + g_param_spec_uint ("keycode", + _("Accelerator keycode"), + _("Accelerator keycode"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + /* FIXME: Register the enum when moving to GTK+ */ + g_object_class_install_property (object_class, + PROP_ACCEL_MODE, + g_param_spec_int ("accel_mode", + _("Accel Mode"), + _("The type of accelerator."), + 0, + 2, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_signal_new ("accel_edited", + EGG_TYPE_CELL_RENDERER_KEYS, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggCellRendererKeysClass, accel_edited), + NULL, NULL, + marshal_VOID__STRING_UINT_FLAGS_UINT, + G_TYPE_NONE, 4, + G_TYPE_STRING, + G_TYPE_UINT, + GDK_TYPE_MODIFIER_TYPE, + G_TYPE_UINT); + + g_signal_new ("accel_cleared", + EGG_TYPE_CELL_RENDERER_KEYS, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EggCellRendererKeysClass, accel_cleared), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); +} + + +GtkCellRenderer * +egg_cell_renderer_keys_new (void) +{ + return GTK_CELL_RENDERER (g_object_new (EGG_TYPE_CELL_RENDERER_KEYS, NULL)); +} + +static void +egg_cell_renderer_keys_finalize (GObject *object) +{ + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +static gchar * +convert_keysym_state_to_string (guint keysym, + guint keycode, + EggVirtualModifierType mask) +{ + if (keysym == 0 && keycode == 0) + return g_strdup (_("Disabled")); + else + return egg_virtual_accelerator_label (keysym, keycode, mask); +} + +static void +egg_cell_renderer_keys_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + EggCellRendererKeys *keys; + + g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (object)); + + keys = EGG_CELL_RENDERER_KEYS (object); + + switch (param_id) + { + case PROP_ACCEL_KEY: + g_value_set_uint (value, keys->accel_key); + break; + + case PROP_ACCEL_MASK: + g_value_set_flags (value, keys->accel_mask); + break; + + case PROP_ACCEL_MODE: + g_value_set_int (value, keys->accel_mode); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +egg_cell_renderer_keys_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + EggCellRendererKeys *keys; + + g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (object)); + + keys = EGG_CELL_RENDERER_KEYS (object); + + switch (param_id) + { + case PROP_ACCEL_KEY: + egg_cell_renderer_keys_set_accelerator (keys, + g_value_get_uint (value), + keys->keycode, + keys->accel_mask); + break; + + case PROP_ACCEL_MASK: + egg_cell_renderer_keys_set_accelerator (keys, + keys->accel_key, + keys->keycode, + g_value_get_flags (value)); + break; + case PROP_KEYCODE: + egg_cell_renderer_keys_set_accelerator (keys, + keys->accel_key, + g_value_get_uint (value), + keys->accel_mask); + break; + + case PROP_ACCEL_MODE: + egg_cell_renderer_keys_set_accel_mode (keys, g_value_get_int (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static gboolean +is_modifier (guint keycode) +{ + gint i; + gint map_size; + XModifierKeymap *mod_keymap; + gboolean retval = FALSE; + + mod_keymap = XGetModifierMapping (gdk_display); + + map_size = 8 * mod_keymap->max_keypermod; + i = 0; + while (i < map_size) + { + if (keycode == mod_keymap->modifiermap[i]) + { + retval = TRUE; + break; + } + ++i; + } + + XFreeModifiermap (mod_keymap); + + return retval; +} + +static void +egg_cell_renderer_keys_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) + +{ + EggCellRendererKeys *keys = (EggCellRendererKeys *) cell; + GtkRequisition requisition; + + if (keys->sizing_label == NULL) + keys->sizing_label = gtk_label_new (TOOLTIP_TEXT); + + gtk_widget_size_request (keys->sizing_label, &requisition); + (* GTK_CELL_RENDERER_CLASS (parent_class)->get_size) (cell, widget, cell_area, x_offset, y_offset, width, height); + /* FIXME: need to take the cell_area et al. into account */ + if (width) + *width = MAX (*width, requisition.width); + if (height) + *height = MAX (*height, requisition.height); +} + +/* FIXME: Currently we don't differentiate between a 'bogus' key (like tab in + * GTK mode) and a removed key. + */ + +static gboolean +grab_key_callback (GtkWidget *widget, + GdkEventKey *event, + void *data) +{ + GdkModifierType accel_mods = 0; + guint accel_keyval; + EggCellRendererKeys *keys; + char *path; + gboolean edited; + gboolean cleared; + GdkModifierType consumed_modifiers; + guint upper; + GdkModifierType ignored_modifiers; + + keys = EGG_CELL_RENDERER_KEYS (data); + + if (is_modifier (event->hardware_keycode)) + return TRUE; + + edited = FALSE; + cleared = FALSE; + + consumed_modifiers = 0; + gdk_keymap_translate_keyboard_state (gdk_keymap_get_default (), + event->hardware_keycode, + event->state, + event->group, + NULL, NULL, NULL, &consumed_modifiers); + + upper = event->keyval; + accel_keyval = gdk_keyval_to_lower (upper); + if (accel_keyval == GDK_ISO_Left_Tab) + accel_keyval = GDK_Tab; + + + + /* Put shift back if it changed the case of the key, not otherwise. + */ + if (upper != accel_keyval && + (consumed_modifiers & GDK_SHIFT_MASK)) + { + consumed_modifiers &= ~(GDK_SHIFT_MASK); + } + + egg_keymap_resolve_virtual_modifiers (gdk_keymap_get_default (), + EGG_VIRTUAL_NUM_LOCK_MASK | + EGG_VIRTUAL_SCROLL_LOCK_MASK | + EGG_VIRTUAL_LOCK_MASK, + &ignored_modifiers); + + /* http://bugzilla.gnome.org/show_bug.cgi?id=139605 + * mouse keys should effect keybindings */ + ignored_modifiers |= GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK | + GDK_BUTTON3_MASK | + GDK_BUTTON4_MASK | + GDK_BUTTON5_MASK; + + /* filter consumed/ignored modifiers */ + + if (keys->accel_mode == EGG_CELL_RENDERER_KEYS_MODE_GTK) + accel_mods = event->state & GDK_MODIFIER_MASK & ~(consumed_modifiers | ignored_modifiers); + else if (keys->accel_mode == EGG_CELL_RENDERER_KEYS_MODE_X) + accel_mods = event->state & GDK_MODIFIER_MASK & ~(ignored_modifiers); + else + g_assert_not_reached (); + + if (accel_mods == 0 && accel_keyval == GDK_Escape) + goto out; /* cancel */ + + /* clear the accelerator on Backspace */ + if (accel_mods == 0 && accel_keyval == GDK_BackSpace) + { + cleared = TRUE; + goto out; + } + + if (keys->accel_mode == EGG_CELL_RENDERER_KEYS_MODE_GTK) + { + if (!gtk_accelerator_valid (accel_keyval, accel_mods)) + { + accel_keyval = 0; + accel_mods = 0; + } + } + + edited = TRUE; + out: + gdk_keyboard_ungrab (event->time); + gdk_pointer_ungrab (event->time); + + path = g_strdup (g_object_get_data (G_OBJECT (keys->edit_widget), EGG_CELL_RENDERER_TEXT_PATH)); + + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (keys->edit_widget)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (keys->edit_widget)); + keys->edit_widget = NULL; + keys->grab_widget = NULL; + + if (edited) + { + g_signal_emit_by_name (G_OBJECT (keys), "accel_edited", path, + accel_keyval, accel_mods, event->hardware_keycode); + } + else if (cleared) + { + g_signal_emit_by_name (G_OBJECT (keys), "accel_cleared", path); + } + + g_free (path); + return TRUE; +} + +static void +ungrab_stuff (GtkWidget *widget, gpointer data) +{ + EggCellRendererKeys *keys = EGG_CELL_RENDERER_KEYS (data); + + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_pointer_ungrab (GDK_CURRENT_TIME); + + g_signal_handlers_disconnect_by_func (G_OBJECT (keys->grab_widget), + G_CALLBACK (grab_key_callback), data); +} + +static void +pointless_eventbox_start_editing (GtkCellEditable *cell_editable, + GdkEvent *event) +{ + /* do nothing, because we are pointless */ +} + +static void +pointless_eventbox_cell_editable_init (GtkCellEditableIface *iface) +{ + iface->start_editing = pointless_eventbox_start_editing; +} + +static GType +pointless_eventbox_subclass_get_type (void) +{ + static GType eventbox_type = 0; + + if (!eventbox_type) + { + static const GTypeInfo eventbox_info = + { + sizeof (GtkEventBoxClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + NULL, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkEventBox), + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL, + }; + + static const GInterfaceInfo cell_editable_info = { + (GInterfaceInitFunc) pointless_eventbox_cell_editable_init, + NULL, NULL }; + + eventbox_type = g_type_register_static (GTK_TYPE_EVENT_BOX, "EggCellEditableEventBox", &eventbox_info, 0); + + g_type_add_interface_static (eventbox_type, + GTK_TYPE_CELL_EDITABLE, + &cell_editable_info); + } + + return eventbox_type; +} + +static GtkCellEditable * +egg_cell_renderer_keys_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererText *celltext; + EggCellRendererKeys *keys; + GtkWidget *label; + GtkWidget *eventbox; + + celltext = GTK_CELL_RENDERER_TEXT (cell); + keys = EGG_CELL_RENDERER_KEYS (cell); + + /* If the cell isn't editable we return NULL. */ + if (celltext->editable == FALSE) + return NULL; + + g_return_val_if_fail (widget->window != NULL, NULL); + + if (gdk_keyboard_grab (widget->window, FALSE, + gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) + return NULL; + + if (gdk_pointer_grab (widget->window, FALSE, + GDK_BUTTON_PRESS_MASK, + NULL, NULL, + gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) + { + gdk_keyboard_ungrab (gdk_event_get_time (event)); + return NULL; + } + + keys->grab_widget = widget; + + g_signal_connect (G_OBJECT (widget), "key_press_event", + G_CALLBACK (grab_key_callback), + keys); + + eventbox = g_object_new (pointless_eventbox_subclass_get_type (), + NULL); + keys->edit_widget = eventbox; + g_object_add_weak_pointer (G_OBJECT (keys->edit_widget), + (void**) &keys->edit_widget); + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, + &widget->style->bg[GTK_STATE_SELECTED]); + + gtk_widget_modify_fg (label, GTK_STATE_NORMAL, + &widget->style->fg[GTK_STATE_SELECTED]); + + gtk_label_set_text (GTK_LABEL (label), + TOOLTIP_TEXT); + + gtk_container_add (GTK_CONTAINER (eventbox), label); + + g_object_set_data_full (G_OBJECT (keys->edit_widget), EGG_CELL_RENDERER_TEXT_PATH, + g_strdup (path), g_free); + + gtk_widget_show_all (keys->edit_widget); + + g_signal_connect (G_OBJECT (keys->edit_widget), "unrealize", + G_CALLBACK (ungrab_stuff), keys); + + keys->edit_key = keys->accel_key; + + return GTK_CELL_EDITABLE (keys->edit_widget); +} + +void +egg_cell_renderer_keys_set_accelerator (EggCellRendererKeys *keys, + guint keyval, + guint keycode, + EggVirtualModifierType mask) +{ + char *text; + gboolean changed; + + g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (keys)); + + g_object_freeze_notify (G_OBJECT (keys)); + + changed = FALSE; + + if (keyval != keys->accel_key) + { + keys->accel_key = keyval; + g_object_notify (G_OBJECT (keys), "accel_key"); + changed = TRUE; + } + + if (mask != keys->accel_mask) + { + keys->accel_mask = mask; + + g_object_notify (G_OBJECT (keys), "accel_mask"); + changed = TRUE; + } + + if (keycode != keys->keycode) + { + keys->keycode = keycode; + + g_object_notify (G_OBJECT (keys), "keycode"); + changed = TRUE; + } + g_object_thaw_notify (G_OBJECT (keys)); + + if (changed) + { + /* sync string to the key values */ + text = convert_keysym_state_to_string (keys->accel_key, keys->keycode, keys->accel_mask); + g_object_set (keys, "text", text, NULL); + g_free (text); + } +} + +void +egg_cell_renderer_keys_get_accelerator (EggCellRendererKeys *keys, + guint *keyval, + EggVirtualModifierType *mask) +{ + g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (keys)); + + if (keyval) + *keyval = keys->accel_key; + + if (mask) + *mask = keys->accel_mask; +} + +void +egg_cell_renderer_keys_set_accel_mode (EggCellRendererKeys *keys, + EggCellRendererKeysMode accel_mode) +{ + g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (keys)); + keys->accel_mode = accel_mode; + g_object_notify (G_OBJECT (keys), "accel_mode"); +} diff --git a/capplets/keyboard/eggcellrendererkeys.h b/capplets/keyboard/eggcellrendererkeys.h new file mode 100644 index 000000000..24e58aea2 --- /dev/null +++ b/capplets/keyboard/eggcellrendererkeys.h @@ -0,0 +1,89 @@ +/* gtkcellrendererkeybinding.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <jrb@redhat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __EGG_CELL_RENDERER_KEYS_H__ +#define __EGG_CELL_RENDERER_KEYS_H__ + +#include <gtk/gtk.h> +#include "eggaccelerators.h" + +G_BEGIN_DECLS + +#define EGG_TYPE_CELL_RENDERER_KEYS (egg_cell_renderer_keys_get_type ()) +#define EGG_CELL_RENDERER_KEYS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_CELL_RENDERER_KEYS, EggCellRendererKeys)) +#define EGG_CELL_RENDERER_KEYS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_CELL_RENDERER_KEYS, EggCellRendererKeysClass)) +#define EGG_IS_CELL_RENDERER_KEYS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_CELL_RENDERER_KEYS)) +#define EGG_IS_CELL_RENDERER_KEYS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_CELL_RENDERER_KEYS)) +#define EGG_CELL_RENDERER_KEYS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_CELL_RENDERER_KEYS, EggCellRendererKeysClass)) + +typedef struct _EggCellRendererKeys EggCellRendererKeys; +typedef struct _EggCellRendererKeysClass EggCellRendererKeysClass; + + +typedef enum +{ + EGG_CELL_RENDERER_KEYS_MODE_GTK, + EGG_CELL_RENDERER_KEYS_MODE_X +} EggCellRendererKeysMode; + +struct _EggCellRendererKeys +{ + GtkCellRendererText parent; + guint accel_key; + guint keycode; + EggVirtualModifierType accel_mask; + GtkWidget *edit_widget; + GtkWidget *grab_widget; + guint edit_key; + GtkWidget *sizing_label; + EggCellRendererKeysMode accel_mode; +}; + +struct _EggCellRendererKeysClass +{ + GtkCellRendererTextClass parent_class; + + void (* accel_edited) (EggCellRendererKeys *keys, + const char *path_string, + guint keyval, + EggVirtualModifierType mask, + guint hardware_keycode); + + void (* accel_cleared) (EggCellRendererKeys *keys, + const char *path_string); +}; + +GType egg_cell_renderer_keys_get_type (void); +GtkCellRenderer *egg_cell_renderer_keys_new (void); + +void egg_cell_renderer_keys_set_accelerator (EggCellRendererKeys *keys, + guint keyval, + guint keycode, + EggVirtualModifierType mask); +void egg_cell_renderer_keys_get_accelerator (EggCellRendererKeys *keys, + guint *keyval, + EggVirtualModifierType *mask); +void egg_cell_renderer_keys_set_accel_mode (EggCellRendererKeys *keys, + EggCellRendererKeysMode accel_mode); + + +G_END_DECLS + + +#endif /* __GTK_CELL_RENDERER_KEYS_H__ */ diff --git a/capplets/keyboard/gnome-keybindings.pc.in b/capplets/keyboard/gnome-keybindings.pc.in new file mode 100644 index 000000000..e099b4c72 --- /dev/null +++ b/capplets/keyboard/gnome-keybindings.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +datarootdir=@datarootdir@ +datadir=@datadir@ +pkgdatadir=${datadir}/@PACKAGE@ +keysdir=${pkgdatadir}/keybindings + +Name: gnome-keybindings +Description: Keybindings configuration for GNOME applications +Version: @VERSION@ + diff --git a/capplets/keyboard/gnome-keyboard-properties-dialog.ui b/capplets/keyboard/gnome-keyboard-properties-dialog.ui index 5de255124..7e054dfd6 100644 --- a/capplets/keyboard/gnome-keyboard-properties-dialog.ui +++ b/capplets/keyboard/gnome-keyboard-properties-dialog.ui @@ -1880,4 +1880,245 @@ <action-widget response="-1">button4</action-widget> </action-widgets> </object> + <object class="GtkWindow" id="window1"> + <child> + <object class="GtkVBox" id="shortcuts_vbox"> + <property name="visible">True</property> + <property name="border_width">10</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkScrolledWindow" id="actions_swindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">never</property> + <property name="vscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkTreeView" id="shortcut_treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="rules_hint">True</property> + </object> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHButtonBox" id="shortcuts_hbuttonbox"> + <property name="visible">True</property> + <property name="spacing">6</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="add-button"> + <property name="label">gtk-add</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="remove-button"> + <property name="label">gtk-remove</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="border_width">6</property> + <property name="spacing">12</property> + <child> + <object class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="yalign">0</property> + <property name="stock">gtk-dialog-info</property> + <property name="icon-size">6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">To edit a shortcut key, click on the corresponding row and type a new key combination, or press backspace to clear.</property> + <property name="justify">fill</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkDialog" id="custom-shortcut-dialog"> + <property name="title" translatable="yes">Custom Shortcut</property> + <property name="type_hint">dialog</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox3"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkVBox" id="vbox44"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Name:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">custom-shortcut-name-entry</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">C_ommand:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">custom-shortcut-command-entry</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom-shortcut-name-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">•</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <object class="GtkEntry" id="custom-shortcut-command-entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">•</property> + <property name="activates_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area3"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="cancelbutton1"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="okbutton1"> + <property name="label">gtk-apply</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">cancelbutton1</action-widget> + <action-widget response="-5">okbutton1</action-widget> + </action-widgets> + </object> </interface> |