diff options
Diffstat (limited to 'capplets/keybindings/eggcellrendererkeys.c')
-rw-r--r-- | capplets/keybindings/eggcellrendererkeys.c | 695 |
1 files changed, 695 insertions, 0 deletions
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"); +} |