[gimp] app: add GimpToolButton



commit 282108ec917b20c3cd73930e0b6f485422c67a89
Author: Ell <ell_se yahoo com>
Date:   Wed Jan 29 21:52:17 2020 +0200

    app: add GimpToolButton
    
    Add a new GimpToolButton class, used for tool-buttons in the
    toolbox, instead of implementing them directly in GimpToolPalette.
    Each GimpToolButton is associated with a GimpToolItem, which can be
    either an individual tool or a group.
    
    When a tool button is associated with a group, it displays the
    group's active tool, with an arrow at the corner.  Clicking the
    button selects the active tool, while clicking-and-holding, or
    right-clicking, shows a menu of all the tools in the group.
    Alternatively, the active tool can be changed using the scroll
    wheel.

 app/actions/tools-commands.c |   42 +-
 app/actions/tools-commands.h |    2 +
 app/widgets/Makefile.am      |    2 +
 app/widgets/gimptoolbutton.c | 1028 ++++++++++++++++++++++++++++++++++++++++++
 app/widgets/gimptoolbutton.h |   63 +++
 app/widgets/gimpuimanager.c  |    2 +-
 app/widgets/meson.build      |    1 +
 app/widgets/widgets-types.h  |    1 +
 8 files changed, 1134 insertions(+), 7 deletions(-)
---
diff --git a/app/actions/tools-commands.c b/app/actions/tools-commands.c
index f5bd9986de..7e84c5397e 100644
--- a/app/actions/tools-commands.c
+++ b/app/actions/tools-commands.c
@@ -62,6 +62,14 @@ static void   tools_activate_enum_action (const gchar *action_desc,
                                           GVariant    *value);
 
 
+/*  local variables  */
+
+/* this is a hack to allow GimpToolButton to activate a tool-selection action
+ * without initializing the tool
+ */
+static gint tools_select_cmd_initialize_blocked = 0;
+
+
 /*  public functions  */
 
 void
@@ -102,10 +110,15 @@ tools_select_cmd_callback (GimpAction *action,
 
   /*  always allocate a new tool when selected from the image menu
    */
-  if (gimp_context_get_tool (context) != tool_info)
-    gimp_context_set_tool (context, tool_info);
+  if (gimp_context_get_tool (context) != tool_info ||
+      tools_select_cmd_initialize_blocked)
+    {
+      gimp_context_set_tool (context, tool_info);
+    }
   else
-    gimp_context_tool_changed (context);
+    {
+      gimp_context_tool_changed (context);
+    }
 
   if (set_transform_type)
     {
@@ -115,10 +128,27 @@ tools_select_cmd_callback (GimpAction *action,
                                     transform_type);
     }
 
-  display = gimp_context_get_display (context);
+  if (! tools_select_cmd_initialize_blocked)
+    {
+      display = gimp_context_get_display (context);
+
+      if (display && gimp_display_get_image (display))
+        tool_manager_initialize_active (gimp, display);
+    }
+}
+
+void
+tools_select_cmd_block_initialize (void)
+{
+  tools_select_cmd_initialize_blocked++;
+}
+
+void
+tools_select_cmd_unblock_initialize (void)
+{
+  g_return_if_fail (tools_select_cmd_initialize_blocked > 0);
 
-  if (display && gimp_display_get_image (display))
-    tool_manager_initialize_active (gimp, display);
+  tools_select_cmd_initialize_blocked--;
 }
 
 void
diff --git a/app/actions/tools-commands.h b/app/actions/tools-commands.h
index b4fab76f1d..38ab455b25 100644
--- a/app/actions/tools-commands.h
+++ b/app/actions/tools-commands.h
@@ -22,6 +22,8 @@
 void   tools_select_cmd_callback                    (GimpAction *action,
                                                      GVariant   *value,
                                                      gpointer    data);
+void   tools_select_cmd_block_initialize            (void);
+void   tools_select_cmd_unblock_initialize          (void);
 
 void   tools_color_average_radius_cmd_callback      (GimpAction *action,
                                                      GVariant   *value,
diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am
index f52b64c9b6..636ad4db65 100644
--- a/app/widgets/Makefile.am
+++ b/app/widgets/Makefile.am
@@ -405,6 +405,8 @@ libappwidgets_a_sources = \
        gimptoolbox-image-area.h        \
        gimptoolbox-indicator-area.c    \
        gimptoolbox-indicator-area.h    \
+        gimptoolbutton.c                \
+        gimptoolbutton.h                \
        gimptooleditor.c                \
        gimptooleditor.h                \
        gimptooloptionseditor.c         \
diff --git a/app/widgets/gimptoolbutton.c b/app/widgets/gimptoolbutton.c
new file mode 100644
index 0000000000..747b338fe7
--- /dev/null
+++ b/app/widgets/gimptoolbutton.c
@@ -0,0 +1,1028 @@
+/* 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
+};
+
+
+struct _GimpToolButtonPrivate
+{
+  GimpToolbox  *toolbox;
+  GimpToolItem *tool_item;
+
+  GtkWidget    *palette;
+
+  GtkWidget    *menu;
+  GHashTable   *menu_items;
+  gint          menu_timeout_id;
+  GdkEvent     *menu_timeout_event;
+};
+
+
+/*  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_draw                (GtkWidget           *widget,
+                                                          cairo_t             *cr);
+
+static void         gimp_tool_button_toggled             (GtkToggleToolButton *toggle_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 void         gimp_tool_button_menu_popped_up      (GtkMenu             *menu,
+                                                          const GdkRectangle  *flipped_rect,
+                                                          const GdkRectangle  *final_rect,
+                                                          gboolean             flipped_x,
+                                                          gboolean             flipped_y,
+                                                          GimpToolButton      *tool_button);
+static void         gimp_tool_button_menu_deactivate     (GtkMenu             *menu,
+                                                          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_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,
+                                                          const GdkEvent      *trigger_event);
+
+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->draw                = gimp_tool_button_draw;
+
+  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_PARAM_CONSTRUCT));
+}
+
+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)),
+                    "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;
+
+    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;
+
+    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_draw (GtkWidget *widget,
+                       cairo_t   *cr)
+{
+  GimpToolButton *tool_button = GIMP_TOOL_BUTTON (widget);
+
+  if (GTK_WIDGET_CLASS (parent_class)->draw)
+    GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
+
+  if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item))
+    {
+      GtkStyleContext *style = gtk_widget_get_style_context (widget);
+      GtkStateFlags    state = gtk_widget_get_state_flags (widget);
+      GdkRGBA          fg;
+      GtkAllocation    allocation;
+      gint             size;
+      gint             x1, y1;
+      gint             x2, y2;
+
+      gtk_style_context_get_color (style, state, &fg);
+
+      gtk_widget_get_allocation (widget, &allocation);
+
+      size = MIN (allocation.width, allocation.height);
+
+      x1 = SIGNED_ROUND (allocation.width  -
+                         (ARROW_BORDER + size * ARROW_SIZE));
+      y1 = SIGNED_ROUND (allocation.height -
+                         (ARROW_BORDER + size * ARROW_SIZE));
+
+      x2 = SIGNED_ROUND (allocation.width  - ARROW_BORDER);
+      y2 = SIGNED_ROUND (allocation.height - ARROW_BORDER);
+
+      cairo_save (cr);
+
+      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_rgba (cr, &fg);
+      cairo_fill (cr);
+
+      cairo_restore (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_button_press (GtkWidget      *widget,
+                               GdkEventButton *event,
+                               GimpToolButton *tool_button)
+{
+  if (GIMP_IS_TOOL_GROUP (tool_button->priv->tool_item))
+    {
+      if (gdk_event_triggers_context_menu ((GdkEvent *) event) &&
+          gimp_tool_button_show_menu (tool_button, (GdkEvent *) event))
+        {
+          return TRUE;
+        }
+      else if (event->type == GDK_BUTTON_PRESS && event->button == 1 &&
+               ! tool_button->priv->menu_timeout_id)
+        {
+          GdkEventButton *timeout_event;
+
+          timeout_event        = (GdkEventButton *) gdk_event_copy (
+            (GdkEvent *) event);
+          timeout_event->time += MENU_TIMEOUT;
+
+          tool_button->priv->menu_timeout_event = (GdkEvent *) timeout_event;
+
+          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),
+        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;
+
+      g_clear_pointer (&tool_button->priv->menu_timeout_event, gdk_event_free);
+    }
+
+  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))
+    {
+      GimpContext   *context;
+      GimpContainer *children;
+      gint           n_children;
+      gint           index;
+      gint           i;
+
+      context = gimp_toolbox_get_context (tool_button->priv->toolbox);
+
+      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);
+
+              if (tool_info == gimp_context_get_tool (context))
+                gimp_context_set_tool (context, 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 void
+gimp_tool_button_menu_popped_up (GtkMenu             *menu,
+                                 const GdkRectangle  *flipped_rect,
+                                 const GdkRectangle  *final_rect,
+                                 gboolean             flipped_x,
+                                 gboolean             flipped_y,
+                                 GimpToolButton      *tool_button)
+{
+  /* avoid initializing the selected tool */
+  tools_select_cmd_block_initialize ();
+}
+
+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)
+{
+  g_idle_add (gimp_tool_button_menu_deactivate_idle, NULL);
+}
+
+static gboolean
+gimp_tool_button_menu_timeout (GimpToolButton *tool_button)
+{
+  GdkEvent *event = tool_button->priv->menu_timeout_event;
+
+  tool_button->priv->menu_timeout_id    = 0;
+  tool_button->priv->menu_timeout_event = NULL;
+
+  gimp_tool_button_show_menu (tool_button, event);
+
+  gdk_event_free (event);
+
+  /* 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 (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);
+    }
+
+  gimp_tool_button_update_toggled (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_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, "popped-up",
+                        G_CALLBACK (gimp_tool_button_menu_popped_up),
+                        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_timeout_id)
+        {
+          g_source_remove (tool_button->priv->menu_timeout_id);
+
+          tool_button->priv->menu_timeout_id = 0;
+
+          g_clear_pointer (&tool_button->priv->menu_timeout_event,
+                           gdk_event_free);
+        }
+    }
+}
+
+static gboolean
+gimp_tool_button_show_menu (GimpToolButton *tool_button,
+                            const GdkEvent *trigger_event)
+{
+  if (! tool_button->priv->menu)
+    return FALSE;
+
+  gtk_menu_popup_at_widget (
+    GTK_MENU (tool_button->priv->menu),
+    GTK_WIDGET (tool_button),
+    GDK_GRAVITY_NORTH_EAST,
+    GDK_GRAVITY_NORTH_WEST,
+    trigger_event);
+
+  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;
+}
diff --git a/app/widgets/gimptoolbutton.h b/app/widgets/gimptoolbutton.h
new file mode 100644
index 0000000000..b53d48a27a
--- /dev/null
+++ b/app/widgets/gimptoolbutton.h
@@ -0,0 +1,63 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolbutton.h
+ * 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/>.
+ */
+
+#ifndef __GIMP_TOOL_BUTTON_H__
+#define __GIMP_TOOL_BUTTON_H__
+
+
+#define GIMP_TYPE_TOOL_BUTTON            (gimp_tool_button_get_type ())
+#define GIMP_TOOL_BUTTON(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_BUTTON, 
GimpToolButton))
+#define GIMP_TOOL_BUTTON_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_BUTTON, 
GimpToolButtonClass))
+#define GIMP_IS_TOOL_BUTTON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, GIMP_TYPE_TOOL_BUTTON))
+#define GIMP_IS_TOOL_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_BUTTON))
+#define GIMP_TOOL_BUTTON_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_BUTTON, 
GimpToolButtonClass))
+
+
+typedef struct _GimpToolButtonPrivate GimpToolButtonPrivate;
+typedef struct _GimpToolButtonClass   GimpToolButtonClass;
+
+struct _GimpToolButton
+{
+  GtkToggleToolButton    parent_instance;
+
+  GimpToolButtonPrivate *priv;
+};
+
+struct _GimpToolButtonClass
+{
+  GtkToggleToolButtonClass  parent_class;
+};
+
+
+GType          gimp_tool_button_get_type      (void) G_GNUC_CONST;
+
+GtkToolItem  * gimp_tool_button_new           (GimpToolbox    *toolbox,
+                                               GimpToolItem   *tool_item);
+
+GimpToolbox  * gimp_tool_button_get_toolbox   (GimpToolButton *tool_button);
+
+void           gimp_tool_button_set_tool_item (GimpToolButton *tool_button,
+                                               GimpToolItem   *tool_item);
+GimpToolItem * gimp_tool_button_get_tool_item (GimpToolButton *tool_button);
+
+GimpToolInfo * gimp_tool_button_get_tool_info (GimpToolButton *tool_button);
+
+
+#endif /* __GIMP_TOOL_BUTTON_H__ */
diff --git a/app/widgets/gimpuimanager.c b/app/widgets/gimpuimanager.c
index b872d692a0..ac8900867c 100644
--- a/app/widgets/gimpuimanager.c
+++ b/app/widgets/gimpuimanager.c
@@ -1120,7 +1120,7 @@ gimp_ui_manager_item_key_press (GtkWidget     *widget,
 {
   gchar *help_id = NULL;
 
-  while (! help_id)
+  while (! help_id && GTK_IS_MENU_SHELL (widget))
     {
       GtkWidget *menu_item;
 
diff --git a/app/widgets/meson.build b/app/widgets/meson.build
index a323f34ff0..bf83fff567 100644
--- a/app/widgets/meson.build
+++ b/app/widgets/meson.build
@@ -202,6 +202,7 @@ libappwidgets_sources = [
   'gimptoolbox-image-area.c',
   'gimptoolbox-indicator-area.c',
   'gimptoolbox.c',
+  'gimptoolbutton.c',
   'gimptooleditor.c',
   'gimptooloptionseditor.c',
   'gimptoolpalette.c',
diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h
index 38e19aab26..e596877f56 100644
--- a/app/widgets/widgets-types.h
+++ b/app/widgets/widgets-types.h
@@ -229,6 +229,7 @@ typedef struct _GimpTagPopup                 GimpTagPopup;
 typedef struct _GimpTemplateEditor           GimpTemplateEditor;
 typedef struct _GimpTextStyleEditor          GimpTextStyleEditor;
 typedef struct _GimpThumbBox                 GimpThumbBox;
+typedef struct _GimpToolButton               GimpToolButton;
 typedef struct _GimpToolPalette              GimpToolPalette;
 typedef struct _GimpTranslationStore         GimpTranslationStore;
 typedef struct _GimpWindow                   GimpWindow;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]