gimp/app/display/gimptoolpath.c

1840 lines
58 KiB
C

/* 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 *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_set_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 *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 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");
}