/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikatoolpath.c * Copyright (C) 2017 Michael Natterer * * Vector tool * Copyright (C) 2003 Simon Budig * * 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 . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "display-types.h" #include "core/pikacontext.h" #include "menus/menus.h" #include "vectors/pikaanchor.h" #include "vectors/pikabezierstroke.h" #include "vectors/pikavectors.h" #include "widgets/pikadialogfactory.h" #include "widgets/pikadockcontainer.h" #include "widgets/pikamenufactory.h" #include "widgets/pikauimanager.h" #include "widgets/pikawidgets-utils.h" #include "tools/pikatools-utils.h" #include "pikacanvashandle.h" #include "pikacanvasitem-utils.h" #include "pikacanvasline.h" #include "pikacanvaspath.h" #include "pikadisplay.h" #include "pikadisplayshell.h" #include "pikatoolpath.h" #include "pika-intl.h" #define TOGGLE_MASK pika_get_extend_selection_mask () #define MOVE_MASK GDK_MOD1_MASK #define INSDEL_MASK pika_get_toggle_behavior_mask () /* possible vector functions */ typedef enum { VECTORS_SELECT_VECTOR, VECTORS_CREATE_VECTOR, VECTORS_CREATE_STROKE, VECTORS_ADD_ANCHOR, VECTORS_MOVE_ANCHOR, VECTORS_MOVE_ANCHORSET, VECTORS_MOVE_HANDLE, VECTORS_MOVE_CURVE, VECTORS_MOVE_STROKE, VECTORS_MOVE_VECTORS, VECTORS_INSERT_ANCHOR, VECTORS_DELETE_ANCHOR, VECTORS_CONNECT_STROKES, VECTORS_DELETE_SEGMENT, VECTORS_CONVERT_EDGE, VECTORS_FINISHED } PikaVectorFunction; enum { PROP_0, PROP_VECTORS, PROP_EDIT_MODE, PROP_POLYGONAL }; enum { BEGIN_CHANGE, END_CHANGE, ACTIVATE, LAST_SIGNAL }; struct _PikaToolPathPrivate { PikaVectors *vectors; /* the current Vector data */ PikaVectorMode edit_mode; gboolean polygonal; PikaVectorFunction function; /* function we're performing */ PikaAnchorFeatureType restriction; /* movement restriction */ gboolean modifier_lock; /* can we toggle the Shift key? */ GdkModifierType saved_state; /* modifier state at button_press */ gdouble last_x; /* last x coordinate */ gdouble last_y; /* last y coordinate */ gboolean undo_motion; /* we need a motion to have an undo */ gboolean have_undo; /* did we push an undo at */ /* ..._button_press? */ PikaAnchor *cur_anchor; /* the current Anchor */ PikaAnchor *cur_anchor2; /* secondary Anchor (end on_curve) */ PikaStroke *cur_stroke; /* the current Stroke */ gdouble cur_position; /* the current Position on a segment */ gint sel_count; /* number of selected anchors */ PikaAnchor *sel_anchor; /* currently selected anchor, NULL */ /* if multiple anchors are selected */ PikaStroke *sel_stroke; /* selected stroke */ PikaVectorMode saved_mode; /* used by modifier_key() */ PikaCanvasItem *path; GList *items; }; /* local function prototypes */ static void pika_tool_path_constructed (GObject *object); static void pika_tool_path_dispose (GObject *object); static void pika_tool_path_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_tool_path_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_tool_path_changed (PikaToolWidget *widget); static gint pika_tool_path_button_press (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type); static void pika_tool_path_button_release (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type); static void pika_tool_path_motion (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state); static PikaHit pika_tool_path_hit (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity); static void pika_tool_path_hover (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity); static gboolean pika_tool_path_key_press (PikaToolWidget *widget, GdkEventKey *kevent); static gboolean pika_tool_path_get_cursor (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, PikaCursorType *cursor, PikaToolCursorType *tool_cursor, PikaCursorModifier *modifier); static PikaUIManager * pika_tool_path_get_popup (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, const gchar **ui_path); static PikaVectorFunction pika_tool_path_get_function (PikaToolPath *path, const PikaCoords *coords, GdkModifierType state); static void pika_tool_path_update_status (PikaToolPath *path, GdkModifierType state, gboolean proximity); static void pika_tool_path_begin_change (PikaToolPath *path, const gchar *desc); static void pika_tool_path_end_change (PikaToolPath *path, gboolean success); static void pika_tool_path_vectors_visible (PikaVectors *vectors, PikaToolPath *path); static void pika_tool_path_vectors_freeze (PikaVectors *vectors, PikaToolPath *path); static void pika_tool_path_vectors_thaw (PikaVectors *vectors, PikaToolPath *path); static void pika_tool_path_verify_state (PikaToolPath *path); static void pika_tool_path_move_selected_anchors (PikaToolPath *path, gdouble x, gdouble y); static void pika_tool_path_delete_selected_anchors (PikaToolPath *path); G_DEFINE_TYPE_WITH_PRIVATE (PikaToolPath, pika_tool_path, PIKA_TYPE_TOOL_WIDGET) #define parent_class pika_tool_path_parent_class static guint path_signals[LAST_SIGNAL] = { 0 }; static void pika_tool_path_class_init (PikaToolPathClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass); object_class->constructed = pika_tool_path_constructed; object_class->dispose = pika_tool_path_dispose; object_class->set_property = pika_tool_path_set_property; object_class->get_property = pika_tool_path_get_property; widget_class->changed = pika_tool_path_changed; widget_class->focus_changed = pika_tool_path_changed; widget_class->button_press = pika_tool_path_button_press; widget_class->button_release = pika_tool_path_button_release; widget_class->motion = pika_tool_path_motion; widget_class->hit = pika_tool_path_hit; widget_class->hover = pika_tool_path_hover; widget_class->key_press = pika_tool_path_key_press; widget_class->get_cursor = pika_tool_path_get_cursor; widget_class->get_popup = pika_tool_path_get_popup; path_signals[BEGIN_CHANGE] = g_signal_new ("begin-change", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaToolPathClass, begin_change), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING); path_signals[END_CHANGE] = g_signal_new ("end-change", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaToolPathClass, end_change), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); path_signals[ACTIVATE] = g_signal_new ("activate", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaToolPathClass, activate), NULL, NULL, NULL, G_TYPE_NONE, 1, GDK_TYPE_MODIFIER_TYPE); g_object_class_install_property (object_class, PROP_VECTORS, g_param_spec_object ("vectors", NULL, NULL, PIKA_TYPE_VECTORS, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_EDIT_MODE, g_param_spec_enum ("edit-mode", _("Edit Mode"), NULL, PIKA_TYPE_VECTOR_MODE, PIKA_VECTOR_MODE_DESIGN, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_POLYGONAL, g_param_spec_boolean ("polygonal", _("Polygonal"), _("Restrict editing to polygons"), FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void pika_tool_path_init (PikaToolPath *path) { path->private = pika_tool_path_get_instance_private (path); } static void pika_tool_path_constructed (GObject *object) { PikaToolPath *path = PIKA_TOOL_PATH (object); PikaToolWidget *widget = PIKA_TOOL_WIDGET (object); PikaToolPathPrivate *private = path->private; G_OBJECT_CLASS (parent_class)->constructed (object); private->path = pika_tool_widget_add_path (widget, NULL); pika_tool_path_changed (widget); } static void pika_tool_path_dispose (GObject *object) { PikaToolPath *path = PIKA_TOOL_PATH (object); pika_tool_path_set_vectors (path, NULL); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_tool_path_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaToolPath *path = PIKA_TOOL_PATH (object); PikaToolPathPrivate *private = path->private; switch (property_id) { case PROP_VECTORS: pika_tool_path_set_vectors (path, g_value_get_object (value)); break; case PROP_EDIT_MODE: private->edit_mode = g_value_get_enum (value); break; case PROP_POLYGONAL: private->polygonal = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_tool_path_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaToolPath *path = PIKA_TOOL_PATH (object); PikaToolPathPrivate *private = path->private; switch (property_id) { case PROP_VECTORS: g_value_set_object (value, private->vectors); break; case PROP_EDIT_MODE: g_value_set_enum (value, private->edit_mode); break; case PROP_POLYGONAL: g_value_set_boolean (value, private->polygonal); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void item_remove_func (PikaCanvasItem *item, PikaToolWidget *widget) { pika_tool_widget_remove_item (widget, item); } static void pika_tool_path_changed (PikaToolWidget *widget) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; PikaVectors *vectors = private->vectors; if (private->items) { g_list_foreach (private->items, (GFunc) item_remove_func, widget); g_list_free (private->items); private->items = NULL; } if (vectors && pika_vectors_get_bezier (vectors)) { PikaStroke *cur_stroke; pika_canvas_path_set (private->path, pika_vectors_get_bezier (vectors)); pika_canvas_item_set_visible (private->path, ! pika_item_get_visible (PIKA_ITEM (vectors))); for (cur_stroke = pika_vectors_stroke_get_next (vectors, NULL); cur_stroke; cur_stroke = pika_vectors_stroke_get_next (vectors, cur_stroke)) { PikaCanvasItem *item; GArray *coords; GList *draw_anchors; GList *list; gboolean first = TRUE; /* anchor handles */ draw_anchors = pika_stroke_get_draw_anchors (cur_stroke); for (list = draw_anchors; list; list = g_list_next (list)) { PikaAnchor *cur_anchor = PIKA_ANCHOR (list->data); if (cur_anchor->type == PIKA_ANCHOR_ANCHOR) { item = pika_tool_widget_add_handle (widget, cur_anchor->selected ? PIKA_HANDLE_CIRCLE : PIKA_HANDLE_FILLED_CIRCLE, cur_anchor->position.x, cur_anchor->position.y, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_HANDLE_ANCHOR_CENTER); if (first) { gdouble angle = 0.0; PikaAnchor *next; for (next = pika_stroke_anchor_get_next (cur_stroke, cur_anchor); next; next = pika_stroke_anchor_get_next (cur_stroke, next)) { if (((next->position.x - cur_anchor->position.x) * (next->position.x - cur_anchor->position.x) + (next->position.y - cur_anchor->position.y) * (next->position.y - cur_anchor->position.y)) >= 0.1) break; } if (next) { angle = atan2 (next->position.y - cur_anchor->position.y, next->position.x - cur_anchor->position.x); g_object_set (item, "type", (cur_anchor->selected ? PIKA_HANDLE_DROP : PIKA_HANDLE_FILLED_DROP), "start-angle", angle, NULL); } } private->items = g_list_prepend (private->items, item); first = FALSE; } } g_list_free (draw_anchors); if (private->sel_count <= 2) { /* the lines to the control handles */ coords = pika_stroke_get_draw_lines (cur_stroke); if (coords) { if (coords->len % 2 == 0) { gint i; for (i = 0; i < coords->len; i += 2) { item = pika_tool_widget_add_line (widget, g_array_index (coords, PikaCoords, i).x, g_array_index (coords, PikaCoords, i).y, g_array_index (coords, PikaCoords, i + 1).x, g_array_index (coords, PikaCoords, i + 1).y); if (pika_tool_widget_get_focus (widget)) pika_canvas_item_set_highlight (item, TRUE); private->items = g_list_prepend (private->items, item); } } g_array_free (coords, TRUE); } /* control handles */ draw_anchors = pika_stroke_get_draw_controls (cur_stroke); for (list = draw_anchors; list; list = g_list_next (list)) { PikaAnchor *cur_anchor = PIKA_ANCHOR (list->data); item = pika_tool_widget_add_handle (widget, PIKA_HANDLE_SQUARE, cur_anchor->position.x, cur_anchor->position.y, PIKA_CANVAS_HANDLE_SIZE_CIRCLE - 3, PIKA_CANVAS_HANDLE_SIZE_CIRCLE - 3, PIKA_HANDLE_ANCHOR_CENTER); private->items = g_list_prepend (private->items, item); } g_list_free (draw_anchors); } } } else { pika_canvas_path_set (private->path, NULL); } } static gboolean pika_tool_path_check_writable (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; PikaToolWidget *widget = PIKA_TOOL_WIDGET (path); PikaDisplayShell *shell = pika_tool_widget_get_shell (widget); PikaItem *locked_item = NULL; if (pika_item_is_content_locked (PIKA_ITEM (private->vectors), &locked_item) || pika_item_is_position_locked (PIKA_ITEM (private->vectors), &locked_item)) { pika_tool_widget_message_literal (PIKA_TOOL_WIDGET (path), _("The selected path is locked.")); if (locked_item == NULL) locked_item = PIKA_ITEM (private->vectors); /* FIXME: this should really be done by the tool */ pika_tools_blink_lock_box (shell->display->pika, locked_item); private->function = VECTORS_FINISHED; return FALSE; } return TRUE; } gboolean pika_tool_path_button_press (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; /* do nothing if we are in a FINISHED state */ if (private->function == VECTORS_FINISHED) return 0; g_return_val_if_fail (private->vectors != NULL || private->function == VECTORS_SELECT_VECTOR || private->function == VECTORS_CREATE_VECTOR, 0); private->undo_motion = FALSE; /* save the current modifier state */ private->saved_state = state; /* select a vectors object */ if (private->function == VECTORS_SELECT_VECTOR) { PikaVectors *vectors; if (pika_canvas_item_on_vectors (private->path, coords, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, NULL, NULL, NULL, NULL, NULL, &vectors)) { pika_tool_path_set_vectors (path, vectors); } private->function = VECTORS_FINISHED; } /* create a new vector from scratch */ if (private->function == VECTORS_CREATE_VECTOR) { PikaDisplayShell *shell = pika_tool_widget_get_shell (widget); PikaImage *image = pika_display_get_image (shell->display); PikaVectors *vectors; vectors = pika_vectors_new (image, _("Unnamed")); g_object_ref_sink (vectors); /* Undo step gets added implicitly */ private->have_undo = TRUE; private->undo_motion = TRUE; pika_tool_path_set_vectors (path, vectors); g_object_unref (vectors); private->function = VECTORS_CREATE_STROKE; } pika_vectors_freeze (private->vectors); /* create a new stroke */ if (private->function == VECTORS_CREATE_STROKE && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Add Stroke")); private->undo_motion = TRUE; private->cur_stroke = pika_bezier_stroke_new (); pika_vectors_stroke_add (private->vectors, private->cur_stroke); g_object_unref (private->cur_stroke); private->sel_stroke = private->cur_stroke; private->cur_anchor = NULL; private->sel_anchor = NULL; private->function = VECTORS_ADD_ANCHOR; } /* add an anchor to an existing stroke */ if (private->function == VECTORS_ADD_ANCHOR && pika_tool_path_check_writable (path)) { PikaCoords position = PIKA_COORDS_DEFAULT_VALUES; position.x = coords->x; position.y = coords->y; pika_tool_path_begin_change (path, _("Add Anchor")); private->undo_motion = TRUE; private->cur_anchor = pika_bezier_stroke_extend (private->sel_stroke, &position, private->sel_anchor, EXTEND_EDITABLE); private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; if (! private->polygonal) private->function = VECTORS_MOVE_HANDLE; else private->function = VECTORS_MOVE_ANCHOR; private->cur_stroke = private->sel_stroke; } /* insertion of an anchor in a curve segment */ if (private->function == VECTORS_INSERT_ANCHOR && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Insert Anchor")); private->undo_motion = TRUE; private->cur_anchor = pika_stroke_anchor_insert (private->cur_stroke, private->cur_anchor, private->cur_position); if (private->cur_anchor) { if (private->polygonal) { pika_stroke_anchor_convert (private->cur_stroke, private->cur_anchor, PIKA_ANCHOR_FEATURE_EDGE); } private->function = VECTORS_MOVE_ANCHOR; } else { private->function = VECTORS_FINISHED; } } /* move a handle */ if (private->function == VECTORS_MOVE_HANDLE && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Drag Handle")); if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR) { if (! private->cur_anchor->selected) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor, TRUE, TRUE); private->undo_motion = TRUE; } pika_canvas_item_on_vectors_handle (private->path, private->vectors, coords, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_ANCHOR_CONTROL, TRUE, &private->cur_anchor, &private->cur_stroke); if (! private->cur_anchor) private->function = VECTORS_FINISHED; } } /* move an anchor */ if (private->function == VECTORS_MOVE_ANCHOR && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Drag Anchor")); if (! private->cur_anchor->selected) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor, TRUE, TRUE); private->undo_motion = TRUE; } } /* move multiple anchors */ if (private->function == VECTORS_MOVE_ANCHORSET && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Drag Anchors")); if (state & TOGGLE_MASK) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor, !private->cur_anchor->selected, FALSE); private->undo_motion = TRUE; if (private->cur_anchor->selected == FALSE) private->function = VECTORS_FINISHED; } } /* move a curve segment directly */ if (private->function == VECTORS_MOVE_CURVE && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Drag Curve")); /* the magic numbers are taken from the "feel good" parameter * from pika_bezier_stroke_point_move_relative in pikabezierstroke.c. */ if (private->cur_position < 5.0 / 6.0) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor, TRUE, TRUE); private->undo_motion = TRUE; } if (private->cur_position > 1.0 / 6.0) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor2, TRUE, (private->cur_position >= 5.0 / 6.0)); private->undo_motion = TRUE; } } /* connect two strokes */ if (private->function == VECTORS_CONNECT_STROKES && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Connect Strokes")); private->undo_motion = TRUE; pika_stroke_connect_stroke (private->sel_stroke, private->sel_anchor, private->cur_stroke, private->cur_anchor); if (private->cur_stroke != private->sel_stroke && pika_stroke_is_empty (private->cur_stroke)) { pika_vectors_stroke_remove (private->vectors, private->cur_stroke); } private->sel_anchor = private->cur_anchor; private->cur_stroke = private->sel_stroke; pika_vectors_anchor_select (private->vectors, private->sel_stroke, private->sel_anchor, TRUE, TRUE); private->function = VECTORS_FINISHED; } /* move a stroke or all strokes of a vectors object */ if ((private->function == VECTORS_MOVE_STROKE || private->function == VECTORS_MOVE_VECTORS) && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Drag Path")); /* Work is being done in pika_tool_path_motion()... */ } /* convert an anchor to something that looks like an edge */ if (private->function == VECTORS_CONVERT_EDGE && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Convert Edge")); private->undo_motion = TRUE; pika_stroke_anchor_convert (private->cur_stroke, private->cur_anchor, PIKA_ANCHOR_FEATURE_EDGE); if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR) { pika_vectors_anchor_select (private->vectors, private->cur_stroke, private->cur_anchor, TRUE, TRUE); private->function = VECTORS_MOVE_ANCHOR; } else { private->cur_stroke = NULL; private->cur_anchor = NULL; /* avoid doing anything stupid */ private->function = VECTORS_FINISHED; } } /* removal of a node in a stroke */ if (private->function == VECTORS_DELETE_ANCHOR && pika_tool_path_check_writable (path)) { pika_tool_path_begin_change (path, _("Delete Anchor")); private->undo_motion = TRUE; pika_stroke_anchor_delete (private->cur_stroke, private->cur_anchor); if (pika_stroke_is_empty (private->cur_stroke)) pika_vectors_stroke_remove (private->vectors, private->cur_stroke); private->cur_stroke = NULL; private->cur_anchor = NULL; private->function = VECTORS_FINISHED; } /* deleting a segment (opening up a stroke) */ if (private->function == VECTORS_DELETE_SEGMENT && pika_tool_path_check_writable (path)) { PikaStroke *new_stroke; pika_tool_path_begin_change (path, _("Delete Segment")); private->undo_motion = TRUE; new_stroke = pika_stroke_open (private->cur_stroke, private->cur_anchor); if (new_stroke) { pika_vectors_stroke_add (private->vectors, new_stroke); g_object_unref (new_stroke); } private->cur_stroke = NULL; private->cur_anchor = NULL; private->function = VECTORS_FINISHED; } private->last_x = coords->x; private->last_y = coords->y; pika_vectors_thaw (private->vectors); return 1; } void pika_tool_path_button_release (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; private->function = VECTORS_FINISHED; if (private->have_undo) { if (! private->undo_motion || release_type == PIKA_BUTTON_RELEASE_CANCEL) { pika_tool_path_end_change (path, FALSE); } else { pika_tool_path_end_change (path, TRUE); } } } void pika_tool_path_motion (PikaToolWidget *widget, const PikaCoords *coords, guint32 time, GdkModifierType state) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; PikaCoords position = PIKA_COORDS_DEFAULT_VALUES; PikaAnchor *anchor; if (private->function == VECTORS_FINISHED) return; position.x = coords->x; position.y = coords->y; pika_vectors_freeze (private->vectors); if ((private->saved_state & TOGGLE_MASK) != (state & TOGGLE_MASK)) private->modifier_lock = FALSE; if (! private->modifier_lock) { if (state & TOGGLE_MASK) { private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; } else { private->restriction = PIKA_ANCHOR_FEATURE_NONE; } } switch (private->function) { case VECTORS_MOVE_ANCHOR: case VECTORS_MOVE_HANDLE: anchor = private->cur_anchor; if (anchor) { pika_stroke_anchor_move_absolute (private->cur_stroke, private->cur_anchor, &position, private->restriction); private->undo_motion = TRUE; } break; case VECTORS_MOVE_CURVE: if (private->polygonal) { pika_tool_path_move_selected_anchors (path, coords->x - private->last_x, coords->y - private->last_y); private->undo_motion = TRUE; } else { pika_stroke_point_move_absolute (private->cur_stroke, private->cur_anchor, private->cur_position, &position, private->restriction); private->undo_motion = TRUE; } break; case VECTORS_MOVE_ANCHORSET: pika_tool_path_move_selected_anchors (path, coords->x - private->last_x, coords->y - private->last_y); private->undo_motion = TRUE; break; case VECTORS_MOVE_STROKE: if (private->cur_stroke) { pika_stroke_translate (private->cur_stroke, coords->x - private->last_x, coords->y - private->last_y); private->undo_motion = TRUE; } else if (private->sel_stroke) { pika_stroke_translate (private->sel_stroke, coords->x - private->last_x, coords->y - private->last_y); private->undo_motion = TRUE; } break; case VECTORS_MOVE_VECTORS: pika_item_translate (PIKA_ITEM (private->vectors), coords->x - private->last_x, coords->y - private->last_y, FALSE); private->undo_motion = TRUE; break; default: break; } pika_vectors_thaw (private->vectors); private->last_x = coords->x; private->last_y = coords->y; } PikaHit pika_tool_path_hit (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity) { PikaToolPath *path = PIKA_TOOL_PATH (widget); switch (pika_tool_path_get_function (path, coords, state)) { case VECTORS_SELECT_VECTOR: case VECTORS_MOVE_ANCHOR: case VECTORS_MOVE_ANCHORSET: case VECTORS_MOVE_HANDLE: case VECTORS_MOVE_CURVE: case VECTORS_MOVE_STROKE: case VECTORS_DELETE_ANCHOR: case VECTORS_DELETE_SEGMENT: case VECTORS_INSERT_ANCHOR: case VECTORS_CONNECT_STROKES: case VECTORS_CONVERT_EDGE: return PIKA_HIT_DIRECT; case VECTORS_CREATE_VECTOR: case VECTORS_CREATE_STROKE: case VECTORS_ADD_ANCHOR: case VECTORS_MOVE_VECTORS: return PIKA_HIT_INDIRECT; case VECTORS_FINISHED: return PIKA_HIT_NONE; } return PIKA_HIT_NONE; } void pika_tool_path_hover (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, gboolean proximity) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; private->function = pika_tool_path_get_function (path, coords, state); pika_tool_path_update_status (path, state, proximity); } static gboolean pika_tool_path_key_press (PikaToolWidget *widget, GdkEventKey *kevent) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; PikaDisplayShell *shell; gdouble xdist, ydist; gdouble pixels = 1.0; if (! private->vectors) return FALSE; shell = pika_tool_widget_get_shell (widget); if (kevent->state & pika_get_extend_selection_mask ()) pixels = 10.0; if (kevent->state & pika_get_toggle_behavior_mask ()) pixels = 50.0; switch (kevent->keyval) { case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_ISO_Enter: g_signal_emit (path, path_signals[ACTIVATE], 0, kevent->state); break; case GDK_KEY_BackSpace: case GDK_KEY_Delete: pika_tool_path_delete_selected_anchors (path); break; case GDK_KEY_Left: case GDK_KEY_Right: case GDK_KEY_Up: case GDK_KEY_Down: xdist = FUNSCALEX (shell, pixels); ydist = FUNSCALEY (shell, pixels); pika_tool_path_begin_change (path, _("Move Anchors")); pika_vectors_freeze (private->vectors); switch (kevent->keyval) { case GDK_KEY_Left: pika_tool_path_move_selected_anchors (path, -xdist, 0); break; case GDK_KEY_Right: pika_tool_path_move_selected_anchors (path, xdist, 0); break; case GDK_KEY_Up: pika_tool_path_move_selected_anchors (path, 0, -ydist); break; case GDK_KEY_Down: pika_tool_path_move_selected_anchors (path, 0, ydist); break; default: break; } pika_vectors_thaw (private->vectors); pika_tool_path_end_change (path, TRUE); break; case GDK_KEY_Escape: if (private->edit_mode != PIKA_VECTOR_MODE_DESIGN) g_object_set (private, "vectors-edit-mode", PIKA_VECTOR_MODE_DESIGN, NULL); break; default: return FALSE; } return TRUE; } static gboolean pika_tool_path_get_cursor (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, PikaCursorType *cursor, PikaToolCursorType *tool_cursor, PikaCursorModifier *modifier) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; *tool_cursor = PIKA_TOOL_CURSOR_PATHS; *modifier = PIKA_CURSOR_MODIFIER_NONE; switch (private->function) { case VECTORS_SELECT_VECTOR: *tool_cursor = PIKA_TOOL_CURSOR_HAND; break; case VECTORS_CREATE_VECTOR: case VECTORS_CREATE_STROKE: *modifier = PIKA_CURSOR_MODIFIER_CONTROL; break; case VECTORS_ADD_ANCHOR: case VECTORS_INSERT_ANCHOR: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR; *modifier = PIKA_CURSOR_MODIFIER_PLUS; break; case VECTORS_DELETE_ANCHOR: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR; *modifier = PIKA_CURSOR_MODIFIER_MINUS; break; case VECTORS_DELETE_SEGMENT: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT; *modifier = PIKA_CURSOR_MODIFIER_MINUS; break; case VECTORS_MOVE_HANDLE: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_CONTROL; *modifier = PIKA_CURSOR_MODIFIER_MOVE; break; case VECTORS_CONVERT_EDGE: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_CONTROL; *modifier = PIKA_CURSOR_MODIFIER_MINUS; break; case VECTORS_MOVE_ANCHOR: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR; *modifier = PIKA_CURSOR_MODIFIER_MOVE; break; case VECTORS_MOVE_CURVE: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT; *modifier = PIKA_CURSOR_MODIFIER_MOVE; break; case VECTORS_MOVE_STROKE: case VECTORS_MOVE_VECTORS: *modifier = PIKA_CURSOR_MODIFIER_MOVE; break; case VECTORS_MOVE_ANCHORSET: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_ANCHOR; *modifier = PIKA_CURSOR_MODIFIER_MOVE; break; case VECTORS_CONNECT_STROKES: *tool_cursor = PIKA_TOOL_CURSOR_PATHS_SEGMENT; *modifier = PIKA_CURSOR_MODIFIER_JOIN; break; default: *modifier = PIKA_CURSOR_MODIFIER_BAD; break; } return TRUE; } static PikaUIManager * pika_tool_path_get_popup (PikaToolWidget *widget, const PikaCoords *coords, GdkModifierType state, const gchar **ui_path) { PikaToolPath *path = PIKA_TOOL_PATH (widget); PikaToolPathPrivate *private = path->private; PikaDisplayShell *shell = pika_tool_widget_get_shell (widget); PikaImageWindow *image_window; PikaDialogFactory *dialog_factory; PikaMenuFactory *menu_factory; PikaUIManager *ui_manager; image_window = pika_display_shell_get_window (shell); dialog_factory = pika_dock_container_get_dialog_factory (PIKA_DOCK_CONTAINER (image_window)); menu_factory = menus_get_global_menu_factory (pika_dialog_factory_get_context (dialog_factory)->pika); ui_manager = pika_menu_factory_get_manager (menu_factory, "", widget); /* we're using a side effects of pika_tool_path_get_function * that update the private->cur_* variables. */ pika_tool_path_get_function (path, coords, state); if (private->cur_stroke) { pika_ui_manager_update (ui_manager, widget); *ui_path = "/vector-toolpath-popup"; return ui_manager; } return NULL; } static PikaVectorFunction pika_tool_path_get_function (PikaToolPath *path, const PikaCoords *coords, GdkModifierType state) { PikaToolPathPrivate *private = path->private; PikaAnchor *anchor = NULL; PikaAnchor *anchor2 = NULL; PikaStroke *stroke = NULL; gdouble position = -1; gboolean on_handle = FALSE; gboolean on_curve = FALSE; gboolean on_vectors = FALSE; PikaVectorFunction function = VECTORS_FINISHED; private->modifier_lock = FALSE; /* are we hovering the current vectors on the current display? */ if (private->vectors) { on_handle = pika_canvas_item_on_vectors_handle (private->path, private->vectors, coords, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_ANCHOR_ANCHOR, private->sel_count > 2, &anchor, &stroke); if (! on_handle) on_curve = pika_canvas_item_on_vectors_curve (private->path, private->vectors, coords, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, NULL, &position, &anchor, &anchor2, &stroke); } if (! on_handle && ! on_curve) { on_vectors = pika_canvas_item_on_vectors (private->path, coords, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, PIKA_CANVAS_HANDLE_SIZE_CIRCLE, NULL, NULL, NULL, NULL, NULL, NULL); } private->cur_position = position; private->cur_anchor = anchor; private->cur_anchor2 = anchor2; private->cur_stroke = stroke; switch (private->edit_mode) { case PIKA_VECTOR_MODE_DESIGN: if (! private->vectors) { if (on_vectors) { function = VECTORS_SELECT_VECTOR; } else { function = VECTORS_CREATE_VECTOR; private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; private->modifier_lock = TRUE; } } else if (on_handle) { if (anchor->type == PIKA_ANCHOR_ANCHOR) { if (state & TOGGLE_MASK) { function = VECTORS_MOVE_ANCHORSET; } else { if (private->sel_count >= 2 && anchor->selected) function = VECTORS_MOVE_ANCHORSET; else function = VECTORS_MOVE_ANCHOR; } } else { function = VECTORS_MOVE_HANDLE; if (state & TOGGLE_MASK) private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; else private->restriction = PIKA_ANCHOR_FEATURE_NONE; } } else if (on_curve) { if (pika_stroke_point_is_movable (stroke, anchor, position)) { function = VECTORS_MOVE_CURVE; if (state & TOGGLE_MASK) private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; else private->restriction = PIKA_ANCHOR_FEATURE_NONE; } else { function = VECTORS_FINISHED; } } else { if (private->sel_stroke && private->sel_anchor && pika_stroke_is_extendable (private->sel_stroke, private->sel_anchor) && ! (state & TOGGLE_MASK)) function = VECTORS_ADD_ANCHOR; else function = VECTORS_CREATE_STROKE; private->restriction = PIKA_ANCHOR_FEATURE_SYMMETRIC; private->modifier_lock = TRUE; } break; case PIKA_VECTOR_MODE_EDIT: if (! private->vectors) { if (on_vectors) { function = VECTORS_SELECT_VECTOR; } else { function = VECTORS_FINISHED; } } else if (on_handle) { if (anchor->type == PIKA_ANCHOR_ANCHOR) { if (! (state & TOGGLE_MASK) && private->sel_anchor && private->sel_anchor != anchor && pika_stroke_is_extendable (private->sel_stroke, private->sel_anchor) && pika_stroke_is_extendable (stroke, anchor)) { function = VECTORS_CONNECT_STROKES; } else { if (state & TOGGLE_MASK) { function = VECTORS_DELETE_ANCHOR; } else { if (private->polygonal) function = VECTORS_MOVE_ANCHOR; else function = VECTORS_MOVE_HANDLE; } } } else { if (state & TOGGLE_MASK) function = VECTORS_CONVERT_EDGE; else function = VECTORS_MOVE_HANDLE; } } else if (on_curve) { if (state & TOGGLE_MASK) { function = VECTORS_DELETE_SEGMENT; } else if (pika_stroke_anchor_is_insertable (stroke, anchor, position)) { function = VECTORS_INSERT_ANCHOR; } else { function = VECTORS_FINISHED; } } else { function = VECTORS_FINISHED; } break; case PIKA_VECTOR_MODE_MOVE: if (! private->vectors) { if (on_vectors) { function = VECTORS_SELECT_VECTOR; } else { function = VECTORS_FINISHED; } } else if (on_handle || on_curve) { if (state & TOGGLE_MASK) { function = VECTORS_MOVE_VECTORS; } else { function = VECTORS_MOVE_STROKE; } } else { if (on_vectors) { function = VECTORS_SELECT_VECTOR; } else { function = VECTORS_MOVE_VECTORS; } } break; } return function; } static void pika_tool_path_update_status (PikaToolPath *path, GdkModifierType state, gboolean proximity) { PikaToolPathPrivate *private = path->private; GdkModifierType extend_mask = pika_get_extend_selection_mask (); GdkModifierType toggle_mask = pika_get_toggle_behavior_mask (); const gchar *status = NULL; gboolean free_status = FALSE; if (! proximity) { pika_tool_widget_set_status (PIKA_TOOL_WIDGET (path), NULL); return; } switch (private->function) { case VECTORS_SELECT_VECTOR: status = _("Click to pick path to edit"); break; case VECTORS_CREATE_VECTOR: status = _("Click to create a new path"); break; case VECTORS_CREATE_STROKE: status = _("Click to create a new component of the path"); break; case VECTORS_ADD_ANCHOR: status = pika_suggest_modifiers (_("Click or Click-Drag to create " "a new anchor"), extend_mask & ~state, NULL, NULL, NULL); free_status = TRUE; break; case VECTORS_MOVE_ANCHOR: if (private->edit_mode != PIKA_VECTOR_MODE_EDIT) { status = pika_suggest_modifiers (_("Click-Drag to move the " "anchor around"), toggle_mask & ~state, NULL, NULL, NULL); free_status = TRUE; } else status = _("Click-Drag to move the anchor around"); break; case VECTORS_MOVE_ANCHORSET: status = _("Click-Drag to move the anchors around"); break; case VECTORS_MOVE_HANDLE: if (private->restriction != PIKA_ANCHOR_FEATURE_SYMMETRIC) { status = pika_suggest_modifiers (_("Click-Drag to move the " "handle around"), extend_mask & ~state, NULL, NULL, NULL); } else { status = pika_suggest_modifiers (_("Click-Drag to move the " "handles around symmetrically"), extend_mask & ~state, NULL, NULL, NULL); } free_status = TRUE; break; case VECTORS_MOVE_CURVE: if (private->polygonal) status = pika_suggest_modifiers (_("Click-Drag to move the " "anchors around"), extend_mask & ~state, NULL, NULL, NULL); else status = pika_suggest_modifiers (_("Click-Drag to change the " "shape of the curve"), extend_mask & ~state, _("%s: symmetrical"), NULL, NULL); free_status = TRUE; break; case VECTORS_MOVE_STROKE: status = pika_suggest_modifiers (_("Click-Drag to move the " "component around"), extend_mask & ~state, NULL, NULL, NULL); free_status = TRUE; break; case VECTORS_MOVE_VECTORS: status = _("Click-Drag to move the path around"); break; case VECTORS_INSERT_ANCHOR: status = pika_suggest_modifiers (_("Click-Drag to insert an anchor " "on the path"), extend_mask & ~state, NULL, NULL, NULL); free_status = TRUE; break; case VECTORS_DELETE_ANCHOR: status = _("Click to delete this anchor"); break; case VECTORS_CONNECT_STROKES: status = _("Click to connect this anchor " "with the selected endpoint"); break; case VECTORS_DELETE_SEGMENT: status = _("Click to open up the path"); break; case VECTORS_CONVERT_EDGE: status = _("Click to make this node angular"); break; case VECTORS_FINISHED: status = _("Clicking here does nothing, try clicking on path elements."); break; } pika_tool_widget_set_status (PIKA_TOOL_WIDGET (path), status); if (free_status) g_free ((gchar *) status); } static void pika_tool_path_begin_change (PikaToolPath *path, const gchar *desc) { PikaToolPathPrivate *private = path->private; g_return_if_fail (private->vectors != NULL); /* don't push two undos */ if (private->have_undo) return; g_signal_emit (path, path_signals[BEGIN_CHANGE], 0, desc); private->have_undo = TRUE; } static void pika_tool_path_end_change (PikaToolPath *path, gboolean success) { PikaToolPathPrivate *private = path->private; private->have_undo = FALSE; private->undo_motion = FALSE; g_signal_emit (path, path_signals[END_CHANGE], 0, success); } static void pika_tool_path_vectors_visible (PikaVectors *vectors, PikaToolPath *path) { PikaToolPathPrivate *private = path->private; pika_canvas_item_set_visible (private->path, ! pika_item_get_visible (PIKA_ITEM (vectors))); } static void pika_tool_path_vectors_freeze (PikaVectors *vectors, PikaToolPath *path) { } static void pika_tool_path_vectors_thaw (PikaVectors *vectors, PikaToolPath *path) { /* Ok, the vector might have changed externally (e.g. Undo) we need * to validate our internal state. */ pika_tool_path_verify_state (path); pika_tool_path_changed (PIKA_TOOL_WIDGET (path)); } static void pika_tool_path_verify_state (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; PikaStroke *cur_stroke = NULL; gboolean cur_anchor_valid = FALSE; gboolean cur_stroke_valid = FALSE; private->sel_count = 0; private->sel_anchor = NULL; private->sel_stroke = NULL; if (! private->vectors) { private->cur_position = -1; private->cur_anchor = NULL; private->cur_stroke = NULL; return; } while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors, cur_stroke))) { GList *anchors; GList *list; /* anchor handles */ anchors = pika_stroke_get_draw_anchors (cur_stroke); if (cur_stroke == private->cur_stroke) cur_stroke_valid = TRUE; for (list = anchors; list; list = g_list_next (list)) { PikaAnchor *cur_anchor = list->data; if (cur_anchor == private->cur_anchor) cur_anchor_valid = TRUE; if (cur_anchor->type == PIKA_ANCHOR_ANCHOR && cur_anchor->selected) { private->sel_count++; if (private->sel_count == 1) { private->sel_anchor = cur_anchor; private->sel_stroke = cur_stroke; } else { private->sel_anchor = NULL; private->sel_stroke = NULL; } } } g_list_free (anchors); anchors = pika_stroke_get_draw_controls (cur_stroke); for (list = anchors; list; list = g_list_next (list)) { PikaAnchor *cur_anchor = list->data; if (cur_anchor == private->cur_anchor) cur_anchor_valid = TRUE; } g_list_free (anchors); } if (! cur_stroke_valid) private->cur_stroke = NULL; if (! cur_anchor_valid) private->cur_anchor = NULL; } static void pika_tool_path_move_selected_anchors (PikaToolPath *path, gdouble x, gdouble y) { PikaToolPathPrivate *private = path->private; PikaAnchor *cur_anchor; PikaStroke *cur_stroke = NULL; GList *anchors; GList *list; PikaCoords offset = { 0.0, }; offset.x = x; offset.y = y; while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors, cur_stroke))) { /* anchors */ anchors = pika_stroke_get_draw_anchors (cur_stroke); for (list = anchors; list; list = g_list_next (list)) { cur_anchor = PIKA_ANCHOR (list->data); if (cur_anchor->selected) pika_stroke_anchor_move_relative (cur_stroke, cur_anchor, &offset, PIKA_ANCHOR_FEATURE_NONE); } g_list_free (anchors); } } static void pika_tool_path_delete_selected_anchors (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; PikaAnchor *cur_anchor; PikaStroke *cur_stroke = NULL; GList *anchors; GList *list; gboolean have_undo = FALSE; pika_vectors_freeze (private->vectors); while ((cur_stroke = pika_vectors_stroke_get_next (private->vectors, cur_stroke))) { /* anchors */ anchors = pika_stroke_get_draw_anchors (cur_stroke); for (list = anchors; list; list = g_list_next (list)) { cur_anchor = PIKA_ANCHOR (list->data); if (cur_anchor->selected) { if (! have_undo) { pika_tool_path_begin_change (path, _("Delete Anchors")); have_undo = TRUE; } pika_stroke_anchor_delete (cur_stroke, cur_anchor); if (pika_stroke_is_empty (cur_stroke)) { pika_vectors_stroke_remove (private->vectors, cur_stroke); cur_stroke = NULL; } } } g_list_free (anchors); } if (have_undo) pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); } /* public functions */ PikaToolWidget * pika_tool_path_new (PikaDisplayShell *shell) { g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL); return g_object_new (PIKA_TYPE_TOOL_PATH, "shell", shell, NULL); } void pika_tool_path_set_vectors (PikaToolPath *path, PikaVectors *vectors) { PikaToolPathPrivate *private; g_return_if_fail (PIKA_IS_TOOL_PATH (path)); g_return_if_fail (vectors == NULL || PIKA_IS_VECTORS (vectors)); private = path->private; if (vectors == private->vectors) return; if (private->vectors) { g_signal_handlers_disconnect_by_func (private->vectors, pika_tool_path_vectors_visible, path); g_signal_handlers_disconnect_by_func (private->vectors, pika_tool_path_vectors_freeze, path); g_signal_handlers_disconnect_by_func (private->vectors, pika_tool_path_vectors_thaw, path); g_object_unref (private->vectors); } private->vectors = vectors; private->function = VECTORS_FINISHED; pika_tool_path_verify_state (path); if (private->vectors) { g_object_ref (private->vectors); g_signal_connect_object (private->vectors, "visibility-changed", G_CALLBACK (pika_tool_path_vectors_visible), path, 0); g_signal_connect_object (private->vectors, "freeze", G_CALLBACK (pika_tool_path_vectors_freeze), path, 0); g_signal_connect_object (private->vectors, "thaw", G_CALLBACK (pika_tool_path_vectors_thaw), path, 0); } g_object_notify (G_OBJECT (path), "vectors"); } void pika_tool_path_get_popup_state (PikaToolPath *path, gboolean *on_handle, gboolean *on_curve) { PikaToolPathPrivate *private = path->private; if (on_handle) *on_handle = private->cur_anchor2 == NULL; if (on_curve) *on_curve = private->cur_stroke != NULL; } void pika_tool_path_delete_anchor (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; g_return_if_fail (private->cur_stroke != NULL); g_return_if_fail (private->cur_anchor != NULL); pika_vectors_freeze (private->vectors); pika_tool_path_begin_change (path, _("Delete Anchors")); if (private->cur_anchor->type == PIKA_ANCHOR_ANCHOR) { pika_stroke_anchor_delete (private->cur_stroke, private->cur_anchor); if (pika_stroke_is_empty (private->cur_stroke)) pika_vectors_stroke_remove (private->vectors, private->cur_stroke); } else { pika_stroke_anchor_convert (private->cur_stroke, private->cur_anchor, PIKA_ANCHOR_FEATURE_EDGE); } pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); } void pika_tool_path_shift_start (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; g_return_if_fail (private->cur_stroke != NULL); g_return_if_fail (private->cur_anchor != NULL); pika_vectors_freeze (private->vectors); pika_tool_path_begin_change (path, _("Shift start")); pika_stroke_shift_start (private->cur_stroke, private->cur_anchor); pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); } void pika_tool_path_insert_anchor (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; g_return_if_fail (private->cur_stroke != NULL); g_return_if_fail (private->cur_anchor != NULL); g_return_if_fail (private->cur_position >= 0.0); pika_vectors_freeze (private->vectors); pika_tool_path_begin_change (path, _("Insert Anchor")); private->cur_anchor = pika_stroke_anchor_insert (private->cur_stroke, private->cur_anchor, private->cur_position); pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); } void pika_tool_path_delete_segment (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; PikaStroke *new_stroke; g_return_if_fail (private->cur_stroke != NULL); g_return_if_fail (private->cur_anchor != NULL); pika_vectors_freeze (private->vectors); pika_tool_path_begin_change (path, _("Delete Segment")); new_stroke = pika_stroke_open (private->cur_stroke, private->cur_anchor); if (new_stroke) { pika_vectors_stroke_add (private->vectors, new_stroke); g_object_unref (new_stroke); } pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); } void pika_tool_path_reverse_stroke (PikaToolPath *path) { PikaToolPathPrivate *private = path->private; g_return_if_fail (private->cur_stroke != NULL); pika_vectors_freeze (private->vectors); pika_tool_path_begin_change (path, _("Insert Anchor")); pika_stroke_reverse (private->cur_stroke); pika_tool_path_end_change (path, TRUE); pika_vectors_thaw (private->vectors); }