diff --git a/ChangeLog b/ChangeLog index 78d8dbbfb3..62443cb162 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +2004-07-21 Michael Natterer + + * app/core/gimpmarshal.list + * app/widgets/Makefile.am + * app/widgets/widgets-types.h + * app/widgets/gimpcellrendereraccel.[ch]: new cell renderer + which displays an accelerator and allows to edit it (ripped + out of libegg and modified). + + * app/widgets/gimpactionview.c: use the new renderer and connect + to its "accel-edited" signal (its callback is one huge mess that + needs to be cleaned up). Added ugly hack to work around GTK+ API + limitation that seems to prevent implementing a shortcut editor in + a sane way. + + * app/actions/file-actions.c + * app/actions/image-actions.c + * app/actions/tools-actions.c: added ugly hacks here, too. + + * app/gui/preferences-dialog.c: relaced Cancel/Ok in the shortcut + editor by Close. + 2004-07-20 Sven Neumann * configure.in (ALL_LINGUAS): added back "pa" for Punjabi now that diff --git a/app/actions/file-actions.c b/app/actions/file-actions.c index b2255ae247..dcfff19550 100644 --- a/app/actions/file-actions.c +++ b/app/actions/file-actions.c @@ -72,7 +72,7 @@ static GimpActionEntry file_actions[] = GIMP_HELP_FILE_OPEN }, { "file-open-from-image", GTK_STOCK_OPEN, - N_("_Open..."), "O", NULL, + N_("_Open..."), NULL, NULL, G_CALLBACK (file_open_from_image_cmd_callback), GIMP_HELP_FILE_OPEN }, @@ -116,6 +116,7 @@ static GimpActionEntry file_actions[] = void file_actions_setup (GimpActionGroup *group) { + GtkAction *action; GimpEnumActionEntry *entries; gint n_entries; gint i; @@ -124,6 +125,16 @@ file_actions_setup (GimpActionGroup *group) file_actions, G_N_ELEMENTS (file_actions)); + action = gtk_action_group_get_action (GTK_ACTION_GROUP (group), + "file-open-from-image"); + gtk_action_set_accel_path (action, "/file/file-open"); + +#ifdef __GNUC__ +#warning FIXME: remove accel_path hack +#endif + g_object_set_data (G_OBJECT (action), "gimp-accel-path", + "/file/file-open"); + n_entries = GIMP_GUI_CONFIG (group->gimp->config)->last_opened_size; entries = g_new0 (GimpEnumActionEntry, n_entries); diff --git a/app/actions/image-actions.c b/app/actions/image-actions.c index 0d5f656ec1..8db7224ddf 100644 --- a/app/actions/image-actions.c +++ b/app/actions/image-actions.c @@ -167,6 +167,12 @@ image_actions_setup (GimpActionGroup *group) "image-new-from-image"); gtk_action_set_accel_path (action, "/image/image-new"); +#ifdef __GNUC__ +#warning FIXME: remove accel_path hack +#endif + g_object_set_data (G_OBJECT (action), "gimp-accel-path", + "/image/image-new"); + gimp_action_group_add_enum_actions (group, image_convert_actions, G_N_ELEMENTS (image_convert_actions), diff --git a/app/actions/tools-actions.c b/app/actions/tools-actions.c index 9433757f02..d88a7339bd 100644 --- a/app/actions/tools-actions.c +++ b/app/actions/tools-actions.c @@ -105,10 +105,22 @@ tools_actions_setup (GimpActionGroup *group) "tools-by-color-select-short"); gtk_action_set_accel_path (action, "/tools/tools-by-color-select"); +#ifdef __GNUC__ +#warning FIXME: remove accel_path hack +#endif + g_object_set_data (G_OBJECT (action), "gimp-accel-path", + "/tools/tools-by-color-select"); + action = gtk_action_group_get_action (GTK_ACTION_GROUP (group), "tools-rotate-arbitrary"); gtk_action_set_accel_path (action, "/tools/tools-rotate"); +#ifdef __GNUC__ +#warning FIXME: remove accel_path hack +#endif + g_object_set_data (G_OBJECT (action), "gimp-accel-path", + "/tools/tools-rotate"); + for (list = GIMP_LIST (group->gimp->tool_info_list)->list; list; list = g_list_next (list)) diff --git a/app/core/gimpmarshal.list b/app/core/gimpmarshal.list index 95e7069db9..4d5c481ecb 100644 --- a/app/core/gimpmarshal.list +++ b/app/core/gimpmarshal.list @@ -45,4 +45,5 @@ VOID: OBJECT, POINTER VOID: POINTER VOID: STRING VOID: STRING, FLAGS +VOID: STRING, UINT, FLAGS VOID: VOID diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c index 8353362130..65efa3c7fe 100644 --- a/app/dialogs/preferences-dialog.c +++ b/app/dialogs/preferences-dialog.c @@ -473,8 +473,7 @@ prefs_keyboard_shortcuts_dialog (GtkWidget *widget, gimp_standard_help_func, GIMP_HELP_PREFS_INTERFACE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, + GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL); diff --git a/app/gui/preferences-dialog.c b/app/gui/preferences-dialog.c index 8353362130..65efa3c7fe 100644 --- a/app/gui/preferences-dialog.c +++ b/app/gui/preferences-dialog.c @@ -473,8 +473,7 @@ prefs_keyboard_shortcuts_dialog (GtkWidget *widget, gimp_standard_help_func, GIMP_HELP_PREFS_INTERFACE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, + GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL); diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am index 1fc3b5dced..8700bda825 100644 --- a/app/widgets/Makefile.am +++ b/app/widgets/Makefile.am @@ -35,6 +35,8 @@ libappwidgets_a_sources = \ gimpbrushselect.h \ gimpbufferview.c \ gimpbufferview.h \ + gimpcellrendereraccel.c \ + gimpcellrendereraccel.h \ gimpcellrenderertoggle.c \ gimpcellrenderertoggle.h \ gimpcellrendererviewable.c \ diff --git a/app/widgets/gimpactionview.c b/app/widgets/gimpactionview.c index 98165d8942..af45b69f31 100644 --- a/app/widgets/gimpactionview.c +++ b/app/widgets/gimpactionview.c @@ -26,12 +26,14 @@ #include #include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" #include "widgets-types.h" #include "gimpaction.h" #include "gimpactiongroup.h" #include "gimpactionview.h" +#include "gimpcellrendereraccel.h" #include "gimpuimanager.h" #include "gimpwidgets-utils.h" @@ -44,7 +46,8 @@ enum COLUMN_STOCK_ID, COLUMN_LABEL, COLUMN_NAME, - COLUMN_SHORTCUT, + COLUMN_ACCEL_KEY, + COLUMN_ACCEL_MASK, COLUMN_MENU_ITEM, NUM_COLUMNS }; @@ -55,8 +58,6 @@ enum static void gimp_action_view_class_init (GimpActionViewClass *klass); static void gimp_action_view_init (GimpActionView *view); -static gchar * gimp_action_view_get_shortcut (GtkAccelGroup *group, - GClosure *accel_closure); static gboolean gimp_action_view_accel_find_func (GtkAccelKey *key, GClosure *closure, gpointer data); @@ -65,6 +66,12 @@ static void gimp_action_view_accel_changed (GtkAccelGroup *accel_group, GdkModifierType unused2, GClosure *accel_closure, GimpActionView *view); +static void gimp_action_view_accel_edited (GimpCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + GimpActionView *view); + GType @@ -119,12 +126,13 @@ gimp_action_view_new (GimpUIManager *manager, g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); store = gtk_tree_store_new (NUM_COLUMNS, - GTK_TYPE_ACTION, /* COLUMN_ACTION */ - G_TYPE_STRING, /* COLUMN_STOCK_ID */ - G_TYPE_STRING, /* COLUMN_LABEL */ - G_TYPE_STRING, /* COLUMN_NAME */ - G_TYPE_STRING, /* COLUMN_SHORTCUT */ - GTK_TYPE_MENU_ITEM); /* COLUMN_MENU_ITEM */ + GTK_TYPE_ACTION, /* COLUMN_ACTION */ + G_TYPE_STRING, /* COLUMN_STOCK_ID */ + G_TYPE_STRING, /* COLUMN_LABEL */ + G_TYPE_STRING, /* COLUMN_NAME */ + G_TYPE_UINT, /* COLUMN_ACCEL_KEY */ + GDK_TYPE_MODIFIER_TYPE, /* COLUMN_ACCEL_MASK */ + GTK_TYPE_MENU_ITEM); /* COLUMN_MENU_ITEM */ accel_group = gtk_ui_manager_get_accel_group (GTK_UI_MANAGER (manager)); @@ -140,12 +148,8 @@ gimp_action_view_new (GimpUIManager *manager, gtk_tree_store_append (store, &group_iter, NULL); gtk_tree_store_set (store, &group_iter, - COLUMN_ACTION, NULL, - COLUMN_STOCK_ID, group->stock_id, - COLUMN_LABEL, group->label, - COLUMN_NAME, NULL, - COLUMN_SHORTCUT, NULL, - COLUMN_MENU_ITEM, NULL, + COLUMN_STOCK_ID, group->stock_id, + COLUMN_LABEL, group->label, -1); actions = gtk_action_group_list_actions (GTK_ACTION_GROUP (group)); @@ -160,12 +164,13 @@ gimp_action_view_new (GimpUIManager *manager, if (! strstr (name, "-menu") && ! strstr (name, "-popup")) { - GtkTreeIter action_iter; - gchar *stock_id; - gchar *label; - gchar *stripped; - gchar *shortcut = NULL; - GtkWidget *menu_item = NULL; + GtkTreeIter action_iter; + gchar *stock_id; + gchar *label; + gchar *stripped; + guint accel_key = 0; + GdkModifierType accel_mask = 0; + GtkWidget *menu_item = NULL; g_object_get (action, "stock-id", &stock_id, @@ -191,9 +196,21 @@ gimp_action_view_new (GimpUIManager *manager, NULL); if (accel_closure) - shortcut = - gimp_action_view_get_shortcut (accel_group, - accel_closure); + { + GtkAccelKey *key; + + key = gtk_accel_group_find (accel_group, + gimp_action_view_accel_find_func, + accel_closure); + + if (key && + key->accel_key && + key->accel_flags & GTK_ACCEL_VISIBLE) + { + accel_key = key->accel_key; + accel_mask = key->accel_mods; + } + } g_object_ref (menu_item); gtk_object_sink (GTK_OBJECT (menu_item)); @@ -208,18 +225,18 @@ gimp_action_view_new (GimpUIManager *manager, gtk_tree_store_append (store, &action_iter, &group_iter); gtk_tree_store_set (store, &action_iter, - COLUMN_ACTION, action, - COLUMN_STOCK_ID, stock_id, - COLUMN_LABEL, stripped, - COLUMN_NAME, name, - COLUMN_SHORTCUT, shortcut, - COLUMN_MENU_ITEM, menu_item, + COLUMN_ACTION, action, + COLUMN_STOCK_ID, stock_id, + COLUMN_LABEL, stripped, + COLUMN_NAME, name, + COLUMN_ACCEL_KEY, accel_key, + COLUMN_ACCEL_MASK, accel_mask, + COLUMN_MENU_ITEM, menu_item, -1); g_free (stock_id); g_free (label); g_free (stripped); - g_free (shortcut); if (menu_item) g_object_unref (menu_item); @@ -255,19 +272,26 @@ gimp_action_view_new (GimpUIManager *manager, if (show_shortcuts) { - g_signal_connect_object (accel_group, "accel_changed", + g_signal_connect_object (accel_group, "accel-changed", G_CALLBACK (gimp_action_view_accel_changed), view, 0); column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, _("Shortcut")); - cell = gtk_cell_renderer_text_new (); + cell = gimp_cell_renderer_accel_new (); + cell->mode = GTK_CELL_RENDERER_MODE_EDITABLE; + GTK_CELL_RENDERER_TEXT (cell)->editable = TRUE; gtk_tree_view_column_pack_start (column, cell, TRUE); gtk_tree_view_column_set_attributes (column, cell, - "text", COLUMN_SHORTCUT, + "accel-key", COLUMN_ACCEL_KEY, + "accel-mask", COLUMN_ACCEL_MASK, NULL); + g_signal_connect (cell, "accel-edited", + G_CALLBACK (gimp_action_view_accel_edited), + view); + gtk_tree_view_append_column (view, column); } else @@ -290,27 +314,6 @@ gimp_action_view_new (GimpUIManager *manager, /* private functions */ -static gchar * -gimp_action_view_get_shortcut (GtkAccelGroup *accel_group, - GClosure *accel_closure) -{ - GtkAccelKey *accel_key; - - accel_key = gtk_accel_group_find (accel_group, - gimp_action_view_accel_find_func, - accel_closure); - - if (accel_key && - accel_key->accel_key && - accel_key->accel_flags & GTK_ACCEL_VISIBLE) - { - return gimp_get_accel_string (accel_key->accel_key, - accel_key->accel_mods); - } - - return NULL; -} - static gboolean gimp_action_view_accel_find_func (GtkAccelKey *key, GClosure *closure, @@ -364,15 +367,26 @@ gimp_action_view_accel_changed (GtkAccelGroup *accel_group, if (accel_closure == item_closure) { - gchar *shortcut; + GtkAccelKey *key; + guint accel_key = 0; + GdkModifierType accel_mask = 0; - shortcut = gimp_action_view_get_shortcut (accel_group, - accel_closure); + key = gtk_accel_group_find (accel_group, + gimp_action_view_accel_find_func, + accel_closure); + + if (key && + key->accel_key && + key->accel_flags & GTK_ACCEL_VISIBLE) + { + accel_key = key->accel_key; + accel_mask = key->accel_mods; + } gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter, - COLUMN_SHORTCUT, shortcut, + COLUMN_ACCEL_KEY, accel_key, + COLUMN_ACCEL_MASK, accel_mask, -1); - g_free (shortcut); return; } @@ -380,3 +394,217 @@ gimp_action_view_accel_changed (GtkAccelGroup *accel_group, } } } + +typedef struct +{ + gchar *accel_path; + guint accel_key; + GdkModifierType accel_mask; +} ConfirmData; + +static void +gimp_action_view_accel_confirm (GtkWidget *query_box, + gboolean value, + gpointer data) +{ + if (value) + { + ConfirmData *confirm_data = data; + + if (! gtk_accel_map_change_entry (confirm_data->accel_path, + confirm_data->accel_key, + confirm_data->accel_mask, + TRUE)) + { + g_message (_("Changing shortcut failed.")); + } + } +} + +static void +gimp_action_view_accel_edited (GimpCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask, + GimpActionView *view) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + if (! model) + return; + + path = gtk_tree_path_new_from_string (path_string); + + if (gtk_tree_model_get_iter (model, &iter, path)) + { + GtkAction *action; + GtkActionGroup *group; + gchar *accel_path; + + gtk_tree_model_get (model, &iter, + COLUMN_ACTION, &action, + -1); + + if (! action) + goto done; + + g_object_get (action, "action-group", &group, NULL); + + if (! group) + { + g_object_unref (action); + goto done; + } + +#ifdef __GNUC__ +#warning FIXME: remove accel_path hack +#endif + accel_path = g_object_get_data (G_OBJECT (action), "gimp-accel-path"); + + if (accel_path) + accel_path = g_strdup (accel_path); + else + accel_path = g_strdup_printf ("/%s/%s", + gtk_action_group_get_name (group), + gtk_action_get_name (action)); + + if (accel_key) + { + if (! gtk_accel_map_change_entry (accel_path, + accel_key, accel_mask, FALSE)) + { + GtkAction *conflict_action = NULL; + GtkTreeIter iter; + gboolean iter_valid; + + for (iter_valid = gtk_tree_model_get_iter_first (model, &iter); + iter_valid; + iter_valid = gtk_tree_model_iter_next (model, &iter)) + { + GtkTreeIter child_iter; + gboolean child_valid; + + for (child_valid = gtk_tree_model_iter_children (model, + &child_iter, + &iter); + child_valid; + child_valid = gtk_tree_model_iter_next (model, + &child_iter)) + { + guint child_accel_key; + GdkModifierType child_accel_mask; + + gtk_tree_model_get (model, &child_iter, + COLUMN_ACCEL_KEY, &child_accel_key, + COLUMN_ACCEL_MASK, &child_accel_mask, + -1); + + if (accel_key == child_accel_key && + accel_mask == child_accel_mask) + { + gtk_tree_model_get (model, &child_iter, + COLUMN_ACTION, &conflict_action, + -1); + break; + } + } + + if (conflict_action) + break; + } + + if (conflict_action) + { + GimpActionGroup *conflict_group; + gchar *accel_string; + gchar *label; + gchar *stripped; + gchar *message; + ConfirmData *confirm_data; + GtkWidget *query_box; + + g_object_get (conflict_action, + "action-group", &conflict_group, + "label", &label, + NULL); + + stripped = gimp_strip_uline (label); + + accel_string = gimp_get_accel_string (accel_key, accel_mask); + + message = + g_strdup_printf ("Shortcut \"%s\" is already taken by " + "\"%s\" from the \"%s\" group.\n" + "\n" + "Click \"Delete Old Shortcut\" to " + "assign the shortcut anyway, " + "deleting %s's shortcut.", + accel_string, + stripped, + conflict_group->label, + stripped); + + confirm_data = g_new0 (ConfirmData, 1); + + confirm_data->accel_path = g_strdup (accel_path); + confirm_data->accel_key = accel_key; + confirm_data->accel_mask = accel_mask; + + query_box = + gimp_query_boolean_box (_("Conflicting shortcuts"), + gtk_widget_get_toplevel (GTK_WIDGET (view)), + gimp_standard_help_func, + NULL, + GIMP_STOCK_WARNING, + message, + _("Delete Old Shortcut"), + GTK_STOCK_CANCEL, + G_OBJECT (view), "unmap", + gimp_action_view_accel_confirm, + confirm_data); + + g_object_weak_ref (G_OBJECT (query_box), + (GWeakNotify) g_free, + confirm_data); + g_object_weak_ref (G_OBJECT (query_box), + (GWeakNotify) g_free, + confirm_data->accel_path); + + g_free (message); + g_free (accel_string); + g_free (label); + g_free (stripped); + g_object_unref (conflict_group); + g_object_unref (conflict_action); + + gtk_widget_show (query_box); + } + else + { + g_message (_("Changing shortcut failed.")); + } + } + } + else + { + accel_mask = 0; + + if (! gtk_accel_map_change_entry (accel_path, + accel_key, accel_mask, FALSE)) + { + g_message (_("Removing shortcut failed.")); + } + } + + g_free (accel_path); + g_object_unref (group); + g_object_unref (action); + } + + done: + + gtk_tree_path_free (path); +} diff --git a/app/widgets/gimpcellrendereraccel.c b/app/widgets/gimpcellrendereraccel.c new file mode 100644 index 0000000000..4e0c4f7392 --- /dev/null +++ b/app/widgets/gimpcellrendereraccel.c @@ -0,0 +1,539 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendereraccel.c + * Copyright (C) 2004 Michael Natterer + * + * Derived from: libegg/libegg/treeviewutils/eggcellrendererkeys.c + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "widgets-types.h" + +#include "core/gimpmarshal.h" + +#include "gimpcellrendereraccel.h" +#include "gimpwidgets-utils.h" + +#include "gimp-intl.h" + + +enum +{ + ACCEL_EDITED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_ACCEL_KEY, + PROP_ACCEL_MASK +}; + +#define GIMP_CELL_RENDERER_ACCEL_PATH "gimp-cell-renderer-accel-path" + + +static void gimp_cell_renderer_accel_init (GimpCellRendererAccel *cell); +static void gimp_cell_renderer_accel_class_init (GimpCellRendererAccelClass *klass); + +static void gimp_cell_renderer_accel_finalize (GObject *object); +static void gimp_cell_renderer_accel_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_cell_renderer_accel_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_cell_renderer_accel_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); +static GtkCellEditable * + gimp_cell_renderer_accel_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + + +static guint accel_cell_signals[LAST_SIGNAL] = { 0 }; + +static GtkCellRendererTextClass *parent_class = NULL; + + +GType +gimp_cell_renderer_accel_get_type (void) +{ + static GType type = 0; + + if (! type) + { + static const GTypeInfo info = + { + sizeof (GimpCellRendererAccelClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc)gimp_cell_renderer_accel_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GimpCellRendererAccel), + 0, /* n_preallocs */ + (GInstanceInitFunc) gimp_cell_renderer_accel_init + }; + + type = g_type_register_static (GTK_TYPE_CELL_RENDERER_TEXT, + "GimpCellRendererAccel", + &info, 0); + } + + return type; +} + +static void +gimp_cell_renderer_accel_init (GimpCellRendererAccel *cell) +{ +} + +static void +gimp_cell_renderer_accel_class_init (GimpCellRendererAccelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); + + parent_class = g_type_class_peek_parent (object_class); + + accel_cell_signals[ACCEL_EDITED] = + g_signal_new ("accel-edited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpCellRendererAccelClass, accel_edited), + NULL, NULL, + gimp_marshal_VOID__STRING_UINT_FLAGS, + G_TYPE_NONE, 3, + G_TYPE_STRING, + G_TYPE_UINT, + GDK_TYPE_MODIFIER_TYPE); + + object_class->finalize = gimp_cell_renderer_accel_finalize; + object_class->set_property = gimp_cell_renderer_accel_set_property; + object_class->get_property = gimp_cell_renderer_accel_get_property; + + cell_class->get_size = gimp_cell_renderer_accel_get_size; + cell_class->start_editing = gimp_cell_renderer_accel_start_editing; + + g_object_class_install_property (object_class, PROP_ACCEL_KEY, + g_param_spec_uint ("accel-key", NULL, NULL, + 0, G_MAXINT, 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_ACCEL_MASK, + g_param_spec_flags ("accel-mask", NULL, NULL, + GDK_TYPE_MODIFIER_TYPE, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_cell_renderer_accel_finalize (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_cell_renderer_accel_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererAccel *accel = GIMP_CELL_RENDERER_ACCEL (object); + + switch (param_id) + { + case PROP_ACCEL_KEY: + gimp_cell_renderer_accel_set_accelerator (accel, + g_value_get_uint (value), + accel->accel_mask); + break; + case PROP_ACCEL_MASK: + gimp_cell_renderer_accel_set_accelerator (accel, + accel->accel_key, + g_value_get_flags (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +gimp_cell_renderer_accel_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCellRendererAccel *accel = GIMP_CELL_RENDERER_ACCEL (object); + + switch (param_id) + { + case PROP_ACCEL_KEY: + g_value_set_uint (value, accel->accel_key); + break; + case PROP_ACCEL_MASK: + g_value_set_flags (value, accel->accel_mask); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + } +} + +static void +gimp_cell_renderer_accel_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + GimpCellRendererAccel *accel = GIMP_CELL_RENDERER_ACCEL (cell); + GtkRequisition requisition; + + if (! accel->sizing_label) + accel->sizing_label = gtk_label_new (_("Type a new accelerator, " + "or press Backspace to clear")); + + gtk_widget_size_request (accel->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, + GimpCellRendererAccel *accel) +{ + GdkDisplay *display; + guint accel_key; + GdkModifierType accel_mods; + gchar *path; + gboolean delete = FALSE; + gboolean edited = FALSE; + GdkModifierType consumed_modifiers; + + switch (event->keyval) + { + case GDK_Shift_L: + case GDK_Shift_R: + case GDK_Control_L: + case GDK_Control_R: + case GDK_Alt_L: + case GDK_Alt_R: + return TRUE; + + case GDK_Delete: + case GDK_KP_Delete: + case GDK_BackSpace: + delete = TRUE; + break; + default: + break; + } + + display = gtk_widget_get_display (widget); + + gdk_keymap_translate_keyboard_state (gdk_keymap_get_for_display (display), + event->hardware_keycode, + event->state, + event->group, + NULL, NULL, NULL, &consumed_modifiers); + + accel_key = gdk_keyval_to_lower (event->keyval); + accel_mods = (event->state & + gtk_accelerator_get_default_mod_mask () & + ~consumed_modifiers); + + if (accel_key == GDK_ISO_Left_Tab) + accel_key = GDK_Tab; + + /* If lowercasing affects the keysym, then we need to include SHIFT + * in the modifiers, We re-upper case when we match against the + * keyval, but display and save in caseless form. + */ + if (accel_key != event->keyval) + accel_mods |= GDK_SHIFT_MASK; + + if (accel_key != GDK_Escape) + { + if (delete || ! gtk_accelerator_valid (accel_key, accel_mods)) + { + accel_key = 0; + accel_mods = 0; + } + + edited = TRUE; + } + + path = g_strdup (g_object_get_data (G_OBJECT (accel->edit_widget), + GIMP_CELL_RENDERER_ACCEL_PATH)); + + gdk_display_keyboard_ungrab (display, event->time); + gdk_display_pointer_ungrab (display, event->time); + + gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (accel->edit_widget)); + gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (accel->edit_widget)); + accel->edit_widget = NULL; + accel->grab_widget = NULL; + + if (edited) + g_signal_emit (accel, accel_cell_signals[ACCEL_EDITED], 0, + path, accel_key, accel_mods); + + g_free (path); + + return TRUE; +} + +static void +ungrab_stuff (GtkWidget *widget, + GimpCellRendererAccel *accel) +{ + 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 (G_OBJECT (accel->grab_widget), + G_CALLBACK (grab_key_callback), + accel); +} + +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 type = 0; + + if (! type) + { + static const GTypeInfo 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 editable_info = + { + (GInterfaceInitFunc) pointless_eventbox_cell_editable_init, + NULL, NULL + }; + + type = g_type_register_static (GTK_TYPE_EVENT_BOX, + "GimpCellEditableEventBox", + &info, 0); + + g_type_add_interface_static (type, GTK_TYPE_CELL_EDITABLE, + &editable_info); + } + + return type; +} + +static GtkCellEditable * +gimp_cell_renderer_accel_start_editing (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GtkCellRendererText *celltext = GTK_CELL_RENDERER_TEXT (cell); + GimpCellRendererAccel *accel = GIMP_CELL_RENDERER_ACCEL (cell); + GtkWidget *label; + GtkWidget *eventbox; + + if (! celltext->editable) + return NULL; + + g_return_val_if_fail (widget->window != NULL, NULL); + + if (gdk_keyboard_grab (widget->window, FALSE, + gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) + return NULL; + + if (gdk_pointer_grab (widget->window, FALSE, + GDK_BUTTON_PRESS_MASK, + FALSE, NULL, + gdk_event_get_time (event)) != GDK_GRAB_SUCCESS) + { + gdk_display_keyboard_ungrab (gtk_widget_get_display (widget), + gdk_event_get_time (event)); + return NULL; + } + + accel->grab_widget = widget; + + g_signal_connect (G_OBJECT (widget), "key_press_event", + G_CALLBACK (grab_key_callback), + accel); + + eventbox = g_object_new (pointless_eventbox_subclass_get_type (), NULL); + accel->edit_widget = eventbox; + g_object_add_weak_pointer (G_OBJECT (accel->edit_widget), + (void **) &accel->edit_widget); + + label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + + gtk_widget_modify_bg (eventbox, GTK_STATE_NORMAL, + &widget->style->bg[GTK_STATE_SELECTED]); + + gtk_widget_modify_fg (label, GTK_STATE_NORMAL, + &widget->style->fg[GTK_STATE_SELECTED]); + + if (accel->accel_key != 0) + gtk_label_set_text (GTK_LABEL (label), + _("Type a new accelerator, or press Backspace to clear")); + else + gtk_label_set_text (GTK_LABEL (label), + _("Type a new accelerator")); + + gtk_container_add (GTK_CONTAINER (eventbox), label); + + g_object_set_data_full (G_OBJECT (accel->edit_widget), + GIMP_CELL_RENDERER_ACCEL_PATH, + g_strdup (path), g_free); + + gtk_widget_show_all (accel->edit_widget); + + g_signal_connect (G_OBJECT (accel->edit_widget), "unrealize", + G_CALLBACK (ungrab_stuff), + accel); + + accel->edit_key = accel->accel_key; + + return GTK_CELL_EDITABLE (accel->edit_widget); +} + + +/* public functions */ + +GtkCellRenderer * +gimp_cell_renderer_accel_new (void) +{ + return g_object_new (GIMP_TYPE_CELL_RENDERER_ACCEL, NULL); +} + +void +gimp_cell_renderer_accel_set_accelerator (GimpCellRendererAccel *accel, + guint accel_key, + GdkModifierType accel_mask) +{ + gboolean changed = FALSE; + + g_return_if_fail (GIMP_IS_CELL_RENDERER_ACCEL (accel)); + + g_object_freeze_notify (G_OBJECT (accel)); + + if (accel_key != accel->accel_key) + { + accel->accel_key = accel_key; + + g_object_notify (G_OBJECT (accel), "accel-key"); + changed = TRUE; + } + + if (accel_mask != accel->accel_mask) + { + accel->accel_mask = accel_mask; + + g_object_notify (G_OBJECT (accel), "accel-mask"); + changed = TRUE; + } + + g_object_thaw_notify (G_OBJECT (accel)); + + if (changed) + { + gchar *text = gimp_get_accel_string (accel->accel_key, + accel->accel_mask); + g_object_set (accel, "text", text, NULL); + g_free (text); + } +} + +void +gimp_cell_renderer_accel_get_accelerator (GimpCellRendererAccel *accel, + guint *accel_key, + GdkModifierType *accel_mask) +{ + g_return_if_fail (GIMP_IS_CELL_RENDERER_ACCEL (accel)); + + if (accel_key) + *accel_key = accel->accel_key; + + if (accel_mask) + *accel_mask = accel->accel_mask; +} diff --git a/app/widgets/gimpcellrendereraccel.h b/app/widgets/gimpcellrendereraccel.h new file mode 100644 index 0000000000..ada8dc1f1f --- /dev/null +++ b/app/widgets/gimpcellrendereraccel.h @@ -0,0 +1,78 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpcellrendereraccel.h + * Copyright (C) 2004 Michael Natterer + * + * Derived from: libegg/libegg/treeviewutils/eggcellrendererkeys.h + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GIMP_CELL_RENDERER_ACCEL_H__ +#define __GIMP_CELL_RENDERER_ACCEL_H__ + + +#include + + +#define GIMP_TYPE_CELL_RENDERER_ACCEL (gimp_cell_renderer_accel_get_type ()) +#define GIMP_CELL_RENDERER_ACCEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CELL_RENDERER_ACCEL, GimpCellRendererAccel)) +#define GIMP_CELL_RENDERER_ACCEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CELL_RENDERER_ACCEL, GimpCellRendererAccelClass)) +#define GIMP_IS_CELL_RENDERER_ACCEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CELL_RENDERER_ACCEL)) +#define GIMP_IS_CELL_RENDERER_ACCEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CELL_RENDERER_ACCEL)) +#define GIMP_CELL_RENDERER_ACCEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CELL_RENDERER_ACCEL, GimpCellRendererAccelClass)) + + +typedef struct _GimpCellRendererAccelClass GimpCellRendererAccelClass; + +struct _GimpCellRendererAccel +{ + GtkCellRendererText parent_instance; + + guint accel_key; + GdkModifierType accel_mask; + + GtkWidget *edit_widget; + GtkWidget *grab_widget; + guint edit_key; + + GtkWidget *sizing_label; +}; + +struct _GimpCellRendererAccelClass +{ + GtkCellRendererTextClass parent_class; + + void (* accel_edited) (GimpCellRendererAccel *accel, + const char *path_string, + guint accel_key, + GdkModifierType accel_mask); +}; + + +GType gimp_cell_renderer_accel_get_type (void); +GtkCellRenderer * gimp_cell_renderer_accel_new (void); + +void gimp_cell_renderer_accel_set_accelerator (GimpCellRendererAccel *accel, + guint accel_kaey, + GdkModifierType accel_mask); +void gimp_cell_renderer_accel_get_accelerator (GimpCellRendererAccel *accel, + guint *accel_key, + GdkModifierType *accel_mask); + + +#endif /* __GIMP_CELL_RENDERER_ACCEL_H__ */ diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h index 61fd18f086..7cb0d8a9e8 100644 --- a/app/widgets/widgets-types.h +++ b/app/widgets/widgets-types.h @@ -177,6 +177,7 @@ typedef struct _GimpPreviewRendererVectors GimpPreviewRendererVectors; /* cell renderers */ +typedef struct _GimpCellRendererAccel GimpCellRendererAccel; typedef struct _GimpCellRendererToggle GimpCellRendererToggle; typedef struct _GimpCellRendererViewable GimpCellRendererViewable;