#include #include #include #include #include #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, const GdkRectangle *background_area, const 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, const 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 inside a function? wow */ 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_x11_display_get_xdisplay(gdk_display_get_default())); 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, const 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_get_preferred_size (keys->sizing_label, &requisition, NULL); (* 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; GdkDisplay *display; #if GTK_CHECK_VERSION (3, 20, 0) GdkSeat *seat; #else GdkDevice *pointer; GdkDevice *keyboard; GdkDeviceManager *device_manager; #endif 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_KEY_ISO_Left_Tab) { accel_keyval = GDK_KEY_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_KEY_Escape) { goto out; /* cancel */ } /* clear the accelerator on Backspace */ if (accel_mods == 0 && accel_keyval == GDK_KEY_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: display = gtk_widget_get_display (widget); #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat (display); gdk_seat_ungrab (seat); #else device_manager = gdk_display_get_device_manager (display); keyboard = gdk_device_get_associated_device (pointer); pointer = gdk_device_manager_get_client_pointer (device_manager); gdk_device_ungrab (keyboard, event->time); gdk_device_ungrab (pointer, event->time); #endif 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); GdkDisplay *display; #if GTK_CHECK_VERSION (3, 20, 0) GdkSeat *seat; #else GdkDevice *keyboard; GdkDevice *pointer; GdkDeviceManager *device_manager; #endif display = gtk_widget_get_display (widget); #if GTK_CHECK_VERSION(3, 20, 0) seat = gdk_display_get_default_seat (display); gdk_seat_ungrab (seat); #else device_manager = gdk_display_get_device_manager (display); keyboard = gdk_device_get_associated_device (pointer); pointer = gdk_device_manager_get_client_pointer (device_manager); gdk_device_ungrab (keyboard, GDK_CURRENT_TIME); gdk_device_ungrab (pointer, GDK_CURRENT_TIME); #endif 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, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags) { GtkCellRendererText *celltext; EggCellRendererKeys *keys; GdkDisplay *display; #if GTK_CHECK_VERSION (3, 20, 0) GdkSeat *seat; #else GdkDeviceManager *device_manager; GdkDevice *keyboard; GdkDevice *pointer; #endif 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); display = gtk_widget_get_display (widget); #if GTK_CHECK_VERSION (3, 20, 0) seat = gdk_display_get_default_seat (display); if (gdk_seat_grab (seat, gtk_widget_get_window (widget), GDK_SEAT_CAPABILITY_ALL, FALSE, NULL, event, NULL, NULL) != GDK_GRAB_SUCCESS) return NULL; #else device_manager = gdk_display_get_device_manager (display); pointer = gdk_device_manager_get_client_pointer (device_manager); keyboard = gdk_device_get_associated_device (pointer); if (gdk_device_grab (keyboard, gtk_widget_get_window (widget), GDK_OWNERSHIP_NONE, FALSE, GDK_KEY_PRESS_MASK, NULL, gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) return NULL; if (gdk_device_grab (pointer, gtk_widget_get_window (widget), GDK_OWNERSHIP_NONE, FALSE, GDK_BUTTON_PRESS_MASK, NULL, gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) { gdk_device_ungrab (keyboard, gdk_event_get_time (event)); return NULL; } #endif 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); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif 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"); }