From 5c1676dfe6d2f3c837a5e074117b45613fd29a72 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:30:19 +0200 Subject: Adding upstream version 2.10.34. Signed-off-by: Daniel Baumann --- app/widgets/gimpuimanager.c | 1369 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1369 insertions(+) create mode 100644 app/widgets/gimpuimanager.c (limited to 'app/widgets/gimpuimanager.c') diff --git a/app/widgets/gimpuimanager.c b/app/widgets/gimpuimanager.c new file mode 100644 index 0000000..59c795c --- /dev/null +++ b/app/widgets/gimpuimanager.c @@ -0,0 +1,1369 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpuimanager.c + * Copyright (C) 2004 Michael Natterer + * + * 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 3 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, see . + */ + +#include "config.h" + +#include + +#include +#undef GSEAL_ENABLE +#include +#include + +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpmarshal.h" + +#include "gimpaction.h" +#include "gimpactiongroup.h" +#include "gimphelp.h" +#include "gimphelp-ids.h" +#include "gimptoggleaction.h" +#include "gimpuimanager.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_NAME, + PROP_GIMP +}; + +enum +{ + UPDATE, + SHOW_TOOLTIP, + HIDE_TOOLTIP, + LAST_SIGNAL +}; + + +static void gimp_ui_manager_constructed (GObject *object); +static void gimp_ui_manager_dispose (GObject *object); +static void gimp_ui_manager_finalize (GObject *object); +static void gimp_ui_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_ui_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gimp_ui_manager_connect_proxy (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy); +static GtkWidget *gimp_ui_manager_get_widget_impl (GtkUIManager *manager, + const gchar *path); +static GtkAction *gimp_ui_manager_get_action_impl (GtkUIManager *manager, + const gchar *path); +static void gimp_ui_manager_real_update (GimpUIManager *manager, + gpointer update_data); +static GimpUIManagerUIEntry * + gimp_ui_manager_entry_get (GimpUIManager *manager, + const gchar *ui_path); +static gboolean gimp_ui_manager_entry_load (GimpUIManager *manager, + GimpUIManagerUIEntry *entry, + GError **error); +static GimpUIManagerUIEntry * + gimp_ui_manager_entry_ensure (GimpUIManager *manager, + const gchar *path); +static void gimp_ui_manager_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data); +static void gimp_ui_manager_menu_pos (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer data); +static void gimp_ui_manager_delete_popdown_data (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_item_realize (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_menu_item_select (GtkWidget *widget, + GimpUIManager *manager); +static void gimp_ui_manager_menu_item_deselect (GtkWidget *widget, + GimpUIManager *manager); +static gboolean gimp_ui_manager_item_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpUIManager *manager); +static GtkWidget *find_widget_under_pointer (GdkWindow *window, + gint *x, + gint *y); + + +G_DEFINE_TYPE (GimpUIManager, gimp_ui_manager, GTK_TYPE_UI_MANAGER) + +#define parent_class gimp_ui_manager_parent_class + +static guint manager_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_ui_manager_class_init (GimpUIManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkUIManagerClass *manager_class = GTK_UI_MANAGER_CLASS (klass); + + object_class->constructed = gimp_ui_manager_constructed; + object_class->dispose = gimp_ui_manager_dispose; + object_class->finalize = gimp_ui_manager_finalize; + object_class->set_property = gimp_ui_manager_set_property; + object_class->get_property = gimp_ui_manager_get_property; + + manager_class->connect_proxy = gimp_ui_manager_connect_proxy; + manager_class->get_widget = gimp_ui_manager_get_widget_impl; + manager_class->get_action = gimp_ui_manager_get_action_impl; + + klass->update = gimp_ui_manager_real_update; + + manager_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, update), + NULL, NULL, + gimp_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + manager_signals[SHOW_TOOLTIP] = + g_signal_new ("show-tooltip", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, show_tooltip), + NULL, NULL, + gimp_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + manager_signals[HIDE_TOOLTIP] = + g_signal_new ("hide-tooltip", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpUIManagerClass, hide_tooltip), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0, + G_TYPE_NONE); + + g_object_class_install_property (object_class, PROP_NAME, + g_param_spec_string ("name", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_GIMP, + g_param_spec_object ("gimp", + NULL, NULL, + GIMP_TYPE_GIMP, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + klass->managers = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); +} + +static void +gimp_ui_manager_init (GimpUIManager *manager) +{ + manager->name = NULL; + manager->gimp = NULL; +} + +static void +gimp_ui_manager_constructed (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + if (manager->name) + { + GimpUIManagerClass *manager_class; + GList *list; + + manager_class = GIMP_UI_MANAGER_GET_CLASS (object); + + list = g_hash_table_lookup (manager_class->managers, manager->name); + + list = g_list_append (list, manager); + + g_hash_table_replace (manager_class->managers, + g_strdup (manager->name), list); + } +} + +static void +gimp_ui_manager_dispose (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + if (manager->name) + { + GimpUIManagerClass *manager_class; + GList *list; + + manager_class = GIMP_UI_MANAGER_GET_CLASS (object); + + list = g_hash_table_lookup (manager_class->managers, manager->name); + + if (list) + { + list = g_list_remove (list, manager); + + if (list) + g_hash_table_replace (manager_class->managers, + g_strdup (manager->name), list); + else + g_hash_table_remove (manager_class->managers, manager->name); + } + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_ui_manager_finalize (GObject *object) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + GList *list; + + for (list = manager->registered_uis; list; list = g_list_next (list)) + { + GimpUIManagerUIEntry *entry = list->data; + + g_free (entry->ui_path); + g_free (entry->basename); + + if (entry->widget) + g_object_unref (entry->widget); + + g_slice_free (GimpUIManagerUIEntry, entry); + } + + g_clear_pointer (&manager->registered_uis, g_list_free); + g_clear_pointer (&manager->name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_ui_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + switch (prop_id) + { + case PROP_NAME: + g_free (manager->name); + manager->name = g_value_dup_string (value); + break; + + case PROP_GIMP: + manager->gimp = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_ui_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GimpUIManager *manager = GIMP_UI_MANAGER (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, manager->name); + break; + + case PROP_GIMP: + g_value_set_object (value, manager->gimp); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gimp_ui_manager_connect_proxy (GtkUIManager *manager, + GtkAction *action, + GtkWidget *proxy) +{ + g_object_set_qdata (G_OBJECT (proxy), GIMP_HELP_ID, + g_object_get_qdata (G_OBJECT (action), GIMP_HELP_ID)); + + if (GTK_IS_MENU_ITEM (proxy)) + { + g_signal_connect (proxy, "select", + G_CALLBACK (gimp_ui_manager_menu_item_select), + manager); + g_signal_connect (proxy, "deselect", + G_CALLBACK (gimp_ui_manager_menu_item_deselect), + manager); + + g_signal_connect_after (proxy, "realize", + G_CALLBACK (gimp_ui_manager_item_realize), + manager); + } +} + +static GtkWidget * +gimp_ui_manager_get_widget_impl (GtkUIManager *manager, + const gchar *path) +{ + GimpUIManagerUIEntry *entry; + + entry = gimp_ui_manager_entry_ensure (GIMP_UI_MANAGER (manager), path); + + if (entry) + { + if (! strcmp (entry->ui_path, path)) + return entry->widget; + + return GTK_UI_MANAGER_CLASS (parent_class)->get_widget (manager, path); + } + + return NULL; +} + +static GtkAction * +gimp_ui_manager_get_action_impl (GtkUIManager *manager, + const gchar *path) +{ + if (gimp_ui_manager_entry_ensure (GIMP_UI_MANAGER (manager), path)) + return GTK_UI_MANAGER_CLASS (parent_class)->get_action (manager, path); + + return NULL; +} + +static void +gimp_ui_manager_real_update (GimpUIManager *manager, + gpointer update_data) +{ + GList *list; + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + gimp_action_group_update (list->data, update_data); + } +} + +/** + * gimp_ui_manager_new: + * @gimp: the @Gimp instance this ui manager belongs to + * @name: the UI manager's name. + * + * Creates a new #GimpUIManager object. + * + * Returns: the new #GimpUIManager + */ +GimpUIManager * +gimp_ui_manager_new (Gimp *gimp, + const gchar *name) +{ + GimpUIManager *manager; + + g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL); + + manager = g_object_new (GIMP_TYPE_UI_MANAGER, + "name", name, + "gimp", gimp, + NULL); + + return manager; +} + +GList * +gimp_ui_managers_from_name (const gchar *name) +{ + GimpUIManagerClass *manager_class; + GList *list; + + g_return_val_if_fail (name != NULL, NULL); + + manager_class = g_type_class_ref (GIMP_TYPE_UI_MANAGER); + + list = g_hash_table_lookup (manager_class->managers, name); + + g_type_class_unref (manager_class); + + return list; +} + +void +gimp_ui_manager_update (GimpUIManager *manager, + gpointer update_data) +{ + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + + g_signal_emit (manager, manager_signals[UPDATE], 0, update_data); +} + +void +gimp_ui_manager_insert_action_group (GimpUIManager *manager, + GimpActionGroup *group, + gint pos) +{ + gtk_ui_manager_insert_action_group ((GtkUIManager *) manager, + (GtkActionGroup *) group, + pos); +} + +GimpActionGroup * +gimp_ui_manager_get_action_group (GimpUIManager *manager, + const gchar *name) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + GimpActionGroup *group = list->data; + + if (! strcmp (name, gimp_action_group_get_name (group))) + return group; + } + + return NULL; +} + +GList * +gimp_ui_manager_get_action_groups (GimpUIManager *manager) +{ + return gtk_ui_manager_get_action_groups ((GtkUIManager *) manager); +} + +GtkAccelGroup * +gimp_ui_manager_get_accel_group (GimpUIManager *manager) +{ + return gtk_ui_manager_get_accel_group ((GtkUIManager *) manager); +} + +GtkWidget * +gimp_ui_manager_get_widget (GimpUIManager *manager, + const gchar *path) +{ + return gtk_ui_manager_get_widget ((GtkUIManager *) manager, path); +} + +gchar * +gimp_ui_manager_get_ui (GimpUIManager *manager) +{ + return gtk_ui_manager_get_ui ((GtkUIManager *) manager); +} + +guint +gimp_ui_manager_new_merge_id (GimpUIManager *manager) +{ + return gtk_ui_manager_new_merge_id ((GtkUIManager *) manager); +} + +void +gimp_ui_manager_add_ui (GimpUIManager *manager, + guint merge_id, + const gchar *path, + const gchar *name, + const gchar *action, + GtkUIManagerItemType type, + gboolean top) +{ + gtk_ui_manager_add_ui ((GtkUIManager *) manager, merge_id, + path, name, action, type, top); +} + +void +gimp_ui_manager_remove_ui (GimpUIManager *manager, + guint merge_id) +{ + gtk_ui_manager_remove_ui ((GtkUIManager *) manager, merge_id); +} + +void +gimp_ui_manager_ensure_update (GimpUIManager *manager) +{ + gtk_ui_manager_ensure_update ((GtkUIManager *) manager); +} + +GimpAction * +gimp_ui_manager_get_action (GimpUIManager *manager, + const gchar *path) +{ + return (GimpAction *) gtk_ui_manager_get_action ((GtkUIManager *) manager, + path); +} + +GimpAction * +gimp_ui_manager_find_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name) +{ + GimpActionGroup *group; + GimpAction *action = NULL; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + if (group_name) + { + group = gimp_ui_manager_get_action_group (manager, group_name); + + if (group) + action = gimp_action_group_get_action (group, action_name); + } + else + { + GList *list; + + for (list = gimp_ui_manager_get_action_groups (manager); + list; + list = g_list_next (list)) + { + group = list->data; + + action = gimp_action_group_get_action (group, action_name); + + if (action) + break; + } + } + + return action; +} + +gboolean +gimp_ui_manager_activate_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name) +{ + GimpAction *action; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), FALSE); + g_return_val_if_fail (action_name != NULL, FALSE); + + action = gimp_ui_manager_find_action (manager, group_name, action_name); + + if (action) + gimp_action_activate (action); + + return (action != NULL); +} + +gboolean +gimp_ui_manager_toggle_action (GimpUIManager *manager, + const gchar *group_name, + const gchar *action_name, + gboolean active) +{ + GimpAction *action; + + g_return_val_if_fail (GIMP_IS_UI_MANAGER (manager), FALSE); + g_return_val_if_fail (action_name != NULL, FALSE); + + action = gimp_ui_manager_find_action (manager, group_name, action_name); + + if (GIMP_IS_TOGGLE_ACTION (action)) + gimp_toggle_action_set_active (GIMP_TOGGLE_ACTION (action), + active ? TRUE : FALSE); + + return GIMP_IS_TOGGLE_ACTION (action); +} + +void +gimp_ui_manager_ui_register (GimpUIManager *manager, + const gchar *ui_path, + const gchar *basename, + GimpUIManagerSetupFunc setup_func) +{ + GimpUIManagerUIEntry *entry; + + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + g_return_if_fail (ui_path != NULL); + g_return_if_fail (basename != NULL); + g_return_if_fail (gimp_ui_manager_entry_get (manager, ui_path) == NULL); + + entry = g_slice_new0 (GimpUIManagerUIEntry); + + entry->ui_path = g_strdup (ui_path); + entry->basename = g_strdup (basename); + entry->setup_func = setup_func; + entry->merge_id = 0; + entry->widget = NULL; + + manager->registered_uis = g_list_prepend (manager->registered_uis, entry); +} + + +typedef struct +{ + guint x; + guint y; +} MenuPos; + +static void +menu_pos_free (MenuPos *pos) +{ + g_slice_free (MenuPos, pos); +} + +void +gimp_ui_manager_ui_popup (GimpUIManager *manager, + const gchar *ui_path, + GtkWidget *parent, + GimpMenuPositionFunc position_func, + gpointer position_data, + GDestroyNotify popdown_func, + gpointer popdown_data) +{ + GtkWidget *widget; + GdkEvent *current_event; + gint x, y; + guint button; + guint32 activate_time; + MenuPos *menu_pos; + + g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); + g_return_if_fail (ui_path != NULL); + g_return_if_fail (parent == NULL || GTK_IS_WIDGET (parent)); + + widget = gimp_ui_manager_get_widget (manager, ui_path); + + if (GTK_IS_MENU_ITEM (widget)) + widget = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + + if (! widget) + return; + + g_return_if_fail (GTK_IS_MENU (widget)); + + if (! position_func) + { + position_func = gimp_ui_manager_menu_position; + position_data = parent; + } + + (* position_func) (GTK_MENU (widget), &x, &y, position_data); + + current_event = gtk_get_current_event (); + + if (current_event && current_event->type == GDK_BUTTON_PRESS) + { + GdkEventButton *bevent = (GdkEventButton *) current_event; + + button = bevent->button; + activate_time = bevent->time; + } + else + { + button = 0; + activate_time = 0; + } + + if (current_event) + gdk_event_free (current_event); + + menu_pos = g_object_get_data (G_OBJECT (widget), "menu-pos"); + + if (! menu_pos) + { + menu_pos = g_slice_new0 (MenuPos); + g_object_set_data_full (G_OBJECT (widget), "menu-pos", menu_pos, + (GDestroyNotify) menu_pos_free); + } + + menu_pos->x = x; + menu_pos->y = y; + + if (popdown_func && popdown_data) + { + g_object_set_data_full (G_OBJECT (manager), "popdown-data", + popdown_data, popdown_func); + g_signal_connect (widget, "selection-done", + G_CALLBACK (gimp_ui_manager_delete_popdown_data), + manager); + } + + gtk_menu_popup (GTK_MENU (widget), + NULL, NULL, + gimp_ui_manager_menu_pos, menu_pos, + button, activate_time); +} + + +/* private functions */ + +static GimpUIManagerUIEntry * +gimp_ui_manager_entry_get (GimpUIManager *manager, + const gchar *ui_path) +{ + GList *list; + gchar *path; + + path = g_strdup (ui_path); + + if (strlen (path) > 1) + { + gchar *p = strchr (path + 1, '/'); + + if (p) + *p = '\0'; + } + + for (list = manager->registered_uis; list; list = g_list_next (list)) + { + GimpUIManagerUIEntry *entry = list->data; + + if (! strcmp (entry->ui_path, path)) + { + g_free (path); + + return entry; + } + } + + g_free (path); + + return NULL; +} + +static gboolean +gimp_ui_manager_entry_load (GimpUIManager *manager, + GimpUIManagerUIEntry *entry, + GError **error) +{ + gchar *filename = NULL; + const gchar *menus_path_override = g_getenv ("GIMP_TESTING_MENUS_PATH"); + + /* In order for test cases to be able to run without GIMP being + * installed yet, allow them to override the menus directory to the + * menus dir in the source root + */ + if (menus_path_override) + { + GList *path = gimp_path_parse (menus_path_override, 2, FALSE, NULL); + GList *list; + + for (list = path; list; list = g_list_next (list)) + { + filename = g_build_filename (list->data, entry->basename, NULL); + + if (! list->next || + g_file_test (filename, G_FILE_TEST_EXISTS)) + break; + + g_free (filename); + } + + g_list_free_full (path, g_free); + } + else + { + filename = g_build_filename (gimp_data_directory (), "menus", + entry->basename, NULL); + } + + if (manager->gimp->be_verbose) + g_print ("loading menu '%s' for %s\n", + gimp_filename_to_utf8 (filename), entry->ui_path); + + entry->merge_id = gtk_ui_manager_add_ui_from_file (GTK_UI_MANAGER (manager), + filename, error); + + g_free (filename); + + if (! entry->merge_id) + return FALSE; + + return TRUE; +} + +static GimpUIManagerUIEntry * +gimp_ui_manager_entry_ensure (GimpUIManager *manager, + const gchar *path) +{ + GimpUIManagerUIEntry *entry; + + entry = gimp_ui_manager_entry_get (manager, path); + + if (! entry) + { + g_warning ("%s: no entry registered for \"%s\"", G_STRFUNC, path); + return NULL; + } + + if (! entry->merge_id) + { + GError *error = NULL; + + if (! gimp_ui_manager_entry_load (manager, entry, &error)) + { + if (error->domain == G_FILE_ERROR && + error->code == G_FILE_ERROR_EXIST) + { + gimp_message (manager->gimp, NULL, GIMP_MESSAGE_ERROR, + "%s\n\n%s\n\n%s", + _("Your GIMP installation is incomplete:"), + error->message, + _("Please make sure the menu XML files are " + "correctly installed.")); + } + else + { + gimp_message (manager->gimp, NULL, GIMP_MESSAGE_ERROR, + _("There was an error parsing the menu definition " + "from %s: %s"), + gimp_filename_to_utf8 (entry->basename), + error->message); + } + + g_clear_error (&error); + return NULL; + } + } + + if (! entry->widget) + { + GtkUIManager *gtk_manager = GTK_UI_MANAGER (manager); + + entry->widget = + GTK_UI_MANAGER_CLASS (parent_class)->get_widget (gtk_manager, + entry->ui_path); + + if (entry->widget) + { + g_object_ref (entry->widget); + + /* take ownership of popup menus */ + if (GTK_IS_MENU (entry->widget)) + { + g_object_ref_sink (entry->widget); + g_object_unref (entry->widget); + } + + if (entry->setup_func) + entry->setup_func (manager, entry->ui_path); + } + else + { + g_warning ("%s: \"%s\" does not contain registered toplevel " + "widget \"%s\"", + G_STRFUNC, entry->basename, entry->ui_path); + return NULL; + } + } + + return entry; +} + +static void +gimp_ui_manager_menu_position (GtkMenu *menu, + gint *x, + gint *y, + gpointer data) +{ + GdkScreen *screen; + GtkRequisition requisition; + GdkRectangle rect; + gint monitor; + gint pointer_x; + gint pointer_y; + + g_return_if_fail (GTK_IS_MENU (menu)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + g_return_if_fail (GTK_IS_WIDGET (data)); + + gdk_display_get_pointer (gtk_widget_get_display (GTK_WIDGET (data)), + &screen, &pointer_x, &pointer_y, NULL); + + monitor = gdk_screen_get_monitor_at_point (screen, pointer_x, pointer_y); + gdk_screen_get_monitor_workarea (screen, monitor, &rect); + + gtk_menu_set_screen (menu, screen); + + gtk_widget_size_request (GTK_WIDGET (menu), &requisition); + + if (gtk_widget_get_direction (GTK_WIDGET (menu)) == GTK_TEXT_DIR_RTL) + { + *x = pointer_x - 2 - requisition.width; + + if (*x < rect.x) + *x = pointer_x + 2; + } + else + { + *x = pointer_x + 2; + + if (*x + requisition.width > rect.x + rect.width) + *x = pointer_x - 2 - requisition.width; + } + + *y = pointer_y + 2; + + if (*y + requisition.height > rect.y + rect.height) + *y = pointer_y - 2 - requisition.height; + + if (*x < rect.x) *x = rect.x; + if (*y < rect.y) *y = rect.y; +} + +static void +gimp_ui_manager_menu_pos (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer data) +{ + MenuPos *menu_pos = data; + + *x = menu_pos->x; + *y = menu_pos->y; +} + +static void +gimp_ui_manager_delete_popdown_data (GtkWidget *widget, + GimpUIManager *manager) +{ + g_signal_handlers_disconnect_by_func (widget, + gimp_ui_manager_delete_popdown_data, + manager); + g_object_set_data (G_OBJECT (manager), "popdown-data", NULL); +} + +static void +gimp_ui_manager_item_realize (GtkWidget *widget, + GimpUIManager *manager) +{ + GtkWidget *menu; + GtkWidget *submenu; + + g_signal_handlers_disconnect_by_func (widget, + gimp_ui_manager_item_realize, + manager); + + menu = gtk_widget_get_parent (widget); + + if (GTK_IS_MENU_SHELL (menu)) + { + static GQuark quark_key_press_connected = 0; + + if (! quark_key_press_connected) + quark_key_press_connected = + g_quark_from_static_string ("gimp-menu-item-key-press-connected"); + + if (! GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (menu), + quark_key_press_connected))) + { + g_signal_connect (menu, "key-press-event", + G_CALLBACK (gimp_ui_manager_item_key_press), + manager); + + g_object_set_qdata (G_OBJECT (menu), + quark_key_press_connected, + GINT_TO_POINTER (TRUE)); + } + } + + submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)); + + if (submenu) + g_object_set_qdata (G_OBJECT (submenu), GIMP_HELP_ID, + g_object_get_qdata (G_OBJECT (widget), + GIMP_HELP_ID)); +} + +static void +gimp_ui_manager_menu_item_select (GtkWidget *widget, + GimpUIManager *manager) +{ + GtkAction *action = + gtk_activatable_get_related_action (GTK_ACTIVATABLE (widget)); + + if (action) + { + const gchar *tooltip = gimp_action_get_tooltip (GIMP_ACTION (action)); + + if (tooltip) + g_signal_emit (manager, manager_signals[SHOW_TOOLTIP], 0, tooltip); + } +} + +static void +gimp_ui_manager_menu_item_deselect (GtkWidget *widget, + GimpUIManager *manager) +{ + g_signal_emit (manager, manager_signals[HIDE_TOOLTIP], 0); +} + +static gboolean +gimp_ui_manager_item_key_press (GtkWidget *widget, + GdkEventKey *kevent, + GimpUIManager *manager) +{ + gchar *help_id = NULL; + + while (! help_id && GTK_IS_MENU_SHELL (widget)) + { + GtkWidget *menu_item = GTK_MENU_SHELL (widget)->active_menu_item; + + if (! menu_item && GTK_IS_MENU (widget)) + { + GtkWidget *parent = gtk_widget_get_parent (widget); + GdkWindow *window = gtk_widget_get_window (parent); + + if (window) + { + gint x, y; + + gdk_window_get_pointer (window, &x, &y, NULL); + menu_item = find_widget_under_pointer (window, &x, &y); + + if (menu_item && ! GTK_IS_MENU_ITEM (menu_item)) + { + menu_item = gtk_widget_get_ancestor (menu_item, + GTK_TYPE_MENU_ITEM); + + if (! GTK_IS_MENU_ITEM (menu_item)) + menu_item = NULL; + } + } + } + + /* first, get the help page from the item... + */ + if (menu_item) + { + help_id = g_object_get_qdata (G_OBJECT (menu_item), GIMP_HELP_ID); + + if (help_id && ! strlen (help_id)) + help_id = NULL; + } + + /* ...then try the parent menu... + */ + if (! help_id) + { + help_id = g_object_get_qdata (G_OBJECT (widget), GIMP_HELP_ID); + + if (help_id && ! strlen (help_id)) + help_id = NULL; + } + + /* ...finally try the menu's parent (if any) + */ + if (! help_id) + { + menu_item = NULL; + + if (GTK_IS_MENU (widget)) + menu_item = gtk_menu_get_attach_widget (GTK_MENU (widget)); + + if (! menu_item) + break; + + widget = gtk_widget_get_parent (menu_item); + + if (! widget) + break; + } + } + + /* For any valid accelerator key except F1, continue with the + * standard GtkMenuShell callback and assign a new shortcut, but + * don't assign a shortcut to the help menu entries ... + */ + if (kevent->keyval != GDK_KEY_F1) + { + if (help_id && + gtk_accelerator_valid (kevent->keyval, 0) && + (strcmp (help_id, GIMP_HELP_HELP) == 0 || + strcmp (help_id, GIMP_HELP_HELP_CONTEXT) == 0)) + { + return TRUE; + } + + return FALSE; + } + + /* ...finally, if F1 was pressed over any menu, show its help page... */ + + if (help_id) + { + gchar *help_domain = NULL; + gchar *help_string = NULL; + gchar *domain_separator; + + help_id = g_strdup (help_id); + + domain_separator = strchr (help_id, '?'); + + if (domain_separator) + { + *domain_separator = '\0'; + + help_domain = g_strdup (help_id); + help_string = g_strdup (domain_separator + 1); + } + else + { + help_string = g_strdup (help_id); + } + + gimp_help (manager->gimp, NULL, help_domain, help_string); + + g_free (help_domain); + g_free (help_string); + g_free (help_id); + } + + return TRUE; +} + + +/* Stuff below taken from gtktooltip.c + */ + +/* FIXME: remove this crack as soon as a GTK+ widget_under_pointer() is available */ + +struct ChildLocation +{ + GtkWidget *child; + GtkWidget *container; + + gint x; + gint y; +}; + +static void +child_location_foreach (GtkWidget *child, + gpointer data) +{ + gint x, y; + struct ChildLocation *child_loc = data; + + /* Ignore invisible widgets */ + if (! gtk_widget_is_drawable (child)) + return; + + /* (child_loc->x, child_loc->y) are relative to + * child_loc->container's allocation. + */ + + if (! child_loc->child && + gtk_widget_translate_coordinates (child_loc->container, child, + child_loc->x, child_loc->y, + &x, &y)) + { + GtkAllocation child_allocation; + + gtk_widget_get_allocation (child, &child_allocation); + +#ifdef DEBUG_TOOLTIP + g_print ("candidate: %s alloc=[(%d,%d) %dx%d] (%d, %d)->(%d, %d)\n", + gtk_widget_get_name (child), + child_allocation.x, + child_allocation.y, + child_allocation.width, + child_allocation.height, + child_loc->x, child_loc->y, + x, y); +#endif /* DEBUG_TOOLTIP */ + + /* (x, y) relative to child's allocation. */ + if (x >= 0 && x < child_allocation.width + && y >= 0 && y < child_allocation.height) + { + if (GTK_IS_CONTAINER (child)) + { + struct ChildLocation tmp = { NULL, NULL, 0, 0 }; + + /* Take (x, y) relative the child's allocation and + * recurse. + */ + tmp.x = x; + tmp.y = y; + tmp.container = child; + + gtk_container_forall (GTK_CONTAINER (child), + child_location_foreach, &tmp); + + if (tmp.child) + child_loc->child = tmp.child; + else + child_loc->child = child; + } + else + { + child_loc->child = child; + } + } + } +} + +/* Translates coordinates from dest_widget->window relative (src_x, src_y), + * to allocation relative (dest_x, dest_y) of dest_widget. + */ +static void +window_to_alloc (GtkWidget *dest_widget, + gint src_x, + gint src_y, + gint *dest_x, + gint *dest_y) +{ + GtkAllocation dest_allocation; + + gtk_widget_get_allocation (dest_widget, &dest_allocation); + + /* Translate from window relative to allocation relative */ + if (gtk_widget_get_has_window (dest_widget) && + gtk_widget_get_parent (dest_widget)) + { + gint wx, wy; + + gdk_window_get_position (gtk_widget_get_window (dest_widget), &wx, &wy); + + /* Offset coordinates if widget->window is smaller than + * widget->allocation. + */ + src_x += wx - dest_allocation.x; + src_y += wy - dest_allocation.y; + } + else + { + src_x -= dest_allocation.x; + src_y -= dest_allocation.y; + } + + if (dest_x) + *dest_x = src_x; + if (dest_y) + *dest_y = src_y; +} + +static GtkWidget * +find_widget_under_pointer (GdkWindow *window, + gint *x, + gint *y) +{ + GtkWidget *event_widget; + struct ChildLocation child_loc = { NULL, NULL, 0, 0 }; + + gdk_window_get_user_data (window, (void **)&event_widget); + + if (! event_widget) + return NULL; + +#ifdef DEBUG_TOOLTIP + g_print ("event window %p (belonging to %p (%s)) (%d, %d)\n", + window, event_widget, gtk_widget_get_name (event_widget), + *x, *y); +#endif + + /* Coordinates are relative to event window */ + child_loc.x = *x; + child_loc.y = *y; + + /* We go down the window hierarchy to the widget->window, + * coordinates stay relative to the current window. + * We end up with window == widget->window, coordinates relative to that. + */ + while (window && window != gtk_widget_get_window (event_widget)) + { + gint px, py; + + gdk_window_get_position (window, &px, &py); + child_loc.x += px; + child_loc.y += py; + + window = gdk_window_get_parent (window); + } + + /* Failing to find widget->window can happen for e.g. a detached handle box; + * chaining ::query-tooltip up to its parent probably makes little sense, + * and users better implement tooltips on handle_box->child. + * so we simply ignore the event for tooltips here. + */ + if (!window) + return NULL; + + /* Convert the window relative coordinates to allocation + * relative coordinates. + */ + window_to_alloc (event_widget, + child_loc.x, child_loc.y, + &child_loc.x, &child_loc.y); + + if (GTK_IS_CONTAINER (event_widget)) + { + GtkWidget *container = event_widget; + + child_loc.container = event_widget; + child_loc.child = NULL; + + gtk_container_forall (GTK_CONTAINER (event_widget), + child_location_foreach, &child_loc); + + /* Here we have a widget, with coordinates relative to + * child_loc.container's allocation. + */ + + if (child_loc.child) + event_widget = child_loc.child; + else if (child_loc.container) + event_widget = child_loc.container; + + /* Translate to event_widget's allocation */ + gtk_widget_translate_coordinates (container, event_widget, + child_loc.x, child_loc.y, + &child_loc.x, &child_loc.y); + + } + + /* We return (x, y) relative to the allocation of event_widget. */ + *x = child_loc.x; + *y = child_loc.y; + + return event_widget; +} -- cgit v1.2.3