/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimptoolbutton.c * Copyright (C) 2020 Ell * * 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 #include #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "libgimpwidgets/gimpwidgets.h" #include "widgets-types.h" #include "core/gimp.h" #include "core/gimp-gui.h" #include "core/gimpcontainer.h" #include "core/gimpcontext.h" #include "core/gimptoolgroup.h" #include "core/gimptoolinfo.h" #include "actions/tools-commands.h" #include "gimpaccellabel.h" #include "gimpaction.h" #include "gimpdock.h" #include "gimptoolbox.h" #include "gimptoolbutton.h" #include "gimpuimanager.h" #include "gimpwidgets-utils.h" #include "gimpwindowstrategy.h" #include "gimp-intl.h" #define ARROW_SIZE 0.125 /* * 100% */ #define ARROW_BORDER 3 /* px */ #define MENU_TIMEOUT 250 /* milliseconds */ enum { PROP_0, PROP_TOOLBOX, PROP_TOOL_ITEM, PROP_SHOW_MENU_ON_HOVER }; struct _GimpToolButtonPrivate { GimpToolbox *toolbox; GimpToolItem *tool_item; gboolean show_menu_on_hover; GtkWidget *palette; GtkWidget *tooltip_widget; GtkWidget *menu; GHashTable *menu_items; gint menu_idle_id; gint menu_timeout_id; gint menu_timeout_button; guint32 menu_timeout_time; gint menu_select_idle_id; }; /* local function prototypes */ static void gimp_tool_button_constructed (GObject *object); static void gimp_tool_button_dispose (GObject *object); static void gimp_tool_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_tool_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gimp_tool_button_hierarchy_changed (GtkWidget *widget, GtkWidget *previous_toplevel); static gboolean gimp_tool_button_expose (GtkWidget *widget, GdkEventExpose *event); static gboolean gimp_tool_button_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip); static void gimp_tool_button_toggled (GtkToggleToolButton *toggle_tool_button); static gboolean gimp_tool_button_enter_notify (GtkWidget *widget, GdkEventCrossing *event, GimpToolButton *tool_button); static gboolean gimp_tool_button_leave_notify (GtkWidget *widget, GdkEventCrossing *event, GimpToolButton *tool_button); static gboolean gimp_tool_button_button_press (GtkWidget *widget, GdkEventButton *event, GimpToolButton *tool_button); static gboolean gimp_tool_button_button_release (GtkWidget *widget, GdkEventButton *event, GimpToolButton *tool_button); static gboolean gimp_tool_button_scroll (GtkWidget *widget, GdkEventScroll *event, GimpToolButton *tool_button); static void gimp_tool_button_tool_changed (GimpContext *context, GimpToolInfo *tool_info, GimpToolButton *tool_button); static void gimp_tool_button_active_tool_changed (GimpToolGroup *tool_group, GimpToolButton *tool_button); static void gimp_tool_button_tool_add (GimpContainer *container, GimpToolInfo *tool_info, GimpToolButton *tool_button); static void gimp_tool_button_tool_remove (GimpContainer *container, GimpToolInfo *tool_info, GimpToolButton *tool_button); static void gimp_tool_button_tool_reorder (GimpContainer *container, GimpToolInfo *tool_info, gint new_index, GimpToolButton *tool_button); static void gimp_tool_button_icon_size_notify (GtkToolPalette *palette, const GParamSpec *pspec, GimpToolButton *tool_button); static gboolean gimp_tool_button_menu_enter_notify (GtkMenu *menu, GdkEventCrossing *event, GimpToolButton *tool_button); static gboolean gimp_tool_button_menu_leave_notify (GtkMenu *menu, GdkEventCrossing *event, GimpToolButton *tool_button); static void gimp_tool_button_menu_deactivate (GtkMenu *menu, GimpToolButton *tool_button); static gboolean gimp_tool_button_menu_idle (GimpToolButton *tool_button); static gboolean gimp_tool_button_menu_timeout (GimpToolButton *tool_button); static void gimp_tool_button_update (GimpToolButton *tool_button); static void gimp_tool_button_update_toggled (GimpToolButton *tool_button); static void gimp_tool_button_update_menu (GimpToolButton *tool_button); static void gimp_tool_button_add_menu_item (GimpToolButton *tool_button, GimpToolInfo *tool_info, gint index); static void gimp_tool_button_remove_menu_item (GimpToolButton *tool_button, GimpToolInfo *tool_info); static void gimp_tool_button_reconstruct_menu (GimpToolButton *tool_button); static void gimp_tool_button_destroy_menu (GimpToolButton *tool_button); static gboolean gimp_tool_button_show_menu (GimpToolButton *tool_button, gint button, guint32 activate_time); static GimpAction * gimp_tool_button_get_action (GimpToolButton *tool_button, GimpToolInfo *tool_info); G_DEFINE_TYPE_WITH_PRIVATE (GimpToolButton, gimp_tool_button, GTK_TYPE_TOGGLE_TOOL_BUTTON) #define parent_class gimp_tool_button_parent_class /* private functions */ static void gimp_tool_button_class_init (GimpToolButtonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkToggleToolButtonClass *toggle_tool_button_class = GTK_TOGGLE_TOOL_BUTTON_CLASS (klass); object_class->constructed = gimp_tool_button_constructed; object_class->dispose = gimp_tool_button_dispose; object_class->get_property = gimp_tool_button_get_property; object_class->set_property = gimp_tool_button_set_property; widget_class->hierarchy_changed = gimp_tool_button_hierarchy_changed; widget_class->expose_event = gimp_tool_button_expose; widget_class->query_tooltip = gimp_tool_button_query_tooltip; toggle_tool_button_class->toggled = gimp_tool_button_toggled; g_object_class_install_property (object_class, PROP_TOOLBOX, g_param_spec_object ("toolbox", NULL, NULL, GIMP_TYPE_TOOLBOX, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_TOOL_ITEM, g_param_spec_object ("tool-item", NULL, NULL, GIMP_TYPE_TOOL_ITEM, GIMP_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SHOW_MENU_ON_HOVER, g_param_spec_boolean ("show-menu-on-hover", NULL, NULL, FALSE, GIMP_PARAM_READWRITE)); } static void gimp_tool_button_init (GimpToolButton *tool_button) { tool_button->priv = gimp_tool_button_get_instance_private (tool_button); } static void gimp_tool_button_constructed (GObject *object) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); GimpContext *context; G_OBJECT_CLASS (parent_class)->constructed (object); context = gimp_toolbox_get_context (tool_button->priv->toolbox); /* Make sure the toolbox buttons won't grab focus, which has * nearly no practical use, and prevents various actions until * you click back in canvas. */ gtk_widget_set_can_focus (gtk_bin_get_child (GTK_BIN (tool_button)), FALSE); gtk_widget_add_events (gtk_bin_get_child (GTK_BIN (tool_button)), GDK_SCROLL_MASK); g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), "enter-notify-event", G_CALLBACK (gimp_tool_button_enter_notify), tool_button); g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), "leave-notify-event", G_CALLBACK (gimp_tool_button_leave_notify), tool_button); g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), "button-press-event", G_CALLBACK (gimp_tool_button_button_press), tool_button); g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), "button-release-event", G_CALLBACK (gimp_tool_button_button_release), tool_button); g_signal_connect (gtk_bin_get_child (GTK_BIN (tool_button)), "scroll-event", G_CALLBACK (gimp_tool_button_scroll), tool_button); g_signal_connect_object (context, "tool-changed", G_CALLBACK (gimp_tool_button_tool_changed), tool_button, 0); gimp_tool_button_update (tool_button); } static void gimp_tool_button_dispose (GObject *object) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); gimp_tool_button_set_tool_item (tool_button, NULL); G_OBJECT_CLASS (parent_class)->dispose (object); } static void gimp_tool_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); switch (property_id) { case PROP_TOOLBOX: tool_button->priv->toolbox = g_value_get_object (value); break; case PROP_TOOL_ITEM: gimp_tool_button_set_tool_item (tool_button, g_value_get_object (value)); break; case PROP_SHOW_MENU_ON_HOVER: gimp_tool_button_set_show_menu_on_hover (tool_button, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_tool_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (object); switch (property_id) { case PROP_TOOLBOX: g_value_set_object (value, tool_button->priv->toolbox); break; case PROP_TOOL_ITEM: g_value_set_object (value, tool_button->priv->tool_item); break; case PROP_SHOW_MENU_ON_HOVER: g_value_set_boolean (value, tool_button->priv->show_menu_on_hover); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_tool_button_hierarchy_changed (GtkWidget *widget, GtkWidget *previous_toplevel) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); GtkWidget *palette; if (GTK_WIDGET_CLASS (parent_class)->hierarchy_changed) { GTK_WIDGET_CLASS (parent_class)->hierarchy_changed (widget, previous_toplevel); } palette = gtk_widget_get_ancestor (GTK_WIDGET (tool_button), GTK_TYPE_TOOL_PALETTE); if (palette != tool_button->priv->palette) { if (tool_button->priv->palette) { g_signal_handlers_disconnect_by_func ( tool_button->priv->palette, gimp_tool_button_icon_size_notify, tool_button); } tool_button->priv->palette = palette; if (tool_button->priv->palette) { g_signal_connect ( tool_button->priv->palette, "notify::icon-size", G_CALLBACK (gimp_tool_button_icon_size_notify), tool_button); } } gimp_tool_button_update (tool_button); gimp_tool_button_reconstruct_menu (tool_button); } static gboolean gimp_tool_button_expose (GtkWidget *widget, GdkEventExpose *event) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); if (GTK_WIDGET_CLASS (parent_class)->expose_event) GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); if (! gtk_widget_is_drawable (widget)) return FALSE; if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { cairo_t *cr; GtkStyle *style = gtk_widget_get_style (widget); GtkStateType state = gtk_widget_get_state (widget); GtkAllocation allocation; gint size; gint x1, y1; gint x2, y2; cr = gdk_cairo_create (event->window); gdk_cairo_region (cr, event->region); cairo_clip (cr); gtk_widget_get_allocation (widget, &allocation); size = MIN (allocation.width, allocation.height); x1 = SIGNED_ROUND (allocation.x + allocation.width - (ARROW_BORDER + size * ARROW_SIZE)); y1 = SIGNED_ROUND (allocation.y + allocation.height - (ARROW_BORDER + size * ARROW_SIZE)); x2 = SIGNED_ROUND (allocation.x + allocation.width - ARROW_BORDER); y2 = SIGNED_ROUND (allocation.y + allocation.height - ARROW_BORDER); cairo_move_to (cr, x2, y1); cairo_line_to (cr, x2, y2); cairo_line_to (cr, x1, y2); cairo_close_path (cr); gdk_cairo_set_source_color (cr, &style->fg[state]); cairo_fill (cr); cairo_destroy (cr); } return FALSE; } static GtkWidget * gimp_tool_button_query_tooltip_add_tool (GimpToolButton *tool_button, GtkTable *table, gint row, GimpToolInfo *tool_info, const gchar *label_str, GtkIconSize icon_size) { GimpUIManager *ui_manager; GimpAction *action = NULL; GtkWidget *label; GtkWidget *image; ui_manager = gimp_dock_get_ui_manager ( GIMP_DOCK (tool_button->priv->toolbox)); if (ui_manager) { gchar *name; name = gimp_tool_info_get_action_name (tool_info); action = gimp_ui_manager_find_action (ui_manager, "tools", name); g_free (name); } image = gtk_image_new_from_icon_name ( gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)), icon_size); gtk_table_attach (table, image, 0, 1, row, row + 1, GTK_FILL, 0, 0, 0); gtk_widget_show (image); label = gtk_label_new (label_str); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_table_attach (table, label, 1, 2, row, row + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0); gtk_widget_show (label); if (action) { GtkWidget *accel_label; accel_label = gimp_accel_label_new (action); gtk_label_set_xalign (GTK_LABEL (accel_label), 1.0); gtk_table_attach (table, accel_label, 2, 3, row, row + 1, GTK_FILL, 0, 0, 0); gtk_widget_show (accel_label); } return label; } static gboolean gimp_tool_button_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget); if (! tool_button->priv->tooltip_widget) { GimpToolInfo *tool_info; GtkWidget *table; GtkWidget *label; gchar **tooltip_labels; GtkIconSize icon_size = GTK_ICON_SIZE_MENU; gint row = 0; tool_info = gimp_tool_button_get_tool_info (tool_button); if (! tool_info) return FALSE; if (tool_button->priv->palette) { icon_size = gtk_tool_palette_get_icon_size ( GTK_TOOL_PALETTE (tool_button->priv->palette)); } table = gtk_table_new (2, 3, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 4); gtk_table_set_col_spacings (GTK_TABLE (table), 4); gtk_table_set_col_spacing (GTK_TABLE (table), 1, 32); gtk_widget_show (table); tool_button->priv->tooltip_widget = g_object_ref_sink (table); tooltip_labels = g_strsplit (tool_info->tooltip, ": ", 2); label = gimp_tool_button_query_tooltip_add_tool (tool_button, GTK_TABLE (table), row++, tool_info, tooltip_labels[0], icon_size); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, -1); if (tooltip_labels[0]) { gtk_table_set_row_spacing (GTK_TABLE (table), 0, 0); label = gtk_label_new (tooltip_labels[1]); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row + 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); gtk_widget_show (label); row++; } g_strfreev (tooltip_labels); if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { GimpContainer *children; gint n_children; children = gimp_viewable_get_children ( GIMP_VIEWABLE (tool_button->priv->tool_item)); n_children = gimp_container_get_n_children (children); if (n_children > 1) { GtkWidget *label; gint i; gtk_table_resize (GTK_TABLE (table), row + n_children, 3); gtk_table_set_row_spacing (GTK_TABLE (table), 1, 12); label = gtk_label_new (_("Also in group:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, -1); gtk_table_attach (GTK_TABLE (table), label, 0, 3, row, row + 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); gtk_widget_show (label); row++; for (i = 0; i < n_children; i++) { GimpToolInfo *other_tool_info; other_tool_info = GIMP_TOOL_INFO ( gimp_container_get_child_by_index (children, i)); if (other_tool_info != tool_info) { gimp_tool_button_query_tooltip_add_tool ( tool_button, GTK_TABLE (table), row++, other_tool_info, other_tool_info->label, icon_size); } } } } } gtk_tooltip_set_custom (tooltip, tool_button->priv->tooltip_widget); return TRUE; } static void gimp_tool_button_toggled (GtkToggleToolButton *toggle_tool_button) { GimpToolButton *tool_button = GIMP_TOOL_BUTTON (toggle_tool_button); GimpContext *context; GimpToolInfo *tool_info; if (GTK_TOGGLE_TOOL_BUTTON_CLASS (parent_class)->toggled) GTK_TOGGLE_TOOL_BUTTON_CLASS (parent_class)->toggled (toggle_tool_button); context = gimp_toolbox_get_context (tool_button->priv->toolbox); tool_info = gimp_tool_button_get_tool_info (tool_button); if (tool_info) { if (gtk_toggle_tool_button_get_active (toggle_tool_button)) gimp_context_set_tool (context, tool_info); else if (tool_info == gimp_context_get_tool (context)) gtk_toggle_tool_button_set_active (toggle_tool_button, TRUE); } else { gtk_toggle_tool_button_set_active (toggle_tool_button, FALSE); } } static gboolean gimp_tool_button_enter_notify (GtkWidget *widget, GdkEventCrossing *event, GimpToolButton *tool_button) { guint button_state; button_state = event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | GDK_BUTTON5_MASK); if (tool_button->priv->menu && tool_button->priv->show_menu_on_hover && ! gtk_widget_get_visible (tool_button->priv->menu) && event->mode == GDK_CROSSING_NORMAL && button_state == 0) { if (tool_button->priv->menu_idle_id) { g_source_remove (tool_button->priv->menu_idle_id); tool_button->priv->menu_idle_id = 0; } gimp_tool_button_show_menu (tool_button, 0, event->time); } return FALSE; } static gboolean gimp_tool_button_leave_notify (GtkWidget *widget, GdkEventCrossing *event, GimpToolButton *tool_button) { if (tool_button->priv->menu && tool_button->priv->show_menu_on_hover && gtk_widget_get_visible (tool_button->priv->menu)) { if (event->mode == GDK_CROSSING_NORMAL) { if (! tool_button->priv->menu_idle_id) { tool_button->priv->menu_idle_id = g_idle_add_full ( G_PRIORITY_DEFAULT + 1, (GSourceFunc) gimp_tool_button_menu_idle, tool_button, NULL); } } else { return TRUE; } } return FALSE; } static gboolean gimp_tool_button_button_press (GtkWidget *widget, GdkEventButton *event, GimpToolButton *tool_button) { if (tool_button->priv->menu) { if (gtk_widget_get_visible (tool_button->priv->menu)) { gtk_menu_shell_deactivate (GTK_MENU_SHELL (tool_button->priv->menu)); } else if (gdk_event_triggers_context_menu ((GdkEvent *) event) || tool_button->priv->show_menu_on_hover) { return gimp_tool_button_show_menu (tool_button, event->button, event->time); } else if (event->type == GDK_BUTTON_PRESS && event->button == 1 && ! tool_button->priv->menu_timeout_id) { tool_button->priv->menu_timeout_button = event->button; tool_button->priv->menu_timeout_time = event->time + MENU_TIMEOUT; tool_button->priv->menu_timeout_id = g_timeout_add ( MENU_TIMEOUT, (GSourceFunc) gimp_tool_button_menu_timeout, tool_button); } } if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { GimpContext *context; GimpDock *dock; context = gimp_toolbox_get_context (tool_button->priv->toolbox); dock = GIMP_DOCK (tool_button->priv->toolbox); gimp_window_strategy_show_dockable_dialog ( GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (context->gimp)), context->gimp, gimp_dock_get_dialog_factory (dock), gtk_widget_get_screen (widget), gimp_widget_get_monitor (widget), "gimp-tool-options"); return TRUE; } return FALSE; } static gboolean gimp_tool_button_button_release (GtkWidget *widget, GdkEventButton *event, GimpToolButton *tool_button) { if (event->button == 1 && tool_button->priv->menu_timeout_id) { g_source_remove (tool_button->priv->menu_timeout_id); tool_button->priv->menu_timeout_id = 0; } return FALSE; } static gboolean gimp_tool_button_scroll (GtkWidget *widget, GdkEventScroll *event, GimpToolButton *tool_button) { GimpToolInfo *tool_info; gint delta; tool_info = gimp_tool_button_get_tool_info (tool_button); switch (event->direction) { case GDK_SCROLL_UP: delta = -1; break; case GDK_SCROLL_DOWN: delta = +1; break; default: return FALSE; } if (tool_info && GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { GimpContainer *children; gint n_children; gint index; gint i; children = gimp_viewable_get_children ( GIMP_VIEWABLE (tool_button->priv->tool_item)); n_children = gimp_container_get_n_children (children); index = gimp_container_get_child_index (children, GIMP_OBJECT (tool_info)); for (i = 1; i < n_children; i++) { GimpToolInfo *new_tool_info; gint new_index; new_index = (index + i * delta) % n_children; if (new_index < 0) new_index += n_children; new_tool_info = GIMP_TOOL_INFO ( gimp_container_get_child_by_index (children, new_index)); if (gimp_tool_item_get_visible (GIMP_TOOL_ITEM (new_tool_info))) { gimp_tool_group_set_active_tool_info ( GIMP_TOOL_GROUP (tool_button->priv->tool_item), new_tool_info); break; } } } return FALSE; } static void gimp_tool_button_tool_changed (GimpContext *context, GimpToolInfo *tool_info, GimpToolButton *tool_button) { gimp_tool_button_update_toggled (tool_button); } static void gimp_tool_button_active_tool_changed (GimpToolGroup *tool_group, GimpToolButton *tool_button) { gimp_tool_button_update (tool_button); } static void gimp_tool_button_tool_add (GimpContainer *container, GimpToolInfo *tool_info, GimpToolButton *tool_button) { gint index; index = gimp_container_get_child_index (container, GIMP_OBJECT (tool_info)); gimp_tool_button_add_menu_item (tool_button, tool_info, index); gimp_tool_button_update (tool_button); } static void gimp_tool_button_tool_remove (GimpContainer *container, GimpToolInfo *tool_info, GimpToolButton *tool_button) { gimp_tool_button_remove_menu_item (tool_button, tool_info); gimp_tool_button_update (tool_button); } static void gimp_tool_button_tool_reorder (GimpContainer *container, GimpToolInfo *tool_info, gint new_index, GimpToolButton *tool_button) { gimp_tool_button_remove_menu_item (tool_button, tool_info); gimp_tool_button_add_menu_item (tool_button, tool_info, new_index); gimp_tool_button_update (tool_button); } static void gimp_tool_button_icon_size_notify (GtkToolPalette *palette, const GParamSpec *pspec, GimpToolButton *tool_button) { gimp_tool_button_reconstruct_menu (tool_button); gimp_tool_button_update (tool_button); } static gboolean gimp_tool_button_menu_enter_notify (GtkMenu *menu, GdkEventCrossing *event, GimpToolButton *tool_button) { if (tool_button->priv->show_menu_on_hover && event->mode == GDK_CROSSING_NORMAL) { if (tool_button->priv->menu_idle_id) { g_source_remove (tool_button->priv->menu_idle_id); tool_button->priv->menu_idle_id = 0; } } return FALSE; } static gboolean gimp_tool_button_menu_leave_notify (GtkMenu *menu, GdkEventCrossing *event, GimpToolButton *tool_button) { if (event->mode == GDK_CROSSING_NORMAL && gtk_widget_get_visible (tool_button->priv->menu)) { gimp_tool_button_update_menu (tool_button); if (tool_button->priv->show_menu_on_hover && ! tool_button->priv->menu_idle_id) { tool_button->priv->menu_idle_id = g_idle_add_full ( G_PRIORITY_DEFAULT + 1, (GSourceFunc) gimp_tool_button_menu_idle, tool_button, NULL); } } return FALSE; } static gboolean gimp_tool_button_menu_deactivate_idle (gpointer data) { tools_select_cmd_unblock_initialize (); return G_SOURCE_REMOVE; } static void gimp_tool_button_menu_deactivate (GtkMenu *menu, GimpToolButton *tool_button) { if (tool_button->priv->menu_select_idle_id) { g_source_remove (tool_button->priv->menu_select_idle_id); tool_button->priv->menu_select_idle_id = 0; } g_idle_add (gimp_tool_button_menu_deactivate_idle, NULL); } static gboolean gimp_tool_button_menu_idle (GimpToolButton *tool_button) { tool_button->priv->menu_idle_id = 0; gtk_menu_shell_deactivate (GTK_MENU_SHELL (tool_button->priv->menu)); return G_SOURCE_REMOVE; } static gboolean gimp_tool_button_menu_timeout (GimpToolButton *tool_button) { tool_button->priv->menu_timeout_id = 0; gimp_tool_button_show_menu (tool_button, tool_button->priv->menu_timeout_button, tool_button->priv->menu_timeout_time); /* work around gtk not properly redrawing the button in the toggled state */ if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (tool_button))) { gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tool_button), FALSE); } return G_SOURCE_REMOVE; } static void gimp_tool_button_update (GimpToolButton *tool_button) { GimpToolInfo *tool_info; tool_info = gimp_tool_button_get_tool_info (tool_button); gtk_tool_button_set_icon_name ( GTK_TOOL_BUTTON (tool_button), tool_info ? gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)) : NULL); g_clear_object (&tool_button->priv->tooltip_widget); if (! tool_button->priv->menu || ! tool_button->priv->show_menu_on_hover) { if (tool_info) { gimp_help_set_help_data (GTK_WIDGET (tool_button), tool_info->tooltip, tool_info->help_id); } else { gimp_help_set_help_data (GTK_WIDGET (tool_button), NULL, NULL); } } else { gimp_help_set_help_data (GTK_WIDGET (tool_button), NULL, NULL); } gimp_tool_button_update_toggled (tool_button); gimp_tool_button_update_menu (tool_button); } static void gimp_tool_button_update_toggled (GimpToolButton *tool_button) { GimpContext *context; GimpToolInfo *tool_info; context = gimp_toolbox_get_context (tool_button->priv->toolbox); tool_info = gimp_tool_button_get_tool_info (tool_button); gtk_toggle_tool_button_set_active ( GTK_TOGGLE_TOOL_BUTTON (tool_button), tool_info && tool_info == gimp_context_get_tool (context)); } static void gimp_tool_button_update_menu (GimpToolButton *tool_button) { if (tool_button->priv->menu && gtk_widget_get_visible (tool_button->priv->menu)) { GimpToolInfo *tool_info = gimp_tool_button_get_tool_info (tool_button); if (tool_info) { gtk_menu_shell_select_item ( GTK_MENU_SHELL (tool_button->priv->menu), g_hash_table_lookup (tool_button->priv->menu_items, tool_info)); } } } static void gimp_tool_button_add_menu_item (GimpToolButton *tool_button, GimpToolInfo *tool_info, gint index) { GimpUIManager *ui_manager; GimpAction *action; GtkWidget *item; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; GtkIconSize icon_size = GTK_ICON_SIZE_MENU; ui_manager = gimp_dock_get_ui_manager ( GIMP_DOCK (tool_button->priv->toolbox)); action = gimp_tool_button_get_action (tool_button, tool_info); if (tool_button->priv->palette) { icon_size = gtk_tool_palette_get_icon_size ( GTK_TOOL_PALETTE (tool_button->priv->palette)); } item = gtk_menu_item_new (); gtk_menu_shell_insert (GTK_MENU_SHELL (tool_button->priv->menu), item, index); gtk_activatable_set_related_action (GTK_ACTIVATABLE (item), GTK_ACTION (action)); gimp_help_set_help_data (item, tool_info->tooltip, tool_info->help_id); g_object_bind_property (tool_info, "visible", item, "visible", G_BINDING_SYNC_CREATE); g_object_set_data (G_OBJECT (item), "gimp-tool-info", tool_info); gimp_gtk_container_clear (GTK_CONTAINER (item)); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_container_add (GTK_CONTAINER (item), hbox); gtk_widget_show (hbox); image = gtk_image_new_from_icon_name ( gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)), icon_size); gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); gtk_widget_show (image); label = gtk_accel_label_new (tool_info->label); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); gtk_widget_show (label); if (action) { gtk_accel_label_set_accel_closure (GTK_ACCEL_LABEL (label), gimp_action_get_accel_closure ( action)); if (ui_manager) { g_signal_emit_by_name (ui_manager, "connect-proxy", action, item); } } g_hash_table_insert (tool_button->priv->menu_items, tool_info, item); } static void gimp_tool_button_remove_menu_item (GimpToolButton *tool_button, GimpToolInfo *tool_info) { GtkWidget *item; item = g_hash_table_lookup (tool_button->priv->menu_items, tool_info); gtk_container_remove (GTK_CONTAINER (tool_button->priv->menu), item); g_hash_table_remove (tool_button->priv->menu_items, tool_info); } static void gimp_tool_button_reconstruct_menu_add_menu_item (GimpToolInfo *tool_info, GimpToolButton *tool_button) { gimp_tool_button_add_menu_item (tool_button, tool_info, -1); } static void gimp_tool_button_reconstruct_menu (GimpToolButton *tool_button) { gimp_tool_button_destroy_menu (tool_button); if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { GimpUIManager *ui_manager; GimpContainer *children; ui_manager = gimp_dock_get_ui_manager ( GIMP_DOCK (tool_button->priv->toolbox)); children = gimp_viewable_get_children ( GIMP_VIEWABLE (tool_button->priv->tool_item)); tool_button->priv->menu = gtk_menu_new (); gtk_menu_attach_to_widget (GTK_MENU (tool_button->priv->menu), GTK_WIDGET (tool_button), NULL); g_signal_connect (tool_button->priv->menu, "enter-notify-event", G_CALLBACK (gimp_tool_button_menu_enter_notify), tool_button); g_signal_connect (tool_button->priv->menu, "leave-notify-event", G_CALLBACK (gimp_tool_button_menu_leave_notify), tool_button); g_signal_connect (tool_button->priv->menu, "deactivate", G_CALLBACK (gimp_tool_button_menu_deactivate), tool_button); if (ui_manager) { gtk_menu_set_accel_group ( GTK_MENU (tool_button->priv->menu), gimp_ui_manager_get_accel_group (ui_manager)); } tool_button->priv->menu_items = g_hash_table_new (g_direct_hash, g_direct_equal); gimp_container_foreach ( children, (GFunc) gimp_tool_button_reconstruct_menu_add_menu_item, tool_button); } } static void gimp_tool_button_destroy_menu (GimpToolButton *tool_button) { if (tool_button->priv->menu) { gtk_menu_detach (GTK_MENU (tool_button->priv->menu)); tool_button->priv->menu = NULL; g_clear_pointer (&tool_button->priv->menu_items, g_hash_table_unref); if (tool_button->priv->menu_idle_id) { g_source_remove (tool_button->priv->menu_idle_id); tool_button->priv->menu_idle_id = 0; } if (tool_button->priv->menu_timeout_id) { g_source_remove (tool_button->priv->menu_timeout_id); tool_button->priv->menu_timeout_id = 0; } if (tool_button->priv->menu_select_idle_id) { g_source_remove (tool_button->priv->menu_select_idle_id); tool_button->priv->menu_select_idle_id = 0; } } } static void gimp_tool_button_show_menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GimpToolButton *tool_button) { gimp_button_menu_position (GTK_WIDGET (tool_button), menu, GTK_POS_RIGHT, x, y); } static gboolean gimp_tool_button_show_menu_select_idle (GimpToolButton *tool_button) { tool_button->priv->menu_select_idle_id = 0; gimp_tool_button_update_menu (tool_button); return G_SOURCE_REMOVE; } static gboolean gimp_tool_button_show_menu (GimpToolButton *tool_button, gint button, guint32 activate_time) { if (! tool_button->priv->menu) return FALSE; /* avoid initializing the selected tool */ tools_select_cmd_block_initialize (); gtk_menu_shell_set_take_focus (GTK_MENU_SHELL (tool_button->priv->menu), ! tool_button->priv->show_menu_on_hover); gtk_menu_popup ( GTK_MENU (tool_button->priv->menu), NULL, NULL, (GtkMenuPositionFunc) gimp_tool_button_show_menu_position_func, tool_button, button, activate_time); if (tool_button->priv->show_menu_on_hover) gtk_grab_remove (tool_button->priv->menu); if (! tool_button->priv->menu_select_idle_id) { tool_button->priv->menu_select_idle_id = g_idle_add ( (GSourceFunc) gimp_tool_button_show_menu_select_idle, tool_button); } return TRUE; } static GimpAction * gimp_tool_button_get_action (GimpToolButton *tool_button, GimpToolInfo *tool_info) { GimpUIManager *ui_manager; GimpAction *action = NULL; ui_manager = gimp_dock_get_ui_manager ( GIMP_DOCK (tool_button->priv->toolbox)); if (ui_manager && tool_info) { gchar *name; name = gimp_tool_info_get_action_name (tool_info); action = gimp_ui_manager_find_action (ui_manager, "tools", name); g_free (name); } return action; } /* public functions */ GtkToolItem * gimp_tool_button_new (GimpToolbox *toolbox, GimpToolItem *tool_item) { g_return_val_if_fail (GIMP_IS_TOOLBOX (toolbox), NULL); g_return_val_if_fail (tool_item == NULL || GIMP_IS_TOOL_ITEM (tool_item), NULL); return g_object_new (GIMP_TYPE_TOOL_BUTTON, "toolbox", toolbox, "tool-item", tool_item, NULL); } GimpToolbox * gimp_tool_button_get_toolbox (GimpToolButton *tool_button) { g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); return tool_button->priv->toolbox; } void gimp_tool_button_set_tool_item (GimpToolButton *tool_button, GimpToolItem *tool_item) { g_return_if_fail (GIMP_IS_TOOL_BUTTON (tool_button)); g_return_if_fail (tool_item == NULL || GIMP_IS_TOOL_ITEM (tool_item)); if (tool_item != tool_button->priv->tool_item) { if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { GimpContainer *children; children = gimp_viewable_get_children ( GIMP_VIEWABLE (tool_button->priv->tool_item)); g_signal_handlers_disconnect_by_func ( tool_button->priv->tool_item, gimp_tool_button_active_tool_changed, tool_button); g_signal_handlers_disconnect_by_func ( children, gimp_tool_button_tool_add, tool_button); g_signal_handlers_disconnect_by_func ( children, gimp_tool_button_tool_remove, tool_button); g_signal_handlers_disconnect_by_func ( children, gimp_tool_button_tool_reorder, tool_button); gimp_tool_button_destroy_menu (tool_button); } g_set_object (&tool_button->priv->tool_item, tool_item); if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item)) { GimpContainer *children; children = gimp_viewable_get_children ( GIMP_VIEWABLE (tool_button->priv->tool_item)); g_signal_connect ( tool_button->priv->tool_item, "active-tool-changed", G_CALLBACK (gimp_tool_button_active_tool_changed), tool_button); g_signal_connect ( children, "add", G_CALLBACK (gimp_tool_button_tool_add), tool_button); g_signal_connect ( children, "remove", G_CALLBACK (gimp_tool_button_tool_remove), tool_button); g_signal_connect ( children, "reorder", G_CALLBACK (gimp_tool_button_tool_reorder), tool_button); gimp_tool_button_reconstruct_menu (tool_button); } gimp_tool_button_update (tool_button); g_object_notify (G_OBJECT (tool_button), "tool-item"); } } GimpToolItem * gimp_tool_button_get_tool_item (GimpToolButton *tool_button) { g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); return tool_button->priv->tool_item; } GimpToolInfo * gimp_tool_button_get_tool_info (GimpToolButton *tool_button) { g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), NULL); if (tool_button->priv->tool_item) { if (GIMP_IS_TOOL_INFO (tool_button->priv->tool_item)) { return GIMP_TOOL_INFO (tool_button->priv->tool_item); } else { return gimp_tool_group_get_active_tool_info ( GIMP_TOOL_GROUP (tool_button->priv->tool_item)); } } return NULL; } void gimp_tool_button_set_show_menu_on_hover (GimpToolButton *tool_button, gboolean show_menu_on_hover) { g_return_if_fail (GIMP_IS_TOOL_BUTTON (tool_button)); if (show_menu_on_hover != tool_button->priv->show_menu_on_hover) { tool_button->priv->show_menu_on_hover = show_menu_on_hover; gimp_tool_button_update (tool_button); g_object_notify (G_OBJECT (tool_button), "show-menu-on-hover"); } } gboolean gimp_tool_button_get_show_menu_on_hover (GimpToolButton *tool_button) { g_return_val_if_fail (GIMP_IS_TOOL_BUTTON (tool_button), FALSE); return tool_button->priv->show_menu_on_hover; }