/* 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; }