/** * @file eggcellrendererkeys.cc * @copyright (C) 2001-2006 Marcus Bjurman\n * @copyright (C) 2007-2012 Piotr Eljasiak\n * @copyright (C) 2013-2023 Uwe Scholz\n * * @copyright This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * @copyright This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * @copyright You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * This file is part of gnome-terminal. * * Gnome-terminal is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Gnome-terminal is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include "gnome-cmd-includes.h" #include "eggcellrendererkeys.h" #include "utils.h" #define EGG_CELL_RENDERER_TEXT_PATH "egg-cell-renderer-text" 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_ACCEL_MODE }; static GtkCellRendererTextClass *parent_class = NULL; GType egg_cell_renderer_keys_get_type () { 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, GTypeFlags(0)); } return cell_keys_type; } static void egg_cell_renderer_keys_init (EggCellRendererKeys *cell_keys) { cell_keys->accel_mode = GTK_CELL_RENDERER_ACCEL_MODE_GTK; } 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) { g_return_if_fail (n_param_values == 5); 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); GMarshalFunc_VOID__STRING_UINT_FLAGS_UINT callback; GCClosure *cc = (GCClosure*) closure; gpointer data1, data2; 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 = G_OBJECT_CLASS (cell_keys_class); GtkCellRendererClass *cell_renderer_class = GTK_CELL_RENDERER_CLASS (cell_keys_class); parent_class = (GtkCellRendererTextClass *) g_type_class_peek_parent (object_class); GTK_CELL_RENDERER_CLASS (cell_keys_class)->start_editing = egg_cell_renderer_keys_start_editing; object_class->get_property = egg_cell_renderer_keys_get_property; object_class->set_property = egg_cell_renderer_keys_set_property; cell_renderer_class->get_size = egg_cell_renderer_keys_get_size; object_class->finalize = egg_cell_renderer_keys_finalize; g_object_class_install_property (object_class, PROP_ACCEL_KEY, g_param_spec_uint ("accel-key", _("Accelerator key"), _("Accelerator key"), 0, G_MAXINT, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_WRITABLE))); g_object_class_install_property (object_class, PROP_ACCEL_MASK, g_param_spec_flags ("accel-mods", _("Accelerator modifiers"), _("Accelerator modifiers"), GDK_TYPE_MODIFIER_TYPE, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_WRITABLE))); g_object_class_install_property (object_class, PROP_ACCEL_MODE, g_param_spec_int ("accel-mode", _("Accelerator Mode"), _("The type of accelerator."), 0, 2, 0, GParamFlags(G_PARAM_READABLE | G_PARAM_WRITABLE))); g_signal_new ("accel-edited", EGG_TYPE_CELL_RENDERER_KEYS, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EggCellRendererKeysClass, keys_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); } GtkCellRenderer *egg_cell_renderer_keys_new () { 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); } gchar *egg_accelerator_get_label (guint accel_key, GdkModifierType accel_mods) { if (accel_key == 0) return g_strdup (_("Disabled")); static const gchar text_shift[] = "Shift+"; static const gchar text_control[] = "Ctrl+"; static const gchar text_mod1[] = "Alt+"; static const gchar text_mod2[] = "Mod2+"; static const gchar text_mod3[] = "Mod3+"; static const gchar text_mod4[] = "Mod4+"; static const gchar text_mod5[] = "Mod5+"; static const gchar text_meta[] = "Meta+"; static const gchar text_super[] = "Super+"; static const gchar text_hyper[] = "Hyper+"; const gchar *keyval_name = gdk_keyval_name (gdk_keyval_to_upper (accel_key)); if (!keyval_name) keyval_name = ""; guint l = strlen (keyval_name); if (accel_mods & GDK_SHIFT_MASK) l += sizeof(text_shift)-1; if (accel_mods & GDK_CONTROL_MASK) l += sizeof(text_control)-1; if (accel_mods & GDK_MOD1_MASK) l += sizeof(text_mod1)-1; if (accel_mods & GDK_MOD2_MASK) l += sizeof(text_mod2)-1; if (accel_mods & GDK_MOD3_MASK) l += sizeof(text_mod3)-1; if (accel_mods & GDK_MOD4_MASK) l += sizeof(text_mod4)-1; if (accel_mods & GDK_MOD5_MASK) l += sizeof(text_mod5)-1; if (accel_mods & GDK_META_MASK) l += sizeof(text_meta)-1; if (accel_mods & GDK_HYPER_MASK) l += sizeof(text_hyper)-1; if (accel_mods & GDK_SUPER_MASK) l += sizeof(text_super)-1; gchar *accelerator = g_new (gchar, l+1); gchar *s = accelerator; if (accel_mods & GDK_SHIFT_MASK) { strcpy (s, text_shift); s += sizeof(text_shift)-1; } if (accel_mods & GDK_CONTROL_MASK) { strcpy (s, text_control); s += sizeof(text_control)-1; } if (accel_mods & GDK_MOD1_MASK) { strcpy (s, text_mod1); s += sizeof(text_mod1)-1; } if (accel_mods & GDK_MOD2_MASK) { strcpy (s, text_mod2); s += sizeof(text_mod2)-1; } if (accel_mods & GDK_MOD3_MASK) { strcpy (s, text_mod3); s += sizeof(text_mod3)-1; } if (accel_mods & GDK_MOD4_MASK) { strcpy (s, text_mod4); s += sizeof(text_mod4)-1; } if (accel_mods & GDK_MOD5_MASK) { strcpy (s, text_mod5); s += sizeof(text_mod5)-1; } if (accel_mods & GDK_META_MASK) { strcpy (s, text_meta); s += sizeof(text_meta)-1; } if (accel_mods & GDK_HYPER_MASK) { strcpy (s, text_hyper); s += sizeof(text_hyper)-1; } if (accel_mods & GDK_SUPER_MASK) { strcpy (s, text_super); s += sizeof(text_super)-1; } strcpy (s, keyval_name); return accelerator; } static void egg_cell_renderer_keys_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec) { g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (object)); #if defined (__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #endif EggCellRendererKeys *keys = EGG_CELL_RENDERER_KEYS (object); #if defined (__GNUC__) #pragma GCC diagnostic pop #endif 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); break; } } static void egg_cell_renderer_keys_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec) { g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (object)); #if defined (__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #endif EggCellRendererKeys *keys = EGG_CELL_RENDERER_KEYS (object); #if defined (__GNUC__) #pragma GCC diagnostic pop #endif switch (param_id) { case PROP_ACCEL_KEY: egg_cell_renderer_keys_set_accelerator (keys, g_value_get_uint (value), keys->accel_mask); break; case PROP_ACCEL_MASK: egg_cell_renderer_keys_set_accelerator (keys, keys->accel_key, (GdkModifierType) g_value_get_flags (value)); break; case PROP_ACCEL_MODE: egg_cell_renderer_keys_set_accel_mode (keys, (GtkCellRendererAccelMode) g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; } } inline gboolean is_modifier (guint keycode) { gboolean retval = FALSE; XModifierKeymap *mod_keymap = XGetModifierMapping (gdk_display); gint map_size = 8 * mod_keymap->max_keypermod; for (gint i=0; imodifiermap[i]) { retval = TRUE; break; } XFreeModifiermap (mod_keymap); return retval; } void egg_cell_renderer_keys_get_size (GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *cell_area, gint *x_offset, gint *y_offset, gint *width, gint *height) { #if defined (__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #endif auto keys = reinterpret_cast (cell); #if defined (__GNUC__) #pragma GCC diagnostic pop #endif GtkRequisition requisition; if (keys->sizing_label == nullptr) keys->sizing_label = gtk_label_new (_("New accelerator…")); 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); } static gboolean grab_key_callback (GtkWidget *widget, GdkEventKey *event, EggCellRendererKeys *keys) { if (event->is_modifier) return TRUE; switch (event->keyval) { case GDK_Super_L: case GDK_Super_R: // case GDK_Meta_L: // case GDK_Meta_R: // case GDK_Hyper_L: // case GDK_Hyper_R: return TRUE; default: break; } GdkDisplay *display = gtk_widget_get_display (widget); gboolean edited = FALSE; guint consumed_modifiers = 0; if (keys->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK) gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display), event->hardware_keycode, (GdkModifierType) event->state, event->group, NULL, NULL, NULL, (GdkModifierType *) &consumed_modifiers); guint accel_key = gdk_keyval_to_lower (event->keyval); guint accel_mods = 0; if (accel_key == GDK_ISO_Left_Tab) accel_key = GDK_Tab; accel_mods = event->state & gtk_accelerator_get_default_mod_mask (); // filter consumed modifiers if (keys->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK) { accel_mods &= ~consumed_modifiers; // put shift back if it changed the case of the key, not otherwise. if (accel_key != event->keyval) { accel_mods |= GDK_SHIFT_MASK; } } if (accel_mods == 0) switch (event->keyval) { case GDK_Escape: accel_key = 0; accel_mods = 0; goto out; // cancel default: break; } if (keys->accel_mode == GTK_CELL_RENDERER_ACCEL_MODE_GTK) if (accel_key != GDK_Tab && !gtk_accelerator_valid (accel_key, (GdkModifierType) accel_mods)) { gtk_widget_error_bell (widget); return TRUE; } edited = TRUE; out: gdk_display_keyboard_ungrab (display, event->time); gdk_display_pointer_ungrab (display, event->time); char *path = g_strdup ((gchar *) 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 (keys, "accel-edited", path, accel_key, accel_mods, event->hardware_keycode); g_free (path); return TRUE; } static void ungrab_stuff (GtkWidget *widget, EggCellRendererKeys *keys) { GdkDisplay *display = gtk_widget_get_display (widget); gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME); gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME); g_signal_handlers_disconnect_by_func (keys->grab_widget, (gpointer) grab_key_callback, keys); } 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 () { 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, GTypeFlags(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) { g_return_val_if_fail (gtk_widget_get_window (widget) != NULL, NULL); GtkWidget *label; GtkWidget *eventbox; #if defined (__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #endif GtkCellRendererText *celltext = GTK_CELL_RENDERER_TEXT (cell); EggCellRendererKeys *keys = EGG_CELL_RENDERER_KEYS (cell); #if defined (__GNUC__) #pragma GCC diagnostic pop #endif gboolean celltext_editable; g_object_get (celltext, "editable", &celltext_editable, NULL); // If the cell isn't editable we return NULL if (celltext_editable == FALSE) return 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 (widget, "key-press-event", G_CALLBACK (grab_key_callback), keys); eventbox = (GtkWidget *) 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), _("New accelerator…")); 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 (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, GdkModifierType mask) { g_return_if_fail (EGG_IS_CELL_RENDERER_KEYS (keys)); g_object_freeze_notify (G_OBJECT (keys)); gboolean 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-mods"); changed = TRUE; } g_object_thaw_notify (G_OBJECT (keys)); if (changed) { // sync string to the key values // GtkCellRendererText *celltext = GTK_CELL_RENDERER_TEXT (keys); char *text = egg_accelerator_get_label (keys->accel_key, keys->accel_mask); g_object_set (keys, "text", text, NULL); g_free (text); } } void egg_cell_renderer_keys_get_accelerator (EggCellRendererKeys *keys, guint *keyval, GdkModifierType *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, GtkCellRendererAccelMode 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"); }