Files
gimp/app/widgets/gimptoolbutton.c
Ell 04d17b7829 app: add option to show tool-button menu on hover
Add a "Menu mode" option to the toolbox preferences, which controls
the menu behavior for tool-group buttons, and can be one of "Show
on click" (current behavior), "Show on hover" (show the menu when
hovering over the button), and "Show on hover in single column"
(behaves like "Show on hover" when the toolbox has a single column,
and "Show on click" otherwise) -- the latter is the default.

Note that "Show on hover" requires the ability to remove the menu
grab, which doesn't seem to work in GTK3, so this change is
restricted to 2.10 for now.
2020-03-26 13:31:55 +02:00

1255 lines
40 KiB
C

/* 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 "gimpaction.h"
#include "gimpdock.h"
#include "gimptoolbox.h"
#include "gimptoolbutton.h"
#include "gimpuimanager.h"
#include "gimpwidgets-utils.h"
#include "gimpwindowstrategy.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 *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 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;
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 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)
{
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 &&
event->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 (
(GSourceFunc) gimp_tool_button_menu_idle,
tool_button);
}
}
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);
}
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);
}
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);
}
static void
gimp_tool_button_icon_size_notify (GtkToolPalette *palette,
const GParamSpec *pspec,
GimpToolButton *tool_button)
{
gimp_tool_button_reconstruct_menu (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 (
(GSourceFunc) gimp_tool_button_menu_idle,
tool_button);
}
}
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;
GimpAction *action;
tool_info = gimp_tool_button_get_tool_info (tool_button);
action = gimp_tool_button_get_action (tool_button, tool_info);
gtk_tool_button_set_icon_name (
GTK_TOOL_BUTTON (tool_button),
tool_info ? gimp_viewable_get_icon_name (GIMP_VIEWABLE (tool_info)) :
NULL);
if (! tool_button->priv->menu || ! tool_button->priv->show_menu_on_hover)
{
if (action)
{
gimp_widget_set_accel_help (GTK_WIDGET (tool_button), action);
}
else 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_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;
}