[gimp] app: add GimpToolWidget subclass GimpToolPath, a complete vectors editor



commit 3ab92c72908a656ca200f6781701578c97b6dfb5
Author: Michael Natterer <mitch gimp org>
Date:   Wed Jun 21 23:27:20 2017 +0200

    app: add GimpToolWidget subclass GimpToolPath, a complete vectors editor

 app/display/Makefile.am     |    2 +
 app/display/display-enums.c |   31 +
 app/display/display-enums.h |   12 +
 app/display/gimptoolpath.c  | 1839 +++++++++++++++++++++++++++++++++++++++++++
 app/display/gimptoolpath.h  |   68 ++
 app/tools/tools-enums.c     |   31 -
 app/tools/tools-enums.h     |   12 -
 po/POTFILES.in              |    1 +
 8 files changed, 1953 insertions(+), 43 deletions(-)
---
diff --git a/app/display/Makefile.am b/app/display/Makefile.am
index f7c1f41..68e79f0 100644
--- a/app/display/Makefile.am
+++ b/app/display/Makefile.am
@@ -170,6 +170,8 @@ libappdisplay_a_sources = \
        gimptoolhandlegrid.h                    \
        gimptoolline.c                          \
        gimptoolline.h                          \
+       gimptoolpath.c                          \
+       gimptoolpath.h                          \
        gimptoolrotategrid.c                    \
        gimptoolrotategrid.h                    \
        gimptoolsheargrid.c                     \
diff --git a/app/display/display-enums.c b/app/display/display-enums.c
index b3fcae7..f3ffdd3 100644
--- a/app/display/display-enums.c
+++ b/app/display/display-enums.c
@@ -326,6 +326,37 @@ gimp_transform_handle_mode_get_type (void)
 }
 
 GType
+gimp_vector_mode_get_type (void)
+{
+  static const GEnumValue values[] =
+  {
+    { GIMP_VECTOR_MODE_DESIGN, "GIMP_VECTOR_MODE_DESIGN", "design" },
+    { GIMP_VECTOR_MODE_EDIT, "GIMP_VECTOR_MODE_EDIT", "edit" },
+    { GIMP_VECTOR_MODE_MOVE, "GIMP_VECTOR_MODE_MOVE", "move" },
+    { 0, NULL, NULL }
+  };
+
+  static const GimpEnumDesc descs[] =
+  {
+    { GIMP_VECTOR_MODE_DESIGN, NC_("vector-mode", "Design"), NULL },
+    { GIMP_VECTOR_MODE_EDIT, NC_("vector-mode", "Edit"), NULL },
+    { GIMP_VECTOR_MODE_MOVE, NC_("vector-mode", "Move"), NULL },
+    { 0, NULL, NULL }
+  };
+
+  static GType type = 0;
+
+  if (G_UNLIKELY (! type))
+    {
+      type = g_enum_register_static ("GimpVectorMode", values);
+      gimp_type_set_translation_context (type, "vector-mode");
+      gimp_enum_set_value_descriptions (type, descs);
+    }
+
+  return type;
+}
+
+GType
 gimp_zoom_focus_get_type (void)
 {
   static const GEnumValue values[] =
diff --git a/app/display/display-enums.h b/app/display/display-enums.h
index 3e1d497..c042d5f 100644
--- a/app/display/display-enums.h
+++ b/app/display/display-enums.h
@@ -146,6 +146,18 @@ typedef enum
 } GimpTransformHandleMode;
 
 
+#define GIMP_TYPE_VECTOR_MODE (gimp_vector_mode_get_type ())
+
+GType gimp_vector_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+  GIMP_VECTOR_MODE_DESIGN,      /*< desc="Design" >*/
+  GIMP_VECTOR_MODE_EDIT,        /*< desc="Edit"   >*/
+  GIMP_VECTOR_MODE_MOVE         /*< desc="Move"   >*/
+} GimpVectorMode;
+
+
 #define GIMP_TYPE_ZOOM_FOCUS (gimp_zoom_focus_get_type ())
 
 GType gimp_zoom_focus_get_type (void) G_GNUC_CONST;
diff --git a/app/display/gimptoolpath.c b/app/display/gimptoolpath.c
new file mode 100644
index 0000000..2e79044
--- /dev/null
+++ b/app/display/gimptoolpath.c
@@ -0,0 +1,1839 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpath.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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "display-types.h"
+
+#include "vectors/gimpanchor.h"
+#include "vectors/gimpbezierstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasitem-utils.h"
+#include "gimpcanvasline.h"
+#include "gimpcanvaspath.h"
+#include "gimpdisplay.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolpath.h"
+
+#include "gimp-intl.h"
+
+
+#define TOGGLE_MASK  gimp_get_extend_selection_mask ()
+#define MOVE_MASK    GDK_MOD1_MASK
+#define INSDEL_MASK  gimp_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
+} GimpVectorFunction;
+
+enum
+{
+  PROP_0,
+  PROP_VECTORS,
+  PROP_EDIT_MODE,
+  PROP_POLYGONAL
+};
+
+enum
+{
+  BEGIN_CHANGE,
+  END_CHANGE,
+  ACTIVATE,
+  LAST_SIGNAL
+};
+
+struct _GimpToolPathPrivate
+{
+  GimpVectors          *vectors;        /* the current Vector data           */
+  GimpVectorMode        edit_mode;
+  gboolean              polygonal;
+
+  GimpVectorFunction    function;       /* function we're performing         */
+  GimpAnchorFeatureType 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?                 */
+
+  GimpAnchor           *cur_anchor;     /* the current Anchor                */
+  GimpAnchor           *cur_anchor2;    /* secondary Anchor (end on_curve)   */
+  GimpStroke           *cur_stroke;     /* the current Stroke                */
+  gdouble               cur_position;   /* the current Position on a segment */
+
+  gint                  sel_count;      /* number of selected anchors        */
+  GimpAnchor           *sel_anchor;     /* currently selected anchor, NULL   */
+                                        /* if multiple anchors are selected  */
+  GimpStroke           *sel_stroke;     /* selected stroke                   */
+
+  GimpVectorMode        saved_mode;     /* used by modifier_key()            */
+
+  GimpCanvasItem       *path;
+  GList                *items;
+};
+
+
+/*  local function prototypes  */
+
+static void     gimp_tool_path_constructed     (GObject               *object);
+static void     gimp_tool_path_dispose         (GObject               *object);
+static void     gimp_tool_path_set_property    (GObject               *object,
+                                                guint                  property_id,
+                                                const GValue          *value,
+                                                GParamSpec            *pspec);
+static void     gimp_tool_path_get_property    (GObject               *object,
+                                                guint                  property_id,
+                                                GValue                *value,
+                                                GParamSpec            *pspec);
+
+static void     gimp_tool_path_changed         (GimpToolWidget        *widget);
+static gint     gimp_tool_path_button_press    (GimpToolWidget        *widget,
+                                                const GimpCoords      *coords,
+                                                guint32                time,
+                                                GdkModifierType        state,
+                                                GimpButtonPressType    press_type);
+static void     gimp_tool_path_button_release  (GimpToolWidget        *widget,
+                                                const GimpCoords      *coords,
+                                                guint32                time,
+                                                GdkModifierType        state,
+                                                GimpButtonReleaseType  release_type);
+static void     gimp_tool_path_motion          (GimpToolWidget        *widget,
+                                                const GimpCoords      *coords,
+                                                guint32                time,
+                                                GdkModifierType        state);
+static void     gimp_tool_path_hover           (GimpToolWidget        *widget,
+                                                const GimpCoords      *coords,
+                                                GdkModifierType        state,
+                                                gboolean               proximity);
+static gboolean gimp_tool_path_key_press       (GimpToolWidget        *widget,
+                                                GdkEventKey           *kevent);
+static gboolean gimp_tool_path_get_cursor      (GimpToolWidget        *widget,
+                                                const GimpCoords      *coords,
+                                                GdkModifierType        state,
+                                                GimpCursorType        *cursor,
+                                                GimpToolCursorType    *tool_cursor,
+                                                GimpCursorModifier    *cursor_modifier);
+
+static void     gimp_tool_path_update_status   (GimpToolPath          *path,
+                                                GdkModifierType        state,
+                                                gboolean               proximity);
+
+static void     gimp_tool_path_begin_change    (GimpToolPath          *path,
+                                                const gchar           *desc);
+static void     gimp_tool_path_end_change      (GimpToolPath          *path,
+                                                gboolean               success);
+
+static void     gimp_tool_path_vectors_visible (GimpVectors           *vectors,
+                                                GimpToolPath          *path);
+static void     gimp_tool_path_vectors_freeze  (GimpVectors           *vectors,
+                                                GimpToolPath          *path);
+static void     gimp_tool_path_vectors_thaw    (GimpVectors           *vectors,
+                                                GimpToolPath          *path);
+static void     gimp_tool_path_verify_state    (GimpToolPath          *path);
+
+static void     gimp_tool_path_move_selected_anchors
+                                               (GimpToolPath          *path,
+                                                gdouble                x,
+                                                gdouble                y);
+static void     gimp_tool_path_delete_selected_anchors
+                                               (GimpToolPath          *path);
+
+
+G_DEFINE_TYPE (GimpToolPath, gimp_tool_path, GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_path_parent_class
+
+static guint path_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_tool_path_class_init (GimpToolPathClass *klass)
+{
+  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+  object_class->constructed     = gimp_tool_path_constructed;
+  object_class->dispose         = gimp_tool_path_dispose;
+  object_class->set_property    = gimp_tool_path_set_property;
+  object_class->get_property    = gimp_tool_path_get_property;
+
+  widget_class->changed         = gimp_tool_path_changed;
+  widget_class->button_press    = gimp_tool_path_button_press;
+  widget_class->button_release  = gimp_tool_path_button_release;
+  widget_class->motion          = gimp_tool_path_motion;
+  widget_class->hover           = gimp_tool_path_hover;
+  widget_class->key_press       = gimp_tool_path_key_press;
+  widget_class->get_cursor      = gimp_tool_path_get_cursor;
+
+  path_signals[BEGIN_CHANGE] =
+    g_signal_new ("begin-change",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (GimpToolPathClass, begin_change),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__STRING,
+                  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 (GimpToolPathClass, end_change),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__BOOLEAN,
+                  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 (GimpToolPathClass, activate),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__FLAGS,
+                  G_TYPE_NONE, 1,
+                  GDK_TYPE_MODIFIER_TYPE);
+
+  g_object_class_install_property (object_class, PROP_VECTORS,
+                                   g_param_spec_object ("vectors", NULL, NULL,
+                                                        GIMP_TYPE_VECTORS,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_EDIT_MODE,
+                                   g_param_spec_enum ("edit-mode",
+                                                      _("Edit Mode"),
+                                                      NULL,
+                                                      GIMP_TYPE_VECTOR_MODE,
+                                                      GIMP_VECTOR_MODE_DESIGN,
+                                                      GIMP_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,
+                                                         GIMP_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT));
+
+  g_type_class_add_private (klass, sizeof (GimpToolPathPrivate));
+}
+
+static void
+gimp_tool_path_init (GimpToolPath *path)
+{
+  path->private = G_TYPE_INSTANCE_GET_PRIVATE (path,
+                                               GIMP_TYPE_TOOL_PATH,
+                                               GimpToolPathPrivate);
+}
+
+static void
+gimp_tool_path_constructed (GObject *object)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (object);
+  GimpToolWidget      *widget  = GIMP_TOOL_WIDGET (object);
+  GimpToolPathPrivate *private = path->private;
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  private->path = gimp_tool_widget_add_path (widget, NULL);
+
+  gimp_tool_path_changed (widget);
+}
+
+static void
+gimp_tool_path_dispose (GObject *object)
+{
+  GimpToolPath *path = GIMP_TOOL_PATH (object);
+
+  gimp_tool_path_set_vectors (path, NULL);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_tool_path_set_property (GObject      *object,
+                             guint         property_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (object);
+  GimpToolPathPrivate *private = path->private;
+
+  switch (property_id)
+    {
+    case PROP_VECTORS:
+      gimp_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
+gimp_tool_path_get_property (GObject    *object,
+                             guint       property_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (object);
+  GimpToolPathPrivate *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 (GimpCanvasItem *item,
+                  GimpToolWidget *widget)
+{
+  gimp_tool_widget_remove_item (widget, item);
+}
+
+static void
+gimp_tool_path_changed (GimpToolWidget *widget)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private = path->private;
+  GimpVectors         *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 && gimp_vectors_get_bezier (vectors))
+    {
+      GimpStroke *cur_stroke;
+
+      gimp_canvas_path_set (private->path,
+                            gimp_vectors_get_bezier (vectors));
+      gimp_canvas_item_set_visible (private->path,
+                                    ! gimp_item_get_visible (GIMP_ITEM (vectors)));
+
+      for (cur_stroke = gimp_vectors_stroke_get_next (vectors, NULL);
+           cur_stroke;
+           cur_stroke = gimp_vectors_stroke_get_next (vectors, cur_stroke))
+        {
+          GimpCanvasItem *item;
+          GArray         *coords;
+          GList          *draw_anchors;
+          GList          *list;
+
+          /* anchor handles */
+          draw_anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+          for (list = draw_anchors; list; list = g_list_next (list))
+            {
+              GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data);
+
+              if (cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+                {
+                  item =
+                    gimp_tool_widget_add_handle (widget,
+                                                 cur_anchor->selected ?
+                                                 GIMP_HANDLE_CIRCLE :
+                                                 GIMP_HANDLE_FILLED_CIRCLE,
+                                                 cur_anchor->position.x,
+                                                 cur_anchor->position.y,
+                                                 GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                 GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                 GIMP_HANDLE_ANCHOR_CENTER);
+
+                  private->items = g_list_prepend (private->items, item);
+                }
+            }
+
+          g_list_free (draw_anchors);
+
+          if (private->sel_count <= 2)
+            {
+              /* the lines to the control handles */
+              coords = gimp_stroke_get_draw_lines (cur_stroke);
+
+              if (coords)
+                {
+                  if (coords->len % 2 == 0)
+                    {
+                      gint i;
+
+                      for (i = 0; i < coords->len; i += 2)
+                        {
+                          item = gimp_tool_widget_add_line
+                            (widget,
+                             g_array_index (coords, GimpCoords, i).x,
+                             g_array_index (coords, GimpCoords, i).y,
+                             g_array_index (coords, GimpCoords, i + 1).x,
+                             g_array_index (coords, GimpCoords, i + 1).y);
+
+                          gimp_canvas_item_set_highlight (item, TRUE);
+
+                          private->items = g_list_prepend (private->items, item);
+                        }
+                    }
+
+                  g_array_free (coords, TRUE);
+                }
+
+              /* control handles */
+              draw_anchors = gimp_stroke_get_draw_controls (cur_stroke);
+
+              for (list = draw_anchors; list; list = g_list_next (list))
+                {
+                  GimpAnchor *cur_anchor = GIMP_ANCHOR (list->data);
+
+                  item =
+                    gimp_tool_widget_add_handle (widget,
+                                                 GIMP_HANDLE_SQUARE,
+                                                 cur_anchor->position.x,
+                                                 cur_anchor->position.y,
+                                                 GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3,
+                                                 GIMP_CANVAS_HANDLE_SIZE_CIRCLE - 3,
+                                                 GIMP_HANDLE_ANCHOR_CENTER);
+
+                  private->items = g_list_prepend (private->items, item);
+                }
+
+              g_list_free (draw_anchors);
+            }
+        }
+    }
+  else
+    {
+      gimp_canvas_path_set (private->path, NULL);
+    }
+}
+
+static gboolean
+gimp_tool_path_check_writable (GimpToolPath *path)
+{
+  GimpToolPathPrivate *private = path->private;
+
+  if (gimp_item_is_content_locked (GIMP_ITEM (private->vectors)) ||
+      gimp_item_is_position_locked (GIMP_ITEM (private->vectors)))
+    {
+      gimp_tool_widget_status (GIMP_TOOL_WIDGET (path),
+                               _("The active path is locked."));
+
+      private->function = VECTORS_FINISHED;
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+gboolean
+gimp_tool_path_button_press (GimpToolWidget      *widget,
+                             const GimpCoords    *coords,
+                             guint32              time,
+                             GdkModifierType      state,
+                             GimpButtonPressType  press_type)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *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)
+    {
+      GimpVectors *vectors;
+
+      if (gimp_canvas_item_on_vectors (private->path,
+                                       coords,
+                                       GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                       GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                       NULL, NULL, NULL, NULL, NULL, &vectors))
+        {
+          gimp_tool_path_set_vectors (path, vectors);
+        }
+
+      private->function = VECTORS_FINISHED;
+    }
+
+
+  /* create a new vector from scratch */
+
+  if (private->function == VECTORS_CREATE_VECTOR)
+    {
+      GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+      GimpImage        *image = gimp_display_get_image (shell->display);
+      GimpVectors      *vectors;
+
+      vectors = gimp_vectors_new (image, _("Unnamed"));
+      g_object_ref_sink (vectors);
+
+      /* Undo step gets added implicitly */
+      private->have_undo = TRUE;
+
+      private->undo_motion = TRUE;
+
+      gimp_tool_path_set_vectors (path, vectors);
+      g_object_unref (vectors);
+
+      private->function = VECTORS_CREATE_STROKE;
+    }
+
+
+  gimp_vectors_freeze (private->vectors);
+
+  /* create a new stroke */
+
+  if (private->function == VECTORS_CREATE_STROKE &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Add Stroke"));
+      private->undo_motion = TRUE;
+
+      private->cur_stroke = gimp_bezier_stroke_new ();
+      gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      GimpCoords position = GIMP_COORDS_DEFAULT_VALUES;
+
+      position.x = coords->x;
+      position.y = coords->y;
+
+      gimp_tool_path_begin_change (path, _("Add Anchor"));
+      private->undo_motion = TRUE;
+
+      private->cur_anchor = gimp_bezier_stroke_extend (private->sel_stroke,
+                                                       &position,
+                                                       private->sel_anchor,
+                                                       EXTEND_EDITABLE);
+
+      private->restriction = GIMP_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Insert Anchor"));
+      private->undo_motion = TRUE;
+
+      private->cur_anchor = gimp_stroke_anchor_insert (private->cur_stroke,
+                                                       private->cur_anchor,
+                                                       private->cur_position);
+      if (private->cur_anchor)
+        {
+          if (private->polygonal)
+            {
+              gimp_stroke_anchor_convert (private->cur_stroke,
+                                          private->cur_anchor,
+                                          GIMP_ANCHOR_FEATURE_EDGE);
+            }
+
+          private->function = VECTORS_MOVE_ANCHOR;
+        }
+      else
+        {
+          private->function = VECTORS_FINISHED;
+        }
+    }
+
+
+  /* move a handle */
+
+  if (private->function == VECTORS_MOVE_HANDLE &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Drag Handle"));
+
+      if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+        {
+          if (! private->cur_anchor->selected)
+            {
+              gimp_vectors_anchor_select (private->vectors,
+                                          private->cur_stroke,
+                                          private->cur_anchor,
+                                          TRUE, TRUE);
+              private->undo_motion = TRUE;
+            }
+
+          gimp_canvas_item_on_vectors_handle (private->path,
+                                              private->vectors, coords,
+                                              GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                              GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                              GIMP_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Drag Anchor"));
+
+      if (! private->cur_anchor->selected)
+        {
+          gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Drag Anchors"));
+
+      if (state & TOGGLE_MASK)
+        {
+          gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Drag Curve"));
+
+      /* the magic numbers are taken from the "feel good" parameter
+       * from gimp_bezier_stroke_point_move_relative in gimpbezierstroke.c. */
+      if (private->cur_position < 5.0 / 6.0)
+        {
+          gimp_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)
+        {
+          gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Connect Strokes"));
+      private->undo_motion = TRUE;
+
+      gimp_stroke_connect_stroke (private->sel_stroke,
+                                  private->sel_anchor,
+                                  private->cur_stroke,
+                                  private->cur_anchor);
+
+      if (private->cur_stroke != private->sel_stroke &&
+          gimp_stroke_is_empty (private->cur_stroke))
+        {
+          gimp_vectors_stroke_remove (private->vectors,
+                                      private->cur_stroke);
+        }
+
+      private->sel_anchor = private->cur_anchor;
+      private->cur_stroke = private->sel_stroke;
+
+      gimp_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) &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Drag Path"));
+
+      /* Work is being done in gimp_tool_path_motion()... */
+    }
+
+
+  /* convert an anchor to something that looks like an edge */
+
+  if (private->function == VECTORS_CONVERT_EDGE &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Convert Edge"));
+      private->undo_motion = TRUE;
+
+      gimp_stroke_anchor_convert (private->cur_stroke,
+                                  private->cur_anchor,
+                                  GIMP_ANCHOR_FEATURE_EDGE);
+
+      if (private->cur_anchor->type == GIMP_ANCHOR_ANCHOR)
+        {
+          gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      gimp_tool_path_begin_change (path, _("Delete Anchor"));
+      private->undo_motion = TRUE;
+
+      gimp_stroke_anchor_delete (private->cur_stroke,
+                                 private->cur_anchor);
+
+      if (gimp_stroke_is_empty (private->cur_stroke))
+        gimp_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 &&
+      gimp_tool_path_check_writable (path))
+    {
+      GimpStroke *new_stroke;
+
+      gimp_tool_path_begin_change (path, _("Delete Segment"));
+      private->undo_motion = TRUE;
+
+      new_stroke = gimp_stroke_open (private->cur_stroke,
+                                     private->cur_anchor);
+      if (new_stroke)
+        {
+          gimp_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;
+
+  gimp_vectors_thaw (private->vectors);
+
+  return 1;
+}
+
+void
+gimp_tool_path_button_release (GimpToolWidget        *widget,
+                               const GimpCoords      *coords,
+                               guint32                time,
+                               GdkModifierType        state,
+                               GimpButtonReleaseType  release_type)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private = path->private;
+
+  private->function = VECTORS_FINISHED;
+
+  if (private->have_undo)
+    {
+      if (! private->undo_motion ||
+          release_type == GIMP_BUTTON_RELEASE_CANCEL)
+        {
+          gimp_tool_path_end_change (path, FALSE);
+        }
+      else
+        {
+          gimp_tool_path_end_change (path, TRUE);
+        }
+    }
+}
+
+void
+gimp_tool_path_motion (GimpToolWidget   *widget,
+                       const GimpCoords *coords,
+                       guint32           time,
+                       GdkModifierType   state)
+{
+  GimpToolPath        *path     = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private  = path->private;
+  GimpCoords           position = GIMP_COORDS_DEFAULT_VALUES;
+  GimpAnchor          *anchor;
+
+  if (private->function == VECTORS_FINISHED)
+    return;
+
+  position.x = coords->x;
+  position.y = coords->y;
+
+  gimp_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 = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+        }
+      else
+        {
+          private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+        }
+    }
+
+  switch (private->function)
+    {
+    case VECTORS_MOVE_ANCHOR:
+    case VECTORS_MOVE_HANDLE:
+      anchor = private->cur_anchor;
+
+      if (anchor)
+        {
+          gimp_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)
+        {
+          gimp_tool_path_move_selected_anchors (path,
+                                                coords->x - private->last_x,
+                                                coords->y - private->last_y);
+          private->undo_motion = TRUE;
+        }
+      else
+        {
+          gimp_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:
+      gimp_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)
+        {
+          gimp_stroke_translate (private->cur_stroke,
+                                 coords->x - private->last_x,
+                                 coords->y - private->last_y);
+          private->undo_motion = TRUE;
+        }
+      else if (private->sel_stroke)
+        {
+          gimp_stroke_translate (private->sel_stroke,
+                                 coords->x - private->last_x,
+                                 coords->y - private->last_y);
+          private->undo_motion = TRUE;
+        }
+      break;
+
+    case VECTORS_MOVE_VECTORS:
+      gimp_item_translate (GIMP_ITEM (private->vectors),
+                           coords->x - private->last_x,
+                           coords->y - private->last_y, FALSE);
+      private->undo_motion = TRUE;
+      break;
+
+    default:
+      break;
+    }
+
+  gimp_vectors_thaw (private->vectors);
+
+  private->last_x = coords->x;
+  private->last_y = coords->y;
+}
+
+void
+gimp_tool_path_hover (GimpToolWidget   *widget,
+                      const GimpCoords *coords,
+                      GdkModifierType   state,
+                      gboolean          proximity)
+{
+  GimpToolPath        *path       = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private    = path->private;
+  GimpAnchor          *anchor     = NULL;
+  GimpAnchor          *anchor2    = NULL;
+  GimpStroke          *stroke     = NULL;
+  gdouble              position   = -1;
+  gboolean             on_handle  = FALSE;
+  gboolean             on_curve   = FALSE;
+  gboolean             on_vectors = FALSE;
+
+  private->modifier_lock = FALSE;
+
+  /* are we hovering the current vectors on the current display? */
+  if (private->vectors)
+    {
+      on_handle = gimp_canvas_item_on_vectors_handle (private->path,
+                                                      private->vectors,
+                                                      coords,
+                                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                      GIMP_ANCHOR_ANCHOR,
+                                                      private->sel_count > 2,
+                                                      &anchor, &stroke);
+
+      if (! on_handle)
+        on_curve = gimp_canvas_item_on_vectors_curve (private->path,
+                                                      private->vectors,
+                                                      coords,
+                                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                      GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                      NULL,
+                                                      &position, &anchor,
+                                                      &anchor2, &stroke);
+    }
+
+  if (! on_handle && ! on_curve)
+    {
+      on_vectors = gimp_canvas_item_on_vectors (private->path,
+                                                coords,
+                                                GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+                                                GIMP_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 GIMP_VECTOR_MODE_DESIGN:
+      if (! private->vectors)
+        {
+          if (on_vectors)
+            {
+              private->function = VECTORS_SELECT_VECTOR;
+            }
+          else
+            {
+              private->function      = VECTORS_CREATE_VECTOR;
+              private->restriction   = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+              private->modifier_lock = TRUE;
+            }
+        }
+      else if (on_handle)
+        {
+          if (anchor->type == GIMP_ANCHOR_ANCHOR)
+            {
+              if (state & TOGGLE_MASK)
+                {
+                  private->function = VECTORS_MOVE_ANCHORSET;
+                }
+              else
+                {
+                  if (private->sel_count >= 2 && anchor->selected)
+                    private->function = VECTORS_MOVE_ANCHORSET;
+                  else
+                    private->function = VECTORS_MOVE_ANCHOR;
+                }
+            }
+          else
+            {
+              private->function = VECTORS_MOVE_HANDLE;
+
+              if (state & TOGGLE_MASK)
+                private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+              else
+                private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+            }
+        }
+      else if (on_curve)
+        {
+          if (gimp_stroke_point_is_movable (stroke, anchor, position))
+            {
+              private->function = VECTORS_MOVE_CURVE;
+
+              if (state & TOGGLE_MASK)
+                private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+              else
+                private->restriction = GIMP_ANCHOR_FEATURE_NONE;
+            }
+          else
+            {
+              private->function = VECTORS_FINISHED;
+            }
+        }
+      else
+        {
+          if (private->sel_stroke &&
+              private->sel_anchor &&
+              gimp_stroke_is_extendable (private->sel_stroke,
+                                         private->sel_anchor) &&
+              ! (state & TOGGLE_MASK))
+            private->function = VECTORS_ADD_ANCHOR;
+          else
+            private->function = VECTORS_CREATE_STROKE;
+
+          private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC;
+          private->modifier_lock = TRUE;
+        }
+
+      break;
+
+    case GIMP_VECTOR_MODE_EDIT:
+      if (! private->vectors)
+        {
+          if (on_vectors)
+            {
+              private->function = VECTORS_SELECT_VECTOR;
+            }
+          else
+            {
+              private->function = VECTORS_FINISHED;
+            }
+        }
+      else if (on_handle)
+        {
+          if (anchor->type == GIMP_ANCHOR_ANCHOR)
+            {
+              if (! (state & TOGGLE_MASK) &&
+                  private->sel_anchor &&
+                  private->sel_anchor != anchor &&
+                  gimp_stroke_is_extendable (private->sel_stroke,
+                                             private->sel_anchor) &&
+                  gimp_stroke_is_extendable (stroke, anchor))
+                {
+                  private->function = VECTORS_CONNECT_STROKES;
+                }
+              else
+                {
+                  if (state & TOGGLE_MASK)
+                    {
+                      private->function = VECTORS_DELETE_ANCHOR;
+                    }
+                  else
+                    {
+                      if (private->polygonal)
+                        private->function = VECTORS_MOVE_ANCHOR;
+                      else
+                        private->function = VECTORS_MOVE_HANDLE;
+                    }
+                }
+            }
+          else
+            {
+              if (state & TOGGLE_MASK)
+                private->function = VECTORS_CONVERT_EDGE;
+              else
+                private->function = VECTORS_MOVE_HANDLE;
+            }
+        }
+      else if (on_curve)
+        {
+          if (state & TOGGLE_MASK)
+            {
+              private->function = VECTORS_DELETE_SEGMENT;
+            }
+          else if (gimp_stroke_anchor_is_insertable (stroke, anchor, position))
+            {
+              private->function = VECTORS_INSERT_ANCHOR;
+            }
+          else
+            {
+              private->function = VECTORS_FINISHED;
+            }
+        }
+      else
+        {
+          private->function = VECTORS_FINISHED;
+        }
+
+      break;
+
+    case GIMP_VECTOR_MODE_MOVE:
+      if (! private->vectors)
+        {
+          if (on_vectors)
+            {
+              private->function = VECTORS_SELECT_VECTOR;
+            }
+          else
+            {
+              private->function = VECTORS_FINISHED;
+            }
+        }
+      else if (on_handle || on_curve)
+        {
+          if (state & TOGGLE_MASK)
+            {
+              private->function = VECTORS_MOVE_VECTORS;
+            }
+          else
+            {
+              private->function = VECTORS_MOVE_STROKE;
+            }
+        }
+      else
+        {
+          if (on_vectors)
+            {
+              private->function = VECTORS_SELECT_VECTOR;
+            }
+          else
+            {
+              private->function = VECTORS_MOVE_VECTORS;
+            }
+        }
+      break;
+    }
+
+  gimp_tool_path_update_status (path, state, proximity);
+}
+
+static gboolean
+gimp_tool_path_key_press (GimpToolWidget *widget,
+                          GdkEventKey    *kevent)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private = path->private;
+  GimpDisplayShell    *shell;
+  gdouble              xdist, ydist;
+  gdouble              pixels = 1.0;
+
+  if (! private->vectors)
+    return FALSE;
+
+  shell = gimp_tool_widget_get_shell (widget);
+
+  if (kevent->state & gimp_get_extend_selection_mask ())
+    pixels = 10.0;
+
+  if (kevent->state & gimp_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:
+      gimp_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);
+
+      gimp_tool_path_begin_change (path, _("Move Anchors"));
+      gimp_vectors_freeze (private->vectors);
+
+      switch (kevent->keyval)
+        {
+        case GDK_KEY_Left:
+          gimp_tool_path_move_selected_anchors (path, -xdist, 0);
+          break;
+
+        case GDK_KEY_Right:
+          gimp_tool_path_move_selected_anchors (path, xdist, 0);
+          break;
+
+        case GDK_KEY_Up:
+          gimp_tool_path_move_selected_anchors (path, 0, -ydist);
+          break;
+
+        case GDK_KEY_Down:
+          gimp_tool_path_move_selected_anchors (path, 0, ydist);
+          break;
+
+        default:
+          break;
+        }
+
+      gimp_vectors_thaw (private->vectors);
+      gimp_tool_path_end_change (path, TRUE);
+      break;
+
+    case GDK_KEY_Escape:
+      if (private->edit_mode != GIMP_VECTOR_MODE_DESIGN)
+        g_object_set (private,
+                      "vectors-edit-mode", GIMP_VECTOR_MODE_DESIGN,
+                      NULL);
+      break;
+
+    default:
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gimp_tool_path_get_cursor (GimpToolWidget     *widget,
+                           const GimpCoords   *coords,
+                           GdkModifierType     state,
+                           GimpCursorType     *cursor,
+                           GimpToolCursorType *tool_cursor,
+                           GimpCursorModifier *cursor_modifier)
+{
+  GimpToolPath        *path    = GIMP_TOOL_PATH (widget);
+  GimpToolPathPrivate *private = path->private;
+
+  *tool_cursor     = GIMP_TOOL_CURSOR_PATHS;
+  *cursor_modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+  switch (private->function)
+    {
+    case VECTORS_SELECT_VECTOR:
+      *tool_cursor = GIMP_TOOL_CURSOR_HAND;
+      break;
+
+    case VECTORS_CREATE_VECTOR:
+    case VECTORS_CREATE_STROKE:
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_CONTROL;
+      break;
+
+    case VECTORS_ADD_ANCHOR:
+    case VECTORS_INSERT_ANCHOR:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_PLUS;
+      break;
+
+    case VECTORS_DELETE_ANCHOR:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MINUS;
+      break;
+
+    case VECTORS_DELETE_SEGMENT:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MINUS;
+      break;
+
+    case VECTORS_MOVE_HANDLE:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_CONTROL;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      break;
+
+    case VECTORS_CONVERT_EDGE:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_CONTROL;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MINUS;
+      break;
+
+    case VECTORS_MOVE_ANCHOR:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      break;
+
+    case VECTORS_MOVE_CURVE:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      break;
+
+    case VECTORS_MOVE_STROKE:
+    case VECTORS_MOVE_VECTORS:
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      break;
+
+    case VECTORS_MOVE_ANCHORSET:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_ANCHOR;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE;
+      break;
+
+    case VECTORS_CONNECT_STROKES:
+      *tool_cursor     = GIMP_TOOL_CURSOR_PATHS_SEGMENT;
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_JOIN;
+      break;
+
+    default:
+      *cursor_modifier = GIMP_CURSOR_MODIFIER_BAD;
+      break;
+    }
+
+  return TRUE;
+}
+
+static void
+gimp_tool_path_update_status (GimpToolPath    *path,
+                              GdkModifierType  state,
+                              gboolean         proximity)
+{
+  GimpToolPathPrivate *private     = path->private;
+  GdkModifierType      extend_mask = gimp_get_extend_selection_mask ();
+  GdkModifierType      toggle_mask = gimp_get_toggle_behavior_mask ();
+  const gchar         *status      = NULL;
+  gboolean             free_status = FALSE;
+
+  if (! proximity)
+    {
+      gimp_tool_widget_status (GIMP_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 = gimp_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 != GIMP_VECTOR_MODE_EDIT)
+        {
+          status = gimp_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 != GIMP_ANCHOR_FEATURE_SYMMETRIC)
+        {
+          status = gimp_suggest_modifiers (_("Click-Drag to move the "
+                                             "handle around"),
+                                           extend_mask & ~state,
+                                           NULL, NULL, NULL);
+        }
+      else
+        {
+          status = gimp_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 = gimp_suggest_modifiers (_("Click-Drag to move the "
+                                           "anchors around"),
+                                         extend_mask & ~state,
+                                         NULL, NULL, NULL);
+      else
+        status = gimp_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 = gimp_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 = gimp_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;
+    }
+
+  gimp_tool_widget_status (GIMP_TOOL_WIDGET (path), status);
+
+  if (free_status)
+    g_free ((gchar *) status);
+}
+
+static void
+gimp_tool_path_begin_change (GimpToolPath *path,
+                             const gchar  *desc)
+{
+  GimpToolPathPrivate *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
+gimp_tool_path_end_change (GimpToolPath *path,
+                           gboolean      success)
+{
+  GimpToolPathPrivate *private = path->private;
+
+  private->have_undo   = FALSE;
+  private->undo_motion = FALSE;
+
+  g_signal_emit (path, path_signals[END_CHANGE], 0,
+                 success);
+}
+
+static void
+gimp_tool_path_vectors_visible (GimpVectors  *vectors,
+                                GimpToolPath *path)
+{
+  GimpToolPathPrivate *private = path->private;
+
+  gimp_canvas_item_set_visible (private->path,
+                                ! gimp_item_get_visible (GIMP_ITEM (vectors)));
+}
+
+static void
+gimp_tool_path_vectors_freeze (GimpVectors  *vectors,
+                               GimpToolPath *path)
+{
+}
+
+static void
+gimp_tool_path_vectors_thaw (GimpVectors  *vectors,
+                             GimpToolPath *path)
+{
+  /*  Ok, the vector might have changed externally (e.g. Undo) we need
+   *  to validate our internal state.
+   */
+  gimp_tool_path_verify_state (path);
+  gimp_tool_path_changed (GIMP_TOOL_WIDGET (path));
+}
+
+static void
+gimp_tool_path_verify_state (GimpToolPath *path)
+{
+  GimpToolPathPrivate *private          = path->private;
+  GimpStroke          *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 = gimp_vectors_stroke_get_next (private->vectors,
+                                                     cur_stroke)))
+    {
+      GList *anchors;
+      GList *list;
+
+      /* anchor handles */
+      anchors = gimp_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))
+        {
+          GimpAnchor *cur_anchor = list->data;
+
+          if (cur_anchor == private->cur_anchor)
+            cur_anchor_valid = TRUE;
+
+          if (cur_anchor->type == GIMP_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 = gimp_stroke_get_draw_controls (cur_stroke);
+
+      for (list = anchors; list; list = g_list_next (list))
+        {
+          GimpAnchor *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
+gimp_tool_path_move_selected_anchors (GimpToolPath *path,
+                                      gdouble       x,
+                                      gdouble       y)
+{
+  GimpToolPathPrivate *private = path->private;
+  GimpAnchor          *cur_anchor;
+  GimpStroke          *cur_stroke = NULL;
+  GList               *anchors;
+  GList               *list;
+  GimpCoords           offset = { 0.0, };
+
+  offset.x = x;
+  offset.y = y;
+
+  while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors,
+                                                     cur_stroke)))
+    {
+      /* anchors */
+      anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+      for (list = anchors; list; list = g_list_next (list))
+        {
+          cur_anchor = GIMP_ANCHOR (list->data);
+
+          if (cur_anchor->selected)
+            gimp_stroke_anchor_move_relative (cur_stroke,
+                                              cur_anchor,
+                                              &offset,
+                                              GIMP_ANCHOR_FEATURE_NONE);
+        }
+
+      g_list_free (anchors);
+    }
+}
+
+static void
+gimp_tool_path_delete_selected_anchors (GimpToolPath *path)
+{
+  GimpToolPathPrivate *private = path->private;
+  GimpAnchor          *cur_anchor;
+  GimpStroke          *cur_stroke = NULL;
+  GList               *anchors;
+  GList               *list;
+  gboolean             have_undo = FALSE;
+
+  gimp_vectors_freeze (private->vectors);
+
+  while ((cur_stroke = gimp_vectors_stroke_get_next (private->vectors,
+                                                     cur_stroke)))
+    {
+      /* anchors */
+      anchors = gimp_stroke_get_draw_anchors (cur_stroke);
+
+      for (list = anchors; list; list = g_list_next (list))
+        {
+          cur_anchor = GIMP_ANCHOR (list->data);
+
+          if (cur_anchor->selected)
+            {
+              if (! have_undo)
+                {
+                  gimp_tool_path_begin_change (path, _("Delete Anchors"));
+                  have_undo = TRUE;
+                }
+
+              gimp_stroke_anchor_delete (cur_stroke, cur_anchor);
+
+              if (gimp_stroke_is_empty (cur_stroke))
+                {
+                  gimp_vectors_stroke_remove (private->vectors, cur_stroke);
+                  cur_stroke = NULL;
+                }
+            }
+        }
+
+      g_list_free (anchors);
+    }
+
+  if (have_undo)
+    gimp_tool_path_end_change (path, TRUE);
+
+  gimp_vectors_thaw (private->vectors);
+}
+
+
+/*  public functions  */
+
+GimpToolWidget *
+gimp_tool_path_new (GimpDisplayShell *shell)
+{
+  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+  return g_object_new (GIMP_TYPE_TOOL_PATH,
+                       "shell", shell,
+                       NULL);
+}
+
+void
+gimp_tool_path_set_vectors (GimpToolPath *path,
+                            GimpVectors  *vectors)
+{
+  GimpToolPathPrivate *private;
+
+  g_return_if_fail (GIMP_IS_TOOL_PATH (path));
+  g_return_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors));
+
+  private = path->private;
+
+  if (vectors == private->vectors)
+    return;
+
+  if (private->vectors)
+    {
+      g_signal_handlers_disconnect_by_func (private->vectors,
+                                            gimp_tool_path_vectors_visible,
+                                            path);
+      g_signal_handlers_disconnect_by_func (private->vectors,
+                                            gimp_tool_path_vectors_freeze,
+                                            path);
+      g_signal_handlers_disconnect_by_func (private->vectors,
+                                            gimp_tool_path_vectors_thaw,
+                                            path);
+
+      g_object_unref (private->vectors);
+    }
+
+  private->vectors  = vectors;
+  private->function = VECTORS_FINISHED;
+  gimp_tool_path_verify_state (path);
+
+  if (private->vectors)
+    {
+      g_object_ref (private->vectors);
+
+      g_signal_connect_object (private->vectors, "visibility-changed",
+                               G_CALLBACK (gimp_tool_path_vectors_visible),
+                               path, 0);
+      g_signal_connect_object (private->vectors, "freeze",
+                               G_CALLBACK (gimp_tool_path_vectors_freeze),
+                               path, 0);
+      g_signal_connect_object (private->vectors, "thaw",
+                               G_CALLBACK (gimp_tool_path_vectors_thaw),
+                               path, 0);
+    }
+
+  g_object_notify (G_OBJECT (path), "vectors");
+}
diff --git a/app/display/gimptoolpath.h b/app/display/gimptoolpath.h
new file mode 100644
index 0000000..efb154e
--- /dev/null
+++ b/app/display/gimptoolpath.h
@@ -0,0 +1,68 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpath.h
+ * Copyright (C) 2017 Michael Natterer <mitch gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_PATH_H__
+#define __GIMP_TOOL_PATH_H__
+
+
+#include "gimptoolwidget.h"
+
+
+#define GIMP_TYPE_TOOL_PATH            (gimp_tool_path_get_type ())
+#define GIMP_TOOL_PATH(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_PATH, 
GimpToolPath))
+#define GIMP_TOOL_PATH_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_PATH, 
GimpToolPathClass))
+#define GIMP_IS_TOOL_PATH(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_PATH))
+#define GIMP_IS_TOOL_PATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_PATH))
+#define GIMP_TOOL_PATH_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_PATH, 
GimpToolPathClass))
+
+
+typedef struct _GimpToolPath        GimpToolPath;
+typedef struct _GimpToolPathPrivate GimpToolPathPrivate;
+typedef struct _GimpToolPathClass   GimpToolPathClass;
+
+struct _GimpToolPath
+{
+  GimpToolWidget       parent_instance;
+
+  GimpToolPathPrivate *private;
+};
+
+struct _GimpToolPathClass
+{
+  GimpToolWidgetClass  parent_class;
+
+  void (* begin_change) (GimpToolPath    *path,
+                         const gchar     *desc);
+  void (* end_change)   (GimpToolPath    *path,
+                         gboolean         success);
+  void (* activate)     (GimpToolPath    *path,
+                         GdkModifierType  state);
+};
+
+
+GType            gimp_tool_path_get_type    (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_path_new         (GimpDisplayShell *shell);
+
+void             gimp_tool_path_set_vectors (GimpToolPath     *path,
+                                             GimpVectors      *vectors);
+
+
+#endif /* __GIMP_TOOL_PATH_H__ */
diff --git a/app/tools/tools-enums.c b/app/tools/tools-enums.c
index 261d78a..497a775 100644
--- a/app/tools/tools-enums.c
+++ b/app/tools/tools-enums.c
@@ -165,37 +165,6 @@ gimp_transform_type_get_type (void)
 }
 
 GType
-gimp_vector_mode_get_type (void)
-{
-  static const GEnumValue values[] =
-  {
-    { GIMP_VECTOR_MODE_DESIGN, "GIMP_VECTOR_MODE_DESIGN", "design" },
-    { GIMP_VECTOR_MODE_EDIT, "GIMP_VECTOR_MODE_EDIT", "edit" },
-    { GIMP_VECTOR_MODE_MOVE, "GIMP_VECTOR_MODE_MOVE", "move" },
-    { 0, NULL, NULL }
-  };
-
-  static const GimpEnumDesc descs[] =
-  {
-    { GIMP_VECTOR_MODE_DESIGN, NC_("vector-mode", "Design"), NULL },
-    { GIMP_VECTOR_MODE_EDIT, NC_("vector-mode", "Edit"), NULL },
-    { GIMP_VECTOR_MODE_MOVE, NC_("vector-mode", "Move"), NULL },
-    { 0, NULL, NULL }
-  };
-
-  static GType type = 0;
-
-  if (G_UNLIKELY (! type))
-    {
-      type = g_enum_register_static ("GimpVectorMode", values);
-      gimp_type_set_translation_context (type, "vector-mode");
-      gimp_enum_set_value_descriptions (type, descs);
-    }
-
-  return type;
-}
-
-GType
 gimp_tool_action_get_type (void)
 {
   static const GEnumValue values[] =
diff --git a/app/tools/tools-enums.h b/app/tools/tools-enums.h
index 43d3882..def8967 100644
--- a/app/tools/tools-enums.h
+++ b/app/tools/tools-enums.h
@@ -83,18 +83,6 @@ typedef enum
 } GimpTransformType;
 
 
-#define GIMP_TYPE_VECTOR_MODE (gimp_vector_mode_get_type ())
-
-GType gimp_vector_mode_get_type (void) G_GNUC_CONST;
-
-typedef enum
-{
-  GIMP_VECTOR_MODE_DESIGN,      /*< desc="Design" >*/
-  GIMP_VECTOR_MODE_EDIT,        /*< desc="Edit"   >*/
-  GIMP_VECTOR_MODE_MOVE         /*< desc="Move"   >*/
-} GimpVectorMode;
-
-
 #define GIMP_TYPE_TOOL_ACTION (gimp_tool_action_get_type ())
 
 GType gimp_tool_action_get_type (void) G_GNUC_CONST;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b4d0d53..d739849 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -256,6 +256,7 @@ app/display/gimpnavigationeditor.c
 app/display/gimpstatusbar.c
 app/display/gimptoolcompass.c
 app/display/gimptoolhandlegrid.c
+app/display/gimptoolpath.c
 app/display/gimptooltransformgrid.c
 
 app/file/file-open.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]