/* 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-2001 Spencer Kimball, Peter Mattis, and others
 *
 * 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 "libpikamath/pikamath.h"

#include "tools-types.h"

#include "core/pikaimage.h"
#include "core/pikapickable.h"

#include "display/pikacanvas.h"
#include "display/pikacanvasarc.h"
#include "display/pikacanvasboundary.h"
#include "display/pikacanvasgroup.h"
#include "display/pikacanvasguide.h"
#include "display/pikacanvashandle.h"
#include "display/pikacanvasitem-utils.h"
#include "display/pikacanvasline.h"
#include "display/pikacanvaspen.h"
#include "display/pikacanvaspolygon.h"
#include "display/pikacanvasrectangle.h"
#include "display/pikacanvassamplepoint.h"
#include "display/pikacanvastextcursor.h"
#include "display/pikacanvastransformpreview.h"
#include "display/pikacanvastext.h"
#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-items.h"
#include "display/pikadisplayshell-transform.h"
#include "display/pikatoolwidget.h"

#include "pikadrawtool.h"
#include "pikatoolcontrol.h"


#define USE_TIMEOUT
#define DRAW_FPS              120
#define DRAW_TIMEOUT          (1000 /* milliseconds */ / (2 * DRAW_FPS))
#define MINIMUM_DRAW_INTERVAL (G_TIME_SPAN_SECOND / DRAW_FPS)


static void            pika_draw_tool_dispose       (GObject           *object);

static gboolean        pika_draw_tool_has_display   (PikaTool          *tool,
                                                     PikaDisplay       *display);
static PikaDisplay   * pika_draw_tool_has_image     (PikaTool          *tool,
                                                     PikaImage         *image);
static void            pika_draw_tool_control       (PikaTool          *tool,
                                                     PikaToolAction     action,
                                                     PikaDisplay       *display);
static gboolean        pika_draw_tool_key_press     (PikaTool          *tool,
                                                     GdkEventKey       *kevent,
                                                     PikaDisplay       *display);
static gboolean        pika_draw_tool_key_release   (PikaTool          *tool,
                                                     GdkEventKey       *kevent,
                                                     PikaDisplay       *display);
static void            pika_draw_tool_modifier_key  (PikaTool          *tool,
                                                     GdkModifierType    key,
                                                     gboolean           press,
                                                     GdkModifierType    state,
                                                     PikaDisplay       *display);
static void            pika_draw_tool_active_modifier_key
                                                    (PikaTool          *tool,
                                                     GdkModifierType    key,
                                                     gboolean           press,
                                                     GdkModifierType    state,
                                                     PikaDisplay       *display);
static void            pika_draw_tool_oper_update   (PikaTool          *tool,
                                                     const PikaCoords  *coords,
                                                     GdkModifierType    state,
                                                     gboolean           proximity,
                                                     PikaDisplay       *display);
static void            pika_draw_tool_cursor_update (PikaTool          *tool,
                                                     const PikaCoords  *coords,
                                                     GdkModifierType    state,
                                                     PikaDisplay       *display);
static PikaUIManager * pika_draw_tool_get_popup     (PikaTool          *tool,
                                                     const PikaCoords  *coords,
                                                     GdkModifierType    state,
                                                     PikaDisplay       *display,
                                                     const gchar      **ui_path);

static void            pika_draw_tool_widget_status (PikaToolWidget    *widget,
                                                     const gchar       *status,
                                                     PikaTool          *tool);
static void            pika_draw_tool_widget_status_coords
                                                    (PikaToolWidget    *widget,
                                                     const gchar       *title,
                                                     gdouble            x,
                                                     const gchar       *separator,
                                                     gdouble            y,
                                                     const gchar       *help,
                                                     PikaTool          *tool);
static void            pika_draw_tool_widget_message
                                                    (PikaToolWidget    *widget,
                                                     const gchar       *message,
                                                     PikaTool          *tool);
static void            pika_draw_tool_widget_snap_offsets
                                                    (PikaToolWidget    *widget,
                                                     gint               offset_x,
                                                     gint               offset_y,
                                                     gint               width,
                                                     gint               height,
                                                     PikaTool          *tool);

static void            pika_draw_tool_draw          (PikaDrawTool      *draw_tool);
static void            pika_draw_tool_undraw        (PikaDrawTool      *draw_tool);
static void            pika_draw_tool_real_draw     (PikaDrawTool      *draw_tool);


G_DEFINE_TYPE (PikaDrawTool, pika_draw_tool, PIKA_TYPE_TOOL)

#define parent_class pika_draw_tool_parent_class


static void
pika_draw_tool_class_init (PikaDrawToolClass *klass)
{
  GObjectClass  *object_class = G_OBJECT_CLASS (klass);
  PikaToolClass *tool_class   = PIKA_TOOL_CLASS (klass);

  object_class->dispose           = pika_draw_tool_dispose;

  tool_class->has_display         = pika_draw_tool_has_display;
  tool_class->has_image           = pika_draw_tool_has_image;
  tool_class->control             = pika_draw_tool_control;
  tool_class->key_press           = pika_draw_tool_key_press;
  tool_class->key_release         = pika_draw_tool_key_release;
  tool_class->modifier_key        = pika_draw_tool_modifier_key;
  tool_class->active_modifier_key = pika_draw_tool_active_modifier_key;
  tool_class->oper_update         = pika_draw_tool_oper_update;
  tool_class->cursor_update       = pika_draw_tool_cursor_update;
  tool_class->get_popup           = pika_draw_tool_get_popup;

  klass->draw                     = pika_draw_tool_real_draw;
}

static void
pika_draw_tool_init (PikaDrawTool *draw_tool)
{
  draw_tool->display      = NULL;
  draw_tool->paused_count = 0;
  draw_tool->preview      = NULL;
  draw_tool->item         = NULL;
}

static void
pika_draw_tool_dispose (GObject *object)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (object);

  if (draw_tool->draw_timeout)
    {
      g_source_remove (draw_tool->draw_timeout);
      draw_tool->draw_timeout = 0;
    }

  pika_draw_tool_set_widget (draw_tool, NULL);
  pika_draw_tool_set_default_status (draw_tool, NULL);

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

static gboolean
pika_draw_tool_has_display (PikaTool    *tool,
                            PikaDisplay *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  return (display == draw_tool->display ||
          PIKA_TOOL_CLASS (parent_class)->has_display (tool, display));
}

static PikaDisplay *
pika_draw_tool_has_image (PikaTool  *tool,
                          PikaImage *image)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);
  PikaDisplay  *display;

  display = PIKA_TOOL_CLASS (parent_class)->has_image (tool, image);

  if (! display && draw_tool->display)
    {
      if (image && pika_display_get_image (draw_tool->display) == image)
        display = draw_tool->display;

      /*  NULL image means any display  */
      if (! image)
        display = draw_tool->display;
    }

  return display;
}

static void
pika_draw_tool_control (PikaTool       *tool,
                        PikaToolAction  action,
                        PikaDisplay    *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  switch (action)
    {
    case PIKA_TOOL_ACTION_PAUSE:
    case PIKA_TOOL_ACTION_RESUME:
      break;

    case PIKA_TOOL_ACTION_HALT:
      if (pika_draw_tool_is_active (draw_tool))
        pika_draw_tool_stop (draw_tool);
      pika_draw_tool_set_widget (draw_tool, NULL);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      break;
    }

  PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
}

static gboolean
pika_draw_tool_key_press (PikaTool    *tool,
                          GdkEventKey *kevent,
                          PikaDisplay *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      return pika_tool_widget_key_press (draw_tool->widget, kevent);
    }

  return PIKA_TOOL_CLASS (parent_class)->key_press (tool, kevent, display);
}

static gboolean
pika_draw_tool_key_release (PikaTool    *tool,
                            GdkEventKey *kevent,
                            PikaDisplay *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      return pika_tool_widget_key_release (draw_tool->widget, kevent);
    }

  return PIKA_TOOL_CLASS (parent_class)->key_release (tool, kevent, display);
}

static void
pika_draw_tool_modifier_key (PikaTool        *tool,
                             GdkModifierType  key,
                             gboolean         press,
                             GdkModifierType  state,
                             PikaDisplay     *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      pika_tool_widget_hover_modifier (draw_tool->widget, key, press, state);
    }

  PIKA_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
                                                display);
}

static void
pika_draw_tool_active_modifier_key (PikaTool        *tool,
                                    GdkModifierType  key,
                                    gboolean         press,
                                    GdkModifierType  state,
                                    PikaDisplay     *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      pika_tool_widget_motion_modifier (draw_tool->widget, key, press, state);
    }

  PIKA_TOOL_CLASS (parent_class)->active_modifier_key (tool, key, press, state,
                                                       display);
}

static void
pika_draw_tool_oper_update (PikaTool         *tool,
                            const PikaCoords *coords,
                            GdkModifierType   state,
                            gboolean          proximity,
                            PikaDisplay      *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      pika_tool_widget_hover (draw_tool->widget, coords, state, proximity);
    }
  else if (proximity && draw_tool->default_status)
    {
      pika_tool_replace_status (tool, display, "%s", draw_tool->default_status);
    }
  else if (! proximity)
    {
      pika_tool_pop_status (tool, display);
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
                                                   proximity, display);
    }
}

static void
pika_draw_tool_cursor_update (PikaTool         *tool,
                              const PikaCoords *coords,
                              GdkModifierType   state,
                              PikaDisplay      *display)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      PikaCursorType     cursor;
      PikaToolCursorType tool_cursor;
      PikaCursorModifier modifier;

      cursor      = pika_tool_control_get_cursor (tool->control);
      tool_cursor = pika_tool_control_get_tool_cursor (tool->control);
      modifier    = pika_tool_control_get_cursor_modifier (tool->control);

      if (pika_tool_widget_get_cursor (draw_tool->widget, coords, state,
                                       &cursor, &tool_cursor, &modifier))
        {
          pika_tool_set_cursor (tool, display,
                                cursor, tool_cursor, modifier);
          return;
        }
    }

  PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
                                                 display);
}

static PikaUIManager *
pika_draw_tool_get_popup (PikaTool          *tool,
                          const PikaCoords  *coords,
                          GdkModifierType    state,
                          PikaDisplay       *display,
                          const gchar      **ui_path)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      PikaUIManager *ui_manager;

      ui_manager = pika_tool_widget_get_popup (draw_tool->widget,
                                               coords, state,
                                               ui_path);

      if (ui_manager)
        return ui_manager;
    }

  return PIKA_TOOL_CLASS (parent_class)->get_popup (tool,
                                                    coords, state, display,
                                                    ui_path);
}

static void
pika_draw_tool_widget_status (PikaToolWidget *widget,
                              const gchar    *status,
                              PikaTool       *tool)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (pika_draw_tool_is_active (draw_tool))
    {
      if (status)
        pika_tool_replace_status (tool, draw_tool->display, "%s", status);
      else
        pika_tool_pop_status (tool, draw_tool->display);
    }
}

static void
pika_draw_tool_widget_status_coords (PikaToolWidget *widget,
                                     const gchar    *title,
                                     gdouble         x,
                                     const gchar    *separator,
                                     gdouble         y,
                                     const gchar    *help,
                                     PikaTool       *tool)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (pika_draw_tool_is_active (draw_tool))
    {
      pika_tool_pop_status (tool, draw_tool->display);
      pika_tool_push_status_coords (tool, draw_tool->display,
                                    pika_tool_control_get_precision (
                                      tool->control),
                                    title, x, separator, y, help);
    }
}

static void
pika_draw_tool_widget_message (PikaToolWidget *widget,
                               const gchar    *message,
                               PikaTool       *tool)
{
  PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool);

  if (pika_draw_tool_is_active (draw_tool))
    pika_tool_message_literal (tool, draw_tool->display, message);
}

static void
pika_draw_tool_widget_snap_offsets (PikaToolWidget   *widget,
                                    gint              offset_x,
                                    gint              offset_y,
                                    gint              width,
                                    gint              height,
                                    PikaTool         *tool)
{
  pika_tool_control_set_snap_offsets (tool->control,
                                      offset_x, offset_y,
                                      width, height);
}

#ifdef USE_TIMEOUT
static gboolean
pika_draw_tool_draw_timeout (PikaDrawTool *draw_tool)
{
  guint64 now = g_get_monotonic_time ();

  /* keep the timeout running if the last drawing just happened */
  if ((now - draw_tool->last_draw_time) <= MINIMUM_DRAW_INTERVAL)
    return TRUE;

  draw_tool->draw_timeout = 0;

  pika_draw_tool_draw (draw_tool);

  return FALSE;
}
#endif

static void
pika_draw_tool_draw (PikaDrawTool *draw_tool)
{
  guint64 now = g_get_monotonic_time ();

  if (draw_tool->display &&
      draw_tool->paused_count == 0 &&
      (! draw_tool->draw_timeout ||
       (now - draw_tool->last_draw_time) > MINIMUM_DRAW_INTERVAL))
    {
      PikaDisplayShell *shell = pika_display_get_shell (draw_tool->display);

      if (draw_tool->draw_timeout)
        {
          g_source_remove (draw_tool->draw_timeout);
          draw_tool->draw_timeout = 0;
        }

      pika_draw_tool_undraw (draw_tool);

      PIKA_DRAW_TOOL_GET_CLASS (draw_tool)->draw (draw_tool);

      if (draw_tool->group_stack)
        {
          g_warning ("%s: draw_tool->group_stack not empty after calling "
                     "PikaDrawTool::draw() of %s",
                     G_STRFUNC,
                     g_type_name (G_TYPE_FROM_INSTANCE (draw_tool)));

          while (draw_tool->group_stack)
            pika_draw_tool_pop_group (draw_tool);
        }

      if (draw_tool->preview)
        pika_display_shell_add_preview_item (shell, draw_tool->preview);

      if (draw_tool->item)
        pika_display_shell_add_tool_item (shell, draw_tool->item);

      draw_tool->last_draw_time = g_get_monotonic_time ();

#if 0
      g_printerr ("drawing tool stuff took %f seconds\n",
                  (draw_tool->last_draw_time - now) / 1000000.0);
#endif
    }
}

static void
pika_draw_tool_undraw (PikaDrawTool *draw_tool)
{
  if (draw_tool->display)
    {
      PikaDisplayShell *shell = pika_display_get_shell (draw_tool->display);

      if (draw_tool->preview)
        {
          pika_display_shell_remove_preview_item (shell, draw_tool->preview);
          g_clear_object (&draw_tool->preview);
        }

      if (draw_tool->item)
        {
          pika_display_shell_remove_tool_item (shell, draw_tool->item);
          g_clear_object (&draw_tool->item);
        }
    }
}

static void
pika_draw_tool_real_draw (PikaDrawTool *draw_tool)
{
  if (draw_tool->widget)
    {
      PikaCanvasItem *item = pika_tool_widget_get_item (draw_tool->widget);

      pika_draw_tool_add_item (draw_tool, item);
    }
}

void
pika_draw_tool_start (PikaDrawTool *draw_tool,
                      PikaDisplay  *display)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (pika_draw_tool_is_active (draw_tool) == FALSE);

  draw_tool->display = display;

  pika_draw_tool_draw (draw_tool);
}

void
pika_draw_tool_stop (PikaDrawTool *draw_tool)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (pika_draw_tool_is_active (draw_tool) == TRUE);

  pika_draw_tool_undraw (draw_tool);

  if (draw_tool->draw_timeout)
    {
      g_source_remove (draw_tool->draw_timeout);
      draw_tool->draw_timeout = 0;
    }

  draw_tool->last_draw_time = 0;

  draw_tool->display = NULL;
}

gboolean
pika_draw_tool_is_active (PikaDrawTool *draw_tool)
{
  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), FALSE);

  return draw_tool->display != NULL;
}

void
pika_draw_tool_pause (PikaDrawTool *draw_tool)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));

  draw_tool->paused_count++;

  if (draw_tool->draw_timeout)
    {
      g_source_remove (draw_tool->draw_timeout);
      draw_tool->draw_timeout = 0;
    }
}

void
pika_draw_tool_resume (PikaDrawTool *draw_tool)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (draw_tool->paused_count > 0);

  draw_tool->paused_count--;

  if (draw_tool->paused_count == 0)
    {
#ifdef USE_TIMEOUT
      /* Don't install the timeout if the draw tool isn't active, so
       * suspend()/resume() can always be called, and have no side
       * effect on an inactive tool. See bug #687851.
       */
      if (pika_draw_tool_is_active (draw_tool) && ! draw_tool->draw_timeout)
        {
          draw_tool->draw_timeout =
            gdk_threads_add_timeout_full (G_PRIORITY_HIGH_IDLE,
                                          DRAW_TIMEOUT,
                                          (GSourceFunc) pika_draw_tool_draw_timeout,
                                          draw_tool, NULL);
        }
#endif

      /* call draw() anyway, it will do nothing if the timeout is
       * running, but will additionally check the drawing times to
       * ensure the minimum framerate
       */
      pika_draw_tool_draw (draw_tool);
    }
}

/**
 * pika_draw_tool_calc_distance:
 * @draw_tool: a #PikaDrawTool
 * @display:   a #PikaDisplay
 * @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
 *
 * If you just need to compare distances, consider to use
 * pika_draw_tool_calc_distance_square() instead.
 *
 * Returns: the distance between the given points in display coordinates
 **/
gdouble
pika_draw_tool_calc_distance (PikaDrawTool *draw_tool,
                              PikaDisplay  *display,
                              gdouble       x1,
                              gdouble       y1,
                              gdouble       x2,
                              gdouble       y2)
{
  return sqrt (pika_draw_tool_calc_distance_square (draw_tool, display,
                                                    x1, y1, x2, y2));
}

/**
 * pika_draw_tool_calc_distance_square:
 * @draw_tool: a #PikaDrawTool
 * @display:   a #PikaDisplay
 * @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 is more effective than pika_draw_tool_calc_distance()
 * as it doesn't perform a sqrt(). Use this if you just need to compare
 * distances.
 *
 * Returns: the square of the distance between the given points in
 *          display coordinates
 **/
gdouble
pika_draw_tool_calc_distance_square (PikaDrawTool *draw_tool,
                                     PikaDisplay  *display,
                                     gdouble       x1,
                                     gdouble       y1,
                                     gdouble       x2,
                                     gdouble       y2)
{
  PikaDisplayShell *shell;
  gdouble           tx1, ty1;
  gdouble           tx2, ty2;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), 0.0);
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), 0.0);

  shell = pika_display_get_shell (display);

  pika_display_shell_transform_xy_f (shell, x1, y1, &tx1, &ty1);
  pika_display_shell_transform_xy_f (shell, x2, y2, &tx2, &ty2);

  return SQR (tx2 - tx1) + SQR (ty2 - ty1);
}

void
pika_draw_tool_set_widget (PikaDrawTool   *draw_tool,
                           PikaToolWidget *widget)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (widget == NULL || PIKA_IS_TOOL_WIDGET (widget));

  if (widget == draw_tool->widget)
    return;

  if (draw_tool->widget)
    {
      pika_tool_widget_set_focus (draw_tool->widget, FALSE);

      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            pika_draw_tool_widget_status,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            pika_draw_tool_widget_status_coords,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            pika_draw_tool_widget_message,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            pika_draw_tool_widget_snap_offsets,
                                            draw_tool);

      if (pika_draw_tool_is_active (draw_tool))
        {
          PikaCanvasItem *item = pika_tool_widget_get_item (draw_tool->widget);

          pika_draw_tool_remove_item (draw_tool, item);
        }

      g_object_unref (draw_tool->widget);
    }

  draw_tool->widget = widget;

  if (draw_tool->widget)
    {
      g_object_ref (draw_tool->widget);

      if (pika_draw_tool_is_active (draw_tool))
        {
          PikaCanvasItem *item = pika_tool_widget_get_item (draw_tool->widget);

          pika_draw_tool_add_item (draw_tool, item);
        }

      g_signal_connect (draw_tool->widget, "status",
                        G_CALLBACK (pika_draw_tool_widget_status),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "status-coords",
                        G_CALLBACK (pika_draw_tool_widget_status_coords),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "message",
                        G_CALLBACK (pika_draw_tool_widget_message),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "snap-offsets",
                        G_CALLBACK (pika_draw_tool_widget_snap_offsets),
                        draw_tool);

      pika_tool_widget_set_focus (draw_tool->widget, TRUE);
    }
}

void
pika_draw_tool_set_default_status (PikaDrawTool *draw_tool,
                                   const gchar  *status)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));

  if (draw_tool->default_status)
    g_free (draw_tool->default_status);

  draw_tool->default_status = g_strdup (status);
}

void
pika_draw_tool_add_preview (PikaDrawTool   *draw_tool,
                            PikaCanvasItem *item)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));

  if (! draw_tool->preview)
    draw_tool->preview =
      pika_canvas_group_new (pika_display_get_shell (draw_tool->display));

  pika_canvas_group_add_item (PIKA_CANVAS_GROUP (draw_tool->preview), item);
}

void
pika_draw_tool_remove_preview (PikaDrawTool   *draw_tool,
                               PikaCanvasItem *item)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));
  g_return_if_fail (draw_tool->preview != NULL);

  pika_canvas_group_remove_item (PIKA_CANVAS_GROUP (draw_tool->preview), item);
}

void
pika_draw_tool_add_item (PikaDrawTool   *draw_tool,
                         PikaCanvasItem *item)
{
  PikaCanvasGroup *group;

  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));

  if (! draw_tool->item)
    draw_tool->item =
      pika_canvas_group_new (pika_display_get_shell (draw_tool->display));

  group = PIKA_CANVAS_GROUP (draw_tool->item);

  if (draw_tool->group_stack)
    group = draw_tool->group_stack->data;

  pika_canvas_group_add_item (group, item);
}

void
pika_draw_tool_remove_item (PikaDrawTool   *draw_tool,
                            PikaCanvasItem *item)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_CANVAS_ITEM (item));
  g_return_if_fail (draw_tool->item != NULL);

  pika_canvas_group_remove_item (PIKA_CANVAS_GROUP (draw_tool->item), item);
}

PikaCanvasGroup *
pika_draw_tool_add_stroke_group (PikaDrawTool *draw_tool)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_group_new (pika_display_get_shell (draw_tool->display));
  pika_canvas_group_set_group_stroking (PIKA_CANVAS_GROUP (item), TRUE);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return PIKA_CANVAS_GROUP (item);
}

PikaCanvasGroup *
pika_draw_tool_add_fill_group (PikaDrawTool *draw_tool)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_group_new (pika_display_get_shell (draw_tool->display));
  pika_canvas_group_set_group_filling (PIKA_CANVAS_GROUP (item), TRUE);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return PIKA_CANVAS_GROUP (item);
}

void
pika_draw_tool_push_group (PikaDrawTool    *draw_tool,
                           PikaCanvasGroup *group)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (PIKA_IS_CANVAS_GROUP (group));

  draw_tool->group_stack = g_list_prepend (draw_tool->group_stack, group);
}

void
pika_draw_tool_pop_group (PikaDrawTool *draw_tool)
{
  g_return_if_fail (PIKA_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (draw_tool->group_stack != NULL);

  draw_tool->group_stack = g_list_remove (draw_tool->group_stack,
                                          draw_tool->group_stack->data);
}

/**
 * pika_draw_tool_add_line:
 * @draw_tool:   the #PikaDrawTool
 * @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 takes image space coordinates and transforms them to
 * screen window coordinates, then draws a line between the resulting
 * coordinates.
 **/
PikaCanvasItem *
pika_draw_tool_add_line (PikaDrawTool *draw_tool,
                         gdouble       x1,
                         gdouble       y1,
                         gdouble       x2,
                         gdouble       y2)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_line_new (pika_display_get_shell (draw_tool->display),
                               x1, y1, x2, y2);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * pika_draw_tool_add_guide:
 * @draw_tool:   the #PikaDrawTool
 * @orientation: the orientation of the guide line
 * @position:    the position of the guide line in image coordinates
 *
 * This function draws a guide line across the canvas.
 **/
PikaCanvasItem *
pika_draw_tool_add_guide (PikaDrawTool        *draw_tool,
                          PikaOrientationType  orientation,
                          gint                 position,
                          PikaGuideStyle       style)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_guide_new (pika_display_get_shell (draw_tool->display),
                                orientation, position,
                                style);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * pika_draw_tool_add_crosshair:
 * @draw_tool:  the #PikaDrawTool
 * @position_x: the position of the vertical guide line in image coordinates
 * @position_y: the position of the horizontal guide line in image coordinates
 *
 * This function draws two crossing guide lines across the canvas.
 **/
PikaCanvasItem *
pika_draw_tool_add_crosshair (PikaDrawTool *draw_tool,
                              gint          position_x,
                              gint          position_y)
{
  PikaCanvasGroup *group;

  group = pika_draw_tool_add_stroke_group (draw_tool);

  pika_draw_tool_push_group (draw_tool, group);
  pika_draw_tool_add_guide (draw_tool,
                            PIKA_ORIENTATION_VERTICAL, position_x,
                            PIKA_GUIDE_STYLE_NONE);
  pika_draw_tool_add_guide (draw_tool,
                            PIKA_ORIENTATION_HORIZONTAL, position_y,
                            PIKA_GUIDE_STYLE_NONE);
  pika_draw_tool_pop_group (draw_tool);

  return PIKA_CANVAS_ITEM (group);
}

/**
 * pika_draw_tool_add_sample_point:
 * @draw_tool: the #PikaDrawTool
 * @x:         X position of the sample point
 * @y:         Y position of the sample point
 * @index:     Index of the sample point
 *
 * This function draws a sample point
 **/
PikaCanvasItem *
pika_draw_tool_add_sample_point (PikaDrawTool *draw_tool,
                                 gint          x,
                                 gint          y,
                                 gint          index)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_sample_point_new (pika_display_get_shell (draw_tool->display),
                                       x, y, index, TRUE);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * pika_draw_tool_add_rectangle:
 * @draw_tool:   the #PikaDrawTool
 * @filled:      whether to fill the rectangle
 * @x:           horizontal image coordinate
 * @y:           vertical image coordinate
 * @width:       width in image coordinates
 * @height:      height in image coordinates
 *
 * This function takes image space coordinates and transforms them to
 * screen window coordinates, then draws the resulting rectangle.
 **/
PikaCanvasItem *
pika_draw_tool_add_rectangle (PikaDrawTool *draw_tool,
                              gboolean      filled,
                              gdouble       x,
                              gdouble       y,
                              gdouble       width,
                              gdouble       height)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_rectangle_new (pika_display_get_shell (draw_tool->display),
                                    x, y, width, height, filled);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_arc (PikaDrawTool *draw_tool,
                        gboolean      filled,
                        gdouble       x,
                        gdouble       y,
                        gdouble       width,
                        gdouble       height,
                        gdouble       start_angle,
                        gdouble       slice_angle)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_arc_new (pika_display_get_shell (draw_tool->display),
                              x + width  / 2.0,
                              y + height / 2.0,
                              width  / 2.0,
                              height / 2.0,
                              start_angle,
                              slice_angle,
                              filled);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_handle (PikaDrawTool     *draw_tool,
                           PikaHandleType    type,
                           gdouble           x,
                           gdouble           y,
                           gint              width,
                           gint              height,
                           PikaHandleAnchor  anchor)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_handle_new (pika_display_get_shell (draw_tool->display),
                                 type, anchor, x, y, width, height);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_lines (PikaDrawTool      *draw_tool,
                          const PikaVector2 *points,
                          gint               n_points,
                          PikaMatrix3       *transform,
                          gboolean           filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  if (points == NULL || n_points < 2)
    return NULL;

  item = pika_canvas_polygon_new (pika_display_get_shell (draw_tool->display),
                                  points, n_points, transform, filled);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_strokes (PikaDrawTool     *draw_tool,
                            const PikaCoords *points,
                            gint              n_points,
                            PikaMatrix3      *transform,
                            gboolean          filled)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  if (points == NULL || n_points < 2)
    return NULL;

  item = pika_canvas_polygon_new_from_coords (pika_display_get_shell (draw_tool->display),
                                              points, n_points, transform, filled);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_pen (PikaDrawTool      *draw_tool,
                        const PikaVector2 *points,
                        gint               n_points,
                        PikaContext       *context,
                        PikaActiveColor    color,
                        gint               width)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  if (points == NULL || n_points < 2)
    return NULL;

  item = pika_canvas_pen_new (pika_display_get_shell (draw_tool->display),
                              points, n_points, context, color, width);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * pika_draw_tool_add_boundary:
 * @draw_tool:    a #PikaDrawTool
 * @bound_segs:   the sorted brush outline
 * @n_bound_segs: the number of segments in @bound_segs
 * @matrix:       transform matrix for the boundary
 * @offset_x:     x offset
 * @offset_y:     y offset
 *
 * Draw the boundary of the brush that @draw_tool uses. The boundary
 * should be sorted with sort_boundary(), and @n_bound_segs should
 * include the sentinel segments inserted by sort_boundary() that
 * indicate the end of connected segment sequences (groups) .
 */
PikaCanvasItem *
pika_draw_tool_add_boundary (PikaDrawTool       *draw_tool,
                             const PikaBoundSeg *bound_segs,
                             gint                n_bound_segs,
                             PikaMatrix3        *transform,
                             gdouble             offset_x,
                             gdouble             offset_y)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);
  g_return_val_if_fail (n_bound_segs > 0, NULL);
  g_return_val_if_fail (bound_segs != NULL, NULL);

  item = pika_canvas_boundary_new (pika_display_get_shell (draw_tool->display),
                                   bound_segs, n_bound_segs,
                                   transform,
                                   offset_x, offset_y);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_text_cursor (PikaDrawTool     *draw_tool,
                                PangoRectangle   *cursor,
                                gboolean          overwrite,
                                PikaTextDirection direction)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_text_cursor_new (pika_display_get_shell (draw_tool->display),
                                      cursor, overwrite, direction);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_transform_preview (PikaDrawTool      *draw_tool,
                                      PikaPickable      *pickable,
                                      const PikaMatrix3 *transform,
                                      gdouble            x1,
                                      gdouble            y1,
                                      gdouble            x2,
                                      gdouble            y2)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);
  g_return_val_if_fail (PIKA_IS_PICKABLE (pickable), NULL);
  g_return_val_if_fail (transform != NULL, NULL);

  item = pika_canvas_transform_preview_new (pika_display_get_shell (draw_tool->display),
                                            pickable, transform,
                                            x1, y1, x2, y2);

  pika_draw_tool_add_preview (draw_tool, item);
  g_object_unref (item);

  return item;
}

PikaCanvasItem *
pika_draw_tool_add_text (PikaDrawTool *draw_tool,
                         gdouble       x,
                         gdouble       y,
                         gdouble       font_size,
                         gchar        *text)
{
  PikaCanvasItem *item;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), NULL);

  item = pika_canvas_text_new (pika_display_get_shell (draw_tool->display),
                               x, y, font_size, text);

  pika_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

gboolean
pika_draw_tool_on_handle (PikaDrawTool     *draw_tool,
                          PikaDisplay      *display,
                          gdouble           x,
                          gdouble           y,
                          PikaHandleType    type,
                          gdouble           handle_x,
                          gdouble           handle_y,
                          gint              width,
                          gint              height,
                          PikaHandleAnchor  anchor)
{
  PikaDisplayShell *shell;
  gdouble           tx, ty;
  gdouble           handle_tx, handle_ty;

  g_return_val_if_fail (PIKA_IS_DRAW_TOOL (draw_tool), FALSE);
  g_return_val_if_fail (PIKA_IS_DISPLAY (display), FALSE);

  shell = pika_display_get_shell (display);

  pika_display_shell_zoom_xy_f (shell,
                                x, y,
                                &tx, &ty);
  pika_display_shell_zoom_xy_f (shell,
                                handle_x, handle_y,
                                &handle_tx, &handle_ty);

  switch (type)
    {
    case PIKA_HANDLE_SQUARE:
    case PIKA_HANDLE_FILLED_SQUARE:
    case PIKA_HANDLE_CROSS:
    case PIKA_HANDLE_CROSSHAIR:
      pika_canvas_item_shift_to_north_west (anchor,
                                            handle_tx, handle_ty,
                                            width, height,
                                            &handle_tx, &handle_ty);

      return (tx == CLAMP (tx, handle_tx, handle_tx + width) &&
              ty == CLAMP (ty, handle_ty, handle_ty + height));

    case PIKA_HANDLE_CIRCLE:
    case PIKA_HANDLE_FILLED_CIRCLE:
      pika_canvas_item_shift_to_center (anchor,
                                        handle_tx, handle_ty,
                                        width, height,
                                        &handle_tx, &handle_ty);

      /* FIXME */
      if (width != height)
        width = (width + height) / 2;

      width /= 2;

      return ((SQR (handle_tx - tx) + SQR (handle_ty - ty)) < SQR (width));

    default:
      g_warning ("%s: invalid handle type %d", G_STRFUNC, type);
      break;
    }

  return FALSE;
}
