diff options
Diffstat (limited to 'capplets/keybindings')
-rw-r--r-- | capplets/keybindings/00-multimedia-key.xml.in | 35 | ||||
-rw-r--r-- | capplets/keybindings/01-desktop-key.xml.in | 29 | ||||
-rw-r--r-- | capplets/keybindings/Makefile.am | 46 | ||||
-rw-r--r-- | capplets/keybindings/eggaccelerators.c | 632 | ||||
-rw-r--r-- | capplets/keybindings/eggaccelerators.h | 99 | ||||
-rw-r--r-- | capplets/keybindings/eggcellrendererkeys.c | 695 | ||||
-rw-r--r-- | capplets/keybindings/eggcellrendererkeys.h | 93 | ||||
-rw-r--r-- | capplets/keybindings/mate-keybinding-properties.c | 1948 | ||||
-rw-r--r-- | capplets/keybindings/mate-keybinding-properties.ui | 301 | ||||
-rw-r--r-- | capplets/keybindings/mate-keybinding.desktop.in.in | 14 | ||||
-rw-r--r-- | capplets/keybindings/mate-keybindings.pc.in | 10 |
11 files changed, 3902 insertions, 0 deletions
diff --git a/capplets/keybindings/00-multimedia-key.xml.in b/capplets/keybindings/00-multimedia-key.xml.in new file mode 100644 index 00000000..cd8263a1 --- /dev/null +++ b/capplets/keybindings/00-multimedia-key.xml.in @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<KeyListEntries _name="Sound"> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/volume_mute" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/volume_down" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/volume_up" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/media" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/play" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/pause" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/stop" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/previous" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/next" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/eject" /> + +</KeyListEntries> + diff --git a/capplets/keybindings/01-desktop-key.xml.in b/capplets/keybindings/01-desktop-key.xml.in new file mode 100644 index 00000000..dc0234bb --- /dev/null +++ b/capplets/keybindings/01-desktop-key.xml.in @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<KeyListEntries _name="Desktop"> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/help" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/calculator" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/email" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/www" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/power" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/screensaver" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/home" /> + + <KeyListEntry + name="/apps/mate_settings_daemon/keybindings/search" /> + +</KeyListEntries> + diff --git a/capplets/keybindings/Makefile.am b/capplets/keybindings/Makefile.am new file mode 100644 index 00000000..ed97253f --- /dev/null +++ b/capplets/keybindings/Makefile.am @@ -0,0 +1,46 @@ +# This is used in MATECC_CAPPLETS_CFLAGS +cappletname = keybinding + +bin_PROGRAMS = mate-keybinding-properties + +mate_keybinding_properties_LDADD = $(MATECC_CAPPLETS_LIBS) +mate_keybinding_properties_SOURCES = \ + mate-keybinding-properties.c \ + eggcellrendererkeys.c \ + eggcellrendererkeys.h \ + eggaccelerators.c \ + eggaccelerators.h + +@INTLTOOL_DESKTOP_RULE@ + +uidir = $(pkgdatadir)/ui +ui_DATA = mate-keybinding-properties.ui + +desktopdir = $(datadir)/applications +Desktop_in_files = mate-keybinding.desktop.in +desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop) + +@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 = mate-keybindings.pc + +INCLUDES = \ + $(MATECC_CAPPLETS_CFLAGS) \ + -DMATELOCALEDIR="\"$(datadir)/locale\"" \ + -DMATECC_DATA_DIR="\"$(pkgdatadir)\"" \ + -DMATECC_UI_DIR="\"$(uidir)\"" \ + -DMATECC_KEYBINDINGS_DIR="\"$(pkgdatadir)/keybindings\"" +CLEANFILES = \ + $(MATECC_CAPPLETS_CLEANFILES) \ + $(Desktop_in_files) \ + $(desktop_DATA) \ + $(xml_DATA) +EXTRA_DIST = $(ui_DATA) $(xml_in_files) mate-keybindings.pc.in + + +-include $(top_srcdir)/git.mk diff --git a/capplets/keybindings/eggaccelerators.c b/capplets/keybindings/eggaccelerators.c new file mode 100644 index 00000000..0728229d --- /dev/null +++ b/capplets/keybindings/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/keybindings/eggaccelerators.h b/capplets/keybindings/eggaccelerators.h new file mode 100644 index 00000000..660f5670 --- /dev/null +++ b/capplets/keybindings/eggaccelerators.h @@ -0,0 +1,99 @@ +/* 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> + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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); + +#ifdef __cplusplus +} +#endif + + +#endif /* __EGG_ACCELERATORS_H__ */ diff --git a/capplets/keybindings/eggcellrendererkeys.c b/capplets/keybindings/eggcellrendererkeys.c new file mode 100644 index 00000000..776a5391 --- /dev/null +++ b/capplets/keybindings/eggcellrendererkeys.c @@ -0,0 +1,695 @@ +#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; + GValue celltext_editable = {0}; + + celltext = GTK_CELL_RENDERER_TEXT (cell); + keys = EGG_CELL_RENDERER_KEYS (cell); + + /* If the cell isn't editable we return NULL. */ + g_value_init (&celltext_editable, G_TYPE_BOOLEAN); + g_object_get_property (G_OBJECT (celltext), "editable", &celltext_editable); + if (g_value_get_boolean (&celltext_editable) == FALSE) + return NULL; + g_return_val_if_fail (gtk_widget_get_window (widget) != NULL, NULL); + + if (gdk_keyboard_grab (gtk_widget_get_window (widget), FALSE, + gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) + return NULL; + + if (gdk_pointer_grab (gtk_widget_get_window (widget), 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, + >k_widget_get_style (widget)->bg[GTK_STATE_SELECTED]); + + gtk_widget_modify_fg (label, GTK_STATE_NORMAL, + >k_widget_get_style (widget)->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/keybindings/eggcellrendererkeys.h b/capplets/keybindings/eggcellrendererkeys.h new file mode 100644 index 00000000..2fe515e1 --- /dev/null +++ b/capplets/keybindings/eggcellrendererkeys.h @@ -0,0 +1,93 @@ +/* gtkcellrendererkeybinding.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford <[email protected]> + * + * 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" + +#ifdef __cplusplus +extern "C" { +#endif + +#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); + + +#ifdef __cplusplus +} +#endif + + +#endif /* __GTK_CELL_RENDERER_KEYS_H__ */ diff --git a/capplets/keybindings/mate-keybinding-properties.c b/capplets/keybindings/mate-keybinding-properties.c new file mode 100644 index 00000000..fb6c8785 --- /dev/null +++ b/capplets/keybindings/mate-keybinding-properties.c @@ -0,0 +1,1948 @@ +/* This program was written with lots of love under the GPL by Jonathan + * Blandford <[email protected]> + */ + +#include <config.h> + +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include <mateconf/mateconf-client.h> +#include <gdk/gdkx.h> +#include <X11/Xatom.h> +#include <glib/gi18n.h> +#include <gdk/gdkkeysyms.h> + +#include "wm-common.h" +#include "capplet-util.h" +#include "eggcellrendererkeys.h" +#include "activate-settings-daemon.h" + +#define MATECONF_BINDING_DIR "/desktop/mate/keybindings" +#define MAX_ELEMENTS_BEFORE_SCROLLING 10 +#define MAX_CUSTOM_SHORTCUTS 1000 +#define RESPONSE_ADD 0 +#define RESPONSE_REMOVE 1 + +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 *mateconf_key; + guint keyval; + guint keycode; + EggVirtualModifierType mask; + gboolean editable; + GtkTreeModel *model; + char *description; + char *desc_mateconf_key; + gboolean desc_editable; + char *command; + char *cmd_mateconf_key; + gboolean cmd_editable; + guint mateconf_cnxn; + guint mateconf_cnxn_desc; + guint mateconf_cnxn_cmd; +} KeyEntry; + +static gboolean block_accels = FALSE; +static GtkWidget *custom_shortcut_dialog = NULL; +static GtkWidget *custom_shortcut_name_entry = NULL; +static GtkWidget *custom_shortcut_command_entry = NULL; + +static GtkWidget* +_gtk_builder_get_widget (GtkBuilder *builder, const gchar *name) +{ + return GTK_WIDGET (gtk_builder_get_object (builder, name)); +} + +static GtkBuilder * +create_builder (void) +{ + GtkBuilder *builder = gtk_builder_new(); + GError *error = NULL; + static const gchar *uifile = MATECC_UI_DIR "/mate-keybinding-properties.ui"; + + if (gtk_builder_add_from_file (builder, uifile, &error) == 0) { + g_warning ("Could not load UI: %s", error->message); + g_error_free (error); + g_object_unref (builder); + builder = NULL; + } + + return builder; +} + +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 (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? mateconf_value_get_string (entry->value) : NULL; + + binding_from_string (key_value, &key_entry->keyval, &key_entry->keycode, &key_entry->mask); + key_entry->editable = mateconf_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 (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? mateconf_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 = mateconf_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 (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + KeyEntry *key_entry; + const gchar *key_value; + + key_entry = (KeyEntry *) user_data; + key_value = entry->value ? mateconf_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 = mateconf_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 (GtkBuilder *builder) +{ + GtkWidget *tree_view; + GtkWidget *actions_swindow; + GtkTreeModel *model; + + tree_view = _gtk_builder_get_widget (builder, "shortcut_treeview"); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (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 (tree_view), model); + + g_object_unref (model); + } + else + { + /* clear the existing model */ + MateConfClient *client; + gboolean valid; + GtkTreeIter iter; + KeyEntry *key_entry; + + client = mateconf_client_get_default (); + /* we need the schema name below; + * cached values do not have that set, though */ + mateconf_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) + { + mateconf_client_remove_dir (client, key_entry->mateconf_key, NULL); + mateconf_client_notify_remove (client, key_entry->mateconf_cnxn); + if (key_entry->mateconf_cnxn_desc != 0) + mateconf_client_notify_remove (client, key_entry->mateconf_cnxn_desc); + if (key_entry->mateconf_cnxn_cmd != 0) + mateconf_client_notify_remove (client, key_entry->mateconf_cnxn_cmd); + g_free (key_entry->mateconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_mateconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_mateconf_key); + g_free (key_entry); + } + } + + gtk_tree_store_clear (GTK_TREE_STORE (model)); + g_object_unref (client); + } + + actions_swindow = _gtk_builder_get_widget (builder, "actions_swindow"); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (actions_swindow), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_widget_set_size_request (actions_swindow, -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->mateconf_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; + MateConfClient *client; + + if (entry->comparison == COMPARISON_NONE) + return TRUE; + + g_return_val_if_fail (entry->key != NULL, FALSE); + + client = mateconf_client_get_default(); + value = mateconf_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 (GtkBuilder *builder, int i) +{ + if (i == MAX_ELEMENTS_BEFORE_SCROLLING) + { + GtkRequisition rectangle; + GObject *actions_swindow = gtk_builder_get_object (builder, + "actions_swindow"); + GtkWidget *treeview = _gtk_builder_get_widget (builder, + "shortcut_treeview"); + gtk_widget_ensure_style (treeview); + gtk_widget_size_request (treeview, &rectangle); + gtk_widget_set_size_request (treeview, -1, rectangle.height); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (actions_swindow), + 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 (GtkBuilder *builder, + const gchar *title, + const KeyListEntry *keys_list) +{ + MateConfClient *client; + GtkTreeIter parent_iter, iter; + GtkTreeModel *model; + gint i, j; + + client = mateconf_client_get_default (); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gtk_builder_get_object (builder, "shortcut_treeview"))); + + /* 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 (builder, i - 1); + + for (j = 0; keys_list[j].name != NULL; j++) + { + MateConfEntry *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 = mateconf_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 = mateconf_client_get_string (client, keys_list[j].description_name, NULL); + } + else + { + description = NULL; + + if (mateconf_entry_get_schema_name (entry)) + { + MateConfSchema *schema; + + schema = mateconf_client_get_schema (client, + mateconf_entry_get_schema_name (entry), + NULL); + if (schema != NULL) + { + description = g_strdup (mateconf_schema_get_short_desc (schema)); + mateconf_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 = mateconf_client_get_string (client, keys_list[j].cmd_name, NULL); + } + else + { + command = NULL; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->mateconf_key = g_strdup (key_string); + key_entry->editable = mateconf_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_mateconf_key = g_strdup (keys_list[j].description_name); + key_entry->desc_editable = mateconf_client_key_is_writable (client, key_entry->desc_mateconf_key, NULL); + key_entry->mateconf_cnxn_desc = mateconf_client_notify_add (client, + key_entry->desc_mateconf_key, + (MateConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); + } + if (keys_list[j].cmd_name != NULL) + { + key_entry->cmd_mateconf_key = g_strdup (keys_list[j].cmd_name); + key_entry->cmd_editable = mateconf_client_key_is_writable (client, key_entry->cmd_mateconf_key, NULL); + key_entry->mateconf_cnxn_cmd = mateconf_client_notify_add (client, + key_entry->cmd_mateconf_key, + (MateConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); + } + + mateconf_client_add_dir (client, key_string, MATECONF_CLIENT_PRELOAD_ONELEVEL, NULL); + key_entry->mateconf_cnxn = mateconf_client_notify_add (client, + key_string, + (MateConfClientNotifyFunc) &keybinding_key_changed, + key_entry, NULL, NULL); + + key_value = mateconf_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); + + mateconf_entry_free (entry); + ensure_scrollbar (builder, 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 (gtk_builder_get_object (builder, "shortcut_treeview"))); + } + + 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 (i == 0) + gtk_widget_hide (_gtk_builder_get_widget (builder, "shortcuts_vbox")); + else + gtk_widget_show (_gtk_builder_get_widget (builder, "shortcuts_vbox")); +} + +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, *mateconf_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; + mateconf_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) { + mateconf_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 (mateconf_key) + key.key = g_strdup (mateconf_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 (GtkBuilder *builder, + 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 (builder, 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_mateconf (GtkBuilder *builder, const gchar *mateconf_path) +{ + MateConfClient *client; + GSList *custom_list, *l; + GArray *entries; + KeyListEntry key; + + /* load custom shortcuts from MateConf */ + entries = g_array_new (FALSE, TRUE, sizeof (KeyListEntry)); + + key.key = NULL; + key.value = 0; + key.comparison = COMPARISON_NONE; + + client = mateconf_client_get_default (); + custom_list = mateconf_client_all_dirs (client, mateconf_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 (builder, _("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 (GtkBuilder *builder) +{ + gchar **wm_keybindings; + GDir *dir; + const char *name; + GList *list, *l; + + wm_keybindings = wm_common_get_current_keybindings(); + + clear_old_model (builder); + + dir = g_dir_open (MATECC_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 (MATECC_KEYBINDINGS_DIR, l->data, NULL); + append_keys_to_tree_from_file (builder, 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_mateconf (builder, MATECONF_BINDING_DIR); + + g_strfreev (wm_keybindings); +} + +static void +key_entry_controlling_key_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + reload_key_entries (user_data); +} + +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->mateconf_key, element->mateconf_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->mateconf_key = element->mateconf_key; + new_key->description = element->description; + new_key->desc_mateconf_key = element->desc_mateconf_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 +accel_edited_callback (GtkCellRendererText *cell, + const char *path_string, + guint keyval, + EggVirtualModifierType mask, + guint keycode, + gpointer data) +{ + MateConfClient *client; + GtkTreeView *view = (GtkTreeView *)data; + GtkTreeModel *model; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + KeyEntry *key_entry, tmp_key; + GError *err = NULL; + char *str; + + block_accels = FALSE; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + 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.mateconf_key = key_entry->mateconf_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 (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut \"%s\" cannot be used because it will become impossible to type using this key.\n" + "Please try with a key such as Control, Alt or Shift at the same time."), + name); + + g_free (name); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + /* 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 (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CANCEL, + _("The shortcut \"%s\" is already used for\n\"%s\""), + name, tmp_key.description ? + tmp_key.description : tmp_key.mateconf_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->mateconf_key, + tmp_key.description ? + tmp_key.description : tmp_key.mateconf_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) + { + MateConfClient *client; + + client = mateconf_client_get_default (); + + mateconf_client_set_string (client, + tmp_key.mateconf_key, + "", &err); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + err); + g_error_free (err); + g_object_unref (client); + return; + } + + str = binding_name (keyval, keycode, mask, FALSE); + mateconf_client_set_string (client, + key_entry->mateconf_key, + str, &err); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + err); + g_error_free (err); + + /* reset the previous shortcut */ + mateconf_client_set_string (client, + tmp_key.mateconf_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 = mateconf_client_get_default (); + mateconf_client_set_string (client, + key_entry->mateconf_key, + str, + &err); + g_free (str); + g_object_unref (client); + + if (err != NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), err); + g_error_free (err); + key_entry->editable = FALSE; + } +} + +static void +accel_cleared_callback (GtkCellRendererText *cell, + const char *path_string, + gpointer data) +{ + MateConfClient *client; + GtkTreeView *view = (GtkTreeView *) data; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + KeyEntry *key_entry; + GtkTreeIter iter; + GError *err = NULL; + GtkTreeModel *model; + + block_accels = FALSE; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL) + return; + + /* Unset the key */ + client = mateconf_client_get_default(); + mateconf_client_set_string (client, + key_entry->mateconf_key, + "", + &err); + g_object_unref (client); + + if (err != NULL) + { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (view))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_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 +description_edited_callback (GtkCellRendererText *renderer, + gchar *path_string, + gchar *new_text, + gpointer user_data) +{ + MateConfClient *client; + GtkTreeView *view = GTK_TREE_VIEW (user_data); + GtkTreeModel *model; + GtkTreePath *path = gtk_tree_path_new_from_string (path_string); + GtkTreeIter iter; + KeyEntry *key_entry; + + model = gtk_tree_view_get_model (view); + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + KEYENTRY_COLUMN, &key_entry, + -1); + + /* sanity check */ + if (key_entry == NULL || key_entry->desc_mateconf_key == NULL) + return; + + client = mateconf_client_get_default (); + if (!mateconf_client_set_string (client, key_entry->desc_mateconf_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 (KeyEntry *key) +{ + gint result; + const gchar *text; + gboolean ret; + + gtk_entry_set_text (GTK_ENTRY (custom_shortcut_name_entry), key->description ? key->description : ""); + gtk_widget_set_sensitive (custom_shortcut_name_entry, key->desc_editable); + gtk_widget_grab_focus (custom_shortcut_name_entry); + gtk_entry_set_text (GTK_ENTRY (custom_shortcut_command_entry), key->command ? key->command : ""); + gtk_widget_set_sensitive (custom_shortcut_command_entry, key->cmd_editable); + + gtk_window_present (GTK_WINDOW (custom_shortcut_dialog)); + result = gtk_dialog_run (GTK_DIALOG (custom_shortcut_dialog)); + switch (result) + { + case GTK_RESPONSE_OK: + text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_name_entry)); + g_free (key->description); + key->description = g_strdup (text); + text = gtk_entry_get_text (GTK_ENTRY (custom_shortcut_command_entry)); + g_free (key->command); + key->command = g_strdup (text); + ret = TRUE; + break; + default: + ret = FALSE; + break; + } + + gtk_widget_hide (custom_shortcut_dialog); + + return ret; +} + +static gboolean +remove_custom_shortcut (GtkTreeModel *model, GtkTreeIter *iter) +{ + GtkTreeIter parent; + MateConfClient *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 = mateconf_client_get_default (); + + mateconf_client_notify_remove (client, key->mateconf_cnxn); + if (key->mateconf_cnxn_desc != 0) + mateconf_client_notify_remove (client, key->mateconf_cnxn_desc); + if (key->mateconf_cnxn_cmd != 0) + mateconf_client_notify_remove (client, key->mateconf_cnxn_cmd); + + base = g_path_get_dirname (key->mateconf_key); + mateconf_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 */ + mateconf_client_suggest_sync (client, NULL); + g_object_unref (client); + + g_free (key->mateconf_key); + g_free (key->description); + g_free (key->desc_mateconf_key); + g_free (key->command); + g_free (key->cmd_mateconf_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 (GtkTreeModel *model, GtkTreeIter *iter) +{ + KeyEntry *key; + + gtk_tree_model_get (model, iter, + KEYENTRY_COLUMN, &key, + -1); + + edit_custom_shortcut (key); + if (key->command == NULL || key->command[0] == '\0') + { + remove_custom_shortcut (model, iter); + } + else + { + MateConfClient *client; + + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + KEYENTRY_COLUMN, key, -1); + client = mateconf_client_get_default (); + if (key->description != NULL) + mateconf_client_set_string (client, key->desc_mateconf_key, key->description, NULL); + else + mateconf_client_unset (client, key->desc_mateconf_key, NULL); + mateconf_client_set_string (client, key->cmd_mateconf_key, key->command, NULL); + g_object_unref (client); + } +} + +static gchar * +find_free_mateconf_key (GError **error) +{ + MateConfClient *client; + + gchar *dir; + int i; + + client = mateconf_client_get_default (); + + for (i = 0; i < MAX_CUSTOM_SHORTCUTS; i++) + { + dir = g_strdup_printf ("%s/custom%d", MATECONF_BINDING_DIR, i); + if (!mateconf_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 (GtkTreeView *tree_view, + GtkTreeModel *model) +{ + KeyEntry *key_entry; + GtkTreeIter iter; + GtkTreeIter parent_iter; + GtkTreePath *path; + gchar *dir; + MateConfClient *client; + GError *error; + + error = NULL; + dir = find_free_mateconf_key (&error); + if (dir == NULL) + { + show_error (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree_view))), error); + + g_error_free (error); + return; + } + + key_entry = g_new0 (KeyEntry, 1); + key_entry->mateconf_key = g_strconcat (dir, "/binding", NULL); + key_entry->editable = TRUE; + key_entry->model = model; + key_entry->desc_mateconf_key = g_strconcat (dir, "/name", NULL); + key_entry->description = g_strdup (""); + key_entry->desc_editable = TRUE; + key_entry->cmd_mateconf_key = g_strconcat (dir, "/action", NULL); + key_entry->command = g_strdup (""); + key_entry->cmd_editable = TRUE; + g_free (dir); + + if (edit_custom_shortcut (key_entry) && + key_entry->command && key_entry->command[0]) + { + find_section (model, &iter, _("Custom Shortcuts")); + parent_iter = iter; + gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter); + gtk_tree_store_set (GTK_TREE_STORE (model), &iter, KEYENTRY_COLUMN, key_entry, -1); + + /* store in mateconf */ + client = mateconf_client_get_default (); + mateconf_client_set_string (client, key_entry->mateconf_key, "", NULL); + mateconf_client_set_string (client, key_entry->desc_mateconf_key, key_entry->description, NULL); + mateconf_client_set_string (client, key_entry->cmd_mateconf_key, key_entry->command, NULL); + + /* add mateconf watches */ + key_entry->mateconf_cnxn_desc = mateconf_client_notify_add (client, + key_entry->desc_mateconf_key, + (MateConfClientNotifyFunc) &keybinding_description_changed, + key_entry, NULL, NULL); + key_entry->mateconf_cnxn_cmd = mateconf_client_notify_add (client, + key_entry->cmd_mateconf_key, + (MateConfClientNotifyFunc) &keybinding_command_changed, + key_entry, NULL, NULL); + key_entry->mateconf_cnxn = mateconf_client_notify_add (client, + key_entry->mateconf_key, + (MateConfClientNotifyFunc) &keybinding_key_changed, + key_entry, NULL, NULL); + + + g_object_unref (client); + + /* make the new shortcut visible */ + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_expand_to_path (tree_view, path); + gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0, 0); + gtk_tree_path_free (path); + } + else + { + g_free (key_entry->mateconf_key); + g_free (key_entry->description); + g_free (key_entry->desc_mateconf_key); + g_free (key_entry->command); + g_free (key_entry->cmd_mateconf_key); + g_free (key_entry); + } +} + +static void +start_editing_kb_cb (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer user_data) +{ + 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 (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 +start_editing_cb (GtkTreeView *tree_view, + GdkEventButton *event, + gpointer user_data) +{ + 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 (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); + 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, + gpointer user_data) +{ + if (block_accels) + { + return gtk_window_propagate_key_event (GTK_WINDOW (widget), event); + } + return FALSE; +} + +static void +cb_dialog_response (GtkWidget *widget, gint response_id, gpointer data) +{ + GtkBuilder *builder = data; + GtkTreeView *treeview; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, + "shortcut_treeview")); + model = gtk_tree_view_get_model (treeview); + + if (response_id == GTK_RESPONSE_HELP) + { + capplet_help (GTK_WINDOW (widget), + "goscustdesk-39"); + } + else if (response_id == RESPONSE_ADD) + { + add_custom_shortcut (treeview, model); + } + else if (response_id == RESPONSE_REMOVE) + { + selection = gtk_tree_view_get_selection (treeview); + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + { + remove_custom_shortcut (model, &iter); + } + } + else + { + clear_old_model (builder); + gtk_main_quit (); + } +} + +static void +selection_changed (GtkTreeSelection *selection, gpointer data) +{ + GtkWidget *button = data; + GtkTreeModel *model; + GtkTreeIter iter; + KeyEntry *key; + gboolean can_remove; + + can_remove = FALSE; + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, KEYENTRY_COLUMN, &key, -1); + if (key && key->command != NULL && key->editable) + can_remove = TRUE; + } + + gtk_widget_set_sensitive (button, can_remove); +} + +static void +setup_dialog (GtkBuilder *builder) +{ + MateConfClient *client; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkWidget *widget; + GtkTreeView *treeview; + GtkTreeSelection *selection; + GSList *allowed_keys; + + treeview = GTK_TREE_VIEW (gtk_builder_get_object (builder, + "shortcut_treeview")); + + client = mateconf_client_get_default (); + + g_signal_connect (treeview, "button_press_event", + G_CALLBACK (start_editing_cb), builder); + g_signal_connect (treeview, "row-activated", + G_CALLBACK (start_editing_kb_cb), NULL); + + renderer = gtk_cell_renderer_text_new (); + + g_signal_connect (renderer, "edited", + G_CALLBACK (description_edited_callback), + treeview); + + 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 (treeview, 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 (accel_edited_callback), + treeview); + + g_signal_connect (renderer, "accel_cleared", + G_CALLBACK (accel_cleared_callback), + treeview); + + 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 (treeview, column); + gtk_tree_view_column_set_sort_column_id (column, KEYENTRY_COLUMN); + + mateconf_client_add_dir (client, MATECONF_BINDING_DIR, MATECONF_CLIENT_PRELOAD_ONELEVEL, NULL); + mateconf_client_add_dir (client, "/apps/marco/general", MATECONF_CLIENT_PRELOAD_ONELEVEL, NULL); + mateconf_client_notify_add (client, + "/apps/marco/general/num_workspaces", + (MateConfClientNotifyFunc) key_entry_controlling_key_changed, + builder, NULL, NULL); + + /* set up the dialog */ + reload_key_entries (builder); + + widget = _gtk_builder_get_widget (builder, "mate-keybinding-dialog"); + capplet_set_icon (widget, "preferences-desktop-keyboard-shortcuts"); + gtk_widget_show (widget); + + g_signal_connect (widget, "key_press_event", G_CALLBACK (maybe_block_accels), NULL); + g_signal_connect (widget, "response", G_CALLBACK (cb_dialog_response), builder); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + g_signal_connect (selection, "changed", + G_CALLBACK (selection_changed), + _gtk_builder_get_widget (builder, "remove-button")); + + allowed_keys = mateconf_client_get_list (client, + MATECONF_BINDING_DIR "/allowed_keys", + MATECONF_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 (_gtk_builder_get_widget (builder, "add-button"), + FALSE); + } + + g_object_unref (client); + + /* setup the custom shortcut dialog */ + custom_shortcut_dialog = _gtk_builder_get_widget (builder, + "custom-shortcut-dialog"); + custom_shortcut_name_entry = _gtk_builder_get_widget (builder, + "custom-shortcut-name-entry"); + custom_shortcut_command_entry = _gtk_builder_get_widget (builder, + "custom-shortcut-command-entry"); + gtk_dialog_set_default_response (GTK_DIALOG (custom_shortcut_dialog), + GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (custom_shortcut_dialog), + GTK_WINDOW (widget)); +} + +static void +on_window_manager_change (const char *wm_name, GtkBuilder *builder) +{ + reload_key_entries (builder); +} + +int +main (int argc, char *argv[]) +{ + GtkBuilder *builder; + + g_thread_init (NULL); + gtk_init (&argc, &argv); + + bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (&argc, &argv); + + activate_settings_daemon (); + + builder = create_builder (); + + if (!builder) /* Warning was already printed to console */ + exit (EXIT_FAILURE); + + wm_common_register_window_manager_change ((GFunc) on_window_manager_change, builder); + setup_dialog (builder); + + gtk_main (); + + g_object_unref (builder); + return 0; +} + +/* + * vim: sw=2 ts=8 cindent noai bs=2 + */ diff --git a/capplets/keybindings/mate-keybinding-properties.ui b/capplets/keybindings/mate-keybinding-properties.ui new file mode 100644 index 00000000..ecd0ae26 --- /dev/null +++ b/capplets/keybindings/mate-keybinding-properties.ui @@ -0,0 +1,301 @@ +<?xml version="1.0"?> +<interface> + <requires lib="gtk+" version="2.14"/> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkDialog" id="mate-keybinding-dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Keyboard Shortcuts</property> + <property name="type_hint">dialog</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="shortcut_dialog"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <object class="GtkVBox" id="vbox3"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="spacing">12</property> + <child> + <object class="GtkVBox" id="shortcuts_vbox"> + <property name="visible">True</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="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">1</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_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="helpbutton1"> + <property name="label">gtk-help</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</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="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">False</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> + <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">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">False</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</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="-11">helpbutton1</action-widget> + <action-widget response="0">add-button</action-widget> + <action-widget response="1">remove-button</action-widget> + <action-widget response="-7">button1</action-widget> + </action-widgets> + </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-vbox1"> + <property name="visible">True</property> + <child> + <object class="GtkVBox" id="vbox4"> + <property name="visible">True</property> + <property name="border_width">5</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_area2"> + <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">False</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">False</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> diff --git a/capplets/keybindings/mate-keybinding.desktop.in.in b/capplets/keybindings/mate-keybinding.desktop.in.in new file mode 100644 index 00000000..b05bb1ef --- /dev/null +++ b/capplets/keybindings/mate-keybinding.desktop.in.in @@ -0,0 +1,14 @@ +[Desktop Entry] +_Name=Keyboard Shortcuts +_Comment=Assign shortcut keys to commands +Exec=mate-keybinding-properties +Icon=preferences-desktop-keyboard-shortcuts +Terminal=false +Type=Application +StartupNotify=true +Categories=MATE;GTK;Settings;X-MATE-PersonalSettings; +OnlyShowIn=MATE; +X-MATE-Bugzilla-Bugzilla=MATE +X-MATE-Bugzilla-Product=mate-control-center +X-MATE-Bugzilla-Component=Keybinding +X-MATE-Bugzilla-Version=@VERSION@ diff --git a/capplets/keybindings/mate-keybindings.pc.in b/capplets/keybindings/mate-keybindings.pc.in new file mode 100644 index 00000000..e9b95105 --- /dev/null +++ b/capplets/keybindings/mate-keybindings.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +datarootdir=@datarootdir@ +datadir=@datadir@ +pkgdatadir=${datadir}/@PACKAGE@ +keysdir=${pkgdatadir}/keybindings + +Name: mate-keybindings +Description: Keybindings configuration for MATE applications +Version: @VERSION@ + |