/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikatoolwidget.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * 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 <stdarg.h>

#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "libpikabase/pikabase.h"

#include "display-types.h"

#include "core/pikamarshal.h"

#include "pikacanvasarc.h"
#include "pikacanvascorner.h"
#include "pikacanvasgroup.h"
#include "pikacanvashandle.h"
#include "pikacanvaslimit.h"
#include "pikacanvasline.h"
#include "pikacanvaspath.h"
#include "pikacanvaspolygon.h"
#include "pikacanvasrectangle.h"
#include "pikacanvasrectangleguides.h"
#include "pikacanvastransformguides.h"
#include "pikadisplayshell.h"
#include "pikatoolwidget.h"


enum
{
  PROP_0,
  PROP_SHELL,
  PROP_ITEM
};

enum
{
  CHANGED,
  RESPONSE,
  SNAP_OFFSETS,
  STATUS,
  STATUS_COORDS,
  MESSAGE,
  FOCUS_CHANGED,
  LAST_SIGNAL
};

struct _PikaToolWidgetPrivate
{
  PikaDisplayShell *shell;
  PikaCanvasItem   *item;
  GList            *group_stack;

  gint              snap_offset_x;
  gint              snap_offset_y;
  gint              snap_width;
  gint              snap_height;

  gboolean          visible;
  gboolean          focus;
};


/*  local function prototypes  */

static void     pika_tool_widget_finalize           (GObject         *object);
static void     pika_tool_widget_constructed        (GObject         *object);
static void     pika_tool_widget_set_property       (GObject         *object,
                                                     guint            property_id,
                                                     const GValue    *value,
                                                     GParamSpec      *pspec);
static void     pika_tool_widget_get_property       (GObject         *object,
                                                     guint            property_id,
                                                     GValue          *value,
                                                     GParamSpec      *pspec);
static void     pika_tool_widget_properties_changed (GObject         *object,
                                                     guint            n_pspecs,
                                                     GParamSpec     **pspecs);

static void     pika_tool_widget_real_leave_notify  (PikaToolWidget  *widget);
static gboolean pika_tool_widget_real_key_press     (PikaToolWidget  *widget,
                                                     GdkEventKey     *kevent);


G_DEFINE_TYPE_WITH_PRIVATE (PikaToolWidget, pika_tool_widget, PIKA_TYPE_OBJECT)

#define parent_class pika_tool_widget_parent_class

static guint widget_signals[LAST_SIGNAL] = { 0 };


static void
pika_tool_widget_class_init (PikaToolWidgetClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize                    = pika_tool_widget_finalize;
  object_class->constructed                 = pika_tool_widget_constructed;
  object_class->set_property                = pika_tool_widget_set_property;
  object_class->get_property                = pika_tool_widget_get_property;
  object_class->dispatch_properties_changed = pika_tool_widget_properties_changed;

  klass->leave_notify                       = pika_tool_widget_real_leave_notify;
  klass->key_press                          = pika_tool_widget_real_key_press;

  widget_signals[CHANGED] =
    g_signal_new ("changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  widget_signals[RESPONSE] =
    g_signal_new ("response",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, response),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_INT);

  widget_signals[SNAP_OFFSETS] =
    g_signal_new ("snap-offsets",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, snap_offsets),
                  NULL, NULL,
                  pika_marshal_VOID__INT_INT_INT_INT,
                  G_TYPE_NONE, 4,
                  G_TYPE_INT,
                  G_TYPE_INT,
                  G_TYPE_INT,
                  G_TYPE_INT);

  widget_signals[STATUS] =
    g_signal_new ("status",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, status),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_STRING);

  widget_signals[STATUS_COORDS] =
    g_signal_new ("status-coords",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, status_coords),
                  NULL, NULL,
                  pika_marshal_VOID__STRING_DOUBLE_STRING_DOUBLE_STRING,
                  G_TYPE_NONE, 5,
                  G_TYPE_STRING,
                  G_TYPE_DOUBLE,
                  G_TYPE_STRING,
                  G_TYPE_DOUBLE,
                  G_TYPE_STRING);

  widget_signals[MESSAGE] =
    g_signal_new ("message",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, message),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_STRING);

  widget_signals[FOCUS_CHANGED] =
    g_signal_new ("focus-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolWidgetClass, focus_changed),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);

  g_object_class_install_property (object_class, PROP_SHELL,
                                   g_param_spec_object ("shell",
                                                        NULL, NULL,
                                                        PIKA_TYPE_DISPLAY_SHELL,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_ITEM,
                                   g_param_spec_object ("item",
                                                        NULL, NULL,
                                                        PIKA_TYPE_CANVAS_ITEM,
                                                        PIKA_PARAM_READABLE));
}

static void
pika_tool_widget_init (PikaToolWidget *widget)
{
  widget->private = pika_tool_widget_get_instance_private (widget);

  widget->private->visible = TRUE;
}

static void
pika_tool_widget_constructed (GObject *object)
{
  PikaToolWidget        *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolWidgetPrivate *private = widget->private;
  PikaToolWidgetClass   *klass   = PIKA_TOOL_WIDGET_GET_CLASS (widget);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika_assert (PIKA_IS_DISPLAY_SHELL (private->shell));

  private->item = pika_canvas_group_new (private->shell);

  pika_canvas_item_set_visible (private->item, private->visible);

  if (klass->changed)
    {
      if (klass->update_on_scale)
        {
          g_signal_connect_object (private->shell, "scaled",
                                   G_CALLBACK (klass->changed),
                                   widget,
                                   G_CONNECT_SWAPPED);
        }

      if (klass->update_on_scroll)
        {
          g_signal_connect_object (private->shell, "scrolled",
                                   G_CALLBACK (klass->changed),
                                   widget,
                                   G_CONNECT_SWAPPED);
        }

      if (klass->update_on_rotate)
        {
          g_signal_connect_object (private->shell, "rotated",
                                   G_CALLBACK (klass->changed),
                                   widget,
                                   G_CONNECT_SWAPPED);
        }
    }
}

static void
pika_tool_widget_finalize (GObject *object)
{
  PikaToolWidget        *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolWidgetPrivate *private = widget->private;

  g_clear_object (&private->item);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
pika_tool_widget_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  PikaToolWidget        *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolWidgetPrivate *private = widget->private;

  switch (property_id)
    {
    case PROP_SHELL:
      private->shell = g_value_get_object (value); /* don't ref */
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_tool_widget_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  PikaToolWidget        *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolWidgetPrivate *private = widget->private;

  switch (property_id)
    {
    case PROP_SHELL:
      g_value_set_object (value, private->shell);
      break;

    case PROP_ITEM:
      g_value_set_object (value, private->item);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_tool_widget_properties_changed (GObject     *object,
                                     guint        n_pspecs,
                                     GParamSpec **pspecs)
{
  PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);

  G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object,
                                                              n_pspecs,
                                                              pspecs);

  pika_tool_widget_changed (widget);
}

static void
pika_tool_widget_real_leave_notify (PikaToolWidget *widget)
{
  pika_tool_widget_set_status (widget, NULL);
}

static gboolean
pika_tool_widget_real_key_press (PikaToolWidget *widget,
                                 GdkEventKey    *kevent)
{
  switch (kevent->keyval)
    {
    case GDK_KEY_Return:
    case GDK_KEY_KP_Enter:
    case GDK_KEY_ISO_Enter:
      pika_tool_widget_response (widget, PIKA_TOOL_WIDGET_RESPONSE_CONFIRM);
      return TRUE;

    case GDK_KEY_Escape:
      pika_tool_widget_response (widget, PIKA_TOOL_WIDGET_RESPONSE_CANCEL);
      return TRUE;

    case GDK_KEY_BackSpace:
      pika_tool_widget_response (widget, PIKA_TOOL_WIDGET_RESPONSE_RESET);
      return TRUE;

    default:
      break;
    }

  return FALSE;
}


/*  public functions  */

PikaDisplayShell *
pika_tool_widget_get_shell (PikaToolWidget *widget)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  return widget->private->shell;
}

PikaCanvasItem *
pika_tool_widget_get_item (PikaToolWidget *widget)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  return widget->private->item;
}

void
pika_tool_widget_set_visible (PikaToolWidget *widget,
                              gboolean        visible)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  if (visible != widget->private->visible)
    {
      widget->private->visible = visible;

      if (widget->private->item)
        pika_canvas_item_set_visible (widget->private->item, visible);

      if (! visible)
        pika_tool_widget_set_status (widget, NULL);
    }
}

gboolean
pika_tool_widget_get_visible (PikaToolWidget *widget)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), FALSE);

  return widget->private->visible;
}

void
pika_tool_widget_set_focus (PikaToolWidget *widget,
                            gboolean        focus)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  if (focus != widget->private->focus)
    {
      widget->private->focus = focus;

      g_signal_emit (widget, widget_signals[FOCUS_CHANGED], 0);
    }
}

gboolean
pika_tool_widget_get_focus (PikaToolWidget *widget)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), FALSE);

  return widget->private->focus;
}

void
pika_tool_widget_changed (PikaToolWidget *widget)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  g_signal_emit (widget, widget_signals[CHANGED], 0);
}

void
pika_tool_widget_response (PikaToolWidget *widget,
                           gint            response_id)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  g_signal_emit (widget, widget_signals[RESPONSE], 0,
                 response_id);
}

void
pika_tool_widget_set_snap_offsets (PikaToolWidget *widget,
                                   gint            offset_x,
                                   gint            offset_y,
                                   gint            width,
                                   gint            height)
{
  PikaToolWidgetPrivate *private;

  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  private = widget->private;

  if (offset_x != private->snap_offset_x ||
      offset_y != private->snap_offset_y ||
      width    != private->snap_width    ||
      height   != private->snap_height)
    {
      private->snap_offset_x = offset_x;
      private->snap_offset_y = offset_y;
      private->snap_width    = width;
      private->snap_height   = height;

      g_signal_emit (widget, widget_signals[SNAP_OFFSETS], 0,
                 offset_x, offset_y, width, height);
    }
}

void
pika_tool_widget_get_snap_offsets (PikaToolWidget *widget,
                                   gint           *offset_x,
                                   gint           *offset_y,
                                   gint           *width,
                                   gint           *height)
{
  PikaToolWidgetPrivate *private;

  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  private = widget->private;

  if (offset_x) *offset_x = private->snap_offset_x;
  if (offset_y) *offset_y = private->snap_offset_y;
  if (width)    *width    = private->snap_width;
  if (height)   *height   = private->snap_height;
}

void
pika_tool_widget_set_status (PikaToolWidget *widget,
                             const gchar    *status)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  g_signal_emit (widget, widget_signals[STATUS], 0,
                 status);
}

void
pika_tool_widget_set_status_coords (PikaToolWidget *widget,
                                    const gchar    *title,
                                    gdouble         x,
                                    const gchar    *separator,
                                    gdouble         y,
                                    const gchar    *help)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  g_signal_emit (widget, widget_signals[STATUS_COORDS], 0,
                 title, x, separator, y, help);
}

void
pika_tool_widget_message (PikaToolWidget *widget,
                          const gchar    *format,
                          ...)
{
  va_list  args;
  gchar   *message;

  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (format != NULL);

  va_start (args, format);

  message = g_strdup_vprintf (format, args);

  va_end (args);

  pika_tool_widget_message_literal (widget, message);

  g_free (message);
}

void
pika_tool_widget_message_literal (PikaToolWidget *widget,
                                  const gchar    *message)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (message != NULL);

  g_signal_emit (widget, widget_signals[MESSAGE], 0,
                 message);
}

void
pika_tool_widget_add_item (PikaToolWidget *widget,
                           PikaCanvasItem *item)
{
  PikaCanvasGroup *group;

  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));

  group = PIKA_CANVAS_GROUP (widget->private->item);

  if (widget->private->group_stack)
    group = widget->private->group_stack->data;

  pika_canvas_group_add_item (group, item);
}

void
pika_tool_widget_remove_item (PikaToolWidget *widget,
                              PikaCanvasItem *item)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));

  pika_canvas_group_remove_item (PIKA_CANVAS_GROUP (widget->private->item),
                                 item);
}

PikaCanvasGroup *
pika_tool_widget_add_group (PikaToolWidget *widget)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_group_new (widget->private->shell);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return PIKA_CANVAS_GROUP (item);
}

PikaCanvasGroup *
pika_tool_widget_add_stroke_group (PikaToolWidget *widget)
{
  PikaCanvasGroup *group;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  group = pika_tool_widget_add_group (widget);
  pika_canvas_group_set_group_stroking (group, TRUE);

  return group;
}

PikaCanvasGroup *
pika_tool_widget_add_fill_group (PikaToolWidget *widget)
{
  PikaCanvasGroup *group;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  group = pika_tool_widget_add_group (widget);
  pika_canvas_group_set_group_filling (group, TRUE);

  return group;
}

void
pika_tool_widget_push_group (PikaToolWidget  *widget,
                             PikaCanvasGroup *group)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (PIKA_IS_CANVAS_GROUP (group));

  widget->private->group_stack = g_list_prepend (widget->private->group_stack,
                                                 group);
}

void
pika_tool_widget_pop_group (PikaToolWidget *widget)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (widget->private->group_stack != NULL);

  widget->private->group_stack = g_list_remove (widget->private->group_stack,
                                                widget->private->group_stack->data);
}

/**
 * pika_tool_widget_add_line:
 * @widget: the #PikaToolWidget
 * @x1:     start point X in image coordinates
 * @y1:     start point Y in image coordinates
 * @x2:     end point X in image coordinates
 * @y2:     end point Y in image coordinates
 *
 * This function adds a #PikaCanvasLine to @widget.
 **/
PikaCanvasItem *
pika_tool_widget_add_line (PikaToolWidget *widget,
                           gdouble         x1,
                           gdouble         y1,
                           gdouble         x2,
                           gdouble         y2)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_line_new (widget->private->shell,
                               x1, y1, x2, y2);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_rectangle (PikaToolWidget *widget,
                                gdouble         x,
                                gdouble         y,
                                gdouble         width,
                                gdouble         height,
                                gboolean        filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_rectangle_new (widget->private->shell,
                                    x, y, width, height, filled);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_arc (PikaToolWidget *widget,
                          gdouble         center_x,
                          gdouble         center_y,
                          gdouble         radius_x,
                          gdouble         radius_y,
                          gdouble         start_angle,
                          gdouble         slice_angle,
                          gboolean        filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_arc_new (widget->private->shell,
                              center_x, center_y,
                              radius_x, radius_y,
                              start_angle, slice_angle,
                              filled);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_limit (PikaToolWidget *widget,
                            PikaLimitType   type,
                            gdouble         x,
                            gdouble         y,
                            gdouble         radius,
                            gdouble         aspect_ratio,
                            gdouble         angle,
                            gboolean        dashed)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_limit_new (widget->private->shell,
                                type,
                                x, y,
                                radius,
                                aspect_ratio,
                                angle,
                                dashed);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_polygon (PikaToolWidget    *widget,
                              PikaMatrix3       *transform,
                              const PikaVector2 *points,
                              gint               n_points,
                              gboolean           filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);
  g_return_val_if_fail (points == NULL || n_points > 0, NULL);

  item = pika_canvas_polygon_new (widget->private->shell,
                                  points, n_points,
                                  transform, filled);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_polygon_from_coords (PikaToolWidget    *widget,
                                          PikaMatrix3       *transform,
                                          const PikaCoords  *points,
                                          gint               n_points,
                                          gboolean           filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);
  g_return_val_if_fail (points == NULL || n_points > 0, NULL);

  item = pika_canvas_polygon_new_from_coords (widget->private->shell,
                                              points, n_points,
                                              transform, filled);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_path (PikaToolWidget       *widget,
                           const PikaBezierDesc *desc)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_path_new (widget->private->shell,
                               desc, 0, 0, FALSE, PIKA_PATH_STYLE_DEFAULT);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_handle (PikaToolWidget   *widget,
                             PikaHandleType    type,
                             gdouble           x,
                             gdouble           y,
                             gint              width,
                             gint              height,
                             PikaHandleAnchor  anchor)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_handle_new (widget->private->shell,
                                 type, anchor, x, y, width, height);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_corner (PikaToolWidget   *widget,
                             gdouble           x,
                             gdouble           y,
                             gdouble           width,
                             gdouble           height,
                             PikaHandleAnchor  anchor,
                             gint              corner_width,
                             gint              corner_height,
                             gboolean          outside)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_corner_new (widget->private->shell,
                                 x, y, width, height,
                                 anchor, corner_width, corner_height,
                                 outside);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_rectangle_guides (PikaToolWidget *widget,
                                       gdouble         x,
                                       gdouble         y,
                                       gdouble         width,
                                       gdouble         height,
                                       PikaGuidesType  type)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_rectangle_guides_new (widget->private->shell,
                                           x, y, width, height, type, 4);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_tool_widget_add_transform_guides (PikaToolWidget    *widget,
                                       const PikaMatrix3 *transform,
                                       gdouble            x1,
                                       gdouble            y1,
                                       gdouble            x2,
                                       gdouble            y2,
                                       PikaGuidesType     type,
                                       gint               n_guides,
                                       gboolean           clip)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);

  item = pika_canvas_transform_guides_new (widget->private->shell,
                                           transform, x1, y1, x2, y2,
                                           type, n_guides, clip);

  pika_tool_widget_add_item (widget, item);
  g_object_unref (item);

  return item;
}

gint
pika_tool_widget_button_press (PikaToolWidget      *widget,
                               const PikaCoords    *coords,
                               guint32              time,
                               GdkModifierType      state,
                               PikaButtonPressType  press_type)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), 0);
  g_return_val_if_fail (coords != NULL, 0);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->button_press)
    {
      return PIKA_TOOL_WIDGET_GET_CLASS (widget)->button_press (widget,
                                                                coords, time,
                                                                state,
                                                                press_type);
    }

  return 0;
}

void
pika_tool_widget_button_release (PikaToolWidget        *widget,
                                 const PikaCoords      *coords,
                                 guint32                time,
                                 GdkModifierType        state,
                                 PikaButtonReleaseType  release_type)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (coords != NULL);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->button_release)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->button_release (widget,
                                                           coords, time, state,
                                                           release_type);
    }
}

void
pika_tool_widget_motion (PikaToolWidget   *widget,
                         const PikaCoords *coords,
                         guint32           time,
                         GdkModifierType   state)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (coords != NULL);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->motion)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->motion (widget,
                                                   coords, time, state);
    }
}

PikaHit
pika_tool_widget_hit (PikaToolWidget   *widget,
                      const PikaCoords *coords,
                      GdkModifierType   state,
                      gboolean          proximity)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), PIKA_HIT_NONE);
  g_return_val_if_fail (coords != NULL, PIKA_HIT_NONE);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->hit)
    {
      return PIKA_TOOL_WIDGET_GET_CLASS (widget)->hit (widget,
                                                       coords, state,
                                                       proximity);
    }

  return PIKA_HIT_NONE;
}

void
pika_tool_widget_hover (PikaToolWidget   *widget,
                        const PikaCoords *coords,
                        GdkModifierType   state,
                        gboolean          proximity)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));
  g_return_if_fail (coords != NULL);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->hover)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->hover (widget,
                                                  coords, state, proximity);
    }
}

void
pika_tool_widget_leave_notify (PikaToolWidget *widget)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->leave_notify)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->leave_notify (widget);
    }
}

gboolean
pika_tool_widget_key_press (PikaToolWidget *widget,
                            GdkEventKey    *kevent)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), FALSE);
  g_return_val_if_fail (kevent != NULL, FALSE);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->key_press)
    {
      return PIKA_TOOL_WIDGET_GET_CLASS (widget)->key_press (widget, kevent);
    }

  return FALSE;
}

gboolean
pika_tool_widget_key_release (PikaToolWidget *widget,
                              GdkEventKey    *kevent)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), FALSE);
  g_return_val_if_fail (kevent != NULL, FALSE);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->key_release)
    {
      return PIKA_TOOL_WIDGET_GET_CLASS (widget)->key_release (widget, kevent);
    }

  return FALSE;
}

void
pika_tool_widget_motion_modifier (PikaToolWidget  *widget,
                                  GdkModifierType  key,
                                  gboolean         press,
                                  GdkModifierType  state)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->motion_modifier (widget,
                                                            key, press, state);
    }
}

void
pika_tool_widget_hover_modifier (PikaToolWidget  *widget,
                                 GdkModifierType  key,
                                 gboolean         press,
                                 GdkModifierType  state)
{
  g_return_if_fail (PIKA_IS_TOOL_WIDGET (widget));

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier)
    {
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->hover_modifier (widget,
                                                           key, press, state);
    }
}

gboolean
pika_tool_widget_get_cursor (PikaToolWidget      *widget,
                             const PikaCoords    *coords,
                             GdkModifierType      state,
                             PikaCursorType      *cursor,
                             PikaToolCursorType  *tool_cursor,
                             PikaCursorModifier  *modifier)

{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), FALSE);
  g_return_val_if_fail (coords != NULL, FALSE);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->get_cursor)
    {
      PikaCursorType     my_cursor;
      PikaToolCursorType my_tool_cursor;
      PikaCursorModifier my_modifier;

      if (cursor)      my_cursor      = *cursor;
      if (tool_cursor) my_tool_cursor = *tool_cursor;
      if (modifier)    my_modifier    = *modifier;

      if (PIKA_TOOL_WIDGET_GET_CLASS (widget)->get_cursor (widget, coords,
                                                           state,
                                                           &my_cursor,
                                                           &my_tool_cursor,
                                                           &my_modifier))
        {
          if (cursor)      *cursor      = my_cursor;
          if (tool_cursor) *tool_cursor = my_tool_cursor;
          if (modifier)    *modifier    = my_modifier;

          return TRUE;
        }
    }

  return FALSE;
}

PikaUIManager *
pika_tool_widget_get_popup (PikaToolWidget        *widget,
                            const PikaCoords      *coords,
                            GdkModifierType        state,
                            const gchar          **ui_path)
{
  g_return_val_if_fail (PIKA_IS_TOOL_WIDGET (widget), NULL);
  g_return_val_if_fail (coords != NULL, NULL);

  if (widget->private->visible &&
      PIKA_TOOL_WIDGET_GET_CLASS (widget)->get_popup)
    {
      return PIKA_TOOL_WIDGET_GET_CLASS (widget)->get_popup (widget, coords,
                                                             state, ui_path);
    }

  return NULL;
}
