/* GIMP - The GNU Image Manipulation Program
 * 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 "libgimpmath/gimpmath.h"

#include "tools-types.h"

#include "core/gimpimage.h"
#include "core/gimppickable.h"

#include "display/gimpcanvas.h"
#include "display/gimpcanvasarc.h"
#include "display/gimpcanvasboundary.h"
#include "display/gimpcanvasgroup.h"
#include "display/gimpcanvasguide.h"
#include "display/gimpcanvashandle.h"
#include "display/gimpcanvasitem-utils.h"
#include "display/gimpcanvasline.h"
#include "display/gimpcanvaspen.h"
#include "display/gimpcanvaspolygon.h"
#include "display/gimpcanvasrectangle.h"
#include "display/gimpcanvassamplepoint.h"
#include "display/gimpcanvastextcursor.h"
#include "display/gimpcanvastransformpreview.h"
#include "display/gimpcanvastext.h"
#include "display/gimpdisplay.h"
#include "display/gimpdisplayshell.h"
#include "display/gimpdisplayshell-items.h"
#include "display/gimpdisplayshell-transform.h"
#include "display/gimptoolwidget.h"

#include "gimpdrawtool.h"
#include "gimptoolcontrol.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            gimp_draw_tool_dispose       (GObject           *object);

static gboolean        gimp_draw_tool_has_display   (GimpTool          *tool,
                                                     GimpDisplay       *display);
static GimpDisplay   * gimp_draw_tool_has_image     (GimpTool          *tool,
                                                     GimpImage         *image);
static void            gimp_draw_tool_control       (GimpTool          *tool,
                                                     GimpToolAction     action,
                                                     GimpDisplay       *display);
static gboolean        gimp_draw_tool_key_press     (GimpTool          *tool,
                                                     GdkEventKey       *kevent,
                                                     GimpDisplay       *display);
static gboolean        gimp_draw_tool_key_release   (GimpTool          *tool,
                                                     GdkEventKey       *kevent,
                                                     GimpDisplay       *display);
static void            gimp_draw_tool_modifier_key  (GimpTool          *tool,
                                                     GdkModifierType    key,
                                                     gboolean           press,
                                                     GdkModifierType    state,
                                                     GimpDisplay       *display);
static void            gimp_draw_tool_active_modifier_key
                                                    (GimpTool          *tool,
                                                     GdkModifierType    key,
                                                     gboolean           press,
                                                     GdkModifierType    state,
                                                     GimpDisplay       *display);
static void            gimp_draw_tool_oper_update   (GimpTool          *tool,
                                                     const GimpCoords  *coords,
                                                     GdkModifierType    state,
                                                     gboolean           proximity,
                                                     GimpDisplay       *display);
static void            gimp_draw_tool_cursor_update (GimpTool          *tool,
                                                     const GimpCoords  *coords,
                                                     GdkModifierType    state,
                                                     GimpDisplay       *display);
static GimpUIManager * gimp_draw_tool_get_popup     (GimpTool          *tool,
                                                     const GimpCoords  *coords,
                                                     GdkModifierType    state,
                                                     GimpDisplay       *display,
                                                     const gchar      **ui_path);

static void            gimp_draw_tool_widget_status (GimpToolWidget    *widget,
                                                     const gchar       *status,
                                                     GimpTool          *tool);
static void            gimp_draw_tool_widget_status_coords
                                                    (GimpToolWidget    *widget,
                                                     const gchar       *title,
                                                     gdouble            x,
                                                     const gchar       *separator,
                                                     gdouble            y,
                                                     const gchar       *help,
                                                     GimpTool          *tool);
static void            gimp_draw_tool_widget_message
                                                    (GimpToolWidget    *widget,
                                                     const gchar       *message,
                                                     GimpTool          *tool);
static void            gimp_draw_tool_widget_snap_offsets
                                                    (GimpToolWidget    *widget,
                                                     gint               offset_x,
                                                     gint               offset_y,
                                                     gint               width,
                                                     gint               height,
                                                     GimpTool          *tool);

static void            gimp_draw_tool_draw          (GimpDrawTool      *draw_tool);
static void            gimp_draw_tool_undraw        (GimpDrawTool      *draw_tool);
static void            gimp_draw_tool_real_draw     (GimpDrawTool      *draw_tool);


G_DEFINE_TYPE (GimpDrawTool, gimp_draw_tool, GIMP_TYPE_TOOL)

#define parent_class gimp_draw_tool_parent_class


static void
gimp_draw_tool_class_init (GimpDrawToolClass *klass)
{
  GObjectClass  *object_class = G_OBJECT_CLASS (klass);
  GimpToolClass *tool_class   = GIMP_TOOL_CLASS (klass);

  object_class->dispose           = gimp_draw_tool_dispose;

  tool_class->has_display         = gimp_draw_tool_has_display;
  tool_class->has_image           = gimp_draw_tool_has_image;
  tool_class->control             = gimp_draw_tool_control;
  tool_class->key_press           = gimp_draw_tool_key_press;
  tool_class->key_release         = gimp_draw_tool_key_release;
  tool_class->modifier_key        = gimp_draw_tool_modifier_key;
  tool_class->active_modifier_key = gimp_draw_tool_active_modifier_key;
  tool_class->oper_update         = gimp_draw_tool_oper_update;
  tool_class->cursor_update       = gimp_draw_tool_cursor_update;
  tool_class->get_popup           = gimp_draw_tool_get_popup;

  klass->draw                     = gimp_draw_tool_real_draw;
}

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

static void
gimp_draw_tool_dispose (GObject *object)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (object);

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

  gimp_draw_tool_set_widget (draw_tool, NULL);
  gimp_draw_tool_set_default_status (draw_tool, NULL);

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

static gboolean
gimp_draw_tool_has_display (GimpTool    *tool,
                            GimpDisplay *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

static GimpDisplay *
gimp_draw_tool_has_image (GimpTool  *tool,
                          GimpImage *image)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
  GimpDisplay  *display;

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

  if (! display && draw_tool->display)
    {
      if (image && gimp_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
gimp_draw_tool_control (GimpTool       *tool,
                        GimpToolAction  action,
                        GimpDisplay    *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  switch (action)
    {
    case GIMP_TOOL_ACTION_PAUSE:
    case GIMP_TOOL_ACTION_RESUME:
      break;

    case GIMP_TOOL_ACTION_HALT:
      if (gimp_draw_tool_is_active (draw_tool))
        gimp_draw_tool_stop (draw_tool);
      gimp_draw_tool_set_widget (draw_tool, NULL);
      break;

    case GIMP_TOOL_ACTION_COMMIT:
      break;
    }

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

static gboolean
gimp_draw_tool_key_press (GimpTool    *tool,
                          GdkEventKey *kevent,
                          GimpDisplay *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

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

static gboolean
gimp_draw_tool_key_release (GimpTool    *tool,
                            GdkEventKey *kevent,
                            GimpDisplay *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

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

static void
gimp_draw_tool_modifier_key (GimpTool        *tool,
                             GdkModifierType  key,
                             gboolean         press,
                             GdkModifierType  state,
                             GimpDisplay     *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

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

static void
gimp_draw_tool_active_modifier_key (GimpTool        *tool,
                                    GdkModifierType  key,
                                    gboolean         press,
                                    GdkModifierType  state,
                                    GimpDisplay     *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

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

static void
gimp_draw_tool_oper_update (GimpTool         *tool,
                            const GimpCoords *coords,
                            GdkModifierType   state,
                            gboolean          proximity,
                            GimpDisplay      *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      gimp_tool_widget_hover (draw_tool->widget, coords, state, proximity);
    }
  else if (proximity && draw_tool->default_status)
    {
      gimp_tool_replace_status (tool, display, "%s", draw_tool->default_status);
    }
  else if (! proximity)
    {
      gimp_tool_pop_status (tool, display);
    }
  else
    {
      GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
                                                   proximity, display);
    }
}

static void
gimp_draw_tool_cursor_update (GimpTool         *tool,
                              const GimpCoords *coords,
                              GdkModifierType   state,
                              GimpDisplay      *display)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  if (draw_tool->widget && display == draw_tool->display)
    {
      GimpCursorType     cursor;
      GimpToolCursorType tool_cursor;
      GimpCursorModifier modifier;

      cursor      = gimp_tool_control_get_cursor (tool->control);
      tool_cursor = gimp_tool_control_get_tool_cursor (tool->control);
      modifier    = gimp_tool_control_get_cursor_modifier (tool->control);

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

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

static GimpUIManager *
gimp_draw_tool_get_popup (GimpTool          *tool,
                          const GimpCoords  *coords,
                          GdkModifierType    state,
                          GimpDisplay       *display,
                          const gchar      **ui_path)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

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

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

      if (ui_manager)
        return ui_manager;
    }

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

static void
gimp_draw_tool_widget_status (GimpToolWidget *widget,
                              const gchar    *status,
                              GimpTool       *tool)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  if (gimp_draw_tool_is_active (draw_tool))
    {
      if (status)
        gimp_tool_replace_status (tool, draw_tool->display, "%s", status);
      else
        gimp_tool_pop_status (tool, draw_tool->display);
    }
}

static void
gimp_draw_tool_widget_status_coords (GimpToolWidget *widget,
                                     const gchar    *title,
                                     gdouble         x,
                                     const gchar    *separator,
                                     gdouble         y,
                                     const gchar    *help,
                                     GimpTool       *tool)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  if (gimp_draw_tool_is_active (draw_tool))
    {
      gimp_tool_pop_status (tool, draw_tool->display);
      gimp_tool_push_status_coords (tool, draw_tool->display,
                                    gimp_tool_control_get_precision (
                                      tool->control),
                                    title, x, separator, y, help);
    }
}

static void
gimp_draw_tool_widget_message (GimpToolWidget *widget,
                               const gchar    *message,
                               GimpTool       *tool)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);

  if (gimp_draw_tool_is_active (draw_tool))
    gimp_tool_message_literal (tool, draw_tool->display, message);
}

static void
gimp_draw_tool_widget_snap_offsets (GimpToolWidget   *widget,
                                    gint              offset_x,
                                    gint              offset_y,
                                    gint              width,
                                    gint              height,
                                    GimpTool         *tool)
{
  gimp_tool_control_set_snap_offsets (tool->control,
                                      offset_x, offset_y,
                                      width, height);
}

#ifdef USE_TIMEOUT
static gboolean
gimp_draw_tool_draw_timeout (GimpDrawTool *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;

  gimp_draw_tool_draw (draw_tool);

  return FALSE;
}
#endif

static void
gimp_draw_tool_draw (GimpDrawTool *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))
    {
      GimpDisplayShell *shell = gimp_display_get_shell (draw_tool->display);

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

      gimp_draw_tool_undraw (draw_tool);

      GIMP_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 "
                     "GimpDrawTool::draw() of %s",
                     G_STRFUNC,
                     g_type_name (G_TYPE_FROM_INSTANCE (draw_tool)));

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

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

      if (draw_tool->item)
        gimp_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
gimp_draw_tool_undraw (GimpDrawTool *draw_tool)
{
  if (draw_tool->display)
    {
      GimpDisplayShell *shell = gimp_display_get_shell (draw_tool->display);

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

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

static void
gimp_draw_tool_real_draw (GimpDrawTool *draw_tool)
{
  if (draw_tool->widget)
    {
      GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);

      gimp_draw_tool_add_item (draw_tool, item);
    }
}

void
gimp_draw_tool_start (GimpDrawTool *draw_tool,
                      GimpDisplay  *display)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_DISPLAY (display));
  g_return_if_fail (gimp_draw_tool_is_active (draw_tool) == FALSE);

  draw_tool->display = display;

  gimp_draw_tool_draw (draw_tool);
}

void
gimp_draw_tool_stop (GimpDrawTool *draw_tool)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (gimp_draw_tool_is_active (draw_tool) == TRUE);

  gimp_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
gimp_draw_tool_is_active (GimpDrawTool *draw_tool)
{
  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), FALSE);

  return draw_tool->display != NULL;
}

void
gimp_draw_tool_pause (GimpDrawTool *draw_tool)
{
  g_return_if_fail (GIMP_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
gimp_draw_tool_resume (GimpDrawTool *draw_tool)
{
  g_return_if_fail (GIMP_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 (gimp_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) gimp_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
       */
      gimp_draw_tool_draw (draw_tool);
    }
}

/**
 * gimp_draw_tool_calc_distance:
 * @draw_tool: a #GimpDrawTool
 * @display:   a #GimpDisplay
 * @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
 * gimp_draw_tool_calc_distance_square() instead.
 *
 * Returns: the distance between the given points in display coordinates
 **/
gdouble
gimp_draw_tool_calc_distance (GimpDrawTool *draw_tool,
                              GimpDisplay  *display,
                              gdouble       x1,
                              gdouble       y1,
                              gdouble       x2,
                              gdouble       y2)
{
  return sqrt (gimp_draw_tool_calc_distance_square (draw_tool, display,
                                                    x1, y1, x2, y2));
}

/**
 * gimp_draw_tool_calc_distance_square:
 * @draw_tool: a #GimpDrawTool
 * @display:   a #GimpDisplay
 * @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 gimp_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
gimp_draw_tool_calc_distance_square (GimpDrawTool *draw_tool,
                                     GimpDisplay  *display,
                                     gdouble       x1,
                                     gdouble       y1,
                                     gdouble       x2,
                                     gdouble       y2)
{
  GimpDisplayShell *shell;
  gdouble           tx1, ty1;
  gdouble           tx2, ty2;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), 0.0);
  g_return_val_if_fail (GIMP_IS_DISPLAY (display), 0.0);

  shell = gimp_display_get_shell (display);

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

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

void
gimp_draw_tool_set_widget (GimpDrawTool   *draw_tool,
                           GimpToolWidget *widget)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (widget == NULL || GIMP_IS_TOOL_WIDGET (widget));

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

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

      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            gimp_draw_tool_widget_status,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            gimp_draw_tool_widget_status_coords,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            gimp_draw_tool_widget_message,
                                            draw_tool);
      g_signal_handlers_disconnect_by_func (draw_tool->widget,
                                            gimp_draw_tool_widget_snap_offsets,
                                            draw_tool);

      if (gimp_draw_tool_is_active (draw_tool))
        {
          GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);

          gimp_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 (gimp_draw_tool_is_active (draw_tool))
        {
          GimpCanvasItem *item = gimp_tool_widget_get_item (draw_tool->widget);

          gimp_draw_tool_add_item (draw_tool, item);
        }

      g_signal_connect (draw_tool->widget, "status",
                        G_CALLBACK (gimp_draw_tool_widget_status),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "status-coords",
                        G_CALLBACK (gimp_draw_tool_widget_status_coords),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "message",
                        G_CALLBACK (gimp_draw_tool_widget_message),
                        draw_tool);
      g_signal_connect (draw_tool->widget, "snap-offsets",
                        G_CALLBACK (gimp_draw_tool_widget_snap_offsets),
                        draw_tool);

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

void
gimp_draw_tool_set_default_status (GimpDrawTool *draw_tool,
                                   const gchar  *status)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));

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

  draw_tool->default_status = g_strdup (status);
}

void
gimp_draw_tool_add_preview (GimpDrawTool   *draw_tool,
                            GimpCanvasItem *item)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));

  if (! draw_tool->preview)
    draw_tool->preview =
      gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));

  gimp_canvas_group_add_item (GIMP_CANVAS_GROUP (draw_tool->preview), item);
}

void
gimp_draw_tool_remove_preview (GimpDrawTool   *draw_tool,
                               GimpCanvasItem *item)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
  g_return_if_fail (draw_tool->preview != NULL);

  gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (draw_tool->preview), item);
}

void
gimp_draw_tool_add_item (GimpDrawTool   *draw_tool,
                         GimpCanvasItem *item)
{
  GimpCanvasGroup *group;

  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));

  if (! draw_tool->item)
    draw_tool->item =
      gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));

  group = GIMP_CANVAS_GROUP (draw_tool->item);

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

  gimp_canvas_group_add_item (group, item);
}

void
gimp_draw_tool_remove_item (GimpDrawTool   *draw_tool,
                            GimpCanvasItem *item)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
  g_return_if_fail (draw_tool->item != NULL);

  gimp_canvas_group_remove_item (GIMP_CANVAS_GROUP (draw_tool->item), item);
}

GimpCanvasGroup *
gimp_draw_tool_add_stroke_group (GimpDrawTool *draw_tool)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
  gimp_canvas_group_set_group_stroking (GIMP_CANVAS_GROUP (item), TRUE);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return GIMP_CANVAS_GROUP (item);
}

GimpCanvasGroup *
gimp_draw_tool_add_fill_group (GimpDrawTool *draw_tool)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_group_new (gimp_display_get_shell (draw_tool->display));
  gimp_canvas_group_set_group_filling (GIMP_CANVAS_GROUP (item), TRUE);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return GIMP_CANVAS_GROUP (item);
}

void
gimp_draw_tool_push_group (GimpDrawTool    *draw_tool,
                           GimpCanvasGroup *group)
{
  g_return_if_fail (GIMP_IS_DRAW_TOOL (draw_tool));
  g_return_if_fail (GIMP_IS_CANVAS_GROUP (group));

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

void
gimp_draw_tool_pop_group (GimpDrawTool *draw_tool)
{
  g_return_if_fail (GIMP_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);
}

/**
 * gimp_draw_tool_add_line:
 * @draw_tool:   the #GimpDrawTool
 * @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.
 **/
GimpCanvasItem *
gimp_draw_tool_add_line (GimpDrawTool *draw_tool,
                         gdouble       x1,
                         gdouble       y1,
                         gdouble       x2,
                         gdouble       y2)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_line_new (gimp_display_get_shell (draw_tool->display),
                               x1, y1, x2, y2);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * gimp_draw_tool_add_guide:
 * @draw_tool:   the #GimpDrawTool
 * @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.
 **/
GimpCanvasItem *
gimp_draw_tool_add_guide (GimpDrawTool        *draw_tool,
                          GimpOrientationType  orientation,
                          gint                 position,
                          GimpGuideStyle       style)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_guide_new (gimp_display_get_shell (draw_tool->display),
                                orientation, position,
                                style);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * gimp_draw_tool_add_crosshair:
 * @draw_tool:  the #GimpDrawTool
 * @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.
 **/
GimpCanvasItem *
gimp_draw_tool_add_crosshair (GimpDrawTool *draw_tool,
                              gint          position_x,
                              gint          position_y)
{
  GimpCanvasGroup *group;

  group = gimp_draw_tool_add_stroke_group (draw_tool);

  gimp_draw_tool_push_group (draw_tool, group);
  gimp_draw_tool_add_guide (draw_tool,
                            GIMP_ORIENTATION_VERTICAL, position_x,
                            GIMP_GUIDE_STYLE_NONE);
  gimp_draw_tool_add_guide (draw_tool,
                            GIMP_ORIENTATION_HORIZONTAL, position_y,
                            GIMP_GUIDE_STYLE_NONE);
  gimp_draw_tool_pop_group (draw_tool);

  return GIMP_CANVAS_ITEM (group);
}

/**
 * gimp_draw_tool_add_sample_point:
 * @draw_tool: the #GimpDrawTool
 * @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
 **/
GimpCanvasItem *
gimp_draw_tool_add_sample_point (GimpDrawTool *draw_tool,
                                 gint          x,
                                 gint          y,
                                 gint          index)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_sample_point_new (gimp_display_get_shell (draw_tool->display),
                                       x, y, index, TRUE);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * gimp_draw_tool_add_rectangle:
 * @draw_tool:   the #GimpDrawTool
 * @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.
 **/
GimpCanvasItem *
gimp_draw_tool_add_rectangle (GimpDrawTool *draw_tool,
                              gboolean      filled,
                              gdouble       x,
                              gdouble       y,
                              gdouble       width,
                              gdouble       height)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_rectangle_new (gimp_display_get_shell (draw_tool->display),
                                    x, y, width, height, filled);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_arc (GimpDrawTool *draw_tool,
                        gboolean      filled,
                        gdouble       x,
                        gdouble       y,
                        gdouble       width,
                        gdouble       height,
                        gdouble       start_angle,
                        gdouble       slice_angle)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_arc_new (gimp_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);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_handle (GimpDrawTool     *draw_tool,
                           GimpHandleType    type,
                           gdouble           x,
                           gdouble           y,
                           gint              width,
                           gint              height,
                           GimpHandleAnchor  anchor)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

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

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_lines (GimpDrawTool      *draw_tool,
                          const GimpVector2 *points,
                          gint               n_points,
                          GimpMatrix3       *transform,
                          gboolean           filled)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

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

  item = gimp_canvas_polygon_new (gimp_display_get_shell (draw_tool->display),
                                  points, n_points, transform, filled);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_strokes (GimpDrawTool     *draw_tool,
                            const GimpCoords *points,
                            gint              n_points,
                            GimpMatrix3      *transform,
                            gboolean          filled)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

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

  item = gimp_canvas_polygon_new_from_coords (gimp_display_get_shell (draw_tool->display),
                                              points, n_points, transform, filled);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_pen (GimpDrawTool      *draw_tool,
                        const GimpVector2 *points,
                        gint               n_points,
                        GimpContext       *context,
                        GimpActiveColor    color,
                        gint               width)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

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

  item = gimp_canvas_pen_new (gimp_display_get_shell (draw_tool->display),
                              points, n_points, context, color, width);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

/**
 * gimp_draw_tool_add_boundary:
 * @draw_tool:    a #GimpDrawTool
 * @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) .
 */
GimpCanvasItem *
gimp_draw_tool_add_boundary (GimpDrawTool       *draw_tool,
                             const GimpBoundSeg *bound_segs,
                             gint                n_bound_segs,
                             GimpMatrix3        *transform,
                             gdouble             offset_x,
                             gdouble             offset_y)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_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 = gimp_canvas_boundary_new (gimp_display_get_shell (draw_tool->display),
                                   bound_segs, n_bound_segs,
                                   transform,
                                   offset_x, offset_y);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_text_cursor (GimpDrawTool     *draw_tool,
                                PangoRectangle   *cursor,
                                gboolean          overwrite,
                                GimpTextDirection direction)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_text_cursor_new (gimp_display_get_shell (draw_tool->display),
                                      cursor, overwrite, direction);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_transform_preview (GimpDrawTool      *draw_tool,
                                      GimpPickable      *pickable,
                                      const GimpMatrix3 *transform,
                                      gdouble            x1,
                                      gdouble            y1,
                                      gdouble            x2,
                                      gdouble            y2)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);
  g_return_val_if_fail (GIMP_IS_PICKABLE (pickable), NULL);
  g_return_val_if_fail (transform != NULL, NULL);

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

  gimp_draw_tool_add_preview (draw_tool, item);
  g_object_unref (item);

  return item;
}

GimpCanvasItem *
gimp_draw_tool_add_text (GimpDrawTool *draw_tool,
                         gdouble       x,
                         gdouble       y,
                         gdouble       font_size,
                         gchar        *text)
{
  GimpCanvasItem *item;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), NULL);

  item = gimp_canvas_text_new (gimp_display_get_shell (draw_tool->display),
                               x, y, font_size, text);

  gimp_draw_tool_add_item (draw_tool, item);
  g_object_unref (item);

  return item;
}

gboolean
gimp_draw_tool_on_handle (GimpDrawTool     *draw_tool,
                          GimpDisplay      *display,
                          gdouble           x,
                          gdouble           y,
                          GimpHandleType    type,
                          gdouble           handle_x,
                          gdouble           handle_y,
                          gint              width,
                          gint              height,
                          GimpHandleAnchor  anchor)
{
  GimpDisplayShell *shell;
  gdouble           tx, ty;
  gdouble           handle_tx, handle_ty;

  g_return_val_if_fail (GIMP_IS_DRAW_TOOL (draw_tool), FALSE);
  g_return_val_if_fail (GIMP_IS_DISPLAY (display), FALSE);

  shell = gimp_display_get_shell (display);

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

  switch (type)
    {
    case GIMP_HANDLE_SQUARE:
    case GIMP_HANDLE_FILLED_SQUARE:
    case GIMP_HANDLE_CROSS:
    case GIMP_HANDLE_CROSSHAIR:
      gimp_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 GIMP_HANDLE_CIRCLE:
    case GIMP_HANDLE_FILLED_CIRCLE:
      gimp_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;
}
