1
#define __SP_NODEPATH_C__
4
* Path handler in node edit mode
7
* Lauris Kaplinski <lauris@kaplinski.com>
8
* bulia byak <buliabyak@users.sf.net>
10
* Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
17
#include <gdk/gdkkeysyms.h>
18
#include "display/canvas-bpath.h"
19
#include "display/curve.h"
20
#include "display/sp-ctrlline.h"
21
#include "display/sodipodi-ctrl.h"
22
#include "display/sp-canvas-util.h"
23
#include <glibmm/i18n.h>
24
#include "2geom/pathvector.h"
25
#include "2geom/sbasis-to-bezier.h"
26
#include "2geom/bezier-curve.h"
27
#include "2geom/hvlinesegment.h"
28
#include "helper/units.h"
29
#include "helper/geom.h"
33
#include "sp-namedview.h"
35
#include "desktop-handles.h"
37
#include "message-stack.h"
38
#include "message-context.h"
39
#include "node-context.h"
40
#include "lpe-tool-context.h"
41
#include "shape-editor.h"
42
#include "selection-chemistry.h"
43
#include "selection.h"
45
#include "preferences.h"
46
#include "sp-metrics.h"
50
#include "libnr/nr-matrix-ops.h"
53
#include <2geom/bezier-utils.h>
58
#include "live_effects/lpeobject.h"
59
#include "live_effects/lpeobject-reference.h"
60
#include "live_effects/effect.h"
61
#include "live_effects/parameter/parameter.h"
62
#include "live_effects/parameter/path.h"
63
#include "util/mathfns.h"
64
#include "display/snap-indicator.h"
65
#include "snapped-point.h"
67
namespace Geom { class Matrix; }
70
/// evil evil evil. FIXME: conflict of two different Path classes!
71
/// There is a conflict in the namespace between two classes named Path.
72
/// #include "sp-flowtext.h"
73
/// #include "sp-flowregion.h"
75
#define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
76
#define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
77
GType sp_flowregion_get_type (void);
78
#define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
79
#define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
80
GType sp_flowtext_get_type (void);
81
// end evil workaround
83
#include "helper/stlport.h"
86
/// \todo fixme: Implement these via preferences */
88
#define NODE_FILL 0xbfbfbf00
89
#define NODE_STROKE 0x000000ff
90
#define NODE_FILL_HI 0xff000000
91
#define NODE_STROKE_HI 0x000000ff
92
#define NODE_FILL_SEL 0x0000ffff
93
#define NODE_STROKE_SEL 0x000000ff
94
#define NODE_FILL_SEL_HI 0xff000000
95
#define NODE_STROKE_SEL_HI 0x000000ff
96
#define KNOT_FILL 0xffffffff
97
#define KNOT_STROKE 0x000000ff
98
#define KNOT_FILL_HI 0xff000000
99
#define KNOT_STROKE_HI 0x000000ff
101
static GMemChunk *nodechunk = NULL;
103
/* Creation from object */
105
static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
106
static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
108
/* Object updating */
110
static void stamp_repr(Inkscape::NodePath::Path *np);
111
static SPCurve *create_curve(Inkscape::NodePath::Path *np);
112
static gchar *create_typestr(Inkscape::NodePath::Path *np);
114
static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
116
static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
118
static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
120
static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
122
/* Adjust handle placement, if the node or the other handle is moved */
123
static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
124
static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
125
static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node);
127
/* Node event callbacks */
128
static void node_clicked(SPKnot *knot, guint state, gpointer data);
129
static void node_grabbed(SPKnot *knot, guint state, gpointer data);
130
static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
131
static gboolean node_request(SPKnot *knot, Geom::Point const &p, guint state, gpointer data);
133
/* Handle event callbacks */
134
static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
135
static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
136
static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
137
static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data);
138
static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data);
139
static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
141
/* Constructors and destructors */
143
static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
144
static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
145
static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
146
static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
147
static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
148
Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos);
149
static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
153
static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
154
static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
155
static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
157
static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
158
static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
160
// active_node indicates mouseover node
161
Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
163
static SPCanvasItem *
164
sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false, guint32 color = 0xff0000ff) {
165
SPCurve *helper_curve = curve->copy();
166
helper_curve->transform(np->i2d);
167
SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
168
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
169
sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
170
sp_canvas_item_move_to_z(helper_path, 0);
172
sp_canvas_item_show(helper_path);
174
helper_curve->unref();
179
sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
180
//std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec;
181
if (!SP_IS_LPE_ITEM(np->item)) {
182
g_print ("Only LPEItems can have helperpaths!\n");
186
SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
187
PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
188
for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
189
Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
190
Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->get_lpe();
192
// create new canvas items from the effect's helper paths
193
std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
194
for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
195
SPCurve *helper_curve = new SPCurve(*j);
196
SPCanvasItem * canvasitem = sp_nodepath_make_helper_item(np, helper_curve, true, 0x509050dd);
197
np->helper_path_vec[lpe].push_back(canvasitem);
198
helper_curve->unref();
205
sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
206
//std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec;
207
if (!SP_IS_LPE_ITEM(np->item)) {
208
g_print ("Only LPEItems can have helperpaths!\n");
212
SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
213
PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
214
for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
215
Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe();
217
/* update canvas items from the effect's helper paths; note that this code relies on the
218
* fact that getHelperPaths() will always return the same number of helperpaths in the same
219
* order as during their creation in sp_nodepath_create_helperpaths
221
std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
222
for (unsigned int j = 0; j < hpaths.size(); ++j) {
223
SPCurve *curve = new SPCurve(hpaths[j]);
224
curve->transform(np->i2d);
225
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH((np->helper_path_vec[lpe])[j]), curve);
226
curve = curve->unref();
233
sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
234
for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) {
235
for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
236
GtkObject *temp = *j;
238
gtk_object_destroy(temp);
241
np->helper_path_vec.clear();
246
* \brief Creates new nodepath from item
248
* If repr_key_in is not NULL, object *has* to be a LivePathEffectObject !
250
* \todo create proper constructor for nodepath::path, this method returns null a constructor cannot so this cannot be simply converted to constructor.
252
Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
255
g_assert(IS_LIVEPATHEFFECT(object));
258
Inkscape::XML::Node *repr = object->repr;
261
* FIXME: remove this. We don't want to edit paths inside flowtext.
262
* Instead we will build our flowtext with cloned paths, so that the
263
* real paths are outside the flowtext and thus editable as usual.
265
if (SP_IS_FLOWTEXT(object)) {
266
for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
267
if SP_IS_FLOWREGION(child) {
268
SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
269
if (grandchild && SP_IS_PATH(grandchild)) {
270
object = SP_ITEM(grandchild);
277
SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
283
if (curve->get_segment_count() < 1) {
285
return NULL; // prevent crash for one-node paths
288
//Create new nodepath
289
Inkscape::NodePath::Path *np = new Inkscape::NodePath::Path();
295
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
298
np->desktop = desktop;
302
np->shape_editor = NULL; //Let the shapeeditor that makes this set it
303
np->local_change = 0;
304
np->show_handles = show_handles;
305
np->helper_path = NULL;
306
np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
307
np->helperpath_width = 1.0;
308
np->curve = curve->copy();
309
np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath");
310
if (SP_IS_LPE_ITEM(object)) {
311
Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
312
if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
313
np->show_helperpath = true;
316
np->straight_path = false;
317
if (IS_LIVEPATHEFFECT(object) && item) {
320
np->item = SP_ITEM(object);
323
np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
325
// we need to update item's transform from the repr here,
326
// because they may be out of sync when we respond
327
// to a change in repr by regenerating nodepath --bb
328
sp_object_read_attr(SP_OBJECT(np->item), "transform");
330
np->i2d = sp_item_i2d_affine(np->item);
331
np->d2i = np->i2d.inverse();
334
if (repr_key_in) { // apparently the object is an LPEObject
335
np->repr_key = g_strdup(repr_key_in);
336
np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
337
Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe();
339
g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!");
342
Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in);
344
lpeparam->param_setup_nodepath(np);
347
np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
348
if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
349
np->repr_key = g_strdup("inkscape:original-d");
351
Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
353
lpe->setup_nodepath(np);
356
np->repr_key = g_strdup("d");
360
/* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
361
* So for example a closed rectangle has a nodetypestring of length 5.
362
* To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
363
Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
364
np->curve->set_pathvector(pathv_sanitized);
365
guint length = np->curve->get_segment_count();
366
for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
367
length += pit->empty() ? 0 : 1;
370
gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
371
Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
373
// create the subpath(s) from the bpath
374
subpaths_from_pathvector(np, pathv_sanitized, typestr);
376
// reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
377
np->subpaths = g_list_reverse(np->subpaths);
383
if (np->show_helperpath) {
384
np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true, np->helperpath_rgba);
387
sp_nodepath_create_helperpaths(np);
393
* Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
395
Inkscape::NodePath::Path::~Path() {
396
while (this->subpaths) {
397
sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) this->subpaths->data);
400
//Inform the ShapeEditor that made me, if any, that I am gone.
401
if (this->shape_editor)
402
this->shape_editor->nodepath_destroyed();
404
g_assert(!this->selected);
406
if (this->helper_path) {
407
GtkObject *temp = this->helper_path;
408
this->helper_path = NULL;
409
gtk_object_destroy(temp);
412
this->curve->unref();
416
if (this->repr_key) {
417
g_free(this->repr_key);
418
this->repr_key = NULL;
420
if (this->repr_nodetypes_key) {
421
g_free(this->repr_nodetypes_key);
422
this->repr_nodetypes_key = NULL;
425
sp_nodepath_destroy_helperpaths(this);
427
this->desktop = NULL;
431
* Return the node count of a given NodeSubPath.
433
static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
438
nodeCount = g_list_length(subpath->nodes);
445
* Return the node count of a given NodePath.
447
static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
451
for (GList *item = np->subpaths ; item ; item=item->next) {
452
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
453
nodeCount += g_list_length(subpath->nodes);
460
* Return the subpath count of a given NodePath.
462
static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
466
nodeCount = g_list_length(np->subpaths);
472
* Return the selected node count of a given NodePath.
474
static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
478
nodeCount = g_list_length(np->selected);
484
* Return the number of subpaths where nodes are selected in a given NodePath.
486
static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
489
if (np && np->selected) {
490
if (!np->selected->next) {
493
for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
494
Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
495
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
496
Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
497
if (node->selected) {
509
* Clean up a nodepath after editing.
511
* Currently we are deleting trivial subpaths.
513
static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
515
GList *badSubPaths = NULL;
517
//Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
518
for (GList *l = nodepath->subpaths; l ; l=l->next) {
519
Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
520
if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
521
badSubPaths = g_list_append(badSubPaths, sp);
524
//Delete them. This second step is because sp_nodepath_subpath_destroy()
525
//also removes the subpath from nodepath->subpaths
526
for (GList *l = badSubPaths; l ; l=l->next) {
527
Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
528
sp_nodepath_subpath_destroy(sp);
531
g_list_free(badSubPaths);
535
* Create new nodepaths from pathvector, make it subpaths of np.
536
* \param t The node type array.
538
static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
540
guint i = 0; // index into node type array
541
for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
543
continue; // don't add single knot paths
545
Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
547
Geom::Point ppos = pit->initialPoint() * np->i2d;
548
NRPathcode pcode = NR_MOVETO;
550
/* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/
551
for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
552
if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
553
dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
554
dynamic_cast<Geom::VLineSegment const*>(&*cit) )
556
Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
557
sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
559
ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
562
else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
563
std::vector<Geom::Point> points = cubic_bezier->points();
564
Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
565
Geom::Point npos = points[1] * (Geom::Matrix)np->i2d;
566
sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
568
ppos = points[2] * (Geom::Matrix)np->i2d;
574
// Add last knot (because sp_nodepath_subpath_close kills the last knot)
575
/* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
576
* If the length is zero, don't add it to the nodepath. */
577
Geom::Curve const &closing_seg = pit->back_closed();
578
// Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
579
if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
580
Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
581
sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
584
sp_nodepath_subpath_close(sp);
590
* Convert from sodipodi:nodetypes to new style type array.
593
Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
595
Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
600
for (guint i = 0; types[i] && ( i < length ); i++) {
601
while ((types[i] > '\0') && (types[i] <= ' ')) i++;
602
if (types[i] != '\0') {
605
typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
608
typestr[pos++] =Inkscape::NodePath::NODE_AUTO;
611
typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
614
typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
617
typestr[pos++] =Inkscape::NodePath::NODE_NONE;
624
while (pos < length) {
625
typestr[pos++] = Inkscape::NodePath::NODE_NONE;
632
* Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
633
* updated but repr is not (for speed). Used during curve and node drag.
635
static void update_object(Inkscape::NodePath::Path *np)
640
np->curve = create_curve(np);
642
sp_nodepath_set_curve(np, np->curve);
644
if (np->show_helperpath) {
645
SPCurve * helper_curve = np->curve->copy();
646
helper_curve->transform(np->i2d);
647
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
648
helper_curve->unref();
651
// updating helperpaths of LPEItems is now done in sp_lpe_item_update();
652
//sp_nodepath_update_helperpaths(np);
654
// now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
655
// TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
656
np->shape_editor->update_knotholder();
660
* Update XML path node with data from path object.
662
static void update_repr_internal(Inkscape::NodePath::Path *np)
666
Inkscape::XML::Node *repr = np->object->repr;
669
np->curve = create_curve(np);
671
gchar *typestr = create_typestr(np);
672
gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
674
// determine if path has an effect applied and write to correct "d" attribute.
675
if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
677
repr->setAttribute(np->repr_key, svgpath);
680
if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
682
repr->setAttribute(np->repr_nodetypes_key, typestr);
688
if (np->show_helperpath) {
689
SPCurve * helper_curve = np->curve->copy();
690
helper_curve->transform(np->i2d);
691
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
692
helper_curve->unref();
695
// TODO: do we need this call here? after all, update_object() should have been called just before
696
//sp_nodepath_update_helperpaths(np);
700
* Update XML path node with data from path object, commit changes forever.
702
void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
704
//fixme: np can be NULL, so check before proceeding
705
g_return_if_fail(np != NULL);
707
update_repr_internal(np);
708
sp_canvas_end_forced_full_redraws(np->desktop->canvas);
710
sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
715
* Update XML path node with data from path object, commit changes with undo.
717
static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
719
update_repr_internal(np);
720
sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
725
* Make duplicate of path, replace corresponding XML node in tree, commit.
727
static void stamp_repr(Inkscape::NodePath::Path *np)
731
Inkscape::XML::Node *old_repr = np->object->repr;
732
Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
734
// remember the position of the item
735
gint pos = old_repr->position();
737
Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
739
SPCurve *curve = create_curve(np);
740
gchar *typestr = create_typestr(np);
742
gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
744
new_repr->setAttribute(np->repr_key, svgpath);
745
new_repr->setAttribute(np->repr_nodetypes_key, typestr);
747
// add the new repr to the parent
748
parent->appendChild(new_repr);
749
// move to the saved position
750
new_repr->setPosition(pos > 0 ? pos : 0);
752
sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
755
Inkscape::GC::release(new_repr);
762
* Create curve from path.
764
static SPCurve *create_curve(Inkscape::NodePath::Path *np)
766
SPCurve *curve = new SPCurve();
768
for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
769
Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
770
curve->moveto(sp->first->pos * np->d2i);
771
Inkscape::NodePath::Node *n = sp->first->n.other;
773
Geom::Point const end_pt = n->pos * np->d2i;
774
if (!IS_FINITE(n->pos[0]) || !IS_FINITE(n->pos[1])){
775
g_message("niet finite");
779
curve->lineto(end_pt);
782
curve->curveto(n->p.other->n.pos * np->d2i,
787
g_assert_not_reached();
805
* Convert path type string to sodipodi:nodetypes style.
807
static gchar *create_typestr(Inkscape::NodePath::Path *np)
809
gchar *typestr = g_new(gchar, 32);
813
for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
814
Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
817
typestr = g_renew(gchar, typestr, len + 32);
821
typestr[pos++] = 'c';
823
Inkscape::NodePath::Node *n;
824
n = sp->first->n.other;
829
case Inkscape::NodePath::NODE_CUSP:
832
case Inkscape::NodePath::NODE_SMOOTH:
835
case Inkscape::NodePath::NODE_AUTO:
838
case Inkscape::NodePath::NODE_SYMM:
842
g_assert_not_reached();
848
typestr = g_renew(gchar, typestr, len + 32);
852
typestr[pos++] = code;
863
typestr = g_renew(gchar, typestr, len + 1);
867
typestr[pos++] = '\0';
872
// Returns different message contexts depending on the current context. This function should only
873
// be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
875
static Inkscape::MessageContext *
876
get_message_context(SPEventContext *ec)
878
Inkscape::MessageContext *mc = 0;
880
if (SP_IS_NODE_CONTEXT(ec)) {
881
mc = SP_NODE_CONTEXT(ec)->_node_message_context;
882
} else if (SP_IS_LPETOOL_CONTEXT(ec)) {
883
mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
885
g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
892
\brief Fills node and handle positions for three nodes, splitting line
893
marked by end at distance t.
895
static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
897
g_assert(new_path != NULL);
898
g_assert(end != NULL);
900
g_assert(end->p.other == new_path);
901
Inkscape::NodePath::Node *start = new_path->p.other;
904
if (end->code == NR_LINETO) {
905
new_path->type =Inkscape::NodePath::NODE_CUSP;
906
new_path->code = NR_LINETO;
907
new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
909
new_path->type =Inkscape::NodePath::NODE_SMOOTH;
910
new_path->code = NR_CURVETO;
912
for (int dim = 0; dim < 2; dim++) {
913
Geom::Coord const f000 = start->pos[dim];
914
Geom::Coord const f001 = start->n.pos[dim];
915
Geom::Coord const f011 = end->p.pos[dim];
916
Geom::Coord const f111 = end->pos[dim];
917
Geom::Coord const f00t = s * f000 + t * f001;
918
Geom::Coord const f01t = s * f001 + t * f011;
919
Geom::Coord const f11t = s * f011 + t * f111;
920
Geom::Coord const f0tt = s * f00t + t * f01t;
921
Geom::Coord const f1tt = s * f01t + t * f11t;
922
Geom::Coord const fttt = s * f0tt + t * f1tt;
923
start->n.pos[dim] = f00t;
924
new_path->p.pos[dim] = f0tt;
925
new_path->pos[dim] = fttt;
926
new_path->n.pos[dim] = f1tt;
927
end->p.pos[dim] = f11t;
933
* Adds new node on direct line between two nodes, activates handles of all
936
static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
939
g_assert(end->subpath);
940
g_assert(g_list_find(end->subpath->nodes, end));
942
Inkscape::NodePath::Node *start = end->p.other;
943
g_assert( start->n.other == end );
944
Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
946
(NRPathcode)end->code == NR_LINETO?
947
Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
948
(NRPathcode)end->code,
949
&start->pos, &start->pos, &start->n.pos);
950
sp_nodepath_line_midpoint(newnode, end, t);
952
sp_node_adjust_handles(start);
953
sp_node_update_handles(start);
954
sp_node_update_handles(newnode);
955
sp_node_adjust_handles(end);
956
sp_node_update_handles(end);
962
\brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
964
static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
967
g_assert(node->subpath);
968
g_assert(g_list_find(node->subpath->nodes, node));
970
Inkscape::NodePath::Node* result = 0;
971
Inkscape::NodePath::SubPath *sp = node->subpath;
972
Inkscape::NodePath::Path *np = sp->nodepath;
975
sp_nodepath_subpath_open(sp, node);
977
} else if ( (node == sp->first) || (node == sp->last ) ){
978
// no break for end nodes
981
// create a new subpath
982
Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
984
// duplicate the break node as start of the new subpath
985
Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
986
static_cast<Inkscape::NodePath::NodeType>(node->type),
987
NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
989
// attach rest of curve to new node
990
g_assert(node->n.other);
991
newnode->n.other = node->n.other; node->n.other = NULL;
992
newnode->n.other->p.other = newnode;
993
newsubpath->last = sp->last;
996
while (node->n.other) {
997
node = node->n.other;
998
node->subpath = newsubpath;
999
sp->nodes = g_list_remove(sp->nodes, node);
1000
newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
1010
* Duplicate node and connect to neighbours.
1012
static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
1015
g_assert(node->subpath);
1016
g_assert(g_list_find(node->subpath->nodes, node));
1018
Inkscape::NodePath::SubPath *sp = node->subpath;
1020
NRPathcode code = (NRPathcode) node->code;
1021
if (code == NR_MOVETO) { // if node is the endnode,
1022
node->code = NR_LINETO; // new one is inserted before it, so change that to line
1025
Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1027
if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1030
return newnode; // otherwise select the newly created node
1034
static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1036
node->p.pos = (node->pos + (node->pos - node->n.pos));
1039
static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1041
node->n.pos = (node->pos + (node->pos - node->p.pos));
1045
* Change line type at node, with side effects on neighbours.
1047
static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1050
g_assert(end->subpath);
1051
g_assert(end->p.other);
1053
if (end->code != static_cast<guint>(code) ) {
1054
Inkscape::NodePath::Node *start = end->p.other;
1058
if (code == NR_LINETO) {
1059
if (start->code == NR_LINETO) {
1060
sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1063
if (end->n.other->code == NR_LINETO) {
1064
sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1068
if (start->type == Inkscape::NodePath::NODE_AUTO)
1069
start->type = Inkscape::NodePath::NODE_SMOOTH;
1070
if (end->type == Inkscape::NodePath::NODE_AUTO)
1071
end->type = Inkscape::NodePath::NODE_SMOOTH;
1073
start->n.pos = start->pos;
1074
end->p.pos = end->pos;
1076
sp_node_adjust_handle(start, -1);
1077
sp_node_adjust_handle(end, 1);
1080
Geom::Point delta = end->pos - start->pos;
1081
start->n.pos = start->pos + delta / 3;
1082
end->p.pos = end->pos - delta / 3;
1083
sp_node_adjust_handle(start, 1);
1084
sp_node_adjust_handle(end, -1);
1087
sp_node_update_handles(start);
1088
sp_node_update_handles(end);
1093
sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1095
if (node->type == Inkscape::NodePath::NODE_CUSP) {
1096
node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1097
node->knot->setSize (node->selected? 11 : 9);
1098
sp_knot_update_ctrl(node->knot);
1099
} else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1100
node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1101
node->knot->setSize (node->selected? 11 : 9);
1102
sp_knot_update_ctrl(node->knot);
1104
node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1105
node->knot->setSize (node->selected? 9 : 7);
1106
sp_knot_update_ctrl(node->knot);
1112
* Change node type, and its handles accordingly.
1114
static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1117
g_assert(node->subpath);
1119
if ((node->p.other != NULL) && (node->n.other != NULL)) {
1120
if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1121
type =Inkscape::NodePath::NODE_CUSP;
1127
sp_nodepath_update_node_knot(node);
1129
// if one of handles is mouseovered, preserve its position
1130
if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1131
sp_node_adjust_handle(node, 1);
1132
} else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1133
sp_node_adjust_handle(node, -1);
1135
sp_node_adjust_handles(node);
1138
sp_node_update_handles(node);
1140
sp_nodepath_update_statusbar(node->subpath->nodepath);
1146
sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1148
// TODO clean up multiple returns
1149
Inkscape::NodePath::Node *othernode = side->other;
1152
NRPathcode const code = sp_node_path_code_from_side(node, side);
1153
if (code == NR_LINETO)
1155
Inkscape::NodePath::NodeSide *other_to_me = NULL;
1156
if (&node->p == side) {
1157
other_to_me = &othernode->n;
1158
} else if (&node->n == side) {
1159
other_to_me = &othernode->p;
1164
(Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1165
Geom::L2(node->pos - side->pos) < 1e-6);
1170
* Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1171
* lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1172
* with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1173
* If already cusp and set to cusp, retracts handles.
1175
void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1177
if (type == Inkscape::NodePath::NODE_AUTO) {
1178
if (node->p.other != NULL)
1179
node->code = NR_CURVETO;
1180
if (node->n.other != NULL)
1181
node->n.other->code = NR_CURVETO;
1184
if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1187
Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1190
// do nothing, adjust_handles called via set_node_type will line them up
1191
} else if (one_handle) {
1192
if (opposite_to_handle_is_line) {
1194
// already half-smooth; pull opposite handle too making it fully smooth
1196
// do nothing, adjust_handles will line the handle up, producing a half-smooth node
1199
// pull opposite handle in line with the existing one
1201
} else if (no_handles) {
1202
if (both_segments_are_lines OR both_segments_are_curves) {
1205
// pull the handle opposite to line segment, making node half-smooth
1209
bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1210
bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1211
bool p_is_line = sp_node_side_is_line(node, &node->p);
1212
bool n_is_line = sp_node_side_is_line(node, &node->n);
1214
if (p_has_handle && n_has_handle) {
1215
// do nothing, adjust_handles will line them up
1216
} else if (p_has_handle || n_has_handle) {
1217
if (p_has_handle && n_is_line) {
1218
Radial line (node->n.other->pos - node->pos);
1219
Radial handle (node->pos - node->p.pos);
1220
if (fabs(line.a - handle.a) < 1e-3) { // lined up
1221
// already half-smooth; pull opposite handle too making it fully smooth
1222
node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1224
// do nothing, adjust_handles will line the handle up, producing a half-smooth node
1226
} else if (n_has_handle && p_is_line) {
1227
Radial line (node->p.other->pos - node->pos);
1228
Radial handle (node->pos - node->n.pos);
1229
if (fabs(line.a - handle.a) < 1e-3) { // lined up
1230
// already half-smooth; pull opposite handle too making it fully smooth
1231
node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1233
// do nothing, adjust_handles will line the handle up, producing a half-smooth node
1235
} else if (p_has_handle && node->n.other) {
1237
node->n.other->code = NR_CURVETO;
1238
double len = (type == Inkscape::NodePath::NODE_SYMM)?
1239
Geom::L2(node->p.pos - node->pos) :
1240
Geom::L2(node->n.other->pos - node->pos) / 3;
1241
node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1242
} else if (n_has_handle && node->p.other) {
1244
node->code = NR_CURVETO;
1245
double len = (type == Inkscape::NodePath::NODE_SYMM)?
1246
Geom::L2(node->n.pos - node->pos) :
1247
Geom::L2(node->p.other->pos - node->pos) / 3;
1248
node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1250
} else if (!p_has_handle && !n_has_handle) {
1251
if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1252
// no handles, but both segments are either lnes or curves:
1255
// convert both to curves:
1256
node->code = NR_CURVETO;
1257
node->n.other->code = NR_CURVETO;
1259
sp_node_adjust_handles_auto(node);
1261
// pull the handle opposite to line segment, making it half-smooth
1262
if (p_is_line && node->n.other) {
1263
if (type != Inkscape::NodePath::NODE_SYMM) {
1265
node->n.other->code = NR_CURVETO;
1266
double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1267
node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1269
} else if (n_is_line && node->p.other) {
1270
if (type != Inkscape::NodePath::NODE_SYMM) {
1272
node->code = NR_CURVETO;
1273
double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1274
node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1279
} else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1280
// cusping a cusp: retract nodes
1281
node->p.pos = node->pos;
1282
node->n.pos = node->pos;
1285
sp_nodepath_set_node_type (node, type);
1289
* Move node to point, and adjust its and neighbouring handles.
1291
void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1293
if (node->type == Inkscape::NodePath::NODE_AUTO) {
1295
sp_node_adjust_handles_auto(node);
1297
Geom::Point delta = p - node->pos;
1300
node->p.pos += delta;
1301
node->n.pos += delta;
1304
Inkscape::NodePath::Node *node_p = NULL;
1305
Inkscape::NodePath::Node *node_n = NULL;
1307
if (node->p.other) {
1308
if (node->code == NR_LINETO) {
1309
sp_node_adjust_handle(node, 1);
1310
sp_node_adjust_handle(node->p.other, -1);
1311
node_p = node->p.other;
1313
if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1314
sp_node_adjust_handles_auto(node->p.other);
1315
node_p = node->p.other;
1318
if (node->n.other) {
1319
if (node->n.other->code == NR_LINETO) {
1320
sp_node_adjust_handle(node, -1);
1321
sp_node_adjust_handle(node->n.other, 1);
1322
node_n = node->n.other;
1324
if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1325
sp_node_adjust_handles_auto(node->n.other);
1326
node_n = node->n.other;
1330
// this function is only called from batch movers that will update display at the end
1331
// themselves, so here we just move all the knots without emitting move signals, for speed
1332
sp_node_update_handles(node, false);
1334
sp_node_update_handles(node_n, false);
1337
sp_node_update_handles(node_p, false);
1342
* Call sp_node_moveto() for node selection and handle possible snapping.
1344
static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1345
bool const snap, bool constrained = false,
1346
Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1348
Geom::Point delta(dx, dy);
1349
Geom::Point best_pt = delta;
1350
Inkscape::SnappedPoint best;
1353
/* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1354
* not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1355
* must provide that information. */
1357
// Build a list of the unselected nodes to which the snapper should snap
1358
std::vector<std::pair<Geom::Point, int> > unselected_nodes;
1359
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1360
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1361
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1362
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1363
if (!node->selected) {
1364
unselected_nodes.push_back(std::make_pair(to_2geom(node->pos), node->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPTARGET_NODE_SMOOTH : Inkscape::SNAPTARGET_NODE_CUSP));
1369
SnapManager &m = nodepath->desktop->namedview->snap_manager;
1371
// When only the node closest to the mouse pointer is to be snapped
1372
// then we will not even try to snap to other points and discard those immediately
1373
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1374
bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
1376
Inkscape::NodePath::Node *closest_node = NULL;
1377
Geom::Coord closest_dist = NR_HUGE;
1380
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1381
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1382
Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
1383
if (dist < closest_dist) {
1385
closest_dist = dist;
1390
// Iterate through all selected nodes
1391
m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes);
1392
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1393
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1394
if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
1395
Inkscape::SnappedPoint s;
1396
Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
1398
Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1399
dedicated_constraint.setPoint(n->pos);
1400
s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type, dedicated_constraint, false);
1402
s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), source_type);
1405
if (s.getSnapped()) {
1406
s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1407
if (!s.isOtherSnapBetter(best, true)) {
1409
best_pt = from_2geom(s.getPoint()) - n->pos;
1415
if (best.getSnapped()) {
1416
nodepath->desktop->snapindicator->set_new_snaptarget(best);
1418
nodepath->desktop->snapindicator->remove_snaptarget();
1422
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1423
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1424
sp_node_moveto(n, n->pos + best_pt);
1427
// do not update repr here so that node dragging is acceptably fast
1428
update_object(nodepath);
1432
Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1433
curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1437
sculpt_profile (double x, double alpha, guint profile)
1443
} else if (x <= 0) {
1447
case SCULPT_PROFILE_LINEAR:
1450
case SCULPT_PROFILE_BELL:
1451
result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1453
case SCULPT_PROFILE_ELLIPTIC:
1454
result = sqrt(1 - x*x);
1457
g_assert_not_reached();
1465
bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1467
// extremely primitive for now, don't have time to look for the real one
1468
double lower = Geom::L2(b - a);
1469
double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1470
return (lower + upper)/2;
1474
sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1476
n->pos = n->origin + delta;
1477
n->n.pos = n->n.origin + delta_n;
1478
n->p.pos = n->p.origin + delta_p;
1479
sp_node_adjust_handles(n);
1480
sp_node_update_handles(n, false);
1484
* Displace selected nodes and their handles by fractions of delta (from their origins), depending
1485
* on how far they are from the dragged node n.
1488
sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1491
g_assert (nodepath);
1492
g_assert (n->subpath->nodepath == nodepath);
1494
double pressure = n->knot->pressure;
1496
pressure = 0.5; // default
1497
pressure = CLAMP (pressure, 0.2, 0.8);
1499
// map pressure to alpha = 1/5 ... 5
1500
double alpha = 1 - 2 * fabs(pressure - 0.5);
1504
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1505
guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1507
if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1508
// Only one subpath has selected nodes:
1509
// use linear mode, where the distance from n to node being dragged is calculated along the path
1511
double n_sel_range = 0, p_sel_range = 0;
1512
guint n_nodes = 0, p_nodes = 0;
1513
guint n_sel_nodes = 0, p_sel_nodes = 0;
1515
// First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1517
double n_range = 0, p_range = 0;
1518
bool n_going = true, p_going = true;
1519
Inkscape::NodePath::Node *n_node = n;
1520
Inkscape::NodePath::Node *p_node = n;
1522
// Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1523
if (n_node && n_going)
1524
n_node = n_node->n.other;
1525
if (n_node == NULL) {
1529
n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1530
if (n_node->selected) {
1532
n_sel_range = n_range;
1534
if (n_node == p_node) {
1539
if (p_node && p_going)
1540
p_node = p_node->p.other;
1541
if (p_node == NULL) {
1545
p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1546
if (p_node->selected) {
1548
p_sel_range = p_range;
1550
if (p_node == n_node) {
1555
} while (n_going || p_going);
1558
// Second pass: actually move nodes in this subpath
1559
sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1561
double n_range = 0, p_range = 0;
1562
bool n_going = true, p_going = true;
1563
Inkscape::NodePath::Node *n_node = n;
1564
Inkscape::NodePath::Node *p_node = n;
1566
// Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1567
if (n_node && n_going)
1568
n_node = n_node->n.other;
1569
if (n_node == NULL) {
1572
n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1573
if (n_node->selected) {
1574
sp_nodepath_move_node_and_handles (n_node,
1575
sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1576
sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1577
sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1579
if (n_node == p_node) {
1584
if (p_node && p_going)
1585
p_node = p_node->p.other;
1586
if (p_node == NULL) {
1589
p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1590
if (p_node->selected) {
1591
sp_nodepath_move_node_and_handles (p_node,
1592
sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1593
sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1594
sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1596
if (p_node == n_node) {
1601
} while (n_going || p_going);
1605
// Multiple subpaths have selected nodes:
1606
// use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1607
// TODO: correct these distances taking into account their angle relative to the bisector, so as to
1608
// fix the pear-like shape when sculpting e.g. a ring
1610
// First pass: calculate range
1611
gdouble direct_range = 0;
1612
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1613
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1614
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1615
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1616
if (node->selected) {
1617
direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1622
// Second pass: actually move nodes
1623
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1624
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1625
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1626
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1627
if (node->selected) {
1628
if (direct_range > 1e-6) {
1629
sp_nodepath_move_node_and_handles (node,
1630
sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1631
sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1632
sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1634
sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1642
// do not update repr here so that node dragging is acceptably fast
1643
update_object(nodepath);
1648
* Move node selection to point, adjust its and neighbouring handles,
1649
* handle possible snapping, and commit the change with possible undo.
1652
sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1654
if (!nodepath) return;
1656
sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1659
sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1660
} else if (dy == 0) {
1661
sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1663
sp_nodepath_update_repr(nodepath, _("Move nodes"));
1668
* Move node selection off screen and commit the change.
1671
sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1673
// borrowed from sp_selection_move_screen in selection-chemistry.c
1674
// we find out the current zoom factor and divide deltas by it
1676
gdouble zoom = desktop->current_zoom();
1677
gdouble zdx = dx / zoom;
1678
gdouble zdy = dy / zoom;
1680
if (!nodepath) return;
1682
sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1685
sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1686
} else if (dy == 0) {
1687
sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1689
sp_nodepath_update_repr(nodepath, _("Move nodes"));
1694
* Move selected nodes to the absolute position given
1696
void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1698
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1699
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1700
Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1701
sp_node_moveto(n, npos);
1704
sp_nodepath_update_repr(nodepath, _("Move nodes"));
1708
* If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1710
boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1712
boost::optional<Geom::Coord> no_coord;
1713
g_return_val_if_fail(nodepath->selected, no_coord);
1715
// determine coordinate of first selected node
1716
GList *nsel = nodepath->selected;
1717
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1718
Geom::Coord coord = n->pos[axis];
1719
bool coincide = true;
1721
// compare it to the coordinates of all the other selected nodes
1722
for (GList *l = nsel->next; l != NULL; l = l->next) {
1723
n = (Inkscape::NodePath::Node *) l->data;
1724
if (n->pos[axis] != coord) {
1731
Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1732
// currently we return the coordinate of the bounding box midpoint because I don't know how
1733
// to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1734
return bbox.midpoint()[axis];
1738
/** If they don't yet exist, creates knot and line for the given side of the node */
1739
static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1742
side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
1744
side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1745
side->knot->setSize (7);
1746
side->knot->setAnchor (GTK_ANCHOR_CENTER);
1747
side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1748
side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1749
sp_knot_update_ctrl(side->knot);
1751
g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1752
g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1753
g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1754
g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1755
g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1756
g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1760
side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1761
SP_TYPE_CTRLLINE, NULL);
1766
* Ensure the given handle of the node is visible/invisible, update its screen position
1768
static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1770
g_assert(node != NULL);
1772
Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1773
NRPathcode code = sp_node_path_code_from_side(node, side);
1775
show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1778
if (!side->knot) { // No handle knot at all
1779
sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1780
// Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1781
side->knot->pos = side->pos;
1782
if (side->knot->item)
1783
SP_CTRL(side->knot->item)->moveto(side->pos);
1784
sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1785
sp_knot_show(side->knot);
1787
if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1788
if (fire_move_signals) {
1789
sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1791
sp_knot_moveto(side->knot, side->pos);
1792
sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1795
if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1796
sp_knot_show(side->knot);
1799
sp_canvas_item_show(side->line);
1802
if (SP_KNOT_IS_VISIBLE(side->knot)) {
1803
sp_knot_hide(side->knot);
1807
sp_canvas_item_hide(side->line);
1813
* Ensure the node itself is visible, its handles and those of the neighbours of the node are
1814
* visible if selected, update their screen positions. If fire_move_signals, move the node and its
1815
* handles so that the corresponding signals are fired, callbacks are activated, and curve is
1816
* updated; otherwise, just move the knots silently (used in batch moves).
1818
static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1820
g_assert(node != NULL);
1822
if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1823
sp_knot_show(node->knot);
1826
if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1827
if (fire_move_signals)
1828
sp_knot_set_position(node->knot, node->pos, 0);
1830
sp_knot_moveto(node->knot, node->pos);
1833
gboolean show_handles = node->selected;
1834
if (node->p.other != NULL) {
1835
if (node->p.other->selected) show_handles = TRUE;
1837
if (node->n.other != NULL) {
1838
if (node->n.other->selected) show_handles = TRUE;
1841
if (node->subpath->nodepath->show_handles == false)
1842
show_handles = FALSE;
1844
sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1845
sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1849
* Call sp_node_update_handles() for all nodes on subpath.
1851
static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1853
g_assert(subpath != NULL);
1855
for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1856
sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1861
* Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1863
static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1865
g_assert(nodepath != NULL);
1867
for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1868
sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1873
sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1876
nodepath->show_handles = show;
1877
sp_nodepath_update_handles(nodepath);
1882
* Adds all selected nodes in nodepath to list.
1884
void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1886
StlConv<Node *>::list(l, selected);
1887
/// \todo this adds a copying, rework when the selection becomes a stl list
1891
* Align selected nodes on the specified axis.
1893
void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1895
if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1899
if ( !nodepath->selected->next ) { // only one node selected
1902
Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1903
Geom::Point dest(pNode->pos);
1904
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1905
pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1907
dest[axis] = pNode->pos[axis];
1908
sp_node_moveto(pNode, dest);
1912
sp_nodepath_update_repr(nodepath, _("Align nodes"));
1918
Inkscape::NodePath::Node *_node;
1920
/// \todo use vectorof pointers instead of calling copy ctor
1921
NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1922
_node(node), _coord(node->pos[axis])
1927
static bool operator<(NodeSort const &a, NodeSort const &b)
1929
return (a._coord < b._coord);
1933
* Distribute selected nodes on the specified axis.
1935
void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1937
if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1941
if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1945
Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1946
std::vector<NodeSort> sorted;
1947
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1948
pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1950
NodeSort n(pNode, axis);
1951
sorted.push_back(n);
1952
//dest[axis] = pNode->pos[axis];
1953
//sp_node_moveto(pNode, dest);
1956
std::sort(sorted.begin(), sorted.end());
1957
unsigned int len = sorted.size();
1958
//overall bboxes span
1959
float dist = (sorted.back()._coord -
1960
sorted.front()._coord);
1961
//new distance between each bbox
1962
float step = (dist) / (len - 1);
1963
float pos = sorted.front()._coord;
1964
for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1968
Geom::Point dest((*it)._node->pos);
1970
sp_node_moveto((*it)._node, dest);
1974
sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1979
* Call sp_nodepath_line_add_node() for all selected segments.
1982
sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1992
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1993
Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1994
g_assert(t->selected);
1995
if (t->p.other && t->p.other->selected) {
1996
nl = g_list_prepend(nl, t);
2001
Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
2002
Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
2003
sp_nodepath_node_select(n, TRUE, FALSE);
2005
nl = g_list_remove(nl, t);
2008
/** \todo fixme: adjust ? */
2009
sp_nodepath_update_handles(nodepath);
2012
sp_nodepath_update_repr(nodepath, _("Add nodes"));
2013
} else if (n_added > 0) {
2014
sp_nodepath_update_repr(nodepath, _("Add node"));
2017
sp_nodepath_update_statusbar(nodepath);
2021
* Select segment nearest to point
2024
sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
2030
SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2031
Geom::PathVector const &pathv = curve->get_pathvector();
2032
boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2034
g_print ("Possible error?\n");
2038
// calculate index for nodepath's representation.
2039
unsigned int segment_index = floor(pvpos->t) + 1;
2040
for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2041
segment_index += pathv[i].size() + 1;
2042
if (pathv[i].closed()) {
2049
//find segment to segment
2050
Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2052
//fixme: this can return NULL, so check before proceeding.
2053
g_return_if_fail(e != NULL);
2055
gboolean force = FALSE;
2056
if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2059
sp_nodepath_node_select(e, (gboolean) toggle, force);
2061
sp_nodepath_node_select(e->p.other, TRUE, force);
2063
sp_nodepath_update_handles(nodepath);
2065
sp_nodepath_update_statusbar(nodepath);
2069
* Add a node nearest to point
2072
sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2078
SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2079
Geom::PathVector const &pathv = curve->get_pathvector();
2080
boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2082
g_print ("Possible error?\n");
2086
// calculate index for nodepath's representation.
2088
double t = std::modf(pvpos->t, &int_part);
2089
unsigned int segment_index = (unsigned int)int_part + 1;
2090
for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2091
segment_index += pathv[i].size() + 1;
2092
if (pathv[i].closed()) {
2099
//find segment to split
2100
Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2105
//don't know why but t seems to flip for lines
2106
if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2110
Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2111
sp_nodepath_node_select(n, FALSE, TRUE);
2113
/* fixme: adjust ? */
2114
sp_nodepath_update_handles(nodepath);
2116
sp_nodepath_update_repr(nodepath, _("Add node"));
2118
sp_nodepath_update_statusbar(nodepath);
2122
* Adjusts a segment so that t moves by a certain delta for dragging
2123
* converts lines to curves
2125
* method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2126
* cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2129
sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2131
Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2133
//fixme: e and e->p can be NULL, so check for those before proceeding
2134
g_return_if_fail(e != NULL);
2135
g_return_if_fail(&e->p != NULL);
2137
if (e->type == Inkscape::NodePath::NODE_AUTO) {
2138
e->type = Inkscape::NodePath::NODE_SMOOTH;
2139
sp_nodepath_update_node_knot (e);
2141
if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2142
e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2143
sp_nodepath_update_node_knot (e->p.other);
2146
/* feel good is an arbitrary parameter that distributes the delta between handles
2147
* if t of the drag point is less than 1/6 distance form the endpoint only
2148
* the corresponding hadle is adjusted. This matches the behavior in GIMP
2154
feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2155
else if (t <= 5.0 / 6.0)
2156
feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2160
//if we're dragging a line convert it to a curve
2161
if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2162
sp_nodepath_set_line_type(e, NR_CURVETO);
2165
Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2166
Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2167
e->p.other->n.pos += offsetcoord0;
2168
e->p.pos += offsetcoord1;
2170
// adjust handles of adjacent nodes where necessary
2171
sp_node_adjust_handle(e,1);
2172
sp_node_adjust_handle(e->p.other,-1);
2174
sp_nodepath_update_handles(e->subpath->nodepath);
2176
update_object(e->subpath->nodepath);
2178
sp_nodepath_update_statusbar(e->subpath->nodepath);
2183
* Call sp_nodepath_break() for all selected segments.
2185
void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2187
if (!nodepath) return;
2189
GList *tempin = g_list_copy(nodepath->selected);
2191
for (GList *l = tempin; l != NULL; l = l->next) {
2192
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2193
Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2194
if (nn == NULL) continue; // no break, no new node
2195
temp = g_list_prepend(temp, nn);
2197
g_list_free(tempin);
2200
sp_nodepath_deselect(nodepath);
2202
for (GList *l = temp; l != NULL; l = l->next) {
2203
sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2206
sp_nodepath_update_handles(nodepath);
2208
sp_nodepath_update_repr(nodepath, _("Break path"));
2212
* Duplicate the selected node(s).
2214
void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2221
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2222
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2223
Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2224
if (nn == NULL) continue; // could not duplicate
2225
temp = g_list_prepend(temp, nn);
2229
sp_nodepath_deselect(nodepath);
2231
for (GList *l = temp; l != NULL; l = l->next) {
2232
sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2235
sp_nodepath_update_handles(nodepath);
2237
sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2241
* Internal function to join two nodes by merging them into one.
2243
static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2245
/* a and b are endpoints */
2247
// if one of the two nodes is mouseovered, fix its position
2249
if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2251
} else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2254
// otherwise, move joined node to the midpoint
2255
c = (a->pos + b->pos) / 2;
2258
if (a->subpath == b->subpath) {
2259
Inkscape::NodePath::SubPath *sp = a->subpath;
2260
sp_nodepath_subpath_close(sp);
2261
sp_node_moveto (sp->first, c);
2263
sp_nodepath_update_handles(sp->nodepath);
2264
sp_nodepath_update_repr(nodepath, _("Close subpath"));
2268
/* a and b are separate subpaths */
2269
Inkscape::NodePath::SubPath *sa = a->subpath;
2270
Inkscape::NodePath::SubPath *sb = b->subpath;
2272
Inkscape::NodePath::Node *n;
2274
if (a == sa->first) {
2275
// we will now reverse sa, so that a is its last node, not first, and drop that node
2276
p = sa->first->n.pos;
2277
code = (NRPathcode)sa->first->n.other->code;
2278
// create new subpath
2279
Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2280
// create a first moveto node on it
2282
sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2284
if (n == sa->first) n = NULL;
2286
// copy the rest of the nodes from sa to t, going backwards
2287
sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2289
if (n == sa->first) n = NULL;
2291
// replace sa with t
2292
sp_nodepath_subpath_destroy(sa);
2294
} else if (a == sa->last) {
2295
// a is already last, just drop it
2296
p = sa->last->p.pos;
2297
code = (NRPathcode)sa->last->code;
2298
sp_nodepath_node_destroy(sa->last);
2301
g_assert_not_reached();
2304
if (b == sb->first) {
2305
// copy all nodes from b to a, forward
2306
sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2307
for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2308
sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2310
} else if (b == sb->last) {
2311
// copy all nodes from b to a, backward
2312
sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2313
for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2314
sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2317
g_assert_not_reached();
2319
/* and now destroy sb */
2321
sp_nodepath_subpath_destroy(sb);
2323
sp_nodepath_update_handles(sa->nodepath);
2325
sp_nodepath_update_repr(nodepath, _("Join nodes"));
2327
sp_nodepath_update_statusbar(nodepath);
2331
* Internal function to join two nodes by adding a segment between them.
2333
static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2335
if (a->subpath == b->subpath) {
2336
Inkscape::NodePath::SubPath *sp = a->subpath;
2338
/*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2341
sp->first->p.other = sp->last;
2342
sp->last->n.other = sp->first;
2344
sp_node_handle_mirror_p_to_n(sp->last);
2345
sp_node_handle_mirror_n_to_p(sp->first);
2347
sp->first->code = sp->last->code;
2348
sp->first = sp->last;
2350
sp_nodepath_update_handles(sp->nodepath);
2352
sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2357
/* a and b are separate subpaths */
2358
Inkscape::NodePath::SubPath *sa = a->subpath;
2359
Inkscape::NodePath::SubPath *sb = b->subpath;
2361
Inkscape::NodePath::Node *n;
2364
if (a == sa->first) {
2365
code = (NRPathcode) sa->first->n.other->code;
2366
Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2368
sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2369
for (n = n->p.other; n != NULL; n = n->p.other) {
2370
sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2372
sp_nodepath_subpath_destroy(sa);
2374
} else if (a == sa->last) {
2375
code = (NRPathcode)sa->last->code;
2378
g_assert_not_reached();
2381
if (b == sb->first) {
2383
sp_node_handle_mirror_p_to_n(sa->last);
2384
sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2385
sp_node_handle_mirror_n_to_p(sa->last);
2386
for (n = n->n.other; n != NULL; n = n->n.other) {
2387
sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2389
} else if (b == sb->last) {
2391
sp_node_handle_mirror_p_to_n(sa->last);
2392
sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2393
sp_node_handle_mirror_n_to_p(sa->last);
2394
for (n = n->p.other; n != NULL; n = n->p.other) {
2395
sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2398
g_assert_not_reached();
2400
/* and now destroy sb */
2402
sp_nodepath_subpath_destroy(sb);
2404
sp_nodepath_update_handles(sa->nodepath);
2406
sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2409
enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2412
* Internal function to handle joining two nodes.
2414
static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2416
if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2418
if (g_list_length(nodepath->selected) != 2) {
2419
nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2423
Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2424
Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2427
if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2428
// someone tried to join an orphan node (i.e. a single-node subpath).
2429
// this is not worth an error message, just fail silently.
2433
if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2434
nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2439
case NODE_JOIN_ENDPOINTS:
2440
do_node_selected_join(nodepath, a, b);
2442
case NODE_JOIN_SEGMENT:
2443
do_node_selected_join_segment(nodepath, a, b);
2449
* Join two nodes by merging them into one.
2451
void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2453
node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2457
* Join two nodes by adding a segment between them.
2459
void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2461
node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2465
* Delete one or more selected nodes and preserve the shape of the path as much as possible.
2467
void sp_node_delete_preserve(GList *nodes_to_delete)
2469
GSList *nodepaths = NULL;
2471
while (nodes_to_delete) {
2472
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2473
Inkscape::NodePath::SubPath *sp = node->subpath;
2474
Inkscape::NodePath::Path *nodepath = sp->nodepath;
2475
Inkscape::NodePath::Node *sample_cursor = NULL;
2476
Inkscape::NodePath::Node *sample_end = NULL;
2477
Inkscape::NodePath::Node *delete_cursor = node;
2478
bool just_delete = false;
2480
//find the start of this contiguous selection
2481
//move left to the first node that is not selected
2482
//or the start of the non-closed path
2483
for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2484
delete_cursor = curr;
2487
//just delete at the beginning of an open path
2488
if (!delete_cursor->p.other) {
2489
sample_cursor = delete_cursor;
2492
sample_cursor = delete_cursor->p.other;
2495
//calculate points for each segment
2497
float period = 1.0 / rate;
2498
std::vector<Geom::Point> data;
2500
data.push_back(sample_cursor->pos);
2501
for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2502
//just delete at the end of an open path
2503
if (!sp->closed && curr == sp->last) {
2508
//sample points on the contiguous selected segment
2510
bez = new Geom::Point [4];
2512
bez[1] = curr->n.pos;
2513
bez[2] = curr->n.other->p.pos;
2514
bez[3] = curr->n.other->pos;
2515
for (int i=1; i<rate; i++) {
2516
gdouble t = i * period;
2517
Geom::Point p = bezier_pt(3, bez, t);
2520
data.push_back(curr->n.other->pos);
2522
sample_end = curr->n.other;
2523
//break if we've come full circle or hit the end of the selection
2524
if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2531
//calculate the best fitting single segment and adjust the endpoints
2533
adata = new Geom::Point [data.size()];
2534
copy(data.begin(), data.end(), adata);
2537
bez = new Geom::Point [4];
2538
//would decreasing error create a better fitting approximation?
2539
gdouble error = 1.0;
2541
ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error);
2543
//if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2544
//make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2545
//the resulting nodes behave as expected.
2546
if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2547
sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2548
if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2549
sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2552
sample_cursor->n.pos = bez[1];
2553
sample_end->p.pos = bez[2];
2556
//destroy this contiguous selection
2557
while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2558
Inkscape::NodePath::Node *temp = delete_cursor;
2559
if (delete_cursor->n.other == delete_cursor) {
2560
// delete_cursor->n points to itself, which means this is the last node on a closed subpath
2561
delete_cursor = NULL;
2563
delete_cursor = delete_cursor->n.other;
2565
nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2566
sp_nodepath_node_destroy(temp);
2569
sp_nodepath_update_handles(nodepath);
2571
if (!g_slist_find(nodepaths, nodepath))
2572
nodepaths = g_slist_prepend (nodepaths, nodepath);
2575
for (GSList *i = nodepaths; i; i = i->next) {
2576
// FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2577
// different nodepaths will give us one undo event per nodepath
2578
Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2580
// if the entire nodepath is removed, delete the selected object.
2581
if (nodepath->subpaths == NULL ||
2582
//FIXME: a closed path CAN legally have one node, it's only an open one which must be
2584
sp_nodepath_get_node_count(nodepath) < 2) {
2585
SPDocument *document = sp_desktop_document (nodepath->desktop);
2586
//FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2587
//delete this nodepath's object, not the entire selection! (though at this time, this
2589
sp_selection_delete(nodepath->desktop);
2590
sp_document_done (document, SP_VERB_CONTEXT_NODE,
2593
sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2594
sp_nodepath_update_statusbar(nodepath);
2598
g_slist_free (nodepaths);
2602
* Delete one or more selected nodes.
2604
void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2606
if (!nodepath) return;
2607
if (!nodepath->selected) return;
2609
/** \todo fixme: do it the right way */
2610
while (nodepath->selected) {
2611
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2612
sp_nodepath_node_destroy(node);
2616
//clean up the nodepath (such as for trivial subpaths)
2617
sp_nodepath_cleanup(nodepath);
2619
sp_nodepath_update_handles(nodepath);
2621
// if the entire nodepath is removed, delete the selected object.
2622
if (nodepath->subpaths == NULL ||
2623
sp_nodepath_get_node_count(nodepath) < 2) {
2624
SPDocument *document = sp_desktop_document (nodepath->desktop);
2625
sp_selection_delete(nodepath->desktop);
2626
sp_document_done (document, SP_VERB_CONTEXT_NODE,
2631
sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2633
sp_nodepath_update_statusbar(nodepath);
2637
* Delete one or more segments between two selected nodes.
2638
* This is the code for 'split'.
2641
sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2643
Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2644
Inkscape::NodePath::Node *curr, *next; //Iterators
2646
if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2648
if (g_list_length(nodepath->selected) != 2) {
2649
nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2650
_("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2654
//Selected nodes, not inclusive
2655
Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2656
Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2658
if ( ( a==b) || //same node
2659
(a->subpath != b->subpath ) || //not the same path
2660
(!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2661
(!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2663
nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2664
_("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2668
//###########################################
2670
//###########################################
2671
//##################################
2673
//##################################
2674
if (a->subpath->closed) {
2677
gboolean reversed = FALSE;
2679
//Since we can go in a circle, we need to find the shorter distance.
2683
int minDistance = 0;
2684
for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2686
//printf("a to b:%d\n", distance);
2687
start = a;//go from a to b
2689
minDistance = distance;
2690
//printf("A to B :\n");
2696
//try again, the other direction
2698
for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2700
//printf("b to a:%d\n", distance);
2701
if (distance < minDistance) {
2702
start = b; //we go from b to a
2705
//printf("B to A\n");
2713
//Copy everything from 'end' to 'start' to a new subpath
2714
Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2715
for (curr=end ; curr ; curr=curr->n.other) {
2716
NRPathcode code = (NRPathcode) curr->code;
2719
sp_nodepath_node_new(t, NULL,
2720
(Inkscape::NodePath::NodeType)curr->type, code,
2721
&curr->p.pos, &curr->pos, &curr->n.pos);
2725
sp_nodepath_subpath_destroy(a->subpath);
2732
//##################################
2734
//##################################
2737
//We need to get the direction of the list between A and B
2738
//Can we walk from a to b?
2740
for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2742
start = a; //did it! we go from a to b
2744
//printf("A to B\n");
2748
if (!start) {//didn't work? let's try the other direction
2749
for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2751
start = b; //did it! we go from b to a
2753
//printf("B to A\n");
2759
nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2760
_("Cannot find path between nodes."));
2766
//Copy everything after 'end' to a new subpath
2767
Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2768
for (curr=end ; curr ; curr=curr->n.other) {
2769
NRPathcode code = (NRPathcode) curr->code;
2772
sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2773
&curr->p.pos, &curr->pos, &curr->n.pos);
2776
//Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2777
for (curr = start->n.other ; curr ; curr=next) {
2778
next = curr->n.other;
2779
sp_nodepath_node_destroy(curr);
2783
//###########################################
2785
//###########################################
2787
//clean up the nodepath (such as for trivial subpaths)
2788
sp_nodepath_cleanup(nodepath);
2790
sp_nodepath_update_handles(nodepath);
2792
sp_nodepath_update_repr(nodepath, _("Delete segment"));
2794
sp_nodepath_update_statusbar(nodepath);
2798
* Call sp_nodepath_set_line() for all selected segments.
2801
sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2803
if (nodepath == NULL) return;
2805
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2806
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2807
g_assert(n->selected);
2808
if (n->p.other && n->p.other->selected) {
2809
sp_nodepath_set_line_type(n, code);
2813
sp_nodepath_update_repr(nodepath, _("Change segment type"));
2817
* Call sp_nodepath_convert_node_type() for all selected nodes.
2820
sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2822
if (nodepath == NULL) return;
2824
if (nodepath->straight_path) return; // don't change type when it is a straight path!
2826
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2827
sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2830
sp_nodepath_update_repr(nodepath, _("Change node type"));
2834
* Change select status of node, update its own and neighbour handles.
2836
static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2838
node->selected = selected;
2841
node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2842
node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2843
node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2844
sp_knot_update_ctrl(node->knot);
2846
node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2847
node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2848
node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2849
sp_knot_update_ctrl(node->knot);
2852
sp_node_update_handles(node);
2853
if (node->n.other) sp_node_update_handles(node->n.other);
2854
if (node->p.other) sp_node_update_handles(node->p.other);
2858
\brief Select a node
2859
\param node The node to select
2860
\param incremental If true, add to selection, otherwise deselect others
2861
\param override If true, always select this node, otherwise toggle selected status
2863
static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2865
Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2869
if (!g_list_find(nodepath->selected, node)) {
2870
nodepath->selected = g_list_prepend(nodepath->selected, node);
2872
sp_node_set_selected(node, TRUE);
2874
if (node->selected) {
2875
g_assert(g_list_find(nodepath->selected, node));
2876
nodepath->selected = g_list_remove(nodepath->selected, node);
2878
g_assert(!g_list_find(nodepath->selected, node));
2879
nodepath->selected = g_list_prepend(nodepath->selected, node);
2881
sp_node_set_selected(node, !node->selected);
2884
sp_nodepath_deselect(nodepath);
2885
nodepath->selected = g_list_prepend(nodepath->selected, node);
2886
sp_node_set_selected(node, TRUE);
2889
sp_nodepath_update_statusbar(nodepath);
2894
\brief Deselect all nodes in the nodepath
2897
sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2899
if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2901
while (nodepath->selected) {
2902
sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2903
nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2905
sp_nodepath_update_statusbar(nodepath);
2909
\brief Select or invert selection of all nodes in the nodepath
2912
sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2914
if (!nodepath) return;
2916
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2917
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2918
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2919
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2920
sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2926
* If nothing selected, does the same as sp_nodepath_select_all();
2927
* otherwise selects/inverts all nodes in all subpaths that have selected nodes
2928
* (i.e., similar to "select all in layer", with the "selected" subpaths
2929
* being treated as "layers" in the path).
2932
sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2934
if (!nodepath) return;
2936
if (g_list_length (nodepath->selected) == 0) {
2937
sp_nodepath_select_all (nodepath, invert);
2941
GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2942
GSList *subpaths = NULL;
2944
for (GList *l = copy; l != NULL; l = l->next) {
2945
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2946
Inkscape::NodePath::SubPath *subpath = n->subpath;
2947
if (!g_slist_find (subpaths, subpath))
2948
subpaths = g_slist_prepend (subpaths, subpath);
2951
for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2952
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2953
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2954
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2955
sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2959
g_slist_free (subpaths);
2964
* \brief Select the node after the last selected; if none is selected,
2965
* select the first within path.
2967
void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2969
if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2971
Inkscape::NodePath::Node *last = NULL;
2972
if (nodepath->selected) {
2973
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2974
Inkscape::NodePath::SubPath *subpath, *subpath_next;
2975
subpath = (Inkscape::NodePath::SubPath *) spl->data;
2976
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2977
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2978
if (node->selected) {
2979
if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2980
if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2981
if (spl->next) { // there's a next subpath
2982
subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2983
last = subpath_next->first;
2984
} else if (spl->prev) { // there's a previous subpath
2985
last = NULL; // to be set later to the first node of first subpath
2987
last = node->n.other;
2990
last = node->n.other;
2993
if (node->n.other) {
2994
last = node->n.other;
2996
if (spl->next) { // there's a next subpath
2997
subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2998
last = subpath_next->first;
2999
} else if (spl->prev) { // there's a previous subpath
3000
last = NULL; // to be set later to the first node of first subpath
3002
last = (Inkscape::NodePath::Node *) subpath->first;
3009
sp_nodepath_deselect(nodepath);
3012
if (last) { // there's at least one more node after selected
3013
sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3014
} else { // no more nodes, select the first one in first subpath
3015
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
3016
sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
3021
* \brief Select the node before the first selected; if none is selected,
3022
* select the last within path
3024
void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
3026
if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
3028
Inkscape::NodePath::Node *last = NULL;
3029
if (nodepath->selected) {
3030
for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3031
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3032
for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3033
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3034
if (node->selected) {
3035
if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3036
if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3037
if (spl->prev) { // there's a prev subpath
3038
Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3039
last = subpath_prev->last;
3040
} else if (spl->next) { // there's a next subpath
3041
last = NULL; // to be set later to the last node of last subpath
3043
last = node->p.other;
3046
last = node->p.other;
3049
if (node->p.other) {
3050
last = node->p.other;
3052
if (spl->prev) { // there's a prev subpath
3053
Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3054
last = subpath_prev->last;
3055
} else if (spl->next) { // there's a next subpath
3056
last = NULL; // to be set later to the last node of last subpath
3058
last = (Inkscape::NodePath::Node *) subpath->last;
3065
sp_nodepath_deselect(nodepath);
3068
if (last) { // there's at least one more node before selected
3069
sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3070
} else { // no more nodes, select the last one in last subpath
3071
GList *spl = g_list_last(nodepath->subpaths);
3072
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3073
sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3078
* \brief Select all nodes that are within the rectangle.
3080
void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3083
sp_nodepath_deselect(nodepath);
3086
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3087
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3088
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3089
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3091
if (b.contains(node->pos)) {
3092
sp_nodepath_node_select(node, TRUE, TRUE);
3100
nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3103
g_assert (nodepath);
3104
g_assert (n->subpath->nodepath == nodepath);
3106
if (g_list_length (nodepath->selected) == 0) {
3108
sp_nodepath_node_select(n, TRUE, TRUE);
3113
if (g_list_length (nodepath->selected) == 1) {
3115
sp_nodepath_deselect (nodepath);
3120
double n_sel_range = 0, p_sel_range = 0;
3121
Inkscape::NodePath::Node *farthest_n_node = n;
3122
Inkscape::NodePath::Node *farthest_p_node = n;
3126
double n_range = 0, p_range = 0;
3127
bool n_going = true, p_going = true;
3128
Inkscape::NodePath::Node *n_node = n;
3129
Inkscape::NodePath::Node *p_node = n;
3131
// Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3132
if (n_node && n_going)
3133
n_node = n_node->n.other;
3134
if (n_node == NULL) {
3137
n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3138
if (n_node->selected) {
3139
n_sel_range = n_range;
3140
farthest_n_node = n_node;
3142
if (n_node == p_node) {
3147
if (p_node && p_going)
3148
p_node = p_node->p.other;
3149
if (p_node == NULL) {
3152
p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3153
if (p_node->selected) {
3154
p_sel_range = p_range;
3155
farthest_p_node = p_node;
3157
if (p_node == n_node) {
3162
} while (n_going || p_going);
3166
if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3167
sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3168
} else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3169
sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3172
if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3173
sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3174
} else if (farthest_p_node && farthest_p_node->selected) {
3175
sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3181
nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3184
g_assert (nodepath);
3185
g_assert (n->subpath->nodepath == nodepath);
3187
if (g_list_length (nodepath->selected) == 0) {
3189
sp_nodepath_node_select(n, TRUE, TRUE);
3194
if (g_list_length (nodepath->selected) == 1) {
3196
sp_nodepath_deselect (nodepath);
3201
Inkscape::NodePath::Node *farthest_selected = NULL;
3202
double farthest_dist = 0;
3204
Inkscape::NodePath::Node *closest_unselected = NULL;
3205
double closest_dist = NR_HUGE;
3207
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3208
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3209
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3210
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3213
if (node->selected) {
3214
if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3215
farthest_dist = Geom::L2(node->pos - n->pos);
3216
farthest_selected = node;
3219
if (Geom::L2(node->pos - n->pos) < closest_dist) {
3220
closest_dist = Geom::L2(node->pos - n->pos);
3221
closest_unselected = node;
3228
if (closest_unselected) {
3229
sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3232
if (farthest_selected) {
3233
sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3240
\brief Saves all nodes' and handles' current positions in their origin members
3243
sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3245
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3246
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3247
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3248
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3250
n->p.origin = n->p.pos;
3251
n->n.origin = n->n.pos;
3257
\brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3259
GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3262
if (nodepath->selected) {
3264
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3265
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3266
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3267
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3269
if (node->selected) {
3270
r = g_list_append(r, GINT_TO_POINTER(i));
3279
\brief Restores selection by selecting nodes whose positions are in the list
3281
void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3283
sp_nodepath_deselect(nodepath);
3286
for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3287
Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3288
for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3289
Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3291
if (g_list_find(r, GINT_TO_POINTER(i))) {
3292
sp_nodepath_node_select(node, TRUE, TRUE);
3300
\brief Adjusts handle according to node type and line code.
3302
static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3306
// nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3307
if (node->type == Inkscape::NodePath::NODE_AUTO)
3310
Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3311
Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3313
// nothing to do if we are an end node
3314
if (me->other == NULL) return;
3315
if (other->other == NULL) return;
3317
// nothing to do if we are a cusp node
3318
if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3320
// nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3322
if (which_adjust == 1) {
3323
mecode = (NRPathcode)me->other->code;
3325
mecode = (NRPathcode)node->code;
3327
if (mecode == NR_LINETO) return;
3329
if (sp_node_side_is_line(node, other)) {
3330
// other is a line, and we are either smooth or symm
3331
Inkscape::NodePath::Node *othernode = other->other;
3332
double len = Geom::L2(me->pos - node->pos);
3333
Geom::Point delta = node->pos - othernode->pos;
3334
double linelen = Geom::L2(delta);
3335
if (linelen < 1e-18)
3337
me->pos = node->pos + (len / linelen)*delta;
3341
if (node->type == Inkscape::NodePath::NODE_SYMM) {
3343
me->pos = 2 * node->pos - other->pos;
3347
double len = Geom::L2(me->pos - node->pos);
3348
Geom::Point delta = other->pos - node->pos;
3349
double otherlen = Geom::L2(delta);
3350
if (otherlen < 1e-18) return;
3351
me->pos = node->pos - (len / otherlen) * delta;
3356
\brief Adjusts both handles according to node type and line code
3358
static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3362
if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3364
/* we are either smooth or symm */
3366
if (node->p.other == NULL) return;
3367
if (node->n.other == NULL) return;
3369
if (node->type == Inkscape::NodePath::NODE_AUTO) {
3370
sp_node_adjust_handles_auto(node);
3374
if (sp_node_side_is_line(node, &node->p)) {
3375
sp_node_adjust_handle(node, 1);
3379
if (sp_node_side_is_line(node, &node->n)) {
3380
sp_node_adjust_handle(node, -1);
3384
/* both are curves */
3385
Geom::Point const delta( node->n.pos - node->p.pos );
3387
if (node->type == Inkscape::NodePath::NODE_SYMM) {
3388
node->p.pos = node->pos - delta / 2;
3389
node->n.pos = node->pos + delta / 2;
3394
double plen = Geom::L2(node->p.pos - node->pos);
3395
if (plen < 1e-18) return;
3396
double nlen = Geom::L2(node->n.pos - node->pos);
3397
if (nlen < 1e-18) return;
3398
node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3399
node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3402
static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3404
if (node->p.other == NULL || node->n.other == NULL) {
3405
node->p.pos = node->pos;
3406
node->n.pos = node->pos;
3410
Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3411
Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3413
double norm_leg_prev = Geom::L2(leg_prev);
3414
double norm_leg_next = Geom::L2(leg_next);
3417
if (norm_leg_next > 0.0) {
3418
delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3422
node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3423
node->n.pos = node->pos + norm_leg_next / 3 * delta;
3427
* Node event callback.
3429
static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3431
gboolean ret = FALSE;
3432
switch (event->type) {
3433
case GDK_ENTER_NOTIFY:
3434
Inkscape::NodePath::Path::active_node = n;
3436
case GDK_LEAVE_NOTIFY:
3437
Inkscape::NodePath::Path::active_node = NULL;
3440
if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3441
switch (event->scroll.direction) {
3443
nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3445
case GDK_SCROLL_DOWN:
3446
nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3452
} else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3453
switch (event->scroll.direction) {
3455
nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3457
case GDK_SCROLL_DOWN:
3458
nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3467
switch (get_group0_keyval (&event->key)) {
3469
if (event->key.state & GDK_BUTTON1_MASK) {
3470
Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3471
stamp_repr(nodepath);
3476
if (event->key.state & GDK_CONTROL_MASK) {
3477
nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3479
nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3483
if (event->key.state & GDK_CONTROL_MASK) {
3484
nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3486
nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3501
* Handle keypress on node; directly called.
3503
gboolean node_key(GdkEvent *event)
3505
Inkscape::NodePath::Path *np;
3507
// there is no way to verify nodes so set active_node to nil when deleting!!
3508
if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3510
if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3512
switch (get_group0_keyval (&event->key)) {
3513
/// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3515
np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3516
sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3517
sp_nodepath_update_repr(np, _("Delete node"));
3518
Inkscape::NodePath::Path::active_node = NULL;
3522
sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3526
sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3530
sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3534
sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3538
sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3548
* Mouseclick on node callback.
3550
static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3552
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3554
if (state & GDK_CONTROL_MASK) {
3555
Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3557
if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3558
if (n->type == Inkscape::NodePath::NODE_CUSP) {
3559
sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3560
} else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3561
sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3562
} else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3563
sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3565
sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3567
sp_nodepath_update_repr(nodepath, _("Change node type"));
3568
sp_nodepath_update_statusbar(nodepath);
3570
} else { //ctrl+alt+click: delete node
3571
GList *node_to_delete = NULL;
3572
node_to_delete = g_list_append(node_to_delete, n);
3573
sp_node_delete_preserve(node_to_delete);
3577
sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3582
* Mouse grabbed node callback.
3584
static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3586
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3589
sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3592
n->is_dragging = true;
3593
//sp_event_context_snap_window_open(n->subpath->nodepath->desktop->canvas);
3594
// Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3595
n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3597
sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3599
sp_nodepath_remember_origins (n->subpath->nodepath);
3603
* Mouse ungrabbed node callback.
3605
static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3607
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3609
n->dragging_out = NULL;
3610
n->is_dragging = false;
3611
//sp_event_context_snap_window_closed(n->subpath->nodepath->desktop->canvas);
3612
n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3613
sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3615
sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3619
* The point on a line, given by its angle, closest to the given point.
3621
* \param a Angle of the line; it is assumed to go through coordinate origin.
3622
* \param closest Pointer to the point struct where the result is stored.
3623
* \todo FIXME: use dot product perhaps?
3625
static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3627
if (a == HUGE_VAL) { // vertical
3628
*closest = Geom::Point(0, (*p)[Geom::Y]);
3630
(*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3631
(*closest)[Geom::Y] = a * (*closest)[Geom::X];
3636
* Distance from the point to a line given by its angle.
3638
* \param a Angle of the line; it is assumed to go through coordinate origin.
3640
static double point_line_distance(Geom::Point *p, double a)
3643
point_line_closest(p, a, &c);
3644
return sqrt(((*p)[Geom::X] - c[Geom::X])*((*p)[Geom::X] - c[Geom::X]) + ((*p)[Geom::Y] - c[Geom::Y])*((*p)[Geom::Y] - c[Geom::Y]));
3648
* Callback for node "request" signal.
3649
* \todo fixme: This goes to "moved" event? (lauris)
3652
node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3654
double yn, xn, yp, xp;
3655
double an, ap, na, pa;
3656
double d_an, d_ap, d_na, d_pa;
3657
gboolean collinear = FALSE;
3661
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3663
n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
3665
// If either (Shift and some handle retracted), or (we're already dragging out a handle)
3666
if ( (!n->subpath->nodepath->straight_path) &&
3667
( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3668
|| n->dragging_out ) )
3670
Geom::Point mouse = p;
3672
if (!n->dragging_out) {
3673
// This is the first drag-out event; find out which handle to drag out
3674
double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3675
double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3677
if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3680
Inkscape::NodePath::NodeSide *opposite;
3681
if (appr_p > appr_n) { // closer to p
3682
n->dragging_out = &n->p;
3684
n->code = NR_CURVETO;
3685
} else if (appr_p < appr_n) { // closer to n
3686
n->dragging_out = &n->n;
3688
n->n.other->code = NR_CURVETO;
3689
} else { // p and n nodes are the same
3690
if (n->n.pos != n->pos) { // n handle already dragged, drag p
3691
n->dragging_out = &n->p;
3693
n->code = NR_CURVETO;
3694
} else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3695
n->dragging_out = &n->n;
3697
n->n.other->code = NR_CURVETO;
3698
} else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3699
double appr_other_n = (n->n.other ? Geom::L2(n->n.other->n.pos - n->pos) - Geom::L2(n->n.other->n.pos - p) : -HUGE_VAL);
3700
double appr_other_p = (n->n.other ? Geom::L2(n->n.other->p.pos - n->pos) - Geom::L2(n->n.other->p.pos - p) : -HUGE_VAL);
3701
if (appr_other_p > appr_other_n) { // closer to other's p handle
3702
n->dragging_out = &n->n;
3704
n->n.other->code = NR_CURVETO;
3705
} else { // closer to other's n handle
3706
n->dragging_out = &n->p;
3708
n->code = NR_CURVETO;
3713
// if there's another handle, make sure the one we drag out starts parallel to it
3714
if (opposite->pos != n->pos) {
3715
mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3718
// knots might not be created yet!
3719
sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3720
sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3723
// pass this on to the handle-moved callback
3724
node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3725
sp_node_update_handles(n);
3729
if (state & GDK_CONTROL_MASK) { // constrained motion
3731
// calculate relative distances of handles
3733
yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3734
xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3735
// if there's no n handle (straight line), see if we can use the direction to the next point on path
3736
if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3737
if (n->n.other) { // if there is the next point
3738
if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3739
yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3740
xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3743
if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3744
if (yn < 0) { xn = -xn; yn = -yn; }
3747
yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3748
xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3749
// if there's no p handle (straight line), see if we can use the direction to the prev point on path
3750
if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3752
if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3753
yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3754
xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3757
if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3758
if (yp < 0) { xp = -xp; yp = -yp; }
3760
if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3761
// sliding on handles, only if at least one of the handles is non-vertical
3762
// (otherwise it's the same as ctrl+drag anyway)
3764
// calculate angles of the handles
3766
if (yn == 0) { // no handle, consider it the continuation of the other one
3770
else an = 0; // vertical; set the angle to horizontal
3774
if (yp == 0) { // no handle, consider it the continuation of the other one
3777
else ap = 0; // vertical; set the angle to horizontal
3780
if (collinear) an = ap;
3782
// angles of the perpendiculars; HUGE_VAL means vertical
3783
if (an == 0) na = HUGE_VAL; else na = -1/an;
3784
if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3786
// mouse point relative to the node's original pos
3789
// distances to the four lines (two handles and two perpendiculars)
3790
d_an = point_line_distance(&pr, an);
3791
d_na = point_line_distance(&pr, na);
3792
d_ap = point_line_distance(&pr, ap);
3793
d_pa = point_line_distance(&pr, pa);
3795
// find out which line is the closest, save its closest point in c
3796
if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3797
point_line_closest(&pr, an, &c);
3798
} else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3799
point_line_closest(&pr, ap, &c);
3800
} else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3801
point_line_closest(&pr, na, &c);
3802
} else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3803
point_line_closest(&pr, pa, &c);
3806
// move the node to the closest point
3807
sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3808
n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3809
n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3812
} else { // constraining to hor/vert
3814
if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3815
sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3816
p[Geom::X] - n->pos[Geom::X],
3817
n->origin[Geom::Y] - n->pos[Geom::Y],
3819
true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3820
} else { // snap to vert
3821
sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3822
n->origin[Geom::X] - n->pos[Geom::X],
3823
p[Geom::Y] - n->pos[Geom::Y],
3825
true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3828
} else { // move freely
3829
if (n->is_dragging) {
3830
if (state & GDK_MOD1_MASK) { // sculpt
3831
sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3833
sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3834
p[Geom::X] - n->pos[Geom::X],
3835
p[Geom::Y] - n->pos[Geom::Y],
3836
(state & GDK_SHIFT_MASK) == 0);
3841
n->subpath->nodepath->desktop->scroll_to_point(p);
3847
* Node handle clicked callback.
3849
static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3851
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3853
if (state & GDK_CONTROL_MASK) { // "delete" handle
3854
if (n->p.knot == knot) {
3856
} else if (n->n.knot == knot) {
3859
sp_node_update_handles(n);
3860
Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3861
sp_nodepath_update_repr(nodepath, _("Retract handle"));
3862
sp_nodepath_update_statusbar(nodepath);
3864
} else { // just select or add to selection, depending in Shift
3865
sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3870
* Node handle grabbed callback.
3872
static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3874
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3876
// convert auto -> smooth when dragging handle
3877
if (n->type == Inkscape::NodePath::NODE_AUTO) {
3878
n->type = Inkscape::NodePath::NODE_SMOOTH;
3879
sp_nodepath_update_node_knot (n);
3883
sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3886
// remember the origin point of the handle
3887
if (n->p.knot == knot) {
3888
n->p.origin_radial = n->p.pos - n->pos;
3889
} else if (n->n.knot == knot) {
3890
n->n.origin_radial = n->n.pos - n->pos;
3892
g_assert_not_reached();
3895
sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3899
* Node handle ungrabbed callback.
3901
static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3903
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3905
// forget origin and set knot position once more (because it can be wrong now due to restrictions)
3906
if (n->p.knot == knot) {
3907
n->p.origin_radial.a = 0;
3908
sp_knot_set_position(knot, n->p.pos, state);
3909
} else if (n->n.knot == knot) {
3910
n->n.origin_radial.a = 0;
3911
sp_knot_set_position(knot, n->n.pos, state);
3913
g_assert_not_reached();
3916
sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3920
* Node handle "request" signal callback.
3922
static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data)
3924
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3926
Inkscape::NodePath::NodeSide *me, *opposite;
3928
if (n->p.knot == knot) {
3932
} else if (n->n.knot == knot) {
3937
me = opposite = NULL;
3939
g_assert_not_reached();
3942
SPDesktop *desktop = n->subpath->nodepath->desktop;
3943
SnapManager &m = desktop->namedview->snap_manager;
3944
m.setup(desktop, true, n->subpath->nodepath->item);
3945
Inkscape::SnappedPoint s;
3947
if ((state & GDK_SHIFT_MASK) != 0) {
3948
// We will not try to snap when the shift-key is pressed
3949
// so remove the old snap indicator and don't wait for it to time-out
3950
desktop->snapindicator->remove_snaptarget();
3953
Inkscape::NodePath::Node *othernode = opposite->other;
3954
Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP);
3956
if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3957
/* We are smooth node adjacent with line */
3958
Geom::Point const delta = p - n->pos;
3959
Geom::Coord const len = Geom::L2(delta);
3960
Inkscape::NodePath::Node *othernode = opposite->other;
3961
Geom::Point const ndelta = n->pos - othernode->pos;
3962
Geom::Coord const linelen = Geom::L2(ndelta);
3963
if (len > NR_EPSILON && linelen > NR_EPSILON) {
3964
Geom::Coord const scal = dot(delta, ndelta) / linelen;
3965
p = n->pos + (scal / linelen) * ndelta;
3967
if ((state & GDK_SHIFT_MASK) == 0) {
3968
s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type, Inkscape::Snapper::ConstraintLine(p, ndelta), false);
3971
if ((state & GDK_SHIFT_MASK) == 0) {
3972
s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type);
3976
if ((state & GDK_SHIFT_MASK) == 0) {
3977
s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, source_type);
3983
sp_node_adjust_handle(n, -which);
3989
* Node handle moved callback.
3991
static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3993
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3994
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3996
Inkscape::NodePath::NodeSide *me;
3997
Inkscape::NodePath::NodeSide *other;
3998
if (n->p.knot == knot) {
4001
} else if (n->n.knot == knot) {
4007
g_assert_not_reached();
4010
// calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
4011
Radial rme(me->pos - n->pos);
4012
Radial rother(other->pos - n->pos);
4013
Radial rnew(p - n->pos);
4015
if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
4016
int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
4017
/* 0 interpreted as "no snapping". */
4019
// 1. Snap to the closest PI/snaps angle, starting from zero.
4020
double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
4022
// 2. Snap to the original angle, its opposite and perpendiculars
4023
if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
4024
/* The closest PI/2 angle, starting from original angle */
4025
double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
4027
// Snap to the closest.
4028
a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
4033
// 3. Snap to the angle of the opposite line, if any
4034
Inkscape::NodePath::Node *othernode = other->other;
4036
Geom::Point other_to_snap(0,0);
4037
if (sp_node_side_is_line(n, other)) {
4038
other_to_snap = othernode->pos - n->pos;
4040
other_to_snap = other->pos - n->pos;
4042
if (Geom::L2(other_to_snap) > 1e-3) {
4043
Radial rother_to_snap(other_to_snap);
4044
/* The closest PI/2 angle, starting from the angle of the opposite line segment */
4045
double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4047
// Snap to the closest.
4048
a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4057
if (state & GDK_MOD1_MASK) {
4058
// lock handle length
4059
rnew.r = me->origin_radial.r;
4062
if (( n->type !=Inkscape::NodePath::NODE_CUSP || (!n->dragging_out && (state & GDK_SHIFT_MASK)))
4063
&& (rme.a != HUGE_VAL) && (rnew.a != HUGE_VAL) && ((fabs(rme.a - rnew.a) > 0.001) || (n->type ==Inkscape::NodePath::NODE_SYMM))) {
4064
// rotate the other handle correspondingly, if both old and new angles exist and are not the same
4065
rother.a += rnew.a - rme.a;
4066
other->pos = Geom::Point(rother) + n->pos;
4068
sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4069
sp_knot_moveto(other->knot, other->pos);
4073
me->pos = Geom::Point(rnew) + n->pos;
4074
sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4076
// move knot, but without emitting the signal:
4077
// we cannot emit a "moved" signal because we're now processing it
4078
sp_knot_moveto(me->knot, me->pos);
4080
update_object(n->subpath->nodepath);
4083
SPDesktop *desktop = n->subpath->nodepath->desktop;
4084
if (!desktop) return;
4085
SPEventContext *ec = desktop->event_context;
4088
Inkscape::MessageContext *mc = get_message_context(ec);
4092
double degrees = 180 / M_PI * rnew.a;
4093
if (degrees > 180) degrees -= 360;
4094
if (degrees < -180) degrees += 360;
4095
if (prefs->getBool("/options/compassangledisplay/value"))
4096
degrees = angle_to_compass (degrees);
4098
GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4100
mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4101
_("<b>Node handle</b>: angle %0.2f°, length %s; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"), degrees, length->str);
4103
g_string_free(length, TRUE);
4107
* Node handle event callback.
4109
static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4111
gboolean ret = FALSE;
4112
switch (event->type) {
4114
switch (get_group0_keyval (&event->key)) {
4116
if (event->key.state & GDK_BUTTON1_MASK) {
4117
Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4118
stamp_repr(nodepath);
4126
case GDK_ENTER_NOTIFY:
4127
// we use an experimentally determined threshold that seems to work fine
4128
if (Geom::L2(n->pos - knot->pos) < 0.75)
4129
Inkscape::NodePath::Path::active_node = n;
4131
case GDK_LEAVE_NOTIFY:
4132
// we use an experimentally determined threshold that seems to work fine
4133
if (Geom::L2(n->pos - knot->pos) < 0.75)
4134
Inkscape::NodePath::Path::active_node = NULL;
4143
static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4144
Radial &rme, Radial &rother, gboolean const both)
4148
|| ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4149
|| ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4155
static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4156
Radial &rme, Radial &rother, gboolean const both)
4158
gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4162
|| ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4163
|| ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4165
r = MAX(rme.r, rother.r);
4170
gdouble const weird_angle = atan2(norm_angle, r);
4171
/* Bulia says norm_angle is just the visible distance that the
4172
* object's end must travel on the screen. Left as 'angle' for want of
4175
rme.a += weird_angle;
4177
|| ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4178
|| ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4180
rother.a += weird_angle;
4187
static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4189
Inkscape::NodePath::NodeSide *me, *other;
4192
double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4193
double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4195
if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4198
} else if (!n->p.other) {
4202
if (which > 0) { // right handle
4210
} else if (which < 0){ // left handle
4218
} else { // both handles
4225
Radial rme(me->pos - n->pos);
4226
Radial rother(other->pos - n->pos);
4229
node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4231
node_rotate_one_internal (*n, angle, rme, rother, both);
4234
me->pos = n->pos + Geom::Point(rme);
4236
if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4237
other->pos = n->pos + Geom::Point(rother);
4240
// this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4241
// so here we just move all the knots without emitting move signals, for speed
4242
sp_node_update_handles(n, false);
4246
* Rotate selected nodes.
4248
void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4250
if (!nodepath || !nodepath->selected) return;
4252
if (g_list_length(nodepath->selected) == 1) {
4253
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4254
node_rotate_one (n, angle, which, screen);
4256
// rotate as an object:
4258
Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4259
Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4260
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4261
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4262
box.expandTo (n->pos); // contain all selected nodes
4267
gdouble const zoom = nodepath->desktop->current_zoom();
4268
gdouble const zmove = angle / zoom;
4269
gdouble const r = Geom::L2(box.max() - box.midpoint());
4270
rot = atan2(zmove, r);
4275
Geom::Point rot_center;
4276
if (Inkscape::NodePath::Path::active_node == NULL)
4277
rot_center = box.midpoint();
4279
rot_center = Inkscape::NodePath::Path::active_node->pos;
4282
Geom::Matrix (Geom::Translate(-rot_center)) *
4283
Geom::Matrix (Geom::Rotate(rot)) *
4284
Geom::Matrix (Geom::Translate(rot_center));
4286
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4287
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4291
sp_node_update_handles(n, false);
4295
sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4301
static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4304
Inkscape::NodePath::NodeSide *me, *other;
4306
double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4307
double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4309
if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4312
n->code = NR_CURVETO;
4313
} else if (!n->p.other) {
4317
n->n.other->code = NR_CURVETO;
4319
if (which > 0) { // right handle
4324
n->n.other->code = NR_CURVETO;
4328
n->code = NR_CURVETO;
4330
} else if (which < 0){ // left handle
4335
n->n.other->code = NR_CURVETO;
4339
n->code = NR_CURVETO;
4341
} else { // both handles
4345
n->code = NR_CURVETO;
4347
n->n.other->code = NR_CURVETO;
4351
Radial rme(me->pos - n->pos);
4352
Radial rother(other->pos - n->pos);
4355
if (rme.r < 0) rme.r = 0;
4356
if (rme.a == HUGE_VAL) {
4357
if (me->other) { // if direction is unknown, initialize it towards the next node
4358
Radial rme_next(me->other->pos - n->pos);
4360
} else { // if there's no next, initialize to 0
4364
if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4366
if (rother.r < 0) rother.r = 0;
4367
if (rother.a == HUGE_VAL) {
4368
rother.a = rme.a + M_PI;
4372
me->pos = n->pos + Geom::Point(rme);
4374
if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4375
other->pos = n->pos + Geom::Point(rother);
4378
// this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4379
// so here we just move all the knots without emitting move signals, for speed
4380
sp_node_update_handles(n, false);
4384
* Scale selected nodes.
4386
void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4388
if (!nodepath || !nodepath->selected) return;
4390
if (g_list_length(nodepath->selected) == 1) {
4391
// scale handles of the single selected node
4392
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4393
node_scale_one (n, grow, which);
4395
// scale nodes as an "object":
4397
Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4398
Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4399
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4400
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4401
box.expandTo (n->pos); // contain all selected nodes
4404
if ( Geom::are_near(box.maxExtent(), 0) ) {
4405
SPEventContext *ec = nodepath->desktop->event_context;
4407
Inkscape::MessageContext *mc = get_message_context(ec);
4409
mc->setF(Inkscape::WARNING_MESSAGE,
4410
_("Cannot scale nodes when all are at the same location."));
4413
double scale = (box.maxExtent() + grow)/box.maxExtent();
4416
Geom::Point scale_center;
4417
if (Inkscape::NodePath::Path::active_node == NULL)
4418
scale_center = box.midpoint();
4420
scale_center = Inkscape::NodePath::Path::active_node->pos;
4423
Geom::Translate(-scale_center) *
4424
Geom::Scale(scale, scale) *
4425
Geom::Translate(scale_center);
4427
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4428
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4432
sp_node_update_handles(n, false);
4436
sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4439
void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4441
if (!nodepath) return;
4442
sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4446
* Flip selected nodes horizontally/vertically.
4448
void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4450
if (!nodepath || !nodepath->selected) return;
4452
if (g_list_length(nodepath->selected) == 1 && !center) {
4453
// flip handles of the single selected node
4454
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4455
double temp = n->p.pos[axis];
4456
n->p.pos[axis] = n->n.pos[axis];
4457
n->n.pos[axis] = temp;
4458
sp_node_update_handles(n, false);
4460
// scale nodes as an "object":
4462
Geom::Rect box = sp_node_selected_bbox (nodepath);
4464
center = box.midpoint();
4467
Geom::Matrix (Geom::Translate(- *center)) *
4468
Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4469
Geom::Matrix (Geom::Translate(*center));
4471
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4472
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4476
sp_node_update_handles(n, false);
4480
sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4483
Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4485
g_assert (nodepath->selected);
4487
Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4488
Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4489
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4490
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4491
box.expandTo (n->pos); // contain all selected nodes
4496
//-----------------------------------------------
4498
* Return new subpath under given nodepath.
4500
static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4503
g_assert(nodepath->desktop);
4505
Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4507
s->nodepath = nodepath;
4513
// using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4514
// the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4515
nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4521
* Destroy nodes in subpath, then subpath itself.
4523
static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4526
g_assert(subpath->nodepath);
4527
g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4529
while (subpath->nodes) {
4530
sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4533
subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4539
* Link head to tail in subpath.
4541
static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4543
g_assert(!sp->closed);
4544
g_assert(sp->last != sp->first);
4545
g_assert(sp->first->code == NR_MOVETO);
4549
//Link the head to the tail
4550
sp->first->p.other = sp->last;
4551
sp->last->n.other = sp->first;
4552
sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4553
sp->first = sp->last;
4555
//Remove the extra end node
4556
sp_nodepath_node_destroy(sp->last->n.other);
4560
* Open closed (loopy) subpath at node.
4562
static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4564
g_assert(sp->closed);
4565
g_assert(n->subpath == sp);
4566
g_assert(sp->first == sp->last);
4568
/* We create new startpoint, current node will become last one */
4570
Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4571
&n->pos, &n->pos, &n->n.pos);
4576
//Unlink to make a head and tail
4577
sp->first = new_path;
4580
new_path->p.other = NULL;
4584
* Return new node in subpath with given properties.
4585
* \param pos Position of node.
4586
* \param ppos Handle position in previous direction
4587
* \param npos Handle position in previous direction
4589
Inkscape::NodePath::Node *
4590
sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos)
4593
g_assert(sp->nodepath);
4594
g_assert(sp->nodepath->desktop);
4596
if (nodechunk == NULL)
4597
nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4599
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4603
if (type != Inkscape::NodePath::NODE_NONE) {
4604
// use the type from sodipodi:nodetypes
4607
if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4608
// points are (almost) collinear
4609
if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4610
// endnode, or a node with a retracted handle
4611
n->type = Inkscape::NodePath::NODE_CUSP;
4613
n->type = Inkscape::NodePath::NODE_SMOOTH;
4616
n->type = Inkscape::NodePath::NODE_CUSP;
4621
n->selected = FALSE;
4626
n->dragging_out = NULL;
4628
Inkscape::NodePath::Node *prev;
4630
//g_assert(g_list_find(sp->nodes, next));
4631
prev = next->p.other;
4649
n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions"));
4650
sp_knot_set_position(n->knot, *pos, 0);
4652
n->knot->setAnchor (GTK_ANCHOR_CENTER);
4653
n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4654
n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4656
sp_nodepath_update_node_knot(n);
4658
g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4659
g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4660
g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4661
g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4662
g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4663
sp_knot_show(n->knot);
4665
// We only create handle knots and lines on demand
4671
sp->nodes = g_list_prepend(sp->nodes, n);
4677
* Destroy node and its knots, link neighbors in subpath.
4679
static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4682
g_assert(node->subpath);
4683
g_assert(SP_IS_KNOT(node->knot));
4685
Inkscape::NodePath::SubPath *sp = node->subpath;
4687
if (node->selected) { // first, deselect
4688
g_assert(g_list_find(node->subpath->nodepath->selected, node));
4689
node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4692
node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4694
g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4695
g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4696
g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4697
g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4698
g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4699
g_object_unref(G_OBJECT(node->knot));
4702
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4703
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4704
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4705
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4706
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4707
g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4708
g_object_unref(G_OBJECT(node->p.knot));
4709
node->p.knot = NULL;
4713
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4714
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4715
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4716
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4717
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4718
g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4719
g_object_unref(G_OBJECT(node->n.knot));
4720
node->n.knot = NULL;
4724
gtk_object_destroy(GTK_OBJECT(node->p.line));
4726
gtk_object_destroy(GTK_OBJECT(node->n.line));
4728
if (sp->nodes) { // there are others nodes on the subpath
4730
if (sp->first == node) {
4731
g_assert(sp->last == node);
4732
sp->first = node->n.other;
4733
sp->last = sp->first;
4735
node->p.other->n.other = node->n.other;
4736
node->n.other->p.other = node->p.other;
4738
if (sp->first == node) {
4739
sp->first = node->n.other;
4740
sp->first->code = NR_MOVETO;
4742
if (sp->last == node) sp->last = node->p.other;
4743
if (node->p.other) node->p.other->n.other = node->n.other;
4744
if (node->n.other) node->n.other->p.other = node->p.other;
4746
} else { // this was the last node on subpath
4747
sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4750
g_mem_chunk_free(nodechunk, node);
4754
* Returns one of the node's two sides.
4755
* \param which Indicates which side.
4756
* \return Pointer to previous node side if which==-1, next if which==1.
4758
static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4761
Inkscape::NodePath::NodeSide * result = 0;
4770
g_assert_not_reached();
4777
* Return the other side of the node, given one of its sides.
4779
static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4782
Inkscape::NodePath::NodeSide *result = 0;
4784
if (me == &node->p) {
4786
} else if (me == &node->n) {
4789
g_assert_not_reached();
4796
* Return NRPathcode on the given side of the node.
4798
static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4802
NRPathcode result = NR_END;
4803
if (me == &node->p) {
4804
if (node->p.other) {
4805
result = (NRPathcode)node->code;
4809
} else if (me == &node->n) {
4810
if (node->n.other) {
4811
result = (NRPathcode)node->n.other->code;
4816
g_assert_not_reached();
4823
* Return node with the given index
4825
Inkscape::NodePath::Node *
4826
sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4828
Inkscape::NodePath::Node *e = NULL;
4835
for (GList *l = nodepath->subpaths; l ; l=l->next) {
4837
Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4838
int n = g_list_length(sp->nodes);
4843
//if the piece belongs to this subpath grab it
4844
//otherwise move onto the next subpath
4847
for (int i = 0; i < index; ++i) {
4864
* Returns plain text meaning of node type.
4866
static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4868
unsigned retracted = 0;
4869
bool endnode = false;
4871
for (int which = -1; which <= 1; which += 2) {
4872
Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4873
if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4879
if (retracted == 0) {
4881
// TRANSLATORS: "end" is an adjective here (NOT a verb)
4882
return _("end node");
4884
switch (node->type) {
4885
case Inkscape::NodePath::NODE_CUSP:
4886
// TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4888
case Inkscape::NodePath::NODE_SMOOTH:
4889
// TRANSLATORS: "smooth" is an adjective here
4891
case Inkscape::NodePath::NODE_AUTO:
4893
case Inkscape::NodePath::NODE_SYMM:
4894
return _("symmetric");
4897
} else if (retracted == 1) {
4899
// TRANSLATORS: "end" is an adjective here (NOT a verb)
4900
return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4902
return _("one handle retracted (drag with <b>Shift</b> to extend)");
4905
return _("both handles retracted (drag with <b>Shift</b> to extend)");
4912
* Handles content of statusbar as long as node tool is active.
4915
sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4917
gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>< ></b> to scale, <b>[ ]</b> to rotate");
4918
gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4920
gint total_nodes = sp_nodepath_get_node_count(nodepath);
4921
gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4922
gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4923
gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4925
SPDesktop *desktop = NULL;
4927
desktop = nodepath->desktop;
4929
desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4932
SPEventContext *ec = desktop->event_context;
4935
Inkscape::MessageContext *mc = get_message_context(ec);
4938
inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4940
if (selected_nodes == 0) {
4941
Inkscape::Selection *sel = desktop->selection;
4942
if (!sel || sel->isEmpty()) {
4943
mc->setF(Inkscape::NORMAL_MESSAGE,
4944
_("Select a single object to edit its nodes or handles."));
4947
mc->setF(Inkscape::NORMAL_MESSAGE,
4948
ngettext("<b>0</b> out of <b>%i</b> node selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
4949
"<b>0</b> out of <b>%i</b> nodes selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
4953
if (g_slist_length((GSList *)sel->itemList()) == 1) {
4954
mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4956
mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4960
} else if (nodepath && selected_nodes == 1) {
4961
mc->setF(Inkscape::NORMAL_MESSAGE,
4962
ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4963
"<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4965
selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4967
if (selected_subpaths > 1) {
4968
mc->setF(Inkscape::NORMAL_MESSAGE,
4969
ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4970
"<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4972
selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4974
mc->setF(Inkscape::NORMAL_MESSAGE,
4975
ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4976
"<b>%i</b> of <b>%i</b> nodes selected. %s.",
4978
selected_nodes, total_nodes, when_selected);
4984
* returns a *copy* of the curve of that object.
4986
SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4990
SPCurve *curve = NULL;
4991
if (SP_IS_PATH(object)) {
4992
SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4993
curve = curve_new->copy();
4994
} else if ( IS_LIVEPATHEFFECT(object) && key) {
4995
const gchar *svgd = object->repr->attribute(key);
4997
Geom::PathVector pv = sp_svg_read_pathv(svgd);
4998
SPCurve *curve_new = new SPCurve(pv);
5000
curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
5008
void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
5009
if (!np || !np->object || !curve)
5012
if (SP_IS_PATH(np->object)) {
5013
if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
5014
sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
5016
sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
5018
} else if ( IS_LIVEPATHEFFECT(np->object) ) {
5019
Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
5021
Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
5023
pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
5024
np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
5032
sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
5033
return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5038
/// \todo this code to generate a helper canvasitem from an spcurve should be moved to different file
5040
sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color = 0xff0000ff) {
5041
SPCurve *flash_curve = curve->copy();
5042
flash_curve->transform(i2d);
5043
SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5044
// would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5045
// unless we also flash the nodes...
5046
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5047
sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5048
sp_canvas_item_show(canvasitem);
5049
flash_curve->unref();
5054
sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item) {
5055
if (!item || !desktop) {
5059
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5060
guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5062
Geom::Matrix i2d = sp_item_i2d_affine(item);
5064
SPCurve *curve = NULL;
5065
if (SP_IS_PATH(item)) {
5066
curve = sp_path_get_curve_for_edit(SP_PATH(item));
5067
} else if ( SP_IS_SHAPE(item) && SP_SHAPE(item)->curve ) {
5068
curve = sp_shape_get_curve (SP_SHAPE(item));
5069
} else if ( SP_IS_TEXT(item) ) {
5070
// do not display helperpath for text - we cannot do anything with it in Node tool anyway
5071
// curve = SP_TEXT(item)->getNormalizedBpath();
5074
g_warning ("-----> sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item): TODO: generate the helper path for this item type!\n");
5078
SPCanvasItem * helperpath = sp_nodepath_generate_helperpath(desktop, curve, i2d, color);
5086
// TODO: Merge this with sp_nodepath_make_helper_item()!
5087
void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5088
np->show_helperpath = show;
5091
SPCurve *helper_curve = np->curve->copy();
5092
helper_curve->transform(np->i2d);
5093
if (!np->helper_path) {
5094
//np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5096
np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5097
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5098
sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5099
sp_canvas_item_move_to_z(np->helper_path, 0);
5100
sp_canvas_item_show(np->helper_path);
5102
sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5104
helper_curve->unref();
5106
if (np->helper_path) {
5107
GtkObject *temp = np->helper_path;
5108
np->helper_path = NULL;
5109
gtk_object_destroy(temp);
5114
/* sp_nodepath_make_straight_path:
5115
* Prevents user from curving the path by dragging a segment or activating handles etc.
5116
* The resulting path is a linear interpolation between nodal points, with only straight segments.
5117
* !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5119
void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5120
np->straight_path = true;
5121
np->show_handles = false;
5122
g_message("add code to make the path straight.");
5123
// do sp_nodepath_convert_node_type on all nodes?
5124
// coding tip: search for this text : "Make selected segments lines"
5130
c-file-style:"stroustrup"
5131
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5132
indent-tabs-mode:nil
5136
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :