/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimptoolpolygon.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * Based on GimpFreeSelectTool
 *
 * Major improvement to support polygonal segments
 * Copyright (C) 2008 Martin Nordholts
 *
 * 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 <gdk/gdkkeysyms.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"

#include "display-types.h"

#include "core/gimp-utils.h"

#include "widgets/gimpwidgets-utils.h"

#include "gimpcanvashandle.h"
#include "gimpcanvasline.h"
#include "gimpcanvaspolygon.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-utils.h"
#include "gimptoolpolygon.h"

#include "gimp-intl.h"


#define POINT_GRAB_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE / 2)
#define POINT_SHOW_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE * 7)
#define N_ITEMS_PER_ALLOC       1024
#define INVALID_INDEX           (-1)
#define NO_CLICK_TIME_AVAILABLE 0


struct _GimpToolPolygonPrivate
{
  /* Index of grabbed segment index. */
  gint               grabbed_segment_index;

  /* We need to keep track of a number of points when we move a
   * segment vertex
   */
  GimpVector2       *saved_points_lower_segment;
  GimpVector2       *saved_points_higher_segment;
  gint               max_n_saved_points_lower_segment;
  gint               max_n_saved_points_higher_segment;

  /* Keeps track whether or not a modification of the polygon has been
   * made between _button_press and _button_release
   */
  gboolean           polygon_modified;

  /* Point which is used to draw the polygon but which is not part of
   * it yet
   */
  GimpVector2        pending_point;
  gboolean           show_pending_point;

  /* The points of the polygon */
  GimpVector2       *points;
  gint               max_n_points;

  /* The number of points actually in use */
  gint               n_points;


  /* Any int array containing the indices for the points in the
   * polygon that connects different segments together
   */
  gint              *segment_indices;
  gint               max_n_segment_indices;

  /* The number of segment indices actually in use */
  gint               n_segment_indices;

  /* Is the polygon closed? */
  gboolean           polygon_closed;

  /* The selection operation active when the tool was started */
  GimpChannelOps     operation_at_start;

  /* Whether or not to constrain the angle for newly created polygonal
   * segments.
   */
  gboolean           constrain_angle;

  /* Whether or not to suppress handles (so that new segments can be
   * created immediately after an existing segment vertex.
   */
  gboolean           suppress_handles;

  /* Last _oper_update or _motion coords */
  gboolean           hover;
  GimpVector2        last_coords;

  /* A double-click commits the selection, keep track of last
   * click-time and click-position.
   */
  guint32            last_click_time;
  GimpCoords         last_click_coord;

  /* Are we in a click-drag-release? */
  gboolean           button_down;

  GimpCanvasItem    *polygon;
  GimpCanvasItem    *pending_line;
  GimpCanvasItem    *closing_line;
  GPtrArray         *handles;
};


/*  local function prototypes  */

static void     gimp_tool_polygon_constructed     (GObject               *object);
static void     gimp_tool_polygon_finalize        (GObject               *object);
static void     gimp_tool_polygon_set_property    (GObject               *object,
                                                   guint                  property_id,
                                                   const GValue          *value,
                                                   GParamSpec            *pspec);
static void     gimp_tool_polygon_get_property    (GObject               *object,
                                                   guint                  property_id,
                                                   GValue                *value,
                                                   GParamSpec            *pspec);

static void     gimp_tool_polygon_changed         (GimpToolWidget        *widget);
static gint     gimp_tool_polygon_button_press    (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   guint32                time,
                                                   GdkModifierType        state,
                                                   GimpButtonPressType    press_type);
static void     gimp_tool_polygon_button_release  (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   guint32                time,
                                                   GdkModifierType        state,
                                                   GimpButtonReleaseType  release_type);
static void     gimp_tool_polygon_motion          (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   guint32                time,
                                                   GdkModifierType        state);
static GimpHit  gimp_tool_polygon_hit             (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   GdkModifierType        state,
                                                   gboolean               proximity);
static void     gimp_tool_polygon_hover           (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   GdkModifierType        state,
                                                   gboolean               proximity);
static void     gimp_tool_polygon_leave_notify    (GimpToolWidget        *widget);
static gboolean gimp_tool_polygon_key_press       (GimpToolWidget        *widget,
                                                   GdkEventKey           *kevent);
static void     gimp_tool_polygon_motion_modifier (GimpToolWidget        *widget,
                                                   GdkModifierType        key,
                                                   gboolean               press,
                                                   GdkModifierType        state);
static void     gimp_tool_polygon_hover_modifier  (GimpToolWidget        *widget,
                                                   GdkModifierType        key,
                                                   gboolean               press,
                                                   GdkModifierType        state);
static gboolean gimp_tool_polygon_get_cursor      (GimpToolWidget        *widget,
                                                   const GimpCoords      *coords,
                                                   GdkModifierType        state,
                                                   GimpCursorType        *cursor,
                                                   GimpToolCursorType    *tool_cursor,
                                                   GimpCursorModifier    *modifier);

static gint   gimp_tool_polygon_get_segment_index (GimpToolPolygon       *polygon,
                                                   const GimpCoords      *coords);


G_DEFINE_TYPE (GimpToolPolygon, gimp_tool_polygon, GIMP_TYPE_TOOL_WIDGET)

#define parent_class gimp_tool_polygon_parent_class

static const GimpVector2 vector2_zero = { 0.0, 0.0 };


static void
gimp_tool_polygon_class_init (GimpToolPolygonClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);

  object_class->constructed     = gimp_tool_polygon_constructed;
  object_class->finalize        = gimp_tool_polygon_finalize;
  object_class->set_property    = gimp_tool_polygon_set_property;
  object_class->get_property    = gimp_tool_polygon_get_property;

  widget_class->changed         = gimp_tool_polygon_changed;
  widget_class->button_press    = gimp_tool_polygon_button_press;
  widget_class->button_release  = gimp_tool_polygon_button_release;
  widget_class->motion          = gimp_tool_polygon_motion;
  widget_class->hit             = gimp_tool_polygon_hit;
  widget_class->hover           = gimp_tool_polygon_hover;
  widget_class->leave_notify    = gimp_tool_polygon_leave_notify;
  widget_class->key_press       = gimp_tool_polygon_key_press;
  widget_class->motion_modifier = gimp_tool_polygon_motion_modifier;
  widget_class->hover_modifier  = gimp_tool_polygon_hover_modifier;
  widget_class->get_cursor      = gimp_tool_polygon_get_cursor;

  g_type_class_add_private (klass, sizeof (GimpToolPolygonPrivate));
}

static void
gimp_tool_polygon_init (GimpToolPolygon *polygon)
{
  polygon->private = G_TYPE_INSTANCE_GET_PRIVATE (polygon,
                                                  GIMP_TYPE_TOOL_POLYGON,
                                                  GimpToolPolygonPrivate);

  polygon->private->grabbed_segment_index = INVALID_INDEX;
  polygon->private->last_click_time       = NO_CLICK_TIME_AVAILABLE;
}

static void
gimp_tool_polygon_constructed (GObject *object)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (object);
  GimpToolWidget         *widget  = GIMP_TOOL_WIDGET (object);
  GimpToolPolygonPrivate *private = polygon->private;
  GimpCanvasGroup        *stroke_group;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  stroke_group = gimp_tool_widget_add_stroke_group (widget);

  gimp_tool_widget_push_group (widget, stroke_group);

  private->polygon = gimp_tool_widget_add_polygon (widget, NULL,
                                                   NULL, 0, FALSE);

  private->pending_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);
  private->closing_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);

  gimp_tool_widget_pop_group (widget);

  private->handles = g_ptr_array_new ();

  gimp_tool_polygon_changed (widget);
}

static void
gimp_tool_polygon_finalize (GObject *object)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (object);
  GimpToolPolygonPrivate *private = polygon->private;

  g_free (private->points);
  g_free (private->segment_indices);
  g_free (private->saved_points_lower_segment);
  g_free (private->saved_points_higher_segment);

  g_clear_pointer (&private->handles, g_ptr_array_unref);

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

static void
gimp_tool_polygon_set_property (GObject      *object,
                                guint         property_id,
                                const GValue *value,
                                GParamSpec   *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_tool_polygon_get_property (GObject    *object,
                                guint       property_id,
                                GValue     *value,
                                GParamSpec *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_tool_polygon_get_segment (GimpToolPolygon  *polygon,
                               GimpVector2     **points,
                               gint             *n_points,
                               gint              segment_start,
                               gint              segment_end)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  *points   = &priv->points[priv->segment_indices[segment_start]];
  *n_points = priv->segment_indices[segment_end] -
              priv->segment_indices[segment_start] +
              1;
}

static void
gimp_tool_polygon_get_segment_point (GimpToolPolygon *polygon,
                                     gdouble         *start_point_x,
                                     gdouble         *start_point_y,
                                     gint             segment_index)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  *start_point_x = priv->points[priv->segment_indices[segment_index]].x;
  *start_point_y = priv->points[priv->segment_indices[segment_index]].y;
}

static gboolean
gimp_tool_polygon_should_close (GimpToolPolygon  *polygon,
                                guint32           time,
                                const GimpCoords *coords)
{
  GimpToolWidget         *widget       = GIMP_TOOL_WIDGET (polygon);
  GimpToolPolygonPrivate *priv         = polygon->private;
  gboolean                double_click = FALSE;
  gdouble                 dist;

  if (priv->polygon_modified      ||
      priv->n_segment_indices < 1 ||
      priv->n_points          < 3 ||
      priv->polygon_closed)
    return FALSE;

  dist = gimp_canvas_item_transform_distance_square (priv->polygon,
                                                     coords->x,
                                                     coords->y,
                                                     priv->points[0].x,
                                                     priv->points[0].y);

  /* Handle double-click. It must be within GTK+ global double-click
   * time since last click, and it must be within GTK+ global
   * double-click distance away from the last point
   */
  if (time != NO_CLICK_TIME_AVAILABLE)
    {
      GimpDisplayShell *shell    = gimp_tool_widget_get_shell (widget);
      GtkSettings      *settings = gtk_widget_get_settings (GTK_WIDGET (shell));
      gint              double_click_time;
      gint              double_click_distance;
      gint              click_time_passed;
      gdouble           dist_from_last_point;

      click_time_passed = time - priv->last_click_time;

      dist_from_last_point =
        gimp_canvas_item_transform_distance_square (priv->polygon,
                                                    coords->x,
                                                    coords->y,
                                                    priv->last_click_coord.x,
                                                    priv->last_click_coord.y);

      g_object_get (settings,
                    "gtk-double-click-time",     &double_click_time,
                    "gtk-double-click-distance", &double_click_distance,
                    NULL);

      double_click = click_time_passed    < double_click_time &&
                     dist_from_last_point < double_click_distance;
    }

  return ((! priv->suppress_handles && dist < POINT_GRAB_THRESHOLD_SQ) ||
          double_click);
}

static void
gimp_tool_polygon_revert_to_last_segment (GimpToolPolygon *polygon)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  priv->n_points = priv->segment_indices[priv->n_segment_indices - 1] + 1;
}

static void
gimp_tool_polygon_remove_last_segment (GimpToolPolygon *polygon)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  if (priv->polygon_closed)
    {
      priv->polygon_closed = FALSE;

      gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));

      return;
    }

  if (priv->n_segment_indices > 0)
    {
      GimpCanvasItem *handle;

      priv->n_segment_indices--;

      handle = g_ptr_array_index (priv->handles,
                                  priv->n_segment_indices);

      gimp_tool_widget_remove_item (GIMP_TOOL_WIDGET (polygon), handle);
      g_ptr_array_remove (priv->handles, handle);
    }

  if (priv->n_segment_indices <= 0)
    {
      priv->grabbed_segment_index = INVALID_INDEX;
      priv->show_pending_point    = FALSE;
      priv->n_points              = 0;
      priv->n_segment_indices     = 0;

      gimp_tool_widget_response (GIMP_TOOL_WIDGET (polygon),
                                 GIMP_TOOL_WIDGET_RESPONSE_CANCEL);
    }
  else
    {
      gimp_tool_polygon_revert_to_last_segment (polygon);

      gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));
    }
}

static void
gimp_tool_polygon_add_point (GimpToolPolygon *polygon,
                             gdouble          x,
                             gdouble          y)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  if (priv->n_points >= priv->max_n_points)
    {
      priv->max_n_points += N_ITEMS_PER_ALLOC;

      priv->points = g_realloc (priv->points,
                                sizeof (GimpVector2) * priv->max_n_points);
    }

  priv->points[priv->n_points].x = x;
  priv->points[priv->n_points].y = y;

  priv->n_points++;
}

static void
gimp_tool_polygon_add_segment_index (GimpToolPolygon *polygon,
                                     gint             index)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  if (priv->n_segment_indices >= priv->max_n_segment_indices)
    {
      priv->max_n_segment_indices += N_ITEMS_PER_ALLOC;

      priv->segment_indices = g_realloc (priv->segment_indices,
                                         sizeof (GimpVector2) *
                                         priv->max_n_segment_indices);
    }

  priv->segment_indices[priv->n_segment_indices] = index;

  g_ptr_array_add (priv->handles,
                   gimp_tool_widget_add_handle (GIMP_TOOL_WIDGET (polygon),
                                                GIMP_HANDLE_CROSS,
                                                0, 0,
                                                GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
                                                GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
                                                GIMP_HANDLE_ANCHOR_CENTER));

  priv->n_segment_indices++;
}

static gboolean
gimp_tool_polygon_is_point_grabbed (GimpToolPolygon *polygon)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  return priv->grabbed_segment_index != INVALID_INDEX;
}

static void
gimp_tool_polygon_fit_segment (GimpToolPolygon   *polygon,
                               GimpVector2       *dest_points,
                               GimpVector2        dest_start_target,
                               GimpVector2        dest_end_target,
                               const GimpVector2 *source_points,
                               gint               n_points)
{
  GimpVector2 origo_translation_offset;
  GimpVector2 untranslation_offset;
  gdouble     rotation;
  gdouble     scale;

  /* Handle some quick special cases */
  if (n_points <= 0)
    {
      return;
    }
  else if (n_points == 1)
    {
      dest_points[0] = dest_end_target;
      return;
    }
  else if (n_points == 2)
    {
      dest_points[0] = dest_start_target;
      dest_points[1] = dest_end_target;
      return;
    }

  /* Copy from source to dest; we work on the dest data */
  memcpy (dest_points, source_points, sizeof (GimpVector2) * n_points);

  /* Transform the destination end point */
  {
    GimpVector2 *dest_end;
    GimpVector2  origo_translated_end_target;
    gdouble      target_rotation;
    gdouble      current_rotation;
    gdouble      target_length;
    gdouble      current_length;

    dest_end = &dest_points[n_points - 1];

    /* Transate to origin */
    gimp_vector2_sub (&origo_translation_offset,
                      &vector2_zero,
                      &dest_points[0]);
    gimp_vector2_add (dest_end,
                      dest_end,
                      &origo_translation_offset);

    /* Calculate origo_translated_end_target */
    gimp_vector2_sub (&origo_translated_end_target,
                      &dest_end_target,
                      &dest_start_target);

    /* Rotate */
    target_rotation  = atan2 (vector2_zero.y - origo_translated_end_target.y,
                              vector2_zero.x - origo_translated_end_target.x);
    current_rotation = atan2 (vector2_zero.y - dest_end->y,
                              vector2_zero.x - dest_end->x);
    rotation         = current_rotation - target_rotation;

    gimp_vector2_rotate (dest_end, rotation);


    /* Scale */
    target_length  = gimp_vector2_length (&origo_translated_end_target);
    current_length = gimp_vector2_length (dest_end);
    scale          = target_length / current_length;

    gimp_vector2_mul (dest_end, scale);


    /* Untranslate */
    gimp_vector2_sub (&untranslation_offset,
                      &dest_end_target,
                      dest_end);
    gimp_vector2_add (dest_end,
                      dest_end,
                      &untranslation_offset);
  }

  /* Do the same transformation for the rest of the points */
  {
    gint i;

    for (i = 0; i < n_points - 1; i++)
      {
        /* Translate */
        gimp_vector2_add (&dest_points[i],
                          &origo_translation_offset,
                          &dest_points[i]);

        /* Rotate */
        gimp_vector2_rotate (&dest_points[i],
                             rotation);

        /* Scale */
        gimp_vector2_mul (&dest_points[i],
                          scale);

        /* Untranslate */
        gimp_vector2_add (&dest_points[i],
                          &dest_points[i],
                          &untranslation_offset);
      }
  }
}

static void
gimp_tool_polygon_move_segment_vertex_to (GimpToolPolygon *polygon,
                                          gint             segment_index,
                                          gdouble          new_x,
                                          gdouble          new_y)
{
  GimpToolPolygonPrivate *priv         = polygon->private;
  GimpVector2             cursor_point = { new_x, new_y };
  GimpVector2            *dest;
  GimpVector2            *dest_start_target;
  GimpVector2            *dest_end_target;
  gint                    n_points;

  /* Handle the segment before the grabbed point */
  if (segment_index > 0)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &dest,
                                     &n_points,
                                     priv->grabbed_segment_index - 1,
                                     priv->grabbed_segment_index);

      dest_start_target = &dest[0];
      dest_end_target   = &cursor_point;

      gimp_tool_polygon_fit_segment (polygon,
                                     dest,
                                     *dest_start_target,
                                     *dest_end_target,
                                     priv->saved_points_lower_segment,
                                     n_points);
    }

  /* Handle the segment after the grabbed point */
  if (segment_index < priv->n_segment_indices - 1)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &dest,
                                     &n_points,
                                     priv->grabbed_segment_index,
                                     priv->grabbed_segment_index + 1);

      dest_start_target = &cursor_point;
      dest_end_target   = &dest[n_points - 1];

      gimp_tool_polygon_fit_segment (polygon,
                                     dest,
                                     *dest_start_target,
                                     *dest_end_target,
                                     priv->saved_points_higher_segment,
                                     n_points);
    }

  /* Handle when there only is one point */
  if (segment_index == 0 &&
      priv->n_segment_indices == 1)
    {
      priv->points[0].x = new_x;
      priv->points[0].y = new_y;
    }
}

static void
gimp_tool_polygon_revert_to_saved_state (GimpToolPolygon *polygon)
{
  GimpToolPolygonPrivate *priv = polygon->private;
  GimpVector2            *dest;
  gint                    n_points;

  /* Without a point grab we have no sensible information to fall back
   * on, bail out
   */
  if (! gimp_tool_polygon_is_point_grabbed (polygon))
    {
      return;
    }

  if (priv->grabbed_segment_index > 0)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &dest,
                                     &n_points,
                                     priv->grabbed_segment_index - 1,
                                     priv->grabbed_segment_index);

      memcpy (dest,
              priv->saved_points_lower_segment,
              sizeof (GimpVector2) * n_points);
    }

  if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &dest,
                                     &n_points,
                                     priv->grabbed_segment_index,
                                     priv->grabbed_segment_index + 1);

      memcpy (dest,
              priv->saved_points_higher_segment,
              sizeof (GimpVector2) * n_points);
    }

  if (priv->grabbed_segment_index == 0 &&
      priv->n_segment_indices     == 1)
    {
      priv->points[0] = *priv->saved_points_lower_segment;
    }
}

static void
gimp_tool_polygon_prepare_for_move (GimpToolPolygon *polygon)
{
  GimpToolPolygonPrivate *priv = polygon->private;
  GimpVector2            *source;
  gint                    n_points;

  if (priv->grabbed_segment_index > 0)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &source,
                                     &n_points,
                                     priv->grabbed_segment_index - 1,
                                     priv->grabbed_segment_index);

      if (n_points > priv->max_n_saved_points_lower_segment)
        {
          priv->max_n_saved_points_lower_segment = n_points;

          priv->saved_points_lower_segment =
            g_realloc (priv->saved_points_lower_segment,
                       sizeof (GimpVector2) * n_points);
        }

      memcpy (priv->saved_points_lower_segment,
              source,
              sizeof (GimpVector2) * n_points);
    }

  if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
    {
      gimp_tool_polygon_get_segment (polygon,
                                     &source,
                                     &n_points,
                                     priv->grabbed_segment_index,
                                     priv->grabbed_segment_index + 1);

      if (n_points > priv->max_n_saved_points_higher_segment)
        {
          priv->max_n_saved_points_higher_segment = n_points;

          priv->saved_points_higher_segment =
            g_realloc (priv->saved_points_higher_segment,
                       sizeof (GimpVector2) * n_points);
        }

      memcpy (priv->saved_points_higher_segment,
              source,
              sizeof (GimpVector2) * n_points);
    }

  /* A special-case when there only is one point */
  if (priv->grabbed_segment_index == 0 &&
      priv->n_segment_indices     == 1)
    {
      if (priv->max_n_saved_points_lower_segment == 0)
        {
          priv->max_n_saved_points_lower_segment = 1;

          priv->saved_points_lower_segment = g_new0 (GimpVector2, 1);
        }

      *priv->saved_points_lower_segment = priv->points[0];
    }
}

static void
gimp_tool_polygon_update_motion (GimpToolPolygon *polygon,
                                 gdouble          new_x,
                                 gdouble          new_y)
{
  GimpToolPolygonPrivate *priv = polygon->private;

  if (gimp_tool_polygon_is_point_grabbed (polygon))
    {
      priv->polygon_modified = TRUE;

      if (priv->constrain_angle &&
          priv->n_segment_indices > 1)
        {
          gdouble start_point_x;
          gdouble start_point_y;
          gint    segment_index;

          /* Base constraints on the last segment vertex if we move
           * the first one, otherwise base on the previous segment
           * vertex
           */
          if (priv->grabbed_segment_index == 0)
            {
              segment_index = priv->n_segment_indices - 1;
            }
          else
            {
              segment_index = priv->grabbed_segment_index - 1;
            }

          gimp_tool_polygon_get_segment_point (polygon,
                                               &start_point_x,
                                               &start_point_y,
                                               segment_index);

          gimp_display_shell_constrain_line (
            gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (polygon)),
            start_point_x,
            start_point_y,
            &new_x,
            &new_y,
            GIMP_CONSTRAIN_LINE_15_DEGREES);
        }

      gimp_tool_polygon_move_segment_vertex_to (polygon,
                                                priv->grabbed_segment_index,
                                                new_x,
                                                new_y);

      /* We also must update the pending point if we are moving the
       * first point
       */
      if (priv->grabbed_segment_index == 0)
        {
          priv->pending_point.x = new_x;
          priv->pending_point.y = new_y;
        }
    }
  else
    {
      /* Don't show the pending point while we are adding points */
      priv->show_pending_point = FALSE;

      gimp_tool_polygon_add_point (polygon, new_x, new_y);
    }
}

static void
gimp_tool_polygon_status_update (GimpToolPolygon  *polygon,
                                 const GimpCoords *coords,
                                 gboolean          proximity)
{
  GimpToolWidget         *widget = GIMP_TOOL_WIDGET (polygon);
  GimpToolPolygonPrivate *priv   = polygon->private;

  if (proximity)
    {
      const gchar *status_text = NULL;

      if (gimp_tool_polygon_is_point_grabbed (polygon))
        {
          if (gimp_tool_polygon_should_close (polygon,
                                              NO_CLICK_TIME_AVAILABLE,
                                              coords))
            {
              status_text = _("Click to close shape");
            }
          else
            {
              status_text = _("Click-Drag to move segment vertex");
            }
        }
      else if (priv->polygon_closed)
        {
          status_text = _("Return commits, Escape cancels, Backspace re-opens shape");
        }
      else if (priv->n_segment_indices >= 3)
        {
          status_text = _("Return commits, Escape cancels, Backspace removes last segment");
        }
      else
        {
          status_text = _("Click-Drag adds a free segment, Click adds a polygonal segment");
        }

      if (status_text)
        {
          gimp_tool_widget_set_status (widget, status_text);
        }
    }
  else
    {
      gimp_tool_widget_set_status (widget, NULL);
    }
}

static void
gimp_tool_polygon_changed (GimpToolWidget *widget)
{
  GimpToolPolygon        *polygon               = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *private               = polygon->private;
  gboolean                hovering_first_point  = FALSE;
  gboolean                handles_wants_to_show = FALSE;
  GimpCoords              coords                = { private->last_coords.x,
                                                    private->last_coords.y,
                                                    /* pad with 0 */ };
  gint                    i;

  gimp_canvas_polygon_set_points (private->polygon,
                                  private->points, private->n_points);

  if (private->show_pending_point)
    {
      GimpVector2 last = private->points[private->n_points - 1];

      gimp_canvas_line_set (private->pending_line,
                            last.x,
                            last.y,
                            private->pending_point.x,
                            private->pending_point.y);
    }

  gimp_canvas_item_set_visible (private->pending_line,
                                private->show_pending_point);

  if (private->polygon_closed)
    {
      GimpVector2 first = private->points[0];
      GimpVector2 last  = private->points[private->n_points - 1];

      gimp_canvas_line_set (private->closing_line,
                            first.x, first.y,
                            last.x,  last.y);
    }

  gimp_canvas_item_set_visible (private->closing_line,
                                private->polygon_closed);

  hovering_first_point =
    gimp_tool_polygon_should_close (polygon,
                                    NO_CLICK_TIME_AVAILABLE,
                                    &coords);

  /* We always show the handle for the first point, even with button1
   * down, since releasing the button on the first point will close
   * the polygon, so it's a significant state which we must give
   * feedback for
   */
  handles_wants_to_show = (hovering_first_point || ! private->button_down);

  for (i = 0; i < private->n_segment_indices; i++)
    {
      GimpCanvasItem *handle;
      GimpVector2    *point;

      handle = g_ptr_array_index (private->handles, i);
      point  = &private->points[private->segment_indices[i]];

      if (private->hover             &&
          handles_wants_to_show      &&
          ! private->suppress_handles &&

          /* If the first point is hovered while button1 is held down,
           * only draw the first handle, the other handles are not
           * relevant (see comment a few lines up)
           */
          (i == 0 ||
           ! (private->button_down || hovering_first_point)))
        {
          gdouble         dist;
          GimpHandleType  handle_type = -1;

          dist =
            gimp_canvas_item_transform_distance_square (handle,
                                                        private->last_coords.x,
                                                        private->last_coords.y,
                                                        point->x,
                                                        point->y);

          /* If the cursor is over the point, fill, if it's just
           * close, draw an outline
           */
          if (dist < POINT_GRAB_THRESHOLD_SQ)
            handle_type = GIMP_HANDLE_FILLED_CIRCLE;
          else if (dist < POINT_SHOW_THRESHOLD_SQ)
            handle_type = GIMP_HANDLE_CIRCLE;

          if (handle_type != -1)
            {
              gint size;

              gimp_canvas_item_begin_change (handle);

              gimp_canvas_handle_set_position (handle, point->x, point->y);

              g_object_set (handle,
                            "type", handle_type,
                            NULL);

              size = gimp_canvas_handle_calc_size (handle,
                                                   private->last_coords.x,
                                                   private->last_coords.y,
                                                   GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
                                                   2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
              if (FALSE)
                gimp_canvas_handle_set_size (handle, size, size);

              if (dist < POINT_GRAB_THRESHOLD_SQ)
                gimp_canvas_item_set_highlight (handle, TRUE);
              else
                gimp_canvas_item_set_highlight (handle, FALSE);

              gimp_canvas_item_set_visible (handle, TRUE);

              gimp_canvas_item_end_change (handle);
            }
          else
            {
              gimp_canvas_item_set_visible (handle, FALSE);
            }
        }
      else
        {
          gimp_canvas_item_set_visible (handle, FALSE);
       }
    }
}

static gboolean
gimp_tool_polygon_button_press (GimpToolWidget      *widget,
                                const GimpCoords    *coords,
                                guint32              time,
                                GdkModifierType      state,
                                GimpButtonPressType  press_type)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  if (gimp_tool_polygon_is_point_grabbed (polygon))
    {
      gimp_tool_polygon_prepare_for_move (polygon);
    }
  else if (priv->polygon_closed)
    {
      if (press_type == GIMP_BUTTON_PRESS_DOUBLE &&
          gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
        {
          gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM);
        }

      return 0;
    }
  else
    {
      GimpVector2 point_to_add;

      /* Note that we add the pending point (unless it is the first
       * point we add) because the pending point is setup correctly
       * with regards to angle constraints.
       */
      if (priv->n_points > 0)
        {
          point_to_add = priv->pending_point;
        }
      else
        {
          point_to_add.x = coords->x;
          point_to_add.y = coords->y;
        }

      /* No point was grabbed, add a new point and mark this as a
       * segment divider. For a line segment, this will be the only
       * new point. For a free segment, this will be the first point
       * of the free segment.
       */
      gimp_tool_polygon_add_point (polygon,
                                   point_to_add.x,
                                   point_to_add.y);
      gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
    }

  priv->button_down = TRUE;

  gimp_tool_polygon_changed (widget);

  return 1;
}

static void
gimp_tool_polygon_button_release (GimpToolWidget        *widget,
                                  const GimpCoords      *coords,
                                  guint32                time,
                                  GdkModifierType        state,
                                  GimpButtonReleaseType  release_type)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  g_object_ref (widget);

  priv->button_down = FALSE;

  switch (release_type)
    {
    case GIMP_BUTTON_RELEASE_CLICK:
    case GIMP_BUTTON_RELEASE_NO_MOTION:
      /* If a click was made, we don't consider the polygon modified */
      priv->polygon_modified = FALSE;

      /* First finish of the line segment if no point was grabbed */
      if (! gimp_tool_polygon_is_point_grabbed (polygon))
        {
          /* Revert any free segment points that might have been added */
          gimp_tool_polygon_revert_to_last_segment (polygon);
        }

      /* After the segments are up to date and we have handled
       * double-click, see if it's committing time
       */
      if (gimp_tool_polygon_should_close (polygon, time, coords))
        {
          /* We can get a click notification even though the end point
           * has been moved a few pixels. Since a move will change the
           * free selection, revert it before doing the commit.
           */
          gimp_tool_polygon_revert_to_saved_state (polygon);

          priv->polygon_closed = TRUE;
        }

      priv->last_click_time  = time;
      priv->last_click_coord = *coords;
      break;

    case GIMP_BUTTON_RELEASE_NORMAL:
      /* First finish of the free segment if no point was grabbed */
      if (! gimp_tool_polygon_is_point_grabbed (polygon))
        {
          /* The points are all setup, just make a segment */
          gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
        }

      /* After the segments are up to date, see if it's committing time */
      if (gimp_tool_polygon_should_close (polygon,
                                          NO_CLICK_TIME_AVAILABLE,
                                          coords))
        {
          priv->polygon_closed = TRUE;
        }
      break;

    case GIMP_BUTTON_RELEASE_CANCEL:
      if (gimp_tool_polygon_is_point_grabbed (polygon))
        gimp_tool_polygon_revert_to_saved_state (polygon);
      else
        gimp_tool_polygon_remove_last_segment (polygon);
      break;

    default:
      break;
    }

  /* Reset */
  priv->polygon_modified = FALSE;

  gimp_tool_polygon_changed (widget);

  g_object_unref (widget);
}

static void
gimp_tool_polygon_motion (GimpToolWidget   *widget,
                          const GimpCoords *coords,
                          guint32           time,
                          GdkModifierType   state)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  priv->last_coords.x = coords->x;
  priv->last_coords.y = coords->y;

  gimp_tool_polygon_update_motion (polygon,
                                   coords->x,
                                   coords->y);

  gimp_tool_polygon_changed (widget);
}

static GimpHit
gimp_tool_polygon_hit (GimpToolWidget   *widget,
                       const GimpCoords *coords,
                       GdkModifierType   state,
                       gboolean          proximity)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  if ((priv->n_points > 0 && ! priv->polygon_closed) ||
      gimp_tool_polygon_get_segment_index (polygon, coords) != INVALID_INDEX)
    {
      return GIMP_HIT_DIRECT;
    }
  else if (priv->polygon_closed &&
           gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
    {
      return GIMP_HIT_INDIRECT;
    }

  return GIMP_HIT_NONE;
}

static void
gimp_tool_polygon_hover (GimpToolWidget   *widget,
                         const GimpCoords *coords,
                         GdkModifierType   state,
                         gboolean          proximity)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;
  gboolean                hovering_first_point;

  priv->grabbed_segment_index = gimp_tool_polygon_get_segment_index (polygon,
                                                                     coords);
  priv->hover                 = TRUE;

  hovering_first_point =
    gimp_tool_polygon_should_close (polygon,
                                    NO_CLICK_TIME_AVAILABLE,
                                    coords);

  priv->last_coords.x = coords->x;
  priv->last_coords.y = coords->y;

  if (priv->n_points == 0  ||
      priv->polygon_closed ||
      (gimp_tool_polygon_is_point_grabbed (polygon) &&
       ! hovering_first_point) ||
      ! proximity)
    {
      priv->show_pending_point = FALSE;
    }
  else
    {
      priv->show_pending_point = TRUE;

      if (hovering_first_point)
        {
          priv->pending_point = priv->points[0];
        }
      else
        {
          priv->pending_point.x = coords->x;
          priv->pending_point.y = coords->y;

          if (priv->constrain_angle && priv->n_points > 0)
            {
              gdouble start_point_x;
              gdouble start_point_y;

              /*  the last point is the line's start point  */
              gimp_tool_polygon_get_segment_point (polygon,
                                                   &start_point_x,
                                                   &start_point_y,
                                                   priv->n_segment_indices - 1);

              gimp_display_shell_constrain_line (
                gimp_tool_widget_get_shell (widget),
                start_point_x, start_point_y,
                &priv->pending_point.x,
                &priv->pending_point.y,
                GIMP_CONSTRAIN_LINE_15_DEGREES);
            }
        }
    }

  gimp_tool_polygon_status_update (polygon, coords, proximity);

  gimp_tool_polygon_changed (widget);
}

static void
gimp_tool_polygon_leave_notify (GimpToolWidget *widget)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  priv->grabbed_segment_index = INVALID_INDEX;
  priv->hover                 = FALSE;
  priv->show_pending_point    = FALSE;

  gimp_tool_polygon_changed (widget);
}

static gboolean
gimp_tool_polygon_key_press (GimpToolWidget *widget,
                             GdkEventKey    *kevent)
{
  GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);

  switch (kevent->keyval)
    {
    case GDK_KEY_BackSpace:
      gimp_tool_polygon_remove_last_segment (polygon);
      return TRUE;

    default:
      break;
    }

  return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
}

static void
gimp_tool_polygon_motion_modifier (GimpToolWidget  *widget,
                                   GdkModifierType  key,
                                   gboolean         press,
                                   GdkModifierType  state)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
                           TRUE : FALSE);

  /* If we didn't came here due to a mouse release, immediately update
   * the position of the thing we move.
   */
  if (state & GDK_BUTTON1_MASK)
    {
      gimp_tool_polygon_update_motion (polygon,
                                       priv->last_coords.x,
                                       priv->last_coords.y);
    }

  gimp_tool_polygon_changed (widget);
}

static void
gimp_tool_polygon_hover_modifier (GimpToolWidget  *widget,
                                  GdkModifierType  key,
                                  gboolean         press,
                                  GdkModifierType  state)
{
  GimpToolPolygon        *polygon = GIMP_TOOL_POLYGON (widget);
  GimpToolPolygonPrivate *priv    = polygon->private;

  priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
                           TRUE : FALSE);

  priv->suppress_handles = ((state & gimp_get_extend_selection_mask ()) ?
                           TRUE : FALSE);

  gimp_tool_polygon_changed (widget);
}

static gboolean
gimp_tool_polygon_get_cursor (GimpToolWidget     *widget,
                              const GimpCoords   *coords,
                              GdkModifierType     state,
                              GimpCursorType     *cursor,
                              GimpToolCursorType *tool_cursor,
                              GimpCursorModifier *modifier)
{
  GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);

  if (gimp_tool_polygon_is_point_grabbed (polygon) &&
      ! gimp_tool_polygon_should_close (polygon,
                                        NO_CLICK_TIME_AVAILABLE,
                                        coords))
    {
      *modifier = GIMP_CURSOR_MODIFIER_MOVE;

      return TRUE;
    }

  return FALSE;
}

static gint
gimp_tool_polygon_get_segment_index (GimpToolPolygon  *polygon,
                                     const GimpCoords *coords)
{
  GimpToolPolygonPrivate *priv          = polygon->private;
  gint                    segment_index = INVALID_INDEX;

  if (! priv->suppress_handles)
    {
      gdouble shortest_dist = POINT_GRAB_THRESHOLD_SQ;
      gint    i;

      for (i = 0; i < priv->n_segment_indices; i++)
        {
          gdouble      dist;
          GimpVector2 *point;

          point = &priv->points[priv->segment_indices[i]];

          dist = gimp_canvas_item_transform_distance_square (priv->polygon,
                                                             coords->x,
                                                             coords->y,
                                                             point->x,
                                                             point->y);

          if (dist < shortest_dist)
            {
              shortest_dist = dist;

              segment_index = i;
            }
        }
    }

  return segment_index;
}


/*  public functions  */

GimpToolWidget *
gimp_tool_polygon_new (GimpDisplayShell *shell)
{
  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);

  return g_object_new (GIMP_TYPE_TOOL_POLYGON,
                       "shell", shell,
                       NULL);
}

void
gimp_tool_polygon_get_points (GimpToolPolygon    *polygon,
                              const GimpVector2 **points,
                              gint               *n_points)
{
  GimpToolPolygonPrivate *private = polygon->private;

  g_return_if_fail (points != NULL && n_points != NULL);

  *points   = private->points;
  *n_points = private->n_points;
}
