/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikatoolpath.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * Vector tool
 * Copyright (C) 2003 Simon Budig  <simon@gimp.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

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

#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"

#include "display-types.h"

#include "core/pikacontext.h"

#include "menus/menus.h"

#include "vectors/pikaanchor.h"
#include "vectors/pikabezierstroke.h"
#include "vectors/pikavectors.h"

#include "widgets/pikadialogfactory.h"
#include "widgets/pikadockcontainer.h"
#include "widgets/pikamenufactory.h"
#include "widgets/pikauimanager.h"
#include "widgets/pikawidgets-utils.h"

#include "tools/pikatools-utils.h"

#include "pikacanvashandle.h"
#include "pikacanvasitem-utils.h"
#include "pikacanvasline.h"
#include "pikacanvaspath.h"
#include "pikadisplay.h"
#include "pikadisplayshell.h"
#include "pikatoolpath.h"

#include "pika-intl.h"


#define TOGGLE_MASK  pika_get_extend_selection_mask ()
#define MOVE_MASK    GDK_MOD1_MASK
#define INSDEL_MASK  pika_get_toggle_behavior_mask ()


/*  possible vector functions  */
typedef enum
{
  VECTORS_SELECT_VECTOR,
  VECTORS_CREATE_VECTOR,
  VECTORS_CREATE_STROKE,
  VECTORS_ADD_ANCHOR,
  VECTORS_MOVE_ANCHOR,
  VECTORS_MOVE_ANCHORSET,
  VECTORS_MOVE_HANDLE,
  VECTORS_MOVE_CURVE,
  VECTORS_MOVE_STROKE,
  VECTORS_MOVE_VECTORS,
  VECTORS_INSERT_ANCHOR,
  VECTORS_DELETE_ANCHOR,
  VECTORS_CONNECT_STROKES,
  VECTORS_DELETE_SEGMENT,
  VECTORS_CONVERT_EDGE,
  VECTORS_FINISHED
} PikaVectorFunction;

enum
{
  PROP_0,
  PROP_VECTORS,
  PROP_EDIT_MODE,
  PROP_POLYGONAL
};

enum
{
  BEGIN_CHANGE,
  END_CHANGE,
  ACTIVATE,
  LAST_SIGNAL
};

struct _PikaToolPathPrivate
{
  PikaVectors          *vectors;        /* the current Vector data           */
  PikaVectorMode        edit_mode;
  gboolean              polygonal;

  PikaVectorFunction    function;       /* function we're performing         */
  PikaAnchorFeatureType restriction;    /* movement restriction              */
  gboolean              modifier_lock;  /* can we toggle the Shift key?      */
  GdkModifierType       saved_state;    /* modifier state at button_press    */
  gdouble               last_x;         /* last x coordinate                 */
  gdouble               last_y;         /* last y coordinate                 */
  gboolean              undo_motion;    /* we need a motion to have an undo  */
  gboolean              have_undo;      /* did we push an undo at            */
                                        /* ..._button_press?                 */

  PikaAnchor           *cur_anchor;     /* the current Anchor                */
  PikaAnchor           *cur_anchor2;    /* secondary Anchor (end on_curve)   */
  PikaStroke           *cur_stroke;     /* the current Stroke                */
  gdouble               cur_position;   /* the current Position on a segment */

  gint                  sel_count;      /* number of selected anchors        */
  PikaAnchor           *sel_anchor;     /* currently selected anchor, NULL   */
                                        /* if multiple anchors are selected  */
  PikaStroke           *sel_stroke;     /* selected stroke                   */

  PikaVectorMode        saved_mode;     /* used by modifier_key()            */

  PikaCanvasItem       *path;
  GList                *items;
};


/*  local function prototypes  */

static void     pika_tool_path_constructed     (GObject               *object);
static void     pika_tool_path_dispose         (GObject               *object);
static void     pika_tool_path_set_property    (GObject               *object,
                                                guint                  property_id,
                                                const GValue          *value,
                                                GParamSpec            *pspec);
static void     pika_tool_path_get_property    (GObject               *object,
                                                guint                  property_id,
                                                GValue                *value,
                                                GParamSpec            *pspec);

static void     pika_tool_path_changed         (PikaToolWidget        *widget);
static gint     pika_tool_path_button_press    (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                PikaButtonPressType    press_type);
static void     pika_tool_path_button_release  (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state,
                                                PikaButtonReleaseType  release_type);
static void     pika_tool_path_motion          (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                guint32                time,
                                                GdkModifierType        state);
static PikaHit  pika_tool_path_hit             (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                gboolean               proximity);
static void     pika_tool_path_hover           (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                gboolean               proximity);
static gboolean pika_tool_path_key_press       (PikaToolWidget        *widget,
                                                GdkEventKey           *kevent);
static gboolean pika_tool_path_get_cursor      (PikaToolWidget        *widget,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                PikaCursorType        *cursor,
                                                PikaToolCursorType    *tool_cursor,
                                                PikaCursorModifier    *modifier);
static PikaUIManager * pika_tool_path_get_popup (PikaToolWidget       *widget,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state,
                                                const gchar          **ui_path);

static PikaVectorFunction
                   pika_tool_path_get_function (PikaToolPath          *path,
                                                const PikaCoords      *coords,
                                                GdkModifierType        state);

static void     pika_tool_path_update_status   (PikaToolPath          *path,
                                                GdkModifierType        state,
                                                gboolean               proximity);

static void     pika_tool_path_begin_change    (PikaToolPath          *path,
                                                const gchar           *desc);
static void     pika_tool_path_end_change      (PikaToolPath          *path,
                                                gboolean               success);

static void     pika_tool_path_vectors_visible (PikaVectors           *vectors,
                                                PikaToolPath          *path);
static void     pika_tool_path_vectors_freeze  (PikaVectors           *vectors,
                                                PikaToolPath          *path);
static void     pika_tool_path_vectors_thaw    (PikaVectors           *vectors,
                                                PikaToolPath          *path);
static void     pika_tool_path_verify_state    (PikaToolPath          *path);

static void     pika_tool_path_move_selected_anchors
                                               (PikaToolPath          *path,
                                                gdouble                x,
                                                gdouble                y);
static void     pika_tool_path_delete_selected_anchors
                                               (PikaToolPath          *path);


G_DEFINE_TYPE_WITH_PRIVATE (PikaToolPath, pika_tool_path, PIKA_TYPE_TOOL_WIDGET)

#define parent_class pika_tool_path_parent_class

static guint path_signals[LAST_SIGNAL] = { 0 };


static void
pika_tool_path_class_init (PikaToolPathClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);

  object_class->constructed     = pika_tool_path_constructed;
  object_class->dispose         = pika_tool_path_dispose;
  object_class->set_property    = pika_tool_path_set_property;
  object_class->get_property    = pika_tool_path_get_property;

  widget_class->changed         = pika_tool_path_changed;
  widget_class->focus_changed   = pika_tool_path_changed;
  widget_class->button_press    = pika_tool_path_button_press;
  widget_class->button_release  = pika_tool_path_button_release;
  widget_class->motion          = pika_tool_path_motion;
  widget_class->hit             = pika_tool_path_hit;
  widget_class->hover           = pika_tool_path_hover;
  widget_class->key_press       = pika_tool_path_key_press;
  widget_class->get_cursor      = pika_tool_path_get_cursor;
  widget_class->get_popup       = pika_tool_path_get_popup;

  path_signals[BEGIN_CHANGE] =
    g_signal_new ("begin-change",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolPathClass, begin_change),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_STRING);

  path_signals[END_CHANGE] =
    g_signal_new ("end-change",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolPathClass, end_change),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  G_TYPE_BOOLEAN);

  path_signals[ACTIVATE] =
    g_signal_new ("activate",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaToolPathClass, activate),
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 1,
                  GDK_TYPE_MODIFIER_TYPE);

  g_object_class_install_property (object_class, PROP_VECTORS,
                                   g_param_spec_object ("vectors", NULL, NULL,
                                                        PIKA_TYPE_VECTORS,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_EDIT_MODE,
                                   g_param_spec_enum ("edit-mode",
                                                      _("Edit Mode"),
                                                      NULL,
                                                      PIKA_TYPE_VECTOR_MODE,
                                                      PIKA_VECTOR_MODE_DESIGN,
                                                      PIKA_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_POLYGONAL,
                                   g_param_spec_boolean ("polygonal",
                                                         _("Polygonal"),
                                                         _("Restrict editing to polygons"),
                                                         FALSE,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));
}

static void
pika_tool_path_init (PikaToolPath *path)
{
  path->private = pika_tool_path_get_instance_private (path);
}

static void
pika_tool_path_constructed (GObject *object)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (object);
  PikaToolWidget      *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolPathPrivate *private = path->private;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  private->path = pika_tool_widget_add_path (widget, NULL);

  pika_tool_path_changed (widget);
}

static void
pika_tool_path_dispose (GObject *object)
{
  PikaToolPath *path = PIKA_TOOL_PATH (object);

  pika_tool_path_set_vectors (path, NULL);

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

static void
pika_tool_path_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (object);
  PikaToolPathPrivate *private = path->private;

  switch (property_id)
    {
    case PROP_VECTORS:
      pika_tool_path_set_vectors (path, g_value_get_object (value));
      break;
    case PROP_EDIT_MODE:
      private->edit_mode = g_value_get_enum (value);
      break;
    case PROP_POLYGONAL:
      private->polygonal = g_value_get_boolean (value);
      break;

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

static void
pika_tool_path_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (object);
  PikaToolPathPrivate *private = path->private;

  switch (property_id)
    {
    case PROP_VECTORS:
      g_value_set_object (value, private->vectors);
      break;
    case PROP_EDIT_MODE:
      g_value_set_enum (value, private->edit_mode);
      break;
    case PROP_POLYGONAL:
      g_value_set_boolean (value, private->polygonal);
      break;

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

static void
item_remove_func (PikaCanvasItem *item,
                  PikaToolWidget *widget)
{
  pika_tool_widget_remove_item (widget, item);
}

static void
pika_tool_path_changed (PikaToolWidget *widget)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;
  PikaVectors         *vectors = private->vectors;

  if (private->items)
    {
      g_list_foreach (private->items, (GFunc) item_remove_func, widget);
      g_list_free (private->items);
      private->items = NULL;
    }

  if (vectors && pika_vectors_get_bezier (vectors))
    {
      PikaStroke *cur_stroke;

      pika_canvas_path_set (private->path,
                            pika_vectors_get_bezier (vectors));
      pika_canvas_item_set_visible (private->path,
                                    ! pika_item_get_visible (PIKA_ITEM (vectors)));

      for (cur_stroke = pika_vectors_stroke_get_next (vectors, NULL);
           cur_stroke;
           cur_stroke = pika_vectors_stroke_get_next (vectors, cur_stroke))
        {
          PikaCanvasItem *item;
          GArray         *coords;
          GList          *draw_anchors;
          GList          *list;
          gboolean        first = TRUE;

          /* anchor handles */
          draw_anchors = pika_stroke_get_draw_anchors (cur_stroke);

          for (list = draw_anchors; list; list = g_list_next (list))
            {
              PikaAnchor *cur_anchor = PIKA_ANCHOR (list->data);

              if (cur_anchor->type == PIKA_ANCHOR_ANCHOR)
                {
                  item =
                    pika_tool_widget_add_handle (widget,
                                                 cur_anchor->selected ?
                                                 PIKA_HANDLE_CIRCLE :
                                                 PIKA_HANDLE_FILLED_CIRCLE,
                                                 cur_anchor->position.x,
                                                 cur_anchor->position.y,
                                                 PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                 PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                 PIKA_HANDLE_ANCHOR_CENTER);

                  if (first)
                    {
                      gdouble angle = 0.0;
                      PikaAnchor *next;

                      for (next = pika_stroke_anchor_get_next (cur_stroke,
                                                               cur_anchor);
                           next;
                           next = pika_stroke_anchor_get_next (cur_stroke, next))
                        {
                          if (((next->position.x - cur_anchor->position.x) *
                               (next->position.x - cur_anchor->position.x) +
                               (next->position.y - cur_anchor->position.y) *
                               (next->position.y - cur_anchor->position.y)) >= 0.1)
                            break;
                        }

                      if (next)
                        {
                          angle = atan2 (next->position.y - cur_anchor->position.y,
                                         next->position.x - cur_anchor->position.x);
                          g_object_set (item,
                                        "type", (cur_anchor->selected ?
                                                 PIKA_HANDLE_DROP :
                                                 PIKA_HANDLE_FILLED_DROP),
                                        "start-angle", angle,
                                        NULL);
                        }
                    }

                  private->items = g_list_prepend (private->items, item);

                  first = FALSE;
                }
            }

          g_list_free (draw_anchors);

          if (private->sel_count <= 2)
            {
              /* the lines to the control handles */
              coords = pika_stroke_get_draw_lines (cur_stroke);

              if (coords)
                {
                  if (coords->len % 2 == 0)
                    {
                      gint i;

                      for (i = 0; i < coords->len; i += 2)
                        {
                          item = pika_tool_widget_add_line
                            (widget,
                             g_array_index (coords, PikaCoords, i).x,
                             g_array_index (coords, PikaCoords, i).y,
                             g_array_index (coords, PikaCoords, i + 1).x,
                             g_array_index (coords, PikaCoords, i + 1).y);

                          if (pika_tool_widget_get_focus (widget))
                            pika_canvas_item_set_highlight (item, TRUE);

                          private->items = g_list_prepend (private->items, item);
                        }
                    }

                  g_array_free (coords, TRUE);
                }

              /* control handles */
              draw_anchors = pika_stroke_get_draw_controls (cur_stroke);

              for (list = draw_anchors; list; list = g_list_next (list))
                {
                  PikaAnchor *cur_anchor = PIKA_ANCHOR (list->data);

                  item =
                    pika_tool_widget_add_handle (widget,
                                                 PIKA_HANDLE_SQUARE,
                                                 cur_anchor->position.x,
                                                 cur_anchor->position.y,
                                                 PIKA_CANVAS_HANDLE_SIZE_CIRCLE - 3,
                                                 PIKA_CANVAS_HANDLE_SIZE_CIRCLE - 3,
                                                 PIKA_HANDLE_ANCHOR_CENTER);

                  private->items = g_list_prepend (private->items, item);
                }

              g_list_free (draw_anchors);
            }
        }
    }
  else
    {
      pika_canvas_path_set (private->path, NULL);
    }
}

static gboolean
pika_tool_path_check_writable (PikaToolPath *path)
{
  PikaToolPathPrivate *private     = path->private;
  PikaToolWidget      *widget      = PIKA_TOOL_WIDGET (path);
  PikaDisplayShell    *shell       = pika_tool_widget_get_shell (widget);
  PikaItem            *locked_item = NULL;

  if (pika_item_is_content_locked (PIKA_ITEM (private->vectors), &locked_item) ||
      pika_item_is_position_locked (PIKA_ITEM (private->vectors), &locked_item))
    {
      pika_tool_widget_message_literal (PIKA_TOOL_WIDGET (path),
                                        _("The selected path is locked."));

      if (locked_item == NULL)
        locked_item = PIKA_ITEM (private->vectors);

      /* FIXME: this should really be done by the tool */
      pika_tools_blink_lock_box (shell->display->pika, locked_item);

      private->function = VECTORS_FINISHED;

      return FALSE;
    }

  return TRUE;
}

gboolean
pika_tool_path_button_press (PikaToolWidget      *widget,
                             const PikaCoords    *coords,
                             guint32              time,
                             GdkModifierType      state,
                             PikaButtonPressType  press_type)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;

  /* do nothing if we are in a FINISHED state */
  if (private->function == VECTORS_FINISHED)
    return 0;

  g_return_val_if_fail (private->vectors  != NULL                  ||
                        private->function == VECTORS_SELECT_VECTOR ||
                        private->function == VECTORS_CREATE_VECTOR, 0);

  private->undo_motion = FALSE;

  /* save the current modifier state */

  private->saved_state = state;


  /* select a vectors object */

  if (private->function == VECTORS_SELECT_VECTOR)
    {
      PikaVectors *vectors;

      if (pika_canvas_item_on_vectors (private->path,
                                       coords,
                                       PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                       PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                       NULL, NULL, NULL, NULL, NULL, &vectors))
        {
          pika_tool_path_set_vectors (path, vectors);
        }

      private->function = VECTORS_FINISHED;
    }


  /* create a new vector from scratch */

  if (private->function == VECTORS_CREATE_VECTOR)
    {
      PikaDisplayShell *shell = pika_tool_widget_get_shell (widget);
      PikaImage        *image = pika_display_get_image (shell->display);
      PikaVectors      *vectors;

      vectors = pika_vectors_new (image, _("Unnamed"));
      g_object_ref_sink (vectors);

      /* Undo step gets added implicitly */
      private->have_undo = TRUE;

      private->undo_motion = TRUE;

      pika_tool_path_set_vectors (path, vectors);
      g_object_unref (vectors);

      private->function = VECTORS_CREATE_STROKE;
    }


  pika_vectors_freeze (private->vectors);

  /* create a new stroke */

  if (private->function == VECTORS_CREATE_STROKE &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Add Stroke"));
      private->undo_motion = TRUE;

      private->cur_stroke = pika_bezier_stroke_new ();
      pika_vectors_stroke_add (private->vectors, private->cur_stroke);
      g_object_unref (private->cur_stroke);

      private->sel_stroke = private->cur_stroke;
      private->cur_anchor = NULL;
      private->sel_anchor = NULL;
      private->function   = VECTORS_ADD_ANCHOR;
    }


  /* add an anchor to an existing stroke */

  if (private->function == VECTORS_ADD_ANCHOR &&
      pika_tool_path_check_writable (path))
    {
      PikaCoords position = PIKA_COORDS_DEFAULT_VALUES;

      position.x = coords->x;
      position.y = coords->y;

      pika_tool_path_begin_change (path, _("Add Anchor"));
      private->undo_motion = TRUE;

      private->cur_anchor = pika_bezier_stroke_extend (private->sel_stroke,
                                                       &position,
                                                       private->sel_anchor,
                                                       EXTEND_EDITABLE);

      private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC;

      if (! private->polygonal)
        private->function = VECTORS_MOVE_HANDLE;
      else
        private->function = VECTORS_MOVE_ANCHOR;

      private->cur_stroke = private->sel_stroke;
    }


  /* insertion of an anchor in a curve segment */

  if (private->function == VECTORS_INSERT_ANCHOR &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Insert Anchor"));
      private->undo_motion = TRUE;

      private->cur_anchor = pika_stroke_anchor_insert (private->cur_stroke,
                                                       private->cur_anchor,
                                                       private->cur_position);
      if (private->cur_anchor)
        {
          if (private->polygonal)
            {
              pika_stroke_anchor_convert (private->cur_stroke,
                                          private->cur_anchor,
                                          PIKA_ANCHOR_FEATURE_EDGE);
            }

          private->function = VECTORS_MOVE_ANCHOR;
        }
      else
        {
          private->function = VECTORS_FINISHED;
        }
    }


  /* move a handle */

  if (private->function == VECTORS_MOVE_HANDLE &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Drag Handle"));

      if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR)
        {
          if (! private->cur_anchor->selected)
            {
              pika_vectors_anchor_select (private->vectors,
                                          private->cur_stroke,
                                          private->cur_anchor,
                                          TRUE, TRUE);
              private->undo_motion = TRUE;
            }

          pika_canvas_item_on_vectors_handle (private->path,
                                              private->vectors, coords,
                                              PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                              PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                              PIKA_ANCHOR_CONTROL, TRUE,
                                              &private->cur_anchor,
                                              &private->cur_stroke);
          if (! private->cur_anchor)
            private->function = VECTORS_FINISHED;
        }
    }


  /* move an anchor */

  if (private->function == VECTORS_MOVE_ANCHOR &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Drag Anchor"));

      if (! private->cur_anchor->selected)
        {
          pika_vectors_anchor_select (private->vectors,
                                      private->cur_stroke,
                                      private->cur_anchor,
                                      TRUE, TRUE);
          private->undo_motion = TRUE;
        }
    }


  /* move multiple anchors */

  if (private->function == VECTORS_MOVE_ANCHORSET &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Drag Anchors"));

      if (state & TOGGLE_MASK)
        {
          pika_vectors_anchor_select (private->vectors,
                                      private->cur_stroke,
                                      private->cur_anchor,
                                      !private->cur_anchor->selected,
                                      FALSE);
          private->undo_motion = TRUE;

          if (private->cur_anchor->selected == FALSE)
            private->function = VECTORS_FINISHED;
        }
    }


  /* move a curve segment directly */

  if (private->function == VECTORS_MOVE_CURVE &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Drag Curve"));

      /* the magic numbers are taken from the "feel good" parameter
       * from pika_bezier_stroke_point_move_relative in pikabezierstroke.c. */
      if (private->cur_position < 5.0 / 6.0)
        {
          pika_vectors_anchor_select (private->vectors,
                                      private->cur_stroke,
                                      private->cur_anchor, TRUE, TRUE);
          private->undo_motion = TRUE;
        }

      if (private->cur_position > 1.0 / 6.0)
        {
          pika_vectors_anchor_select (private->vectors,
                                      private->cur_stroke,
                                      private->cur_anchor2, TRUE,
                                      (private->cur_position >= 5.0 / 6.0));
          private->undo_motion = TRUE;
        }

    }


  /* connect two strokes */

  if (private->function == VECTORS_CONNECT_STROKES &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Connect Strokes"));
      private->undo_motion = TRUE;

      pika_stroke_connect_stroke (private->sel_stroke,
                                  private->sel_anchor,
                                  private->cur_stroke,
                                  private->cur_anchor);

      if (private->cur_stroke != private->sel_stroke &&
          pika_stroke_is_empty (private->cur_stroke))
        {
          pika_vectors_stroke_remove (private->vectors,
                                      private->cur_stroke);
        }

      private->sel_anchor = private->cur_anchor;
      private->cur_stroke = private->sel_stroke;

      pika_vectors_anchor_select (private->vectors,
                                  private->sel_stroke,
                                  private->sel_anchor, TRUE, TRUE);

      private->function = VECTORS_FINISHED;
    }


  /* move a stroke or all strokes of a vectors object */

  if ((private->function == VECTORS_MOVE_STROKE ||
       private->function == VECTORS_MOVE_VECTORS) &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Drag Path"));

      /* Work is being done in pika_tool_path_motion()... */
    }


  /* convert an anchor to something that looks like an edge */

  if (private->function == VECTORS_CONVERT_EDGE &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Convert Edge"));
      private->undo_motion = TRUE;

      pika_stroke_anchor_convert (private->cur_stroke,
                                  private->cur_anchor,
                                  PIKA_ANCHOR_FEATURE_EDGE);

      if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR)
        {
          pika_vectors_anchor_select (private->vectors,
                                      private->cur_stroke,
                                      private->cur_anchor, TRUE, TRUE);

          private->function = VECTORS_MOVE_ANCHOR;
        }
      else
        {
          private->cur_stroke = NULL;
          private->cur_anchor = NULL;

          /* avoid doing anything stupid */
          private->function = VECTORS_FINISHED;
        }
    }


  /* removal of a node in a stroke */

  if (private->function == VECTORS_DELETE_ANCHOR &&
      pika_tool_path_check_writable (path))
    {
      pika_tool_path_begin_change (path, _("Delete Anchor"));
      private->undo_motion = TRUE;

      pika_stroke_anchor_delete (private->cur_stroke,
                                 private->cur_anchor);

      if (pika_stroke_is_empty (private->cur_stroke))
        pika_vectors_stroke_remove (private->vectors,
                                    private->cur_stroke);

      private->cur_stroke = NULL;
      private->cur_anchor = NULL;
      private->function = VECTORS_FINISHED;
    }


  /* deleting a segment (opening up a stroke) */

  if (private->function == VECTORS_DELETE_SEGMENT &&
      pika_tool_path_check_writable (path))
    {
      PikaStroke *new_stroke;

      pika_tool_path_begin_change (path, _("Delete Segment"));
      private->undo_motion = TRUE;

      new_stroke = pika_stroke_open (private->cur_stroke,
                                     private->cur_anchor);
      if (new_stroke)
        {
          pika_vectors_stroke_add (private->vectors, new_stroke);
          g_object_unref (new_stroke);
        }

      private->cur_stroke = NULL;
      private->cur_anchor = NULL;
      private->function   = VECTORS_FINISHED;
    }

  private->last_x = coords->x;
  private->last_y = coords->y;

  pika_vectors_thaw (private->vectors);

  return 1;
}

void
pika_tool_path_button_release (PikaToolWidget        *widget,
                               const PikaCoords      *coords,
                               guint32                time,
                               GdkModifierType        state,
                               PikaButtonReleaseType  release_type)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;

  private->function = VECTORS_FINISHED;

  if (private->have_undo)
    {
      if (! private->undo_motion ||
          release_type == PIKA_BUTTON_RELEASE_CANCEL)
        {
          pika_tool_path_end_change (path, FALSE);
        }
      else
        {
          pika_tool_path_end_change (path, TRUE);
        }
    }
}

void
pika_tool_path_motion (PikaToolWidget   *widget,
                       const PikaCoords *coords,
                       guint32           time,
                       GdkModifierType   state)
{
  PikaToolPath        *path     = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private  = path->private;
  PikaCoords           position = PIKA_COORDS_DEFAULT_VALUES;
  PikaAnchor          *anchor;

  if (private->function == VECTORS_FINISHED)
    return;

  position.x = coords->x;
  position.y = coords->y;

  pika_vectors_freeze (private->vectors);

  if ((private->saved_state & TOGGLE_MASK) != (state & TOGGLE_MASK))
    private->modifier_lock = FALSE;

  if (! private->modifier_lock)
    {
      if (state & TOGGLE_MASK)
        {
          private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC;
        }
      else
        {
          private->restriction = PIKA_ANCHOR_FEATURE_NONE;
        }
    }

  switch (private->function)
    {
    case VECTORS_MOVE_ANCHOR:
    case VECTORS_MOVE_HANDLE:
      anchor = private->cur_anchor;

      if (anchor)
        {
          pika_stroke_anchor_move_absolute (private->cur_stroke,
                                            private->cur_anchor,
                                            &position,
                                            private->restriction);
          private->undo_motion = TRUE;
        }
      break;

    case VECTORS_MOVE_CURVE:
      if (private->polygonal)
        {
          pika_tool_path_move_selected_anchors (path,
                                                coords->x - private->last_x,
                                                coords->y - private->last_y);
          private->undo_motion = TRUE;
        }
      else
        {
          pika_stroke_point_move_absolute (private->cur_stroke,
                                           private->cur_anchor,
                                           private->cur_position,
                                           &position,
                                           private->restriction);
          private->undo_motion = TRUE;
        }
      break;

    case VECTORS_MOVE_ANCHORSET:
      pika_tool_path_move_selected_anchors (path,
                                            coords->x - private->last_x,
                                            coords->y - private->last_y);
      private->undo_motion = TRUE;
      break;

    case VECTORS_MOVE_STROKE:
      if (private->cur_stroke)
        {
          pika_stroke_translate (private->cur_stroke,
                                 coords->x - private->last_x,
                                 coords->y - private->last_y);
          private->undo_motion = TRUE;
        }
      else if (private->sel_stroke)
        {
          pika_stroke_translate (private->sel_stroke,
                                 coords->x - private->last_x,
                                 coords->y - private->last_y);
          private->undo_motion = TRUE;
        }
      break;

    case VECTORS_MOVE_VECTORS:
      pika_item_translate (PIKA_ITEM (private->vectors),
                           coords->x - private->last_x,
                           coords->y - private->last_y, FALSE);
      private->undo_motion = TRUE;
      break;

    default:
      break;
    }

  pika_vectors_thaw (private->vectors);

  private->last_x = coords->x;
  private->last_y = coords->y;
}

PikaHit
pika_tool_path_hit (PikaToolWidget   *widget,
                    const PikaCoords *coords,
                    GdkModifierType   state,
                    gboolean          proximity)
{
  PikaToolPath *path = PIKA_TOOL_PATH (widget);

  switch (pika_tool_path_get_function (path, coords, state))
    {
    case VECTORS_SELECT_VECTOR:
    case VECTORS_MOVE_ANCHOR:
    case VECTORS_MOVE_ANCHORSET:
    case VECTORS_MOVE_HANDLE:
    case VECTORS_MOVE_CURVE:
    case VECTORS_MOVE_STROKE:
    case VECTORS_DELETE_ANCHOR:
    case VECTORS_DELETE_SEGMENT:
    case VECTORS_INSERT_ANCHOR:
    case VECTORS_CONNECT_STROKES:
    case VECTORS_CONVERT_EDGE:
      return PIKA_HIT_DIRECT;

    case VECTORS_CREATE_VECTOR:
    case VECTORS_CREATE_STROKE:
    case VECTORS_ADD_ANCHOR:
    case VECTORS_MOVE_VECTORS:
      return PIKA_HIT_INDIRECT;

    case VECTORS_FINISHED:
      return PIKA_HIT_NONE;
    }

  return PIKA_HIT_NONE;
}

void
pika_tool_path_hover (PikaToolWidget   *widget,
                      const PikaCoords *coords,
                      GdkModifierType   state,
                      gboolean          proximity)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;

  private->function = pika_tool_path_get_function (path, coords, state);

  pika_tool_path_update_status (path, state, proximity);
}

static gboolean
pika_tool_path_key_press (PikaToolWidget *widget,
                          GdkEventKey    *kevent)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;
  PikaDisplayShell    *shell;
  gdouble              xdist, ydist;
  gdouble              pixels = 1.0;

  if (! private->vectors)
    return FALSE;

  shell = pika_tool_widget_get_shell (widget);

  if (kevent->state & pika_get_extend_selection_mask ())
    pixels = 10.0;

  if (kevent->state & pika_get_toggle_behavior_mask ())
    pixels = 50.0;

  switch (kevent->keyval)
    {
    case GDK_KEY_Return:
    case GDK_KEY_KP_Enter:
    case GDK_KEY_ISO_Enter:
      g_signal_emit (path, path_signals[ACTIVATE], 0,
                     kevent->state);
      break;

    case GDK_KEY_BackSpace:
    case GDK_KEY_Delete:
      pika_tool_path_delete_selected_anchors (path);
      break;

    case GDK_KEY_Left:
    case GDK_KEY_Right:
    case GDK_KEY_Up:
    case GDK_KEY_Down:
      xdist = FUNSCALEX (shell, pixels);
      ydist = FUNSCALEY (shell, pixels);

      pika_tool_path_begin_change (path, _("Move Anchors"));
      pika_vectors_freeze (private->vectors);

      switch (kevent->keyval)
        {
        case GDK_KEY_Left:
          pika_tool_path_move_selected_anchors (path, -xdist, 0);
          break;

        case GDK_KEY_Right:
          pika_tool_path_move_selected_anchors (path, xdist, 0);
          break;

        case GDK_KEY_Up:
          pika_tool_path_move_selected_anchors (path, 0, -ydist);
          break;

        case GDK_KEY_Down:
          pika_tool_path_move_selected_anchors (path, 0, ydist);
          break;

        default:
          break;
        }

      pika_vectors_thaw (private->vectors);
      pika_tool_path_end_change (path, TRUE);
      break;

    case GDK_KEY_Escape:
      if (private->edit_mode != PIKA_VECTOR_MODE_DESIGN)
        g_object_set (private,
                      "vectors-edit-mode", PIKA_VECTOR_MODE_DESIGN,
                      NULL);
      break;

    default:
      return FALSE;
    }

  return TRUE;
}

static gboolean
pika_tool_path_get_cursor (PikaToolWidget     *widget,
                           const PikaCoords   *coords,
                           GdkModifierType     state,
                           PikaCursorType     *cursor,
                           PikaToolCursorType *tool_cursor,
                           PikaCursorModifier *modifier)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;

  *tool_cursor = PIKA_TOOL_CURSOR_PATHS;
  *modifier    = PIKA_CURSOR_MODIFIER_NONE;

  switch (private->function)
    {
    case VECTORS_SELECT_VECTOR:
      *tool_cursor = PIKA_TOOL_CURSOR_HAND;
      break;

    case VECTORS_CREATE_VECTOR:
    case VECTORS_CREATE_STROKE:
      *modifier = PIKA_CURSOR_MODIFIER_CONTROL;
      break;

    case VECTORS_ADD_ANCHOR:
    case VECTORS_INSERT_ANCHOR:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR;
      *modifier    = PIKA_CURSOR_MODIFIER_PLUS;
      break;

    case VECTORS_DELETE_ANCHOR:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR;
      *modifier    = PIKA_CURSOR_MODIFIER_MINUS;
      break;

    case VECTORS_DELETE_SEGMENT:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT;
      *modifier    = PIKA_CURSOR_MODIFIER_MINUS;
      break;

    case VECTORS_MOVE_HANDLE:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_CONTROL;
      *modifier    = PIKA_CURSOR_MODIFIER_MOVE;
      break;

    case VECTORS_CONVERT_EDGE:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_CONTROL;
      *modifier    = PIKA_CURSOR_MODIFIER_MINUS;
      break;

    case VECTORS_MOVE_ANCHOR:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR;
      *modifier    = PIKA_CURSOR_MODIFIER_MOVE;
      break;

    case VECTORS_MOVE_CURVE:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT;
      *modifier    = PIKA_CURSOR_MODIFIER_MOVE;
      break;

    case VECTORS_MOVE_STROKE:
    case VECTORS_MOVE_VECTORS:
      *modifier = PIKA_CURSOR_MODIFIER_MOVE;
      break;

    case VECTORS_MOVE_ANCHORSET:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR;
      *modifier    = PIKA_CURSOR_MODIFIER_MOVE;
      break;

    case VECTORS_CONNECT_STROKES:
      *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT;
      *modifier    = PIKA_CURSOR_MODIFIER_JOIN;
      break;

    default:
      *modifier = PIKA_CURSOR_MODIFIER_BAD;
      break;
    }

  return TRUE;
}

static PikaUIManager *
pika_tool_path_get_popup (PikaToolWidget    *widget,
                          const PikaCoords  *coords,
                          GdkModifierType    state,
                          const gchar      **ui_path)
{
  PikaToolPath        *path    = PIKA_TOOL_PATH (widget);
  PikaToolPathPrivate *private = path->private;
  PikaDisplayShell    *shell   = pika_tool_widget_get_shell (widget);
  PikaImageWindow     *image_window;
  PikaDialogFactory   *dialog_factory;
  PikaMenuFactory     *menu_factory;
  PikaUIManager       *ui_manager;

  image_window   = pika_display_shell_get_window (shell);
  dialog_factory = pika_dock_container_get_dialog_factory (PIKA_DOCK_CONTAINER (image_window));

  menu_factory   = menus_get_global_menu_factory (pika_dialog_factory_get_context (dialog_factory)->pika);
  ui_manager     = pika_menu_factory_get_manager (menu_factory, "<VectorToolPath>", widget);

  /* we're using a side effects of pika_tool_path_get_function
   * that update the private->cur_* variables. */
  pika_tool_path_get_function (path, coords, state);

  if (private->cur_stroke)
    {
      pika_ui_manager_update (ui_manager, widget);

      *ui_path = "/vector-toolpath-popup";
      return ui_manager;
    }

  return NULL;
}


static PikaVectorFunction
pika_tool_path_get_function (PikaToolPath     *path,
                             const PikaCoords *coords,
                             GdkModifierType   state)
{
  PikaToolPathPrivate *private    = path->private;
  PikaAnchor          *anchor     = NULL;
  PikaAnchor          *anchor2    = NULL;
  PikaStroke          *stroke     = NULL;
  gdouble              position   = -1;
  gboolean             on_handle  = FALSE;
  gboolean             on_curve   = FALSE;
  gboolean             on_vectors = FALSE;
  PikaVectorFunction   function   = VECTORS_FINISHED;

  private->modifier_lock = FALSE;

  /* are we hovering the current vectors on the current display? */
  if (private->vectors)
    {
      on_handle = pika_canvas_item_on_vectors_handle (private->path,
                                                      private->vectors,
                                                      coords,
                                                      PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                      PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                      PIKA_ANCHOR_ANCHOR,
                                                      private->sel_count > 2,
                                                      &anchor, &stroke);

      if (! on_handle)
        on_curve = pika_canvas_item_on_vectors_curve (private->path,
                                                      private->vectors,
                                                      coords,
                                                      PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                      PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                      NULL,
                                                      &position, &anchor,
                                                      &anchor2, &stroke);
    }

  if (! on_handle && ! on_curve)
    {
      on_vectors = pika_canvas_item_on_vectors (private->path,
                                                coords,
                                                PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                                NULL, NULL, NULL, NULL, NULL,
                                                NULL);
    }

  private->cur_position = position;
  private->cur_anchor   = anchor;
  private->cur_anchor2  = anchor2;
  private->cur_stroke   = stroke;

  switch (private->edit_mode)
    {
    case PIKA_VECTOR_MODE_DESIGN:
      if (! private->vectors)
        {
          if (on_vectors)
            {
              function = VECTORS_SELECT_VECTOR;
            }
          else
            {
              function               = VECTORS_CREATE_VECTOR;
              private->restriction   = PIKA_ANCHOR_FEATURE_SYMMETRIC;
              private->modifier_lock = TRUE;
            }
        }
      else if (on_handle)
        {
          if (anchor->type == PIKA_ANCHOR_ANCHOR)
            {
              if (state & TOGGLE_MASK)
                {
                  function = VECTORS_MOVE_ANCHORSET;
                }
              else
                {
                  if (private->sel_count >= 2 && anchor->selected)
                    function = VECTORS_MOVE_ANCHORSET;
                  else
                    function = VECTORS_MOVE_ANCHOR;
                }
            }
          else
            {
              function = VECTORS_MOVE_HANDLE;

              if (state & TOGGLE_MASK)
                private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC;
              else
                private->restriction = PIKA_ANCHOR_FEATURE_NONE;
            }
        }
      else if (on_curve)
        {
          if (pika_stroke_point_is_movable (stroke, anchor, position))
            {
              function = VECTORS_MOVE_CURVE;

              if (state & TOGGLE_MASK)
                private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC;
              else
                private->restriction = PIKA_ANCHOR_FEATURE_NONE;
            }
          else
            {
              function = VECTORS_FINISHED;
            }
        }
      else
        {
          if (private->sel_stroke &&
              private->sel_anchor &&
              pika_stroke_is_extendable (private->sel_stroke,
                                         private->sel_anchor) &&
              ! (state & TOGGLE_MASK))
            function = VECTORS_ADD_ANCHOR;
          else
            function = VECTORS_CREATE_STROKE;

          private->restriction   = PIKA_ANCHOR_FEATURE_SYMMETRIC;
          private->modifier_lock = TRUE;
        }

      break;

    case PIKA_VECTOR_MODE_EDIT:
      if (! private->vectors)
        {
          if (on_vectors)
            {
              function = VECTORS_SELECT_VECTOR;
            }
          else
            {
              function = VECTORS_FINISHED;
            }
        }
      else if (on_handle)
        {
          if (anchor->type == PIKA_ANCHOR_ANCHOR)
            {
              if (! (state & TOGGLE_MASK) &&
                  private->sel_anchor &&
                  private->sel_anchor != anchor &&
                  pika_stroke_is_extendable (private->sel_stroke,
                                             private->sel_anchor) &&
                  pika_stroke_is_extendable (stroke, anchor))
                {
                  function = VECTORS_CONNECT_STROKES;
                }
              else
                {
                  if (state & TOGGLE_MASK)
                    {
                      function = VECTORS_DELETE_ANCHOR;
                    }
                  else
                    {
                      if (private->polygonal)
                        function = VECTORS_MOVE_ANCHOR;
                      else
                        function = VECTORS_MOVE_HANDLE;
                    }
                }
            }
          else
            {
              if (state & TOGGLE_MASK)
                function = VECTORS_CONVERT_EDGE;
              else
                function = VECTORS_MOVE_HANDLE;
            }
        }
      else if (on_curve)
        {
          if (state & TOGGLE_MASK)
            {
              function = VECTORS_DELETE_SEGMENT;
            }
          else if (pika_stroke_anchor_is_insertable (stroke, anchor, position))
            {
              function = VECTORS_INSERT_ANCHOR;
            }
          else
            {
              function = VECTORS_FINISHED;
            }
        }
      else
        {
          function = VECTORS_FINISHED;
        }

      break;

    case PIKA_VECTOR_MODE_MOVE:
      if (! private->vectors)
        {
          if (on_vectors)
            {
              function = VECTORS_SELECT_VECTOR;
            }
          else
            {
              function = VECTORS_FINISHED;
            }
        }
      else if (on_handle || on_curve)
        {
          if (state & TOGGLE_MASK)
            {
              function = VECTORS_MOVE_VECTORS;
            }
          else
            {
              function = VECTORS_MOVE_STROKE;
            }
        }
      else
        {
          if (on_vectors)
            {
              function = VECTORS_SELECT_VECTOR;
            }
          else
            {
              function = VECTORS_MOVE_VECTORS;
            }
        }
      break;
    }

  return function;
}

static void
pika_tool_path_update_status (PikaToolPath    *path,
                              GdkModifierType  state,
                              gboolean         proximity)
{
  PikaToolPathPrivate *private     = path->private;
  GdkModifierType      extend_mask = pika_get_extend_selection_mask ();
  GdkModifierType      toggle_mask = pika_get_toggle_behavior_mask ();
  const gchar         *status      = NULL;
  gboolean             free_status = FALSE;

  if (! proximity)
    {
      pika_tool_widget_set_status (PIKA_TOOL_WIDGET (path), NULL);
      return;
    }

  switch (private->function)
    {
    case VECTORS_SELECT_VECTOR:
      status = _("Click to pick path to edit");
      break;

    case VECTORS_CREATE_VECTOR:
      status = _("Click to create a new path");
      break;

    case VECTORS_CREATE_STROKE:
      status = _("Click to create a new component of the path");
      break;

    case VECTORS_ADD_ANCHOR:
      status = pika_suggest_modifiers (_("Click or Click-Drag to create "
                                         "a new anchor"),
                                       extend_mask & ~state,
                                       NULL, NULL, NULL);
      free_status = TRUE;
      break;

    case VECTORS_MOVE_ANCHOR:
      if (private->edit_mode != PIKA_VECTOR_MODE_EDIT)
        {
          status = pika_suggest_modifiers (_("Click-Drag to move the "
                                             "anchor around"),
                                           toggle_mask & ~state,
                                           NULL, NULL, NULL);
          free_status = TRUE;
        }
      else
        status = _("Click-Drag to move the anchor around");
      break;

    case VECTORS_MOVE_ANCHORSET:
      status = _("Click-Drag to move the anchors around");
      break;

    case VECTORS_MOVE_HANDLE:
      if (private->restriction != PIKA_ANCHOR_FEATURE_SYMMETRIC)
        {
          status = pika_suggest_modifiers (_("Click-Drag to move the "
                                             "handle around"),
                                           extend_mask & ~state,
                                           NULL, NULL, NULL);
        }
      else
        {
          status = pika_suggest_modifiers (_("Click-Drag to move the "
                                             "handles around symmetrically"),
                                           extend_mask & ~state,
                                           NULL, NULL, NULL);
        }
      free_status = TRUE;
      break;

    case VECTORS_MOVE_CURVE:
      if (private->polygonal)
        status = pika_suggest_modifiers (_("Click-Drag to move the "
                                           "anchors around"),
                                         extend_mask & ~state,
                                         NULL, NULL, NULL);
      else
        status = pika_suggest_modifiers (_("Click-Drag to change the "
                                           "shape of the curve"),
                                         extend_mask & ~state,
                                         _("%s: symmetrical"), NULL, NULL);
      free_status = TRUE;
      break;

    case VECTORS_MOVE_STROKE:
      status = pika_suggest_modifiers (_("Click-Drag to move the "
                                         "component around"),
                                       extend_mask & ~state,
                                       NULL, NULL, NULL);
      free_status = TRUE;
      break;

    case VECTORS_MOVE_VECTORS:
      status = _("Click-Drag to move the path around");
      break;

    case VECTORS_INSERT_ANCHOR:
      status = pika_suggest_modifiers (_("Click-Drag to insert an anchor "
                                         "on the path"),
                                       extend_mask & ~state,
                                       NULL, NULL, NULL);
      free_status = TRUE;
      break;

    case VECTORS_DELETE_ANCHOR:
      status = _("Click to delete this anchor");
      break;

    case VECTORS_CONNECT_STROKES:
      status = _("Click to connect this anchor "
                 "with the selected endpoint");
      break;

    case VECTORS_DELETE_SEGMENT:
      status = _("Click to open up the path");
      break;

    case VECTORS_CONVERT_EDGE:
      status = _("Click to make this node angular");
      break;

    case VECTORS_FINISHED:
      status = _("Clicking here does nothing, try clicking on path elements.");
      break;
    }

  pika_tool_widget_set_status (PIKA_TOOL_WIDGET (path), status);

  if (free_status)
    g_free ((gchar *) status);
}

static void
pika_tool_path_begin_change (PikaToolPath *path,
                             const gchar  *desc)
{
  PikaToolPathPrivate *private = path->private;

  g_return_if_fail (private->vectors != NULL);

  /* don't push two undos */
  if (private->have_undo)
    return;

  g_signal_emit (path, path_signals[BEGIN_CHANGE], 0,
                 desc);

  private->have_undo = TRUE;
}

static void
pika_tool_path_end_change (PikaToolPath *path,
                           gboolean      success)
{
  PikaToolPathPrivate *private = path->private;

  private->have_undo   = FALSE;
  private->undo_motion = FALSE;

  g_signal_emit (path, path_signals[END_CHANGE], 0,
                 success);
}

static void
pika_tool_path_vectors_visible (PikaVectors  *vectors,
                                PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;

  pika_canvas_item_set_visible (private->path,
                                ! pika_item_get_visible (PIKA_ITEM (vectors)));
}

static void
pika_tool_path_vectors_freeze (PikaVectors  *vectors,
                               PikaToolPath *path)
{
}

static void
pika_tool_path_vectors_thaw (PikaVectors  *vectors,
                             PikaToolPath *path)
{
  /*  Ok, the vector might have changed externally (e.g. Undo) we need
   *  to validate our internal state.
   */
  pika_tool_path_verify_state (path);
  pika_tool_path_changed (PIKA_TOOL_WIDGET (path));
}

static void
pika_tool_path_verify_state (PikaToolPath *path)
{
  PikaToolPathPrivate *private          = path->private;
  PikaStroke          *cur_stroke       = NULL;
  gboolean             cur_anchor_valid = FALSE;
  gboolean             cur_stroke_valid = FALSE;

  private->sel_count  = 0;
  private->sel_anchor = NULL;
  private->sel_stroke = NULL;

  if (! private->vectors)
    {
      private->cur_position = -1;
      private->cur_anchor   = NULL;
      private->cur_stroke   = NULL;
      return;
    }

  while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors,
                                                     cur_stroke)))
    {
      GList *anchors;
      GList *list;

      /* anchor handles */
      anchors = pika_stroke_get_draw_anchors (cur_stroke);

      if (cur_stroke == private->cur_stroke)
        cur_stroke_valid = TRUE;

      for (list = anchors; list; list = g_list_next (list))
        {
          PikaAnchor *cur_anchor = list->data;

          if (cur_anchor == private->cur_anchor)
            cur_anchor_valid = TRUE;

          if (cur_anchor->type == PIKA_ANCHOR_ANCHOR &&
              cur_anchor->selected)
            {
              private->sel_count++;
              if (private->sel_count == 1)
                {
                  private->sel_anchor = cur_anchor;
                  private->sel_stroke = cur_stroke;
                }
              else
                {
                  private->sel_anchor = NULL;
                  private->sel_stroke = NULL;
                }
            }
        }

      g_list_free (anchors);

      anchors = pika_stroke_get_draw_controls (cur_stroke);

      for (list = anchors; list; list = g_list_next (list))
        {
          PikaAnchor *cur_anchor = list->data;

          if (cur_anchor == private->cur_anchor)
            cur_anchor_valid = TRUE;
        }

      g_list_free (anchors);
    }

  if (! cur_stroke_valid)
    private->cur_stroke = NULL;

  if (! cur_anchor_valid)
    private->cur_anchor = NULL;
}

static void
pika_tool_path_move_selected_anchors (PikaToolPath *path,
                                      gdouble       x,
                                      gdouble       y)
{
  PikaToolPathPrivate *private = path->private;
  PikaAnchor          *cur_anchor;
  PikaStroke          *cur_stroke = NULL;
  GList               *anchors;
  GList               *list;
  PikaCoords           offset = { 0.0, };

  offset.x = x;
  offset.y = y;

  while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors,
                                                     cur_stroke)))
    {
      /* anchors */
      anchors = pika_stroke_get_draw_anchors (cur_stroke);

      for (list = anchors; list; list = g_list_next (list))
        {
          cur_anchor = PIKA_ANCHOR (list->data);

          if (cur_anchor->selected)
            pika_stroke_anchor_move_relative (cur_stroke,
                                              cur_anchor,
                                              &offset,
                                              PIKA_ANCHOR_FEATURE_NONE);
        }

      g_list_free (anchors);
    }
}

static void
pika_tool_path_delete_selected_anchors (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  PikaAnchor          *cur_anchor;
  PikaStroke          *cur_stroke = NULL;
  GList               *anchors;
  GList               *list;
  gboolean             have_undo = FALSE;

  pika_vectors_freeze (private->vectors);

  while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors,
                                                     cur_stroke)))
    {
      /* anchors */
      anchors = pika_stroke_get_draw_anchors (cur_stroke);

      for (list = anchors; list; list = g_list_next (list))
        {
          cur_anchor = PIKA_ANCHOR (list->data);

          if (cur_anchor->selected)
            {
              if (! have_undo)
                {
                  pika_tool_path_begin_change (path, _("Delete Anchors"));
                  have_undo = TRUE;
                }

              pika_stroke_anchor_delete (cur_stroke, cur_anchor);

              if (pika_stroke_is_empty (cur_stroke))
                {
                  pika_vectors_stroke_remove (private->vectors, cur_stroke);
                  cur_stroke = NULL;
                }
            }
        }

      g_list_free (anchors);
    }

  if (have_undo)
    pika_tool_path_end_change (path, TRUE);

  pika_vectors_thaw (private->vectors);
}


/*  public functions  */

PikaToolWidget *
pika_tool_path_new (PikaDisplayShell *shell)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);

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

void
pika_tool_path_set_vectors (PikaToolPath *path,
                            PikaVectors  *vectors)
{
  PikaToolPathPrivate *private;

  g_return_if_fail (PIKA_IS_TOOL_PATH (path));
  g_return_if_fail (vectors == NULL || PIKA_IS_VECTORS (vectors));

  private = path->private;

  if (vectors == private->vectors)
    return;

  if (private->vectors)
    {
      g_signal_handlers_disconnect_by_func (private->vectors,
                                            pika_tool_path_vectors_visible,
                                            path);
      g_signal_handlers_disconnect_by_func (private->vectors,
                                            pika_tool_path_vectors_freeze,
                                            path);
      g_signal_handlers_disconnect_by_func (private->vectors,
                                            pika_tool_path_vectors_thaw,
                                            path);

      g_object_unref (private->vectors);
    }

  private->vectors  = vectors;
  private->function = VECTORS_FINISHED;
  pika_tool_path_verify_state (path);

  if (private->vectors)
    {
      g_object_ref (private->vectors);

      g_signal_connect_object (private->vectors, "visibility-changed",
                               G_CALLBACK (pika_tool_path_vectors_visible),
                               path, 0);
      g_signal_connect_object (private->vectors, "freeze",
                               G_CALLBACK (pika_tool_path_vectors_freeze),
                               path, 0);
      g_signal_connect_object (private->vectors, "thaw",
                               G_CALLBACK (pika_tool_path_vectors_thaw),
                               path, 0);
    }

  g_object_notify (G_OBJECT (path), "vectors");
}

void
pika_tool_path_get_popup_state (PikaToolPath *path,
                                gboolean     *on_handle,
                                gboolean     *on_curve)
{
  PikaToolPathPrivate *private = path->private;

  if (on_handle)
    *on_handle = private->cur_anchor2 == NULL;

  if (on_curve)
    *on_curve = private->cur_stroke != NULL;

}

void
pika_tool_path_delete_anchor (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  g_return_if_fail (private->cur_stroke != NULL);
  g_return_if_fail (private->cur_anchor != NULL);

  pika_vectors_freeze (private->vectors);
  pika_tool_path_begin_change (path, _("Delete Anchors"));
  if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR)
    {
      pika_stroke_anchor_delete (private->cur_stroke, private->cur_anchor);
      if (pika_stroke_is_empty (private->cur_stroke))
        pika_vectors_stroke_remove (private->vectors,
                                    private->cur_stroke);
    }
  else
    {
      pika_stroke_anchor_convert (private->cur_stroke,
                                  private->cur_anchor,
                                  PIKA_ANCHOR_FEATURE_EDGE);
    }

  pika_tool_path_end_change (path, TRUE);
  pika_vectors_thaw (private->vectors);
}

void
pika_tool_path_shift_start (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  g_return_if_fail (private->cur_stroke != NULL);
  g_return_if_fail (private->cur_anchor != NULL);

  pika_vectors_freeze (private->vectors);
  pika_tool_path_begin_change (path, _("Shift start"));
  pika_stroke_shift_start (private->cur_stroke, private->cur_anchor);
  pika_tool_path_end_change (path, TRUE);
  pika_vectors_thaw (private->vectors);
}

void
pika_tool_path_insert_anchor (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  g_return_if_fail (private->cur_stroke != NULL);
  g_return_if_fail (private->cur_anchor != NULL);
  g_return_if_fail (private->cur_position >= 0.0);

  pika_vectors_freeze (private->vectors);
  pika_tool_path_begin_change (path, _("Insert Anchor"));
  private->cur_anchor = pika_stroke_anchor_insert (private->cur_stroke,
                                                   private->cur_anchor,
                                                   private->cur_position);
  pika_tool_path_end_change (path, TRUE);
  pika_vectors_thaw (private->vectors);
}

void
pika_tool_path_delete_segment (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  PikaStroke *new_stroke;
  g_return_if_fail (private->cur_stroke != NULL);
  g_return_if_fail (private->cur_anchor != NULL);

  pika_vectors_freeze (private->vectors);
  pika_tool_path_begin_change (path, _("Delete Segment"));

  new_stroke = pika_stroke_open (private->cur_stroke,
                                 private->cur_anchor);
  if (new_stroke)
    {
      pika_vectors_stroke_add (private->vectors, new_stroke);
      g_object_unref (new_stroke);
    }
  pika_tool_path_end_change (path, TRUE);
  pika_vectors_thaw (private->vectors);
}

void
pika_tool_path_reverse_stroke (PikaToolPath *path)
{
  PikaToolPathPrivate *private = path->private;
  g_return_if_fail (private->cur_stroke != NULL);

  pika_vectors_freeze (private->vectors);
  pika_tool_path_begin_change (path, _("Insert Anchor"));
  pika_stroke_reverse (private->cur_stroke);
  pika_tool_path_end_change (path, TRUE);
  pika_vectors_thaw (private->vectors);
}
