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