diff options
Diffstat (limited to '')
-rw-r--r-- | app/display/gimptoolpath.c | 1904 |
1 files changed, 1904 insertions, 0 deletions
diff --git a/app/display/gimptoolpath.c b/app/display/gimptoolpath.c new file mode 100644 index 0000000..9e2cf54 --- /dev/null +++ b/app/display/gimptoolpath.c @@ -0,0 +1,1904 @@ +/* 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 <https://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 "tools/gimptools-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 GimpHit gimp_tool_path_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +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 *modifier); + +static GimpVectorFunction + gimp_tool_path_get_function (GimpToolPath *path, + const GimpCoords *coords, + GdkModifierType state); + +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_WITH_PRIVATE (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->focus_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->hit = gimp_tool_path_hit; + 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)); +} + +static void +gimp_tool_path_init (GimpToolPath *path) +{ + path->private = gimp_tool_path_get_instance_private (path); +} + +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); + + if (gimp_tool_widget_get_focus (widget)) + 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; + GimpToolWidget *widget = GIMP_TOOL_WIDGET (path); + GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); + + if (gimp_item_is_content_locked (GIMP_ITEM (private->vectors)) || + gimp_item_is_position_locked (GIMP_ITEM (private->vectors))) + { + gimp_tool_widget_message_literal (GIMP_TOOL_WIDGET (path), + _("The active path is locked.")); + + /* FIXME: this should really be done by the tool */ + gimp_tools_blink_lock_box (shell->display->gimp, + GIMP_ITEM (private->vectors)); + + 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; +} + +GimpHit +gimp_tool_path_hit (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + + switch (gimp_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 GIMP_HIT_DIRECT; + + case VECTORS_CREATE_VECTOR: + case VECTORS_CREATE_STROKE: + case VECTORS_ADD_ANCHOR: + case VECTORS_MOVE_VECTORS: + return GIMP_HIT_INDIRECT; + + case VECTORS_FINISHED: + return GIMP_HIT_NONE; + } + + return GIMP_HIT_NONE; +} + +void +gimp_tool_path_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + private->function = gimp_tool_path_get_function (path, coords, state); + + 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 *modifier) +{ + GimpToolPath *path = GIMP_TOOL_PATH (widget); + GimpToolPathPrivate *private = path->private; + + *tool_cursor = GIMP_TOOL_CURSOR_PATHS; + *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: + *modifier = GIMP_CURSOR_MODIFIER_CONTROL; + break; + + case VECTORS_ADD_ANCHOR: + case VECTORS_INSERT_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_PLUS; + break; + + case VECTORS_DELETE_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_DELETE_SEGMENT: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_MOVE_HANDLE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_CONVERT_EDGE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_CONTROL; + *modifier = GIMP_CURSOR_MODIFIER_MINUS; + break; + + case VECTORS_MOVE_ANCHOR: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_CURVE: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_STROKE: + case VECTORS_MOVE_VECTORS: + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_MOVE_ANCHORSET: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_ANCHOR; + *modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case VECTORS_CONNECT_STROKES: + *tool_cursor = GIMP_TOOL_CURSOR_PATHS_SEGMENT; + *modifier = GIMP_CURSOR_MODIFIER_JOIN; + break; + + default: + *modifier = GIMP_CURSOR_MODIFIER_BAD; + break; + } + + return TRUE; +} + +static GimpVectorFunction +gimp_tool_path_get_function (GimpToolPath *path, + const GimpCoords *coords, + GdkModifierType state) +{ + 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; + GimpVectorFunction function = VECTORS_FINISHED; + + 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) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + 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) + { + 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 = GIMP_ANCHOR_FEATURE_SYMMETRIC; + else + private->restriction = GIMP_ANCHOR_FEATURE_NONE; + } + } + else if (on_curve) + { + if (gimp_stroke_point_is_movable (stroke, anchor, position)) + { + function = VECTORS_MOVE_CURVE; + + if (state & TOGGLE_MASK) + private->restriction = GIMP_ANCHOR_FEATURE_SYMMETRIC; + else + private->restriction = GIMP_ANCHOR_FEATURE_NONE; + } + else + { + function = VECTORS_FINISHED; + } + } + else + { + if (private->sel_stroke && + private->sel_anchor && + gimp_stroke_is_extendable (private->sel_stroke, + private->sel_anchor) && + ! (state & TOGGLE_MASK)) + function = VECTORS_ADD_ANCHOR; + else + 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) + { + function = VECTORS_SELECT_VECTOR; + } + else + { + 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)) + { + 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 (gimp_stroke_anchor_is_insertable (stroke, anchor, position)) + { + function = VECTORS_INSERT_ANCHOR; + } + else + { + function = VECTORS_FINISHED; + } + } + else + { + function = VECTORS_FINISHED; + } + + break; + + case GIMP_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 +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_set_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_set_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"); +} |