diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:23:22 +0000 |
commit | e42129241681dde7adae7d20697e7b421682fbb4 (patch) | |
tree | af1fe815a5e639e68e59fabd8395ec69458b3e5e /app/widgets/gimptoolbutton.c | |
parent | Initial commit. (diff) | |
download | gimp-upstream/2.10.22.tar.xz gimp-upstream/2.10.22.zip |
Adding upstream version 2.10.22.upstream/2.10.22upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/widgets/gimptoolbutton.c')
-rw-r--r-- | app/widgets/gimptoolbutton.c | 1477 |
1 files changed, 1477 insertions, 0 deletions
diff --git a/app/widgets/gimptoolbutton.c b/app/widgets/gimptoolbutton.c new file mode 100644 index 0000000..d5d8587 --- /dev/null +++ b/app/widgets/gimptoolbutton.c @@ -0,0 +1,1477 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#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; +} |