1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
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 "libnr/n-art-bpath.h"
25 #include "libnr/nr-path.h"
26 #include "helper/units.h"
27 #include "knot.h"
28 #include "inkscape.h"
29 #include "document.h"
30 #include "sp-namedview.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "snap.h"
34 #include "message-stack.h"
35 #include "message-context.h"
36 #include "node-context.h"
37 #include "shape-editor.h"
38 #include "selection-chemistry.h"
39 #include "selection.h"
40 #include "xml/repr.h"
41 #include "prefs-utils.h"
42 #include "sp-metrics.h"
43 #include "sp-path.h"
44 #include "libnr/nr-matrix-ops.h"
45 #include "splivarot.h"
46 #include "svg/svg.h"
47 #include "verbs.h"
48 #include "display/bezier-utils.h"
49 #include <vector>
50 #include <algorithm>
51 #include <cstring>
52 #include <string>
53 #include "live_effects/lpeobject.h"
54 #include "live_effects/parameter/parameter.h"
55 #include "util/mathfns.h"
56 #include "display/snap-indicator.h"
57 #include "snapped-point.h"
59 class NR::Matrix;
61 /// \todo
62 /// evil evil evil. FIXME: conflict of two different Path classes!
63 /// There is a conflict in the namespace between two classes named Path.
64 /// #include "sp-flowtext.h"
65 /// #include "sp-flowregion.h"
67 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
68 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
69 GType sp_flowregion_get_type (void);
70 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
71 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
72 GType sp_flowtext_get_type (void);
73 // end evil workaround
75 #include "helper/stlport.h"
78 /// \todo fixme: Implement these via preferences */
80 #define NODE_FILL 0xbfbfbf00
81 #define NODE_STROKE 0x000000ff
82 #define NODE_FILL_HI 0xff000000
83 #define NODE_STROKE_HI 0x000000ff
84 #define NODE_FILL_SEL 0x0000ffff
85 #define NODE_STROKE_SEL 0x000000ff
86 #define NODE_FILL_SEL_HI 0xff000000
87 #define NODE_STROKE_SEL_HI 0x000000ff
88 #define KNOT_FILL 0xffffffff
89 #define KNOT_STROKE 0x000000ff
90 #define KNOT_FILL_HI 0xff000000
91 #define KNOT_STROKE_HI 0x000000ff
93 static GMemChunk *nodechunk = NULL;
95 /* Creation from object */
97 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
98 static gchar *parse_nodetypes(gchar const *types, gint length);
100 /* Object updating */
102 static void stamp_repr(Inkscape::NodePath::Path *np);
103 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
104 static gchar *create_typestr(Inkscape::NodePath::Path *np);
106 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
108 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
110 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
112 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
114 /* Adjust handle placement, if the node or the other handle is moved */
115 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
116 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
118 /* Node event callbacks */
119 static void node_clicked(SPKnot *knot, guint state, gpointer data);
120 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
121 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
122 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
124 /* Handle event callbacks */
125 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
126 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
127 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
128 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
129 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
130 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
132 /* Constructors and destructors */
134 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
135 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
136 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
137 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
138 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
139 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
140 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
142 /* Helpers */
144 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
145 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
146 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
148 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
149 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
151 // active_node indicates mouseover node
152 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
154 /**
155 * \brief Creates new nodepath from item
156 */
157 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
158 {
159 Inkscape::XML::Node *repr = object->repr;
161 /** \todo
162 * FIXME: remove this. We don't want to edit paths inside flowtext.
163 * Instead we will build our flowtext with cloned paths, so that the
164 * real paths are outside the flowtext and thus editable as usual.
165 */
166 if (SP_IS_FLOWTEXT(object)) {
167 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
168 if SP_IS_FLOWREGION(child) {
169 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
170 if (grandchild && SP_IS_PATH(grandchild)) {
171 object = SP_ITEM(grandchild);
172 break;
173 }
174 }
175 }
176 }
178 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
180 if (curve == NULL)
181 return NULL;
183 NArtBpath *bpath = sp_curve_first_bpath(curve);
184 gint length = curve->end;
185 if (length == 0) {
186 sp_curve_unref(curve);
187 return NULL; // prevent crash for one-node paths
188 }
190 //Create new nodepath
191 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
192 if (!np) {
193 sp_curve_unref(curve);
194 return NULL;
195 }
197 // Set defaults
198 np->desktop = desktop;
199 np->object = object;
200 np->subpaths = NULL;
201 np->selected = NULL;
202 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
203 np->livarot_path = NULL;
204 np->local_change = 0;
205 np->show_handles = show_handles;
206 np->helper_path = NULL;
207 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
208 np->helperpath_width = 1.0;
209 np->curve = sp_curve_copy(curve);
210 np->show_helperpath = prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1;
211 np->straight_path = false;
212 if (IS_LIVEPATHEFFECT(object) && item) {
213 np->item = item;
214 } else {
215 np->item = SP_ITEM(object);
216 }
218 // we need to update item's transform from the repr here,
219 // because they may be out of sync when we respond
220 // to a change in repr by regenerating nodepath --bb
221 sp_object_read_attr(SP_OBJECT(np->item), "transform");
223 np->i2d = sp_item_i2d_affine(np->item);
224 np->d2i = np->i2d.inverse();
226 np->repr = repr;
227 if (repr_key_in) { // apparantly the object is an LPEObject
228 np->repr_key = g_strdup(repr_key_in);
229 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
230 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
231 if (lpeparam) {
232 lpeparam->param_setup_nodepath(np);
233 }
234 } else {
235 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
236 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
237 np->repr_key = g_strdup("inkscape:original-d");
239 LivePathEffectObject *lpeobj = sp_lpe_item_get_livepatheffectobject(SP_LPE_ITEM(np->object));
240 if (lpeobj && lpeobj->lpe) {
241 lpeobj->lpe->setup_nodepath(np);
242 }
243 } else {
244 np->repr_key = g_strdup("d");
245 }
246 }
248 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
249 gchar *typestr = parse_nodetypes(nodetypes, length);
251 // create the subpath(s) from the bpath
252 NArtBpath *b = bpath;
253 while (b->code != NR_END) {
254 b = subpath_from_bpath(np, b, typestr + (b - bpath));
255 }
257 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
258 np->subpaths = g_list_reverse(np->subpaths);
260 g_free(typestr);
261 sp_curve_unref(curve);
263 // create the livarot representation from the same item
264 sp_nodepath_ensure_livarot_path(np);
266 // Draw helper curve
267 if (np->show_helperpath) {
268 SPCurve *helper_curve = sp_curve_copy(np->curve);
269 sp_curve_transform(helper_curve, np->i2d );
270 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
271 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);
272 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
273 sp_canvas_item_move_to_z(np->helper_path, 0);
274 sp_canvas_item_show(np->helper_path);
275 sp_curve_unref(helper_curve);
276 }
278 return np;
279 }
281 /**
282 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
283 */
284 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
286 if (!np) //soft fail, like delete
287 return;
289 while (np->subpaths) {
290 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
291 }
293 //Inform the ShapeEditor that made me, if any, that I am gone.
294 if (np->shape_editor)
295 np->shape_editor->nodepath_destroyed();
297 g_assert(!np->selected);
299 if (np->livarot_path) {
300 delete np->livarot_path;
301 np->livarot_path = NULL;
302 }
304 if (np->helper_path) {
305 GtkObject *temp = np->helper_path;
306 np->helper_path = NULL;
307 gtk_object_destroy(temp);
308 }
309 if (np->curve) {
310 sp_curve_unref(np->curve);
311 np->curve = NULL;
312 }
314 if (np->repr_key) {
315 g_free(np->repr_key);
316 np->repr_key = NULL;
317 }
318 if (np->repr_nodetypes_key) {
319 g_free(np->repr_nodetypes_key);
320 np->repr_nodetypes_key = NULL;
321 }
323 np->desktop = NULL;
325 g_free(np);
326 }
329 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
330 {
331 if (np && np->livarot_path == NULL) {
332 SPCurve *curve = create_curve(np);
333 NArtBpath *bpath = SP_CURVE_BPATH(curve);
334 np->livarot_path = bpath_to_Path(bpath);
336 if (np->livarot_path)
337 np->livarot_path->ConvertWithBackData(0.01);
339 sp_curve_unref(curve);
340 }
341 }
344 /**
345 * Return the node count of a given NodeSubPath.
346 */
347 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
348 {
349 if (!subpath)
350 return 0;
351 gint nodeCount = g_list_length(subpath->nodes);
352 return nodeCount;
353 }
355 /**
356 * Return the node count of a given NodePath.
357 */
358 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
359 {
360 if (!np)
361 return 0;
362 gint nodeCount = 0;
363 for (GList *item = np->subpaths ; item ; item=item->next) {
364 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
365 nodeCount += g_list_length(subpath->nodes);
366 }
367 return nodeCount;
368 }
370 /**
371 * Return the subpath count of a given NodePath.
372 */
373 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
374 {
375 if (!np)
376 return 0;
377 return g_list_length (np->subpaths);
378 }
380 /**
381 * Return the selected node count of a given NodePath.
382 */
383 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
384 {
385 if (!np)
386 return 0;
387 return g_list_length (np->selected);
388 }
390 /**
391 * Return the number of subpaths where nodes are selected in a given NodePath.
392 */
393 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
394 {
395 if (!np)
396 return 0;
397 if (!np->selected)
398 return 0;
399 if (!np->selected->next)
400 return 1;
401 gint count = 0;
402 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
403 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
404 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
405 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
406 if (node->selected) {
407 count ++;
408 break;
409 }
410 }
411 }
412 return count;
413 }
415 /**
416 * Clean up a nodepath after editing.
417 *
418 * Currently we are deleting trivial subpaths.
419 */
420 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
421 {
422 GList *badSubPaths = NULL;
424 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
425 for (GList *l = nodepath->subpaths; l ; l=l->next) {
426 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
427 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
428 badSubPaths = g_list_append(badSubPaths, sp);
429 }
431 //Delete them. This second step is because sp_nodepath_subpath_destroy()
432 //also removes the subpath from nodepath->subpaths
433 for (GList *l = badSubPaths; l ; l=l->next) {
434 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
435 sp_nodepath_subpath_destroy(sp);
436 }
438 g_list_free(badSubPaths);
439 }
441 /**
442 * Create new nodepath from b, make it subpath of np.
443 * \param t The node type.
444 * \todo Fixme: t should be a proper type, rather than gchar
445 */
446 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
447 {
448 NR::Point ppos, pos, npos;
450 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
452 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
453 bool const closed = (b->code == NR_MOVETO);
455 pos = NR::Point(b->x3, b->y3) * np->i2d;
456 if (b[1].code == NR_CURVETO) {
457 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
458 } else {
459 npos = pos;
460 }
461 Inkscape::NodePath::Node *n;
462 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
463 g_assert(sp->first == n);
464 g_assert(sp->last == n);
466 b++;
467 t++;
468 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
469 pos = NR::Point(b->x3, b->y3) * np->i2d;
470 if (b->code == NR_CURVETO) {
471 ppos = NR::Point(b->x2, b->y2) * np->i2d;
472 } else {
473 ppos = pos;
474 }
475 if (b[1].code == NR_CURVETO) {
476 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
477 } else {
478 npos = pos;
479 }
480 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
481 b++;
482 t++;
483 }
485 if (closed) sp_nodepath_subpath_close(sp);
487 return b;
488 }
490 /**
491 * Convert from sodipodi:nodetypes to new style type string.
492 */
493 static gchar *parse_nodetypes(gchar const *types, gint length)
494 {
495 g_assert(length > 0);
497 gchar *typestr = g_new(gchar, length + 1);
499 gint pos = 0;
501 if (types) {
502 for (gint i = 0; types[i] && ( i < length ); i++) {
503 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
504 if (types[i] != '\0') {
505 switch (types[i]) {
506 case 's':
507 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
508 break;
509 case 'z':
510 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
511 break;
512 case 'c':
513 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
514 break;
515 default:
516 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
517 break;
518 }
519 }
520 }
521 }
523 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
525 return typestr;
526 }
528 /**
529 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
530 * updated but repr is not (for speed). Used during curve and node drag.
531 */
532 static void update_object(Inkscape::NodePath::Path *np)
533 {
534 g_assert(np);
536 sp_curve_unref(np->curve);
537 np->curve = create_curve(np);
539 sp_nodepath_set_curve(np, np->curve);
541 if (np->show_helperpath) {
542 SPCurve * helper_curve = sp_curve_copy(np->curve);
543 sp_curve_transform(helper_curve, np->i2d );
544 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
545 sp_curve_unref(helper_curve);
546 }
547 }
549 /**
550 * Update XML path node with data from path object.
551 */
552 static void update_repr_internal(Inkscape::NodePath::Path *np)
553 {
554 g_assert(np);
556 Inkscape::XML::Node *repr = np->object->repr;
558 sp_curve_unref(np->curve);
559 np->curve = create_curve(np);
561 gchar *typestr = create_typestr(np);
562 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
564 // determine if path has an effect applied and write to correct "d" attribute.
565 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
566 np->local_change++;
567 repr->setAttribute(np->repr_key, svgpath);
568 }
570 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
571 np->local_change++;
572 repr->setAttribute(np->repr_nodetypes_key, typestr);
573 }
575 g_free(svgpath);
576 g_free(typestr);
578 if (np->show_helperpath) {
579 SPCurve * helper_curve = sp_curve_copy(np->curve);
580 sp_curve_transform(helper_curve, np->i2d );
581 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
582 sp_curve_unref(helper_curve);
583 }
584 }
586 /**
587 * Update XML path node with data from path object, commit changes forever.
588 */
589 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
590 {
591 //fixme: np can be NULL, so check before proceeding
592 g_return_if_fail(np != NULL);
594 if (np->livarot_path) {
595 delete np->livarot_path;
596 np->livarot_path = NULL;
597 }
599 update_repr_internal(np);
600 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
602 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
603 annotation);
604 }
606 /**
607 * Update XML path node with data from path object, commit changes with undo.
608 */
609 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
610 {
611 if (np->livarot_path) {
612 delete np->livarot_path;
613 np->livarot_path = NULL;
614 }
616 update_repr_internal(np);
617 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
618 annotation);
619 }
621 /**
622 * Make duplicate of path, replace corresponding XML node in tree, commit.
623 */
624 static void stamp_repr(Inkscape::NodePath::Path *np)
625 {
626 g_assert(np);
628 Inkscape::XML::Node *old_repr = np->object->repr;
629 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
631 // remember the position of the item
632 gint pos = old_repr->position();
633 // remember parent
634 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
636 SPCurve *curve = create_curve(np);
637 gchar *typestr = create_typestr(np);
639 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
641 new_repr->setAttribute(np->repr_key, svgpath);
642 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
644 // add the new repr to the parent
645 parent->appendChild(new_repr);
646 // move to the saved position
647 new_repr->setPosition(pos > 0 ? pos : 0);
649 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
650 _("Stamp"));
652 Inkscape::GC::release(new_repr);
653 g_free(svgpath);
654 g_free(typestr);
655 sp_curve_unref(curve);
656 }
658 /**
659 * Create curve from path.
660 */
661 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
662 {
663 SPCurve *curve = sp_curve_new();
665 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
666 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
667 sp_curve_moveto(curve,
668 sp->first->pos * np->d2i);
669 Inkscape::NodePath::Node *n = sp->first->n.other;
670 while (n) {
671 NR::Point const end_pt = n->pos * np->d2i;
672 switch (n->code) {
673 case NR_LINETO:
674 sp_curve_lineto(curve, end_pt);
675 break;
676 case NR_CURVETO:
677 sp_curve_curveto(curve,
678 n->p.other->n.pos * np->d2i,
679 n->p.pos * np->d2i,
680 end_pt);
681 break;
682 default:
683 g_assert_not_reached();
684 break;
685 }
686 if (n != sp->last) {
687 n = n->n.other;
688 } else {
689 n = NULL;
690 }
691 }
692 if (sp->closed) {
693 sp_curve_closepath(curve);
694 }
695 }
697 return curve;
698 }
700 /**
701 * Convert path type string to sodipodi:nodetypes style.
702 */
703 static gchar *create_typestr(Inkscape::NodePath::Path *np)
704 {
705 gchar *typestr = g_new(gchar, 32);
706 gint len = 32;
707 gint pos = 0;
709 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
710 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
712 if (pos >= len) {
713 typestr = g_renew(gchar, typestr, len + 32);
714 len += 32;
715 }
717 typestr[pos++] = 'c';
719 Inkscape::NodePath::Node *n;
720 n = sp->first->n.other;
721 while (n) {
722 gchar code;
724 switch (n->type) {
725 case Inkscape::NodePath::NODE_CUSP:
726 code = 'c';
727 break;
728 case Inkscape::NodePath::NODE_SMOOTH:
729 code = 's';
730 break;
731 case Inkscape::NodePath::NODE_SYMM:
732 code = 'z';
733 break;
734 default:
735 g_assert_not_reached();
736 code = '\0';
737 break;
738 }
740 if (pos >= len) {
741 typestr = g_renew(gchar, typestr, len + 32);
742 len += 32;
743 }
745 typestr[pos++] = code;
747 if (n != sp->last) {
748 n = n->n.other;
749 } else {
750 n = NULL;
751 }
752 }
753 }
755 if (pos >= len) {
756 typestr = g_renew(gchar, typestr, len + 1);
757 len += 1;
758 }
760 typestr[pos++] = '\0';
762 return typestr;
763 }
765 /**
766 * Returns current path in context. // later eliminate this function at all!
767 */
768 static Inkscape::NodePath::Path *sp_nodepath_current()
769 {
770 if (!SP_ACTIVE_DESKTOP) {
771 return NULL;
772 }
774 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
776 if (!SP_IS_NODE_CONTEXT(event_context)) {
777 return NULL;
778 }
780 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
781 }
785 /**
786 \brief Fills node and handle positions for three nodes, splitting line
787 marked by end at distance t.
788 */
789 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
790 {
791 g_assert(new_path != NULL);
792 g_assert(end != NULL);
794 g_assert(end->p.other == new_path);
795 Inkscape::NodePath::Node *start = new_path->p.other;
796 g_assert(start);
798 if (end->code == NR_LINETO) {
799 new_path->type =Inkscape::NodePath::NODE_CUSP;
800 new_path->code = NR_LINETO;
801 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
802 } else {
803 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
804 new_path->code = NR_CURVETO;
805 gdouble s = 1 - t;
806 for (int dim = 0; dim < 2; dim++) {
807 NR::Coord const f000 = start->pos[dim];
808 NR::Coord const f001 = start->n.pos[dim];
809 NR::Coord const f011 = end->p.pos[dim];
810 NR::Coord const f111 = end->pos[dim];
811 NR::Coord const f00t = s * f000 + t * f001;
812 NR::Coord const f01t = s * f001 + t * f011;
813 NR::Coord const f11t = s * f011 + t * f111;
814 NR::Coord const f0tt = s * f00t + t * f01t;
815 NR::Coord const f1tt = s * f01t + t * f11t;
816 NR::Coord const fttt = s * f0tt + t * f1tt;
817 start->n.pos[dim] = f00t;
818 new_path->p.pos[dim] = f0tt;
819 new_path->pos[dim] = fttt;
820 new_path->n.pos[dim] = f1tt;
821 end->p.pos[dim] = f11t;
822 }
823 }
824 }
826 /**
827 * Adds new node on direct line between two nodes, activates handles of all
828 * three nodes.
829 */
830 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
831 {
832 g_assert(end);
833 g_assert(end->subpath);
834 g_assert(g_list_find(end->subpath->nodes, end));
836 Inkscape::NodePath::Node *start = end->p.other;
837 g_assert( start->n.other == end );
838 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
839 end,
840 (NRPathcode)end->code == NR_LINETO?
841 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
842 (NRPathcode)end->code,
843 &start->pos, &start->pos, &start->n.pos);
844 sp_nodepath_line_midpoint(newnode, end, t);
846 sp_node_adjust_handles(start);
847 sp_node_update_handles(start);
848 sp_node_update_handles(newnode);
849 sp_node_adjust_handles(end);
850 sp_node_update_handles(end);
852 return newnode;
853 }
855 /**
856 \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
857 */
858 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
859 {
860 g_assert(node);
861 g_assert(node->subpath);
862 g_assert(g_list_find(node->subpath->nodes, node));
864 Inkscape::NodePath::SubPath *sp = node->subpath;
865 Inkscape::NodePath::Path *np = sp->nodepath;
867 if (sp->closed) {
868 sp_nodepath_subpath_open(sp, node);
869 return sp->first;
870 } else {
871 // no break for end nodes
872 if (node == sp->first) return NULL;
873 if (node == sp->last ) return NULL;
875 // create a new subpath
876 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
878 // duplicate the break node as start of the new subpath
879 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
881 while (node->n.other) { // copy the remaining nodes into the new subpath
882 Inkscape::NodePath::Node *n = node->n.other;
883 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
884 if (n->selected) {
885 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
886 }
887 sp_nodepath_node_destroy(n); // remove the point on the original subpath
888 }
890 return newnode;
891 }
892 }
894 /**
895 * Duplicate node and connect to neighbours.
896 */
897 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
898 {
899 g_assert(node);
900 g_assert(node->subpath);
901 g_assert(g_list_find(node->subpath->nodes, node));
903 Inkscape::NodePath::SubPath *sp = node->subpath;
905 NRPathcode code = (NRPathcode) node->code;
906 if (code == NR_MOVETO) { // if node is the endnode,
907 node->code = NR_LINETO; // new one is inserted before it, so change that to line
908 }
910 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
912 if (!node->n.other || !node->p.other) // if node is an endnode, select it
913 return node;
914 else
915 return newnode; // otherwise select the newly created node
916 }
918 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
919 {
920 node->p.pos = (node->pos + (node->pos - node->n.pos));
921 }
923 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
924 {
925 node->n.pos = (node->pos + (node->pos - node->p.pos));
926 }
928 /**
929 * Change line type at node, with side effects on neighbours.
930 */
931 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
932 {
933 g_assert(end);
934 g_assert(end->subpath);
935 g_assert(end->p.other);
937 if (end->code == static_cast< guint > ( code ) )
938 return;
940 Inkscape::NodePath::Node *start = end->p.other;
942 end->code = code;
944 if (code == NR_LINETO) {
945 if (start->code == NR_LINETO) {
946 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
947 }
948 if (end->n.other) {
949 if (end->n.other->code == NR_LINETO) {
950 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
951 }
952 }
953 } else {
954 NR::Point delta = end->pos - start->pos;
955 start->n.pos = start->pos + delta / 3;
956 end->p.pos = end->pos - delta / 3;
957 sp_node_adjust_handle(start, 1);
958 sp_node_adjust_handle(end, -1);
959 }
961 sp_node_update_handles(start);
962 sp_node_update_handles(end);
963 }
965 /**
966 * Change node type, and its handles accordingly.
967 */
968 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
969 {
970 g_assert(node);
971 g_assert(node->subpath);
973 if ((node->p.other != NULL) && (node->n.other != NULL)) {
974 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
975 type =Inkscape::NodePath::NODE_CUSP;
976 }
977 }
979 node->type = type;
981 if (node->type == Inkscape::NodePath::NODE_CUSP) {
982 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
983 node->knot->setSize (node->selected? 11 : 9);
984 sp_knot_update_ctrl(node->knot);
985 } else {
986 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
987 node->knot->setSize (node->selected? 9 : 7);
988 sp_knot_update_ctrl(node->knot);
989 }
991 // if one of handles is mouseovered, preserve its position
992 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
993 sp_node_adjust_handle(node, 1);
994 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
995 sp_node_adjust_handle(node, -1);
996 } else {
997 sp_node_adjust_handles(node);
998 }
1000 sp_node_update_handles(node);
1002 sp_nodepath_update_statusbar(node->subpath->nodepath);
1004 return node;
1005 }
1007 bool
1008 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1009 {
1010 Inkscape::NodePath::Node *othernode = side->other;
1011 if (!othernode)
1012 return false;
1013 NRPathcode const code = sp_node_path_code_from_side(node, side);
1014 if (code == NR_LINETO)
1015 return true;
1016 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1017 if (&node->p == side) {
1018 other_to_me = &othernode->n;
1019 } else if (&node->n == side) {
1020 other_to_me = &othernode->p;
1021 }
1022 if (!other_to_me)
1023 return false;
1024 bool is_line =
1025 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1026 NR::L2(node->pos - side->pos) < 1e-6);
1027 return is_line;
1028 }
1030 /**
1031 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1032 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1033 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1034 * If already cusp and set to cusp, retracts handles.
1035 */
1036 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1037 {
1038 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1040 /*
1041 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1043 if (two_handles) {
1044 // do nothing, adjust_handles called via set_node_type will line them up
1045 } else if (one_handle) {
1046 if (opposite_to_handle_is_line) {
1047 if (lined_up) {
1048 // already half-smooth; pull opposite handle too making it fully smooth
1049 } else {
1050 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1051 }
1052 } else {
1053 // pull opposite handle in line with the existing one
1054 }
1055 } else if (no_handles) {
1056 if (both_segments_are_lines OR both_segments_are_curves) {
1057 //pull both handles
1058 } else {
1059 // pull the handle opposite to line segment, making node half-smooth
1060 }
1061 }
1062 */
1063 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1064 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1065 bool p_is_line = sp_node_side_is_line(node, &node->p);
1066 bool n_is_line = sp_node_side_is_line(node, &node->n);
1068 if (p_has_handle && n_has_handle) {
1069 // do nothing, adjust_handles will line them up
1070 } else if (p_has_handle || n_has_handle) {
1071 if (p_has_handle && n_is_line) {
1072 Radial line (node->n.other->pos - node->pos);
1073 Radial handle (node->pos - node->p.pos);
1074 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1075 // already half-smooth; pull opposite handle too making it fully smooth
1076 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1077 } else {
1078 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1079 }
1080 } else if (n_has_handle && p_is_line) {
1081 Radial line (node->p.other->pos - node->pos);
1082 Radial handle (node->pos - node->n.pos);
1083 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1084 // already half-smooth; pull opposite handle too making it fully smooth
1085 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1086 } else {
1087 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1088 }
1089 } else if (p_has_handle && node->n.other) {
1090 // pull n handle
1091 node->n.other->code = NR_CURVETO;
1092 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1093 NR::L2(node->p.pos - node->pos) :
1094 NR::L2(node->n.other->pos - node->pos) / 3;
1095 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1096 } else if (n_has_handle && node->p.other) {
1097 // pull p handle
1098 node->code = NR_CURVETO;
1099 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1100 NR::L2(node->n.pos - node->pos) :
1101 NR::L2(node->p.other->pos - node->pos) / 3;
1102 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1103 }
1104 } else if (!p_has_handle && !n_has_handle) {
1105 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1106 // no handles, but both segments are either lnes or curves:
1107 //pull both handles
1109 // convert both to curves:
1110 node->code = NR_CURVETO;
1111 node->n.other->code = NR_CURVETO;
1113 NR::Point leg_prev = node->pos - node->p.other->pos;
1114 NR::Point leg_next = node->pos - node->n.other->pos;
1116 double norm_leg_prev = L2(leg_prev);
1117 double norm_leg_next = L2(leg_next);
1119 NR::Point delta;
1120 if (norm_leg_next > 0.0) {
1121 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1122 (&delta)->normalize();
1123 }
1125 if (type == Inkscape::NodePath::NODE_SYMM) {
1126 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1127 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1128 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1129 } else {
1130 // length of handle is proportional to distance to adjacent node
1131 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1132 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1133 }
1135 } else {
1136 // pull the handle opposite to line segment, making it half-smooth
1137 if (p_is_line && node->n.other) {
1138 if (type != Inkscape::NodePath::NODE_SYMM) {
1139 // pull n handle
1140 node->n.other->code = NR_CURVETO;
1141 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1142 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1143 }
1144 } else if (n_is_line && node->p.other) {
1145 if (type != Inkscape::NodePath::NODE_SYMM) {
1146 // pull p handle
1147 node->code = NR_CURVETO;
1148 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1149 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1150 }
1151 }
1152 }
1153 }
1154 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1155 // cusping a cusp: retract nodes
1156 node->p.pos = node->pos;
1157 node->n.pos = node->pos;
1158 }
1160 sp_nodepath_set_node_type (node, type);
1161 }
1163 /**
1164 * Move node to point, and adjust its and neighbouring handles.
1165 */
1166 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1167 {
1168 NR::Point delta = p - node->pos;
1169 node->pos = p;
1171 node->p.pos += delta;
1172 node->n.pos += delta;
1174 Inkscape::NodePath::Node *node_p = NULL;
1175 Inkscape::NodePath::Node *node_n = NULL;
1177 if (node->p.other) {
1178 if (node->code == NR_LINETO) {
1179 sp_node_adjust_handle(node, 1);
1180 sp_node_adjust_handle(node->p.other, -1);
1181 node_p = node->p.other;
1182 }
1183 }
1184 if (node->n.other) {
1185 if (node->n.other->code == NR_LINETO) {
1186 sp_node_adjust_handle(node, -1);
1187 sp_node_adjust_handle(node->n.other, 1);
1188 node_n = node->n.other;
1189 }
1190 }
1192 // this function is only called from batch movers that will update display at the end
1193 // themselves, so here we just move all the knots without emitting move signals, for speed
1194 sp_node_update_handles(node, false);
1195 if (node_n) {
1196 sp_node_update_handles(node_n, false);
1197 }
1198 if (node_p) {
1199 sp_node_update_handles(node_p, false);
1200 }
1201 }
1203 /**
1204 * Call sp_node_moveto() for node selection and handle possible snapping.
1205 */
1206 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1207 bool const snap = true)
1208 {
1209 NR::Coord best = NR_HUGE;
1210 NR::Point delta(dx, dy);
1211 NR::Point best_pt = delta;
1212 Inkscape::SnappedPoint best_abs;
1215 if (snap) {
1216 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1217 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1218 * must provide that information. */
1220 // Build a list of the unselected nodes to which the snapper should snap
1221 std::vector<NR::Point> unselected_nodes;
1222 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1223 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1224 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1225 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1226 if (!node->selected) {
1227 unselected_nodes.push_back(node->pos);
1228 }
1229 }
1230 }
1232 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1234 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1235 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1236 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1237 if (s.getDistance() < best) {
1238 best = s.getDistance();
1239 best_abs = s;
1240 best_pt = s.getPoint() - n->pos;
1241 }
1242 }
1244 if (best_abs.getSnapped()) {
1245 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1246 }
1247 }
1249 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1250 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1251 sp_node_moveto(n, n->pos + best_pt);
1252 }
1254 // do not update repr here so that node dragging is acceptably fast
1255 update_object(nodepath);
1256 }
1258 /**
1259 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1260 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1261 near x = 0.
1262 */
1263 double
1264 sculpt_profile (double x, double alpha, guint profile)
1265 {
1266 if (x >= 1)
1267 return 0;
1268 if (x <= 0)
1269 return 1;
1271 switch (profile) {
1272 case SCULPT_PROFILE_LINEAR:
1273 return 1 - x;
1274 case SCULPT_PROFILE_BELL:
1275 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1276 case SCULPT_PROFILE_ELLIPTIC:
1277 return sqrt(1 - x*x);
1278 }
1280 return 1;
1281 }
1283 double
1284 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1285 {
1286 // extremely primitive for now, don't have time to look for the real one
1287 double lower = NR::L2(b - a);
1288 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1289 return (lower + upper)/2;
1290 }
1292 void
1293 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1294 {
1295 n->pos = n->origin + delta;
1296 n->n.pos = n->n.origin + delta_n;
1297 n->p.pos = n->p.origin + delta_p;
1298 sp_node_adjust_handles(n);
1299 sp_node_update_handles(n, false);
1300 }
1302 /**
1303 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1304 * on how far they are from the dragged node n.
1305 */
1306 static void
1307 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1308 {
1309 g_assert (n);
1310 g_assert (nodepath);
1311 g_assert (n->subpath->nodepath == nodepath);
1313 double pressure = n->knot->pressure;
1314 if (pressure == 0)
1315 pressure = 0.5; // default
1316 pressure = CLAMP (pressure, 0.2, 0.8);
1318 // map pressure to alpha = 1/5 ... 5
1319 double alpha = 1 - 2 * fabs(pressure - 0.5);
1320 if (pressure > 0.5)
1321 alpha = 1/alpha;
1323 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1325 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1326 // Only one subpath has selected nodes:
1327 // use linear mode, where the distance from n to node being dragged is calculated along the path
1329 double n_sel_range = 0, p_sel_range = 0;
1330 guint n_nodes = 0, p_nodes = 0;
1331 guint n_sel_nodes = 0, p_sel_nodes = 0;
1333 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1334 {
1335 double n_range = 0, p_range = 0;
1336 bool n_going = true, p_going = true;
1337 Inkscape::NodePath::Node *n_node = n;
1338 Inkscape::NodePath::Node *p_node = n;
1339 do {
1340 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1341 if (n_node && n_going)
1342 n_node = n_node->n.other;
1343 if (n_node == NULL) {
1344 n_going = false;
1345 } else {
1346 n_nodes ++;
1347 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1348 if (n_node->selected) {
1349 n_sel_nodes ++;
1350 n_sel_range = n_range;
1351 }
1352 if (n_node == p_node) {
1353 n_going = false;
1354 p_going = false;
1355 }
1356 }
1357 if (p_node && p_going)
1358 p_node = p_node->p.other;
1359 if (p_node == NULL) {
1360 p_going = false;
1361 } else {
1362 p_nodes ++;
1363 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1364 if (p_node->selected) {
1365 p_sel_nodes ++;
1366 p_sel_range = p_range;
1367 }
1368 if (p_node == n_node) {
1369 n_going = false;
1370 p_going = false;
1371 }
1372 }
1373 } while (n_going || p_going);
1374 }
1376 // Second pass: actually move nodes in this subpath
1377 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1378 {
1379 double n_range = 0, p_range = 0;
1380 bool n_going = true, p_going = true;
1381 Inkscape::NodePath::Node *n_node = n;
1382 Inkscape::NodePath::Node *p_node = n;
1383 do {
1384 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1385 if (n_node && n_going)
1386 n_node = n_node->n.other;
1387 if (n_node == NULL) {
1388 n_going = false;
1389 } else {
1390 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1391 if (n_node->selected) {
1392 sp_nodepath_move_node_and_handles (n_node,
1393 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1394 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1395 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1396 }
1397 if (n_node == p_node) {
1398 n_going = false;
1399 p_going = false;
1400 }
1401 }
1402 if (p_node && p_going)
1403 p_node = p_node->p.other;
1404 if (p_node == NULL) {
1405 p_going = false;
1406 } else {
1407 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1408 if (p_node->selected) {
1409 sp_nodepath_move_node_and_handles (p_node,
1410 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1411 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1412 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1413 }
1414 if (p_node == n_node) {
1415 n_going = false;
1416 p_going = false;
1417 }
1418 }
1419 } while (n_going || p_going);
1420 }
1422 } else {
1423 // Multiple subpaths have selected nodes:
1424 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1425 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1426 // fix the pear-like shape when sculpting e.g. a ring
1428 // First pass: calculate range
1429 gdouble direct_range = 0;
1430 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1431 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1432 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1433 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1434 if (node->selected) {
1435 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1436 }
1437 }
1438 }
1440 // Second pass: actually move nodes
1441 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1442 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1443 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1444 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1445 if (node->selected) {
1446 if (direct_range > 1e-6) {
1447 sp_nodepath_move_node_and_handles (node,
1448 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1449 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1450 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1451 } else {
1452 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1453 }
1455 }
1456 }
1457 }
1458 }
1460 // do not update repr here so that node dragging is acceptably fast
1461 update_object(nodepath);
1462 }
1465 /**
1466 * Move node selection to point, adjust its and neighbouring handles,
1467 * handle possible snapping, and commit the change with possible undo.
1468 */
1469 void
1470 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1471 {
1472 if (!nodepath) return;
1474 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1476 if (dx == 0) {
1477 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1478 } else if (dy == 0) {
1479 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1480 } else {
1481 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1482 }
1483 }
1485 /**
1486 * Move node selection off screen and commit the change.
1487 */
1488 void
1489 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1490 {
1491 // borrowed from sp_selection_move_screen in selection-chemistry.c
1492 // we find out the current zoom factor and divide deltas by it
1493 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1495 gdouble zoom = desktop->current_zoom();
1496 gdouble zdx = dx / zoom;
1497 gdouble zdy = dy / zoom;
1499 if (!nodepath) return;
1501 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1503 if (dx == 0) {
1504 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1505 } else if (dy == 0) {
1506 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1507 } else {
1508 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1509 }
1510 }
1512 /**
1513 * Move selected nodes to the absolute position given
1514 */
1515 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1516 {
1517 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1518 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1519 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1520 sp_node_moveto(n, npos);
1521 }
1523 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1524 }
1526 /**
1527 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1528 */
1529 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1530 {
1531 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1532 g_return_val_if_fail(nodepath->selected, no_coord);
1534 // determine coordinate of first selected node
1535 GList *nsel = nodepath->selected;
1536 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1537 NR::Coord coord = n->pos[axis];
1538 bool coincide = true;
1540 // compare it to the coordinates of all the other selected nodes
1541 for (GList *l = nsel->next; l != NULL; l = l->next) {
1542 n = (Inkscape::NodePath::Node *) l->data;
1543 if (n->pos[axis] != coord) {
1544 coincide = false;
1545 }
1546 }
1547 if (coincide) {
1548 return coord;
1549 } else {
1550 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1551 // currently we return the coordinate of the bounding box midpoint because I don't know how
1552 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1553 return bbox.midpoint()[axis];
1554 }
1555 }
1557 /** If they don't yet exist, creates knot and line for the given side of the node */
1558 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1559 {
1560 if (!side->knot) {
1561 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"));
1563 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1564 side->knot->setSize (7);
1565 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1566 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1567 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1568 sp_knot_update_ctrl(side->knot);
1570 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1571 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1572 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1573 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1574 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1575 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1576 }
1578 if (!side->line) {
1579 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1580 SP_TYPE_CTRLLINE, NULL);
1581 }
1582 }
1584 /**
1585 * Ensure the given handle of the node is visible/invisible, update its screen position
1586 */
1587 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1588 {
1589 g_assert(node != NULL);
1591 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1592 NRPathcode code = sp_node_path_code_from_side(node, side);
1594 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1596 if (show_handle) {
1597 if (!side->knot) { // No handle knot at all
1598 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1599 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1600 side->knot->pos = side->pos;
1601 if (side->knot->item)
1602 SP_CTRL(side->knot->item)->moveto(side->pos);
1603 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1604 sp_knot_show(side->knot);
1605 } else {
1606 if (side->knot->pos != side->pos) { // only if it's really moved
1607 if (fire_move_signals) {
1608 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1609 } else {
1610 sp_knot_moveto(side->knot, &side->pos);
1611 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1612 }
1613 }
1614 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1615 sp_knot_show(side->knot);
1616 }
1617 }
1618 sp_canvas_item_show(side->line);
1619 } else {
1620 if (side->knot) {
1621 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1622 sp_knot_hide(side->knot);
1623 }
1624 }
1625 if (side->line) {
1626 sp_canvas_item_hide(side->line);
1627 }
1628 }
1629 }
1631 /**
1632 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1633 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1634 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1635 * updated; otherwise, just move the knots silently (used in batch moves).
1636 */
1637 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1638 {
1639 g_assert(node != NULL);
1641 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1642 sp_knot_show(node->knot);
1643 }
1645 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1646 if (fire_move_signals)
1647 sp_knot_set_position(node->knot, &node->pos, 0);
1648 else
1649 sp_knot_moveto(node->knot, &node->pos);
1650 }
1652 gboolean show_handles = node->selected;
1653 if (node->p.other != NULL) {
1654 if (node->p.other->selected) show_handles = TRUE;
1655 }
1656 if (node->n.other != NULL) {
1657 if (node->n.other->selected) show_handles = TRUE;
1658 }
1660 if (node->subpath->nodepath->show_handles == false)
1661 show_handles = FALSE;
1663 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1664 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1665 }
1667 /**
1668 * Call sp_node_update_handles() for all nodes on subpath.
1669 */
1670 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1671 {
1672 g_assert(subpath != NULL);
1674 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1675 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1676 }
1677 }
1679 /**
1680 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1681 */
1682 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1683 {
1684 g_assert(nodepath != NULL);
1686 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1687 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1688 }
1689 }
1691 void
1692 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1693 {
1694 if (nodepath == NULL) return;
1696 nodepath->show_handles = show;
1697 sp_nodepath_update_handles(nodepath);
1698 }
1700 /**
1701 * Adds all selected nodes in nodepath to list.
1702 */
1703 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1704 {
1705 StlConv<Node *>::list(l, selected);
1706 /// \todo this adds a copying, rework when the selection becomes a stl list
1707 }
1709 /**
1710 * Align selected nodes on the specified axis.
1711 */
1712 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1713 {
1714 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1715 return;
1716 }
1718 if ( !nodepath->selected->next ) { // only one node selected
1719 return;
1720 }
1721 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1722 NR::Point dest(pNode->pos);
1723 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1724 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1725 if (pNode) {
1726 dest[axis] = pNode->pos[axis];
1727 sp_node_moveto(pNode, dest);
1728 }
1729 }
1731 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1732 }
1734 /// Helper struct.
1735 struct NodeSort
1736 {
1737 Inkscape::NodePath::Node *_node;
1738 NR::Coord _coord;
1739 /// \todo use vectorof pointers instead of calling copy ctor
1740 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1741 _node(node), _coord(node->pos[axis])
1742 {}
1744 };
1746 static bool operator<(NodeSort const &a, NodeSort const &b)
1747 {
1748 return (a._coord < b._coord);
1749 }
1751 /**
1752 * Distribute selected nodes on the specified axis.
1753 */
1754 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1755 {
1756 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1757 return;
1758 }
1760 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1761 return;
1762 }
1764 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1765 std::vector<NodeSort> sorted;
1766 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1767 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1768 if (pNode) {
1769 NodeSort n(pNode, axis);
1770 sorted.push_back(n);
1771 //dest[axis] = pNode->pos[axis];
1772 //sp_node_moveto(pNode, dest);
1773 }
1774 }
1775 std::sort(sorted.begin(), sorted.end());
1776 unsigned int len = sorted.size();
1777 //overall bboxes span
1778 float dist = (sorted.back()._coord -
1779 sorted.front()._coord);
1780 //new distance between each bbox
1781 float step = (dist) / (len - 1);
1782 float pos = sorted.front()._coord;
1783 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1784 it < sorted.end();
1785 it ++ )
1786 {
1787 NR::Point dest((*it)._node->pos);
1788 dest[axis] = pos;
1789 sp_node_moveto((*it)._node, dest);
1790 pos += step;
1791 }
1793 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1794 }
1797 /**
1798 * Call sp_nodepath_line_add_node() for all selected segments.
1799 */
1800 void
1801 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1802 {
1803 if (!nodepath) {
1804 return;
1805 }
1807 GList *nl = NULL;
1809 int n_added = 0;
1811 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1812 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1813 g_assert(t->selected);
1814 if (t->p.other && t->p.other->selected) {
1815 nl = g_list_prepend(nl, t);
1816 }
1817 }
1819 while (nl) {
1820 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1821 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1822 sp_nodepath_node_select(n, TRUE, FALSE);
1823 n_added ++;
1824 nl = g_list_remove(nl, t);
1825 }
1827 /** \todo fixme: adjust ? */
1828 sp_nodepath_update_handles(nodepath);
1830 if (n_added > 1) {
1831 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1832 } else if (n_added > 0) {
1833 sp_nodepath_update_repr(nodepath, _("Add node"));
1834 }
1836 sp_nodepath_update_statusbar(nodepath);
1837 }
1839 /**
1840 * Select segment nearest to point
1841 */
1842 void
1843 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1844 {
1845 if (!nodepath) {
1846 return;
1847 }
1849 sp_nodepath_ensure_livarot_path(nodepath);
1850 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1851 if (!maybe_position) {
1852 return;
1853 }
1854 Path::cut_position position = *maybe_position;
1856 //find segment to segment
1857 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1859 //fixme: this can return NULL, so check before proceeding.
1860 g_return_if_fail(e != NULL);
1862 gboolean force = FALSE;
1863 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1864 force = TRUE;
1865 }
1866 sp_nodepath_node_select(e, (gboolean) toggle, force);
1867 if (e->p.other)
1868 sp_nodepath_node_select(e->p.other, TRUE, force);
1870 sp_nodepath_update_handles(nodepath);
1872 sp_nodepath_update_statusbar(nodepath);
1873 }
1875 /**
1876 * Add a node nearest to point
1877 */
1878 void
1879 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1880 {
1881 if (!nodepath) {
1882 return;
1883 }
1885 sp_nodepath_ensure_livarot_path(nodepath);
1886 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1887 if (!maybe_position) {
1888 return;
1889 }
1890 Path::cut_position position = *maybe_position;
1892 //find segment to split
1893 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1895 //don't know why but t seems to flip for lines
1896 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1897 position.t = 1.0 - position.t;
1898 }
1899 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1900 sp_nodepath_node_select(n, FALSE, TRUE);
1902 /* fixme: adjust ? */
1903 sp_nodepath_update_handles(nodepath);
1905 sp_nodepath_update_repr(nodepath, _("Add node"));
1907 sp_nodepath_update_statusbar(nodepath);
1908 }
1910 /*
1911 * Adjusts a segment so that t moves by a certain delta for dragging
1912 * converts lines to curves
1913 *
1914 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1915 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1916 */
1917 void
1918 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1919 {
1920 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1922 //fixme: e and e->p can be NULL, so check for those before proceeding
1923 g_return_if_fail(e != NULL);
1924 g_return_if_fail(&e->p != NULL);
1926 /* feel good is an arbitrary parameter that distributes the delta between handles
1927 * if t of the drag point is less than 1/6 distance form the endpoint only
1928 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1929 */
1930 double feel_good;
1931 if (t <= 1.0 / 6.0)
1932 feel_good = 0;
1933 else if (t <= 0.5)
1934 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1935 else if (t <= 5.0 / 6.0)
1936 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1937 else
1938 feel_good = 1;
1940 //if we're dragging a line convert it to a curve
1941 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1942 sp_nodepath_set_line_type(e, NR_CURVETO);
1943 }
1945 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1946 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1947 e->p.other->n.pos += offsetcoord0;
1948 e->p.pos += offsetcoord1;
1950 // adjust handles of adjacent nodes where necessary
1951 sp_node_adjust_handle(e,1);
1952 sp_node_adjust_handle(e->p.other,-1);
1954 sp_nodepath_update_handles(e->subpath->nodepath);
1956 update_object(e->subpath->nodepath);
1958 sp_nodepath_update_statusbar(e->subpath->nodepath);
1959 }
1962 /**
1963 * Call sp_nodepath_break() for all selected segments.
1964 */
1965 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1966 {
1967 if (!nodepath) return;
1969 GList *temp = NULL;
1970 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1971 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1972 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1973 if (nn == NULL) continue; // no break, no new node
1974 temp = g_list_prepend(temp, nn);
1975 }
1977 if (temp) {
1978 sp_nodepath_deselect(nodepath);
1979 }
1980 for (GList *l = temp; l != NULL; l = l->next) {
1981 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1982 }
1984 sp_nodepath_update_handles(nodepath);
1986 sp_nodepath_update_repr(nodepath, _("Break path"));
1987 }
1989 /**
1990 * Duplicate the selected node(s).
1991 */
1992 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1993 {
1994 if (!nodepath) {
1995 return;
1996 }
1998 GList *temp = NULL;
1999 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2000 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2001 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2002 if (nn == NULL) continue; // could not duplicate
2003 temp = g_list_prepend(temp, nn);
2004 }
2006 if (temp) {
2007 sp_nodepath_deselect(nodepath);
2008 }
2009 for (GList *l = temp; l != NULL; l = l->next) {
2010 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2011 }
2013 sp_nodepath_update_handles(nodepath);
2015 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2016 }
2018 /**
2019 * Internal function to join two nodes by merging them into one.
2020 */
2021 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2022 {
2023 /* a and b are endpoints */
2025 // if one of the two nodes is mouseovered, fix its position
2026 NR::Point c;
2027 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2028 c = a->pos;
2029 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2030 c = b->pos;
2031 } else {
2032 // otherwise, move joined node to the midpoint
2033 c = (a->pos + b->pos) / 2;
2034 }
2036 if (a->subpath == b->subpath) {
2037 Inkscape::NodePath::SubPath *sp = a->subpath;
2038 sp_nodepath_subpath_close(sp);
2039 sp_node_moveto (sp->first, c);
2041 sp_nodepath_update_handles(sp->nodepath);
2042 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2043 return;
2044 }
2046 /* a and b are separate subpaths */
2047 Inkscape::NodePath::SubPath *sa = a->subpath;
2048 Inkscape::NodePath::SubPath *sb = b->subpath;
2049 NR::Point p;
2050 Inkscape::NodePath::Node *n;
2051 NRPathcode code;
2052 if (a == sa->first) {
2053 // we will now reverse sa, so that a is its last node, not first, and drop that node
2054 p = sa->first->n.pos;
2055 code = (NRPathcode)sa->first->n.other->code;
2056 // create new subpath
2057 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2058 // create a first moveto node on it
2059 n = sa->last;
2060 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2061 n = n->p.other;
2062 if (n == sa->first) n = NULL;
2063 while (n) {
2064 // copy the rest of the nodes from sa to t, going backwards
2065 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2066 n = n->p.other;
2067 if (n == sa->first) n = NULL;
2068 }
2069 // replace sa with t
2070 sp_nodepath_subpath_destroy(sa);
2071 sa = t;
2072 } else if (a == sa->last) {
2073 // a is already last, just drop it
2074 p = sa->last->p.pos;
2075 code = (NRPathcode)sa->last->code;
2076 sp_nodepath_node_destroy(sa->last);
2077 } else {
2078 code = NR_END;
2079 g_assert_not_reached();
2080 }
2082 if (b == sb->first) {
2083 // copy all nodes from b to a, forward
2084 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2085 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2086 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2087 }
2088 } else if (b == sb->last) {
2089 // copy all nodes from b to a, backward
2090 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2091 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2092 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2093 }
2094 } else {
2095 g_assert_not_reached();
2096 }
2097 /* and now destroy sb */
2099 sp_nodepath_subpath_destroy(sb);
2101 sp_nodepath_update_handles(sa->nodepath);
2103 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2105 sp_nodepath_update_statusbar(nodepath);
2106 }
2108 /**
2109 * Internal function to join two nodes by adding a segment between them.
2110 */
2111 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2112 {
2113 if (a->subpath == b->subpath) {
2114 Inkscape::NodePath::SubPath *sp = a->subpath;
2116 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2117 sp->closed = TRUE;
2119 sp->first->p.other = sp->last;
2120 sp->last->n.other = sp->first;
2122 sp_node_handle_mirror_p_to_n(sp->last);
2123 sp_node_handle_mirror_n_to_p(sp->first);
2125 sp->first->code = sp->last->code;
2126 sp->first = sp->last;
2128 sp_nodepath_update_handles(sp->nodepath);
2130 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2132 return;
2133 }
2135 /* a and b are separate subpaths */
2136 Inkscape::NodePath::SubPath *sa = a->subpath;
2137 Inkscape::NodePath::SubPath *sb = b->subpath;
2139 Inkscape::NodePath::Node *n;
2140 NR::Point p;
2141 NRPathcode code;
2142 if (a == sa->first) {
2143 code = (NRPathcode) sa->first->n.other->code;
2144 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2145 n = sa->last;
2146 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2147 for (n = n->p.other; n != NULL; n = n->p.other) {
2148 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2149 }
2150 sp_nodepath_subpath_destroy(sa);
2151 sa = t;
2152 } else if (a == sa->last) {
2153 code = (NRPathcode)sa->last->code;
2154 } else {
2155 code = NR_END;
2156 g_assert_not_reached();
2157 }
2159 if (b == sb->first) {
2160 n = sb->first;
2161 sp_node_handle_mirror_p_to_n(sa->last);
2162 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2163 sp_node_handle_mirror_n_to_p(sa->last);
2164 for (n = n->n.other; n != NULL; n = n->n.other) {
2165 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2166 }
2167 } else if (b == sb->last) {
2168 n = sb->last;
2169 sp_node_handle_mirror_p_to_n(sa->last);
2170 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2171 sp_node_handle_mirror_n_to_p(sa->last);
2172 for (n = n->p.other; n != NULL; n = n->p.other) {
2173 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2174 }
2175 } else {
2176 g_assert_not_reached();
2177 }
2178 /* and now destroy sb */
2180 sp_nodepath_subpath_destroy(sb);
2182 sp_nodepath_update_handles(sa->nodepath);
2184 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2185 }
2187 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2189 /**
2190 * Internal function to handle joining two nodes.
2191 */
2192 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2193 {
2194 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2196 if (g_list_length(nodepath->selected) != 2) {
2197 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2198 return;
2199 }
2201 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2202 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2204 g_assert(a != b);
2205 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2206 // someone tried to join an orphan node (i.e. a single-node subpath).
2207 // this is not worth an error message, just fail silently.
2208 return;
2209 }
2211 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2212 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2213 return;
2214 }
2216 switch(mode) {
2217 case NODE_JOIN_ENDPOINTS:
2218 do_node_selected_join(nodepath, a, b);
2219 break;
2220 case NODE_JOIN_SEGMENT:
2221 do_node_selected_join_segment(nodepath, a, b);
2222 break;
2223 }
2224 }
2226 /**
2227 * Join two nodes by merging them into one.
2228 */
2229 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2230 {
2231 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2232 }
2234 /**
2235 * Join two nodes by adding a segment between them.
2236 */
2237 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2238 {
2239 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2240 }
2242 /**
2243 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2244 */
2245 void sp_node_delete_preserve(GList *nodes_to_delete)
2246 {
2247 GSList *nodepaths = NULL;
2249 while (nodes_to_delete) {
2250 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2251 Inkscape::NodePath::SubPath *sp = node->subpath;
2252 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2253 Inkscape::NodePath::Node *sample_cursor = NULL;
2254 Inkscape::NodePath::Node *sample_end = NULL;
2255 Inkscape::NodePath::Node *delete_cursor = node;
2256 bool just_delete = false;
2258 //find the start of this contiguous selection
2259 //move left to the first node that is not selected
2260 //or the start of the non-closed path
2261 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2262 delete_cursor = curr;
2263 }
2265 //just delete at the beginning of an open path
2266 if (!delete_cursor->p.other) {
2267 sample_cursor = delete_cursor;
2268 just_delete = true;
2269 } else {
2270 sample_cursor = delete_cursor->p.other;
2271 }
2273 //calculate points for each segment
2274 int rate = 5;
2275 float period = 1.0 / rate;
2276 std::vector<NR::Point> data;
2277 if (!just_delete) {
2278 data.push_back(sample_cursor->pos);
2279 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2280 //just delete at the end of an open path
2281 if (!sp->closed && curr == sp->last) {
2282 just_delete = true;
2283 break;
2284 }
2286 //sample points on the contiguous selected segment
2287 NR::Point *bez;
2288 bez = new NR::Point [4];
2289 bez[0] = curr->pos;
2290 bez[1] = curr->n.pos;
2291 bez[2] = curr->n.other->p.pos;
2292 bez[3] = curr->n.other->pos;
2293 for (int i=1; i<rate; i++) {
2294 gdouble t = i * period;
2295 NR::Point p = bezier_pt(3, bez, t);
2296 data.push_back(p);
2297 }
2298 data.push_back(curr->n.other->pos);
2300 sample_end = curr->n.other;
2301 //break if we've come full circle or hit the end of the selection
2302 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2303 break;
2304 }
2305 }
2306 }
2308 if (!just_delete) {
2309 //calculate the best fitting single segment and adjust the endpoints
2310 NR::Point *adata;
2311 adata = new NR::Point [data.size()];
2312 copy(data.begin(), data.end(), adata);
2314 NR::Point *bez;
2315 bez = new NR::Point [4];
2316 //would decreasing error create a better fitting approximation?
2317 gdouble error = 1.0;
2318 gint ret;
2319 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2321 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2322 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2323 //the resulting nodes behave as expected.
2324 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2325 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2326 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2327 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2329 //adjust endpoints
2330 sample_cursor->n.pos = bez[1];
2331 sample_end->p.pos = bez[2];
2332 }
2334 //destroy this contiguous selection
2335 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2336 Inkscape::NodePath::Node *temp = delete_cursor;
2337 if (delete_cursor->n.other == delete_cursor) {
2338 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2339 delete_cursor = NULL;
2340 } else {
2341 delete_cursor = delete_cursor->n.other;
2342 }
2343 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2344 sp_nodepath_node_destroy(temp);
2345 }
2347 sp_nodepath_update_handles(nodepath);
2349 if (!g_slist_find(nodepaths, nodepath))
2350 nodepaths = g_slist_prepend (nodepaths, nodepath);
2351 }
2353 for (GSList *i = nodepaths; i; i = i->next) {
2354 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2355 // different nodepaths will give us one undo event per nodepath
2356 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2358 // if the entire nodepath is removed, delete the selected object.
2359 if (nodepath->subpaths == NULL ||
2360 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2361 //at least 2
2362 sp_nodepath_get_node_count(nodepath) < 2) {
2363 SPDocument *document = sp_desktop_document (nodepath->desktop);
2364 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2365 //delete this nodepath's object, not the entire selection! (though at this time, this
2366 //does not matter)
2367 sp_selection_delete();
2368 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2369 _("Delete nodes"));
2370 } else {
2371 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2372 sp_nodepath_update_statusbar(nodepath);
2373 }
2374 }
2376 g_slist_free (nodepaths);
2377 }
2379 /**
2380 * Delete one or more selected nodes.
2381 */
2382 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2383 {
2384 if (!nodepath) return;
2385 if (!nodepath->selected) return;
2387 /** \todo fixme: do it the right way */
2388 while (nodepath->selected) {
2389 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2390 sp_nodepath_node_destroy(node);
2391 }
2394 //clean up the nodepath (such as for trivial subpaths)
2395 sp_nodepath_cleanup(nodepath);
2397 sp_nodepath_update_handles(nodepath);
2399 // if the entire nodepath is removed, delete the selected object.
2400 if (nodepath->subpaths == NULL ||
2401 sp_nodepath_get_node_count(nodepath) < 2) {
2402 SPDocument *document = sp_desktop_document (nodepath->desktop);
2403 sp_selection_delete();
2404 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2405 _("Delete nodes"));
2406 return;
2407 }
2409 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2411 sp_nodepath_update_statusbar(nodepath);
2412 }
2414 /**
2415 * Delete one or more segments between two selected nodes.
2416 * This is the code for 'split'.
2417 */
2418 void
2419 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2420 {
2421 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2422 Inkscape::NodePath::Node *curr, *next; //Iterators
2424 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2426 if (g_list_length(nodepath->selected) != 2) {
2427 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2428 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2429 return;
2430 }
2432 //Selected nodes, not inclusive
2433 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2434 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2436 if ( ( a==b) || //same node
2437 (a->subpath != b->subpath ) || //not the same path
2438 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2439 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2440 {
2441 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2442 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2443 return;
2444 }
2446 //###########################################
2447 //# BEGIN EDITS
2448 //###########################################
2449 //##################################
2450 //# CLOSED PATH
2451 //##################################
2452 if (a->subpath->closed) {
2455 gboolean reversed = FALSE;
2457 //Since we can go in a circle, we need to find the shorter distance.
2458 // a->b or b->a
2459 start = end = NULL;
2460 int distance = 0;
2461 int minDistance = 0;
2462 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2463 if (curr==b) {
2464 //printf("a to b:%d\n", distance);
2465 start = a;//go from a to b
2466 end = b;
2467 minDistance = distance;
2468 //printf("A to B :\n");
2469 break;
2470 }
2471 distance++;
2472 }
2474 //try again, the other direction
2475 distance = 0;
2476 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2477 if (curr==a) {
2478 //printf("b to a:%d\n", distance);
2479 if (distance < minDistance) {
2480 start = b; //we go from b to a
2481 end = a;
2482 reversed = TRUE;
2483 //printf("B to A\n");
2484 }
2485 break;
2486 }
2487 distance++;
2488 }
2491 //Copy everything from 'end' to 'start' to a new subpath
2492 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2493 for (curr=end ; curr ; curr=curr->n.other) {
2494 NRPathcode code = (NRPathcode) curr->code;
2495 if (curr == end)
2496 code = NR_MOVETO;
2497 sp_nodepath_node_new(t, NULL,
2498 (Inkscape::NodePath::NodeType)curr->type, code,
2499 &curr->p.pos, &curr->pos, &curr->n.pos);
2500 if (curr == start)
2501 break;
2502 }
2503 sp_nodepath_subpath_destroy(a->subpath);
2506 }
2510 //##################################
2511 //# OPEN PATH
2512 //##################################
2513 else {
2515 //We need to get the direction of the list between A and B
2516 //Can we walk from a to b?
2517 start = end = NULL;
2518 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2519 if (curr==b) {
2520 start = a; //did it! we go from a to b
2521 end = b;
2522 //printf("A to B\n");
2523 break;
2524 }
2525 }
2526 if (!start) {//didn't work? let's try the other direction
2527 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2528 if (curr==a) {
2529 start = b; //did it! we go from b to a
2530 end = a;
2531 //printf("B to A\n");
2532 break;
2533 }
2534 }
2535 }
2536 if (!start) {
2537 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2538 _("Cannot find path between nodes."));
2539 return;
2540 }
2544 //Copy everything after 'end' to a new subpath
2545 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2546 for (curr=end ; curr ; curr=curr->n.other) {
2547 NRPathcode code = (NRPathcode) curr->code;
2548 if (curr == end)
2549 code = NR_MOVETO;
2550 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2551 &curr->p.pos, &curr->pos, &curr->n.pos);
2552 }
2554 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2555 for (curr = start->n.other ; curr ; curr=next) {
2556 next = curr->n.other;
2557 sp_nodepath_node_destroy(curr);
2558 }
2560 }
2561 //###########################################
2562 //# END EDITS
2563 //###########################################
2565 //clean up the nodepath (such as for trivial subpaths)
2566 sp_nodepath_cleanup(nodepath);
2568 sp_nodepath_update_handles(nodepath);
2570 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2572 sp_nodepath_update_statusbar(nodepath);
2573 }
2575 /**
2576 * Call sp_nodepath_set_line() for all selected segments.
2577 */
2578 void
2579 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2580 {
2581 if (nodepath == NULL) return;
2583 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2584 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2585 g_assert(n->selected);
2586 if (n->p.other && n->p.other->selected) {
2587 sp_nodepath_set_line_type(n, code);
2588 }
2589 }
2591 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2592 }
2594 /**
2595 * Call sp_nodepath_convert_node_type() for all selected nodes.
2596 */
2597 void
2598 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2599 {
2600 if (nodepath == NULL) return;
2602 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2604 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2605 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2606 }
2608 sp_nodepath_update_repr(nodepath, _("Change node type"));
2609 }
2611 /**
2612 * Change select status of node, update its own and neighbour handles.
2613 */
2614 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2615 {
2616 node->selected = selected;
2618 if (selected) {
2619 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2620 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2621 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2622 sp_knot_update_ctrl(node->knot);
2623 } else {
2624 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2625 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2626 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2627 sp_knot_update_ctrl(node->knot);
2628 }
2630 sp_node_update_handles(node);
2631 if (node->n.other) sp_node_update_handles(node->n.other);
2632 if (node->p.other) sp_node_update_handles(node->p.other);
2633 }
2635 /**
2636 \brief Select a node
2637 \param node The node to select
2638 \param incremental If true, add to selection, otherwise deselect others
2639 \param override If true, always select this node, otherwise toggle selected status
2640 */
2641 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2642 {
2643 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2645 if (incremental) {
2646 if (override) {
2647 if (!g_list_find(nodepath->selected, node)) {
2648 nodepath->selected = g_list_prepend(nodepath->selected, node);
2649 }
2650 sp_node_set_selected(node, TRUE);
2651 } else { // toggle
2652 if (node->selected) {
2653 g_assert(g_list_find(nodepath->selected, node));
2654 nodepath->selected = g_list_remove(nodepath->selected, node);
2655 } else {
2656 g_assert(!g_list_find(nodepath->selected, node));
2657 nodepath->selected = g_list_prepend(nodepath->selected, node);
2658 }
2659 sp_node_set_selected(node, !node->selected);
2660 }
2661 } else {
2662 sp_nodepath_deselect(nodepath);
2663 nodepath->selected = g_list_prepend(nodepath->selected, node);
2664 sp_node_set_selected(node, TRUE);
2665 }
2667 sp_nodepath_update_statusbar(nodepath);
2668 }
2671 /**
2672 \brief Deselect all nodes in the nodepath
2673 */
2674 void
2675 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2676 {
2677 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2679 while (nodepath->selected) {
2680 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2681 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2682 }
2683 sp_nodepath_update_statusbar(nodepath);
2684 }
2686 /**
2687 \brief Select or invert selection of all nodes in the nodepath
2688 */
2689 void
2690 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2691 {
2692 if (!nodepath) return;
2694 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2695 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2696 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2697 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2698 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2699 }
2700 }
2701 }
2703 /**
2704 * If nothing selected, does the same as sp_nodepath_select_all();
2705 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2706 * (i.e., similar to "select all in layer", with the "selected" subpaths
2707 * being treated as "layers" in the path).
2708 */
2709 void
2710 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2711 {
2712 if (!nodepath) return;
2714 if (g_list_length (nodepath->selected) == 0) {
2715 sp_nodepath_select_all (nodepath, invert);
2716 return;
2717 }
2719 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2720 GSList *subpaths = NULL;
2722 for (GList *l = copy; l != NULL; l = l->next) {
2723 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2724 Inkscape::NodePath::SubPath *subpath = n->subpath;
2725 if (!g_slist_find (subpaths, subpath))
2726 subpaths = g_slist_prepend (subpaths, subpath);
2727 }
2729 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2730 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2731 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2732 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2733 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2734 }
2735 }
2737 g_slist_free (subpaths);
2738 g_list_free (copy);
2739 }
2741 /**
2742 * \brief Select the node after the last selected; if none is selected,
2743 * select the first within path.
2744 */
2745 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2746 {
2747 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2749 Inkscape::NodePath::Node *last = NULL;
2750 if (nodepath->selected) {
2751 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2752 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2753 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2754 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2755 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2756 if (node->selected) {
2757 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2758 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2759 if (spl->next) { // there's a next subpath
2760 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2761 last = subpath_next->first;
2762 } else if (spl->prev) { // there's a previous subpath
2763 last = NULL; // to be set later to the first node of first subpath
2764 } else {
2765 last = node->n.other;
2766 }
2767 } else {
2768 last = node->n.other;
2769 }
2770 } else {
2771 if (node->n.other) {
2772 last = node->n.other;
2773 } else {
2774 if (spl->next) { // there's a next subpath
2775 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2776 last = subpath_next->first;
2777 } else if (spl->prev) { // there's a previous subpath
2778 last = NULL; // to be set later to the first node of first subpath
2779 } else {
2780 last = (Inkscape::NodePath::Node *) subpath->first;
2781 }
2782 }
2783 }
2784 }
2785 }
2786 }
2787 sp_nodepath_deselect(nodepath);
2788 }
2790 if (last) { // there's at least one more node after selected
2791 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2792 } else { // no more nodes, select the first one in first subpath
2793 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2794 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2795 }
2796 }
2798 /**
2799 * \brief Select the node before the first selected; if none is selected,
2800 * select the last within path
2801 */
2802 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2803 {
2804 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2806 Inkscape::NodePath::Node *last = NULL;
2807 if (nodepath->selected) {
2808 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2809 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2810 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2811 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2812 if (node->selected) {
2813 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2814 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2815 if (spl->prev) { // there's a prev subpath
2816 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2817 last = subpath_prev->last;
2818 } else if (spl->next) { // there's a next subpath
2819 last = NULL; // to be set later to the last node of last subpath
2820 } else {
2821 last = node->p.other;
2822 }
2823 } else {
2824 last = node->p.other;
2825 }
2826 } else {
2827 if (node->p.other) {
2828 last = node->p.other;
2829 } else {
2830 if (spl->prev) { // there's a prev subpath
2831 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2832 last = subpath_prev->last;
2833 } else if (spl->next) { // there's a next subpath
2834 last = NULL; // to be set later to the last node of last subpath
2835 } else {
2836 last = (Inkscape::NodePath::Node *) subpath->last;
2837 }
2838 }
2839 }
2840 }
2841 }
2842 }
2843 sp_nodepath_deselect(nodepath);
2844 }
2846 if (last) { // there's at least one more node before selected
2847 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2848 } else { // no more nodes, select the last one in last subpath
2849 GList *spl = g_list_last(nodepath->subpaths);
2850 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2851 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2852 }
2853 }
2855 /**
2856 * \brief Select all nodes that are within the rectangle.
2857 */
2858 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2859 {
2860 if (!incremental) {
2861 sp_nodepath_deselect(nodepath);
2862 }
2864 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2865 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2866 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2867 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2869 if (b.contains(node->pos)) {
2870 sp_nodepath_node_select(node, TRUE, TRUE);
2871 }
2872 }
2873 }
2874 }
2877 void
2878 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2879 {
2880 g_assert (n);
2881 g_assert (nodepath);
2882 g_assert (n->subpath->nodepath == nodepath);
2884 if (g_list_length (nodepath->selected) == 0) {
2885 if (grow > 0) {
2886 sp_nodepath_node_select(n, TRUE, TRUE);
2887 }
2888 return;
2889 }
2891 if (g_list_length (nodepath->selected) == 1) {
2892 if (grow < 0) {
2893 sp_nodepath_deselect (nodepath);
2894 return;
2895 }
2896 }
2898 double n_sel_range = 0, p_sel_range = 0;
2899 Inkscape::NodePath::Node *farthest_n_node = n;
2900 Inkscape::NodePath::Node *farthest_p_node = n;
2902 // Calculate ranges
2903 {
2904 double n_range = 0, p_range = 0;
2905 bool n_going = true, p_going = true;
2906 Inkscape::NodePath::Node *n_node = n;
2907 Inkscape::NodePath::Node *p_node = n;
2908 do {
2909 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2910 if (n_node && n_going)
2911 n_node = n_node->n.other;
2912 if (n_node == NULL) {
2913 n_going = false;
2914 } else {
2915 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2916 if (n_node->selected) {
2917 n_sel_range = n_range;
2918 farthest_n_node = n_node;
2919 }
2920 if (n_node == p_node) {
2921 n_going = false;
2922 p_going = false;
2923 }
2924 }
2925 if (p_node && p_going)
2926 p_node = p_node->p.other;
2927 if (p_node == NULL) {
2928 p_going = false;
2929 } else {
2930 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2931 if (p_node->selected) {
2932 p_sel_range = p_range;
2933 farthest_p_node = p_node;
2934 }
2935 if (p_node == n_node) {
2936 n_going = false;
2937 p_going = false;
2938 }
2939 }
2940 } while (n_going || p_going);
2941 }
2943 if (grow > 0) {
2944 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2945 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2946 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2947 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2948 }
2949 } else {
2950 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2951 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2952 } else if (farthest_p_node && farthest_p_node->selected) {
2953 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2954 }
2955 }
2956 }
2958 void
2959 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2960 {
2961 g_assert (n);
2962 g_assert (nodepath);
2963 g_assert (n->subpath->nodepath == nodepath);
2965 if (g_list_length (nodepath->selected) == 0) {
2966 if (grow > 0) {
2967 sp_nodepath_node_select(n, TRUE, TRUE);
2968 }
2969 return;
2970 }
2972 if (g_list_length (nodepath->selected) == 1) {
2973 if (grow < 0) {
2974 sp_nodepath_deselect (nodepath);
2975 return;
2976 }
2977 }
2979 Inkscape::NodePath::Node *farthest_selected = NULL;
2980 double farthest_dist = 0;
2982 Inkscape::NodePath::Node *closest_unselected = NULL;
2983 double closest_dist = NR_HUGE;
2985 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2986 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2987 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2988 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2989 if (node == n)
2990 continue;
2991 if (node->selected) {
2992 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2993 farthest_dist = NR::L2(node->pos - n->pos);
2994 farthest_selected = node;
2995 }
2996 } else {
2997 if (NR::L2(node->pos - n->pos) < closest_dist) {
2998 closest_dist = NR::L2(node->pos - n->pos);
2999 closest_unselected = node;
3000 }
3001 }
3002 }
3003 }
3005 if (grow > 0) {
3006 if (closest_unselected) {
3007 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3008 }
3009 } else {
3010 if (farthest_selected) {
3011 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3012 }
3013 }
3014 }
3017 /**
3018 \brief Saves all nodes' and handles' current positions in their origin members
3019 */
3020 void
3021 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3022 {
3023 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3024 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3025 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3026 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3027 n->origin = n->pos;
3028 n->p.origin = n->p.pos;
3029 n->n.origin = n->n.pos;
3030 }
3031 }
3032 }
3034 /**
3035 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3036 */
3037 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3038 {
3039 if (!nodepath->selected) {
3040 return NULL;
3041 }
3043 GList *r = NULL;
3044 guint i = 0;
3045 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3046 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3047 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3048 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3049 i++;
3050 if (node->selected) {
3051 r = g_list_append(r, GINT_TO_POINTER(i));
3052 }
3053 }
3054 }
3055 return r;
3056 }
3058 /**
3059 \brief Restores selection by selecting nodes whose positions are in the list
3060 */
3061 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3062 {
3063 sp_nodepath_deselect(nodepath);
3065 guint i = 0;
3066 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3067 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3068 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3069 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3070 i++;
3071 if (g_list_find(r, GINT_TO_POINTER(i))) {
3072 sp_nodepath_node_select(node, TRUE, TRUE);
3073 }
3074 }
3075 }
3076 }
3079 /**
3080 \brief Adjusts handle according to node type and line code.
3081 */
3082 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3083 {
3084 g_assert(node);
3086 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3087 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3089 // nothing to do if we are an end node
3090 if (me->other == NULL) return;
3091 if (other->other == NULL) return;
3093 // nothing to do if we are a cusp node
3094 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3096 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3097 NRPathcode mecode;
3098 if (which_adjust == 1) {
3099 mecode = (NRPathcode)me->other->code;
3100 } else {
3101 mecode = (NRPathcode)node->code;
3102 }
3103 if (mecode == NR_LINETO) return;
3105 if (sp_node_side_is_line(node, other)) {
3106 // other is a line, and we are either smooth or symm
3107 Inkscape::NodePath::Node *othernode = other->other;
3108 double len = NR::L2(me->pos - node->pos);
3109 NR::Point delta = node->pos - othernode->pos;
3110 double linelen = NR::L2(delta);
3111 if (linelen < 1e-18)
3112 return;
3113 me->pos = node->pos + (len / linelen)*delta;
3114 return;
3115 }
3117 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3118 // symmetrize
3119 me->pos = 2 * node->pos - other->pos;
3120 return;
3121 } else {
3122 // smoothify
3123 double len = NR::L2(me->pos - node->pos);
3124 NR::Point delta = other->pos - node->pos;
3125 double otherlen = NR::L2(delta);
3126 if (otherlen < 1e-18) return;
3127 me->pos = node->pos - (len / otherlen) * delta;
3128 }
3129 }
3131 /**
3132 \brief Adjusts both handles according to node type and line code
3133 */
3134 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3135 {
3136 g_assert(node);
3138 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3140 /* we are either smooth or symm */
3142 if (node->p.other == NULL) return;
3143 if (node->n.other == NULL) return;
3145 if (sp_node_side_is_line(node, &node->p)) {
3146 sp_node_adjust_handle(node, 1);
3147 return;
3148 }
3150 if (sp_node_side_is_line(node, &node->n)) {
3151 sp_node_adjust_handle(node, -1);
3152 return;
3153 }
3155 /* both are curves */
3156 NR::Point const delta( node->n.pos - node->p.pos );
3158 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3159 node->p.pos = node->pos - delta / 2;
3160 node->n.pos = node->pos + delta / 2;
3161 return;
3162 }
3164 /* We are smooth */
3165 double plen = NR::L2(node->p.pos - node->pos);
3166 if (plen < 1e-18) return;
3167 double nlen = NR::L2(node->n.pos - node->pos);
3168 if (nlen < 1e-18) return;
3169 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3170 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3171 }
3173 /**
3174 * Node event callback.
3175 */
3176 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3177 {
3178 gboolean ret = FALSE;
3179 switch (event->type) {
3180 case GDK_ENTER_NOTIFY:
3181 Inkscape::NodePath::Path::active_node = n;
3182 break;
3183 case GDK_LEAVE_NOTIFY:
3184 Inkscape::NodePath::Path::active_node = NULL;
3185 break;
3186 case GDK_SCROLL:
3187 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3188 switch (event->scroll.direction) {
3189 case GDK_SCROLL_UP:
3190 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3191 break;
3192 case GDK_SCROLL_DOWN:
3193 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3194 break;
3195 default:
3196 break;
3197 }
3198 ret = TRUE;
3199 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3200 switch (event->scroll.direction) {
3201 case GDK_SCROLL_UP:
3202 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3203 break;
3204 case GDK_SCROLL_DOWN:
3205 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3206 break;
3207 default:
3208 break;
3209 }
3210 ret = TRUE;
3211 }
3212 break;
3213 case GDK_KEY_PRESS:
3214 switch (get_group0_keyval (&event->key)) {
3215 case GDK_space:
3216 if (event->key.state & GDK_BUTTON1_MASK) {
3217 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3218 stamp_repr(nodepath);
3219 ret = TRUE;
3220 }
3221 break;
3222 case GDK_Page_Up:
3223 if (event->key.state & GDK_CONTROL_MASK) {
3224 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3225 } else {
3226 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3227 }
3228 break;
3229 case GDK_Page_Down:
3230 if (event->key.state & GDK_CONTROL_MASK) {
3231 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3232 } else {
3233 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3234 }
3235 break;
3236 default:
3237 break;
3238 }
3239 break;
3240 default:
3241 break;
3242 }
3244 return ret;
3245 }
3247 /**
3248 * Handle keypress on node; directly called.
3249 */
3250 gboolean node_key(GdkEvent *event)
3251 {
3252 Inkscape::NodePath::Path *np;
3254 // there is no way to verify nodes so set active_node to nil when deleting!!
3255 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3257 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3258 gint ret = FALSE;
3259 switch (get_group0_keyval (&event->key)) {
3260 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3261 case GDK_BackSpace:
3262 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3263 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3264 sp_nodepath_update_repr(np, _("Delete node"));
3265 Inkscape::NodePath::Path::active_node = NULL;
3266 ret = TRUE;
3267 break;
3268 case GDK_c:
3269 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3270 ret = TRUE;
3271 break;
3272 case GDK_s:
3273 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3274 ret = TRUE;
3275 break;
3276 case GDK_y:
3277 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3278 ret = TRUE;
3279 break;
3280 case GDK_b:
3281 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3282 ret = TRUE;
3283 break;
3284 }
3285 return ret;
3286 }
3287 return FALSE;
3288 }
3290 /**
3291 * Mouseclick on node callback.
3292 */
3293 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3294 {
3295 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3297 if (state & GDK_CONTROL_MASK) {
3298 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3300 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3301 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3302 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3303 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3304 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3305 } else {
3306 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3307 }
3308 sp_nodepath_update_repr(nodepath, _("Change node type"));
3309 sp_nodepath_update_statusbar(nodepath);
3311 } else { //ctrl+alt+click: delete node
3312 GList *node_to_delete = NULL;
3313 node_to_delete = g_list_append(node_to_delete, n);
3314 sp_node_delete_preserve(node_to_delete);
3315 }
3317 } else {
3318 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3319 }
3320 }
3322 /**
3323 * Mouse grabbed node callback.
3324 */
3325 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3326 {
3327 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3329 if (!n->selected) {
3330 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3331 }
3333 n->is_dragging = true;
3334 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3336 sp_nodepath_remember_origins (n->subpath->nodepath);
3337 }
3339 /**
3340 * Mouse ungrabbed node callback.
3341 */
3342 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3343 {
3344 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3346 n->dragging_out = NULL;
3347 n->is_dragging = false;
3348 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3350 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3351 }
3353 /**
3354 * The point on a line, given by its angle, closest to the given point.
3355 * \param p A point.
3356 * \param a Angle of the line; it is assumed to go through coordinate origin.
3357 * \param closest Pointer to the point struct where the result is stored.
3358 * \todo FIXME: use dot product perhaps?
3359 */
3360 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3361 {
3362 if (a == HUGE_VAL) { // vertical
3363 *closest = NR::Point(0, (*p)[NR::Y]);
3364 } else {
3365 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3366 (*closest)[NR::Y] = a * (*closest)[NR::X];
3367 }
3368 }
3370 /**
3371 * Distance from the point to a line given by its angle.
3372 * \param p A point.
3373 * \param a Angle of the line; it is assumed to go through coordinate origin.
3374 */
3375 static double point_line_distance(NR::Point *p, double a)
3376 {
3377 NR::Point c;
3378 point_line_closest(p, a, &c);
3379 return sqrt(((*p)[NR::X] - c[NR::X])*((*p)[NR::X] - c[NR::X]) + ((*p)[NR::Y] - c[NR::Y])*((*p)[NR::Y] - c[NR::Y]));
3380 }
3382 /**
3383 * Callback for node "request" signal.
3384 * \todo fixme: This goes to "moved" event? (lauris)
3385 */
3386 static gboolean
3387 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3388 {
3389 double yn, xn, yp, xp;
3390 double an, ap, na, pa;
3391 double d_an, d_ap, d_na, d_pa;
3392 gboolean collinear = FALSE;
3393 NR::Point c;
3394 NR::Point pr;
3396 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3398 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3400 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3401 if ( (!n->subpath->nodepath->straight_path) &&
3402 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3403 || n->dragging_out ) )
3404 {
3405 NR::Point mouse = (*p);
3407 if (!n->dragging_out) {
3408 // This is the first drag-out event; find out which handle to drag out
3409 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3410 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3412 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3413 return FALSE;
3415 Inkscape::NodePath::NodeSide *opposite;
3416 if (appr_p > appr_n) { // closer to p
3417 n->dragging_out = &n->p;
3418 opposite = &n->n;
3419 n->code = NR_CURVETO;
3420 } else if (appr_p < appr_n) { // closer to n
3421 n->dragging_out = &n->n;
3422 opposite = &n->p;
3423 n->n.other->code = NR_CURVETO;
3424 } else { // p and n nodes are the same
3425 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3426 n->dragging_out = &n->p;
3427 opposite = &n->n;
3428 n->code = NR_CURVETO;
3429 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3430 n->dragging_out = &n->n;
3431 opposite = &n->p;
3432 n->n.other->code = NR_CURVETO;
3433 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3434 double appr_other_n = (n->n.other ? NR::L2(n->n.other->n.pos - n->pos) - NR::L2(n->n.other->n.pos - (*p)) : -HUGE_VAL);
3435 double appr_other_p = (n->n.other ? NR::L2(n->n.other->p.pos - n->pos) - NR::L2(n->n.other->p.pos - (*p)) : -HUGE_VAL);
3436 if (appr_other_p > appr_other_n) { // closer to other's p handle
3437 n->dragging_out = &n->n;
3438 opposite = &n->p;
3439 n->n.other->code = NR_CURVETO;
3440 } else { // closer to other's n handle
3441 n->dragging_out = &n->p;
3442 opposite = &n->n;
3443 n->code = NR_CURVETO;
3444 }
3445 }
3446 }
3448 // if there's another handle, make sure the one we drag out starts parallel to it
3449 if (opposite->pos != n->pos) {
3450 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3451 }
3453 // knots might not be created yet!
3454 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3455 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3456 }
3458 // pass this on to the handle-moved callback
3459 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3460 sp_node_update_handles(n);
3461 return TRUE;
3462 }
3464 if (state & GDK_CONTROL_MASK) { // constrained motion
3466 // calculate relative distances of handles
3467 // n handle:
3468 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3469 xn = n->n.pos[NR::X] - n->pos[NR::X];
3470 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3471 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3472 if (n->n.other) { // if there is the next point
3473 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3474 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3475 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3476 }
3477 }
3478 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3479 if (yn < 0) { xn = -xn; yn = -yn; }
3481 // p handle:
3482 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3483 xp = n->p.pos[NR::X] - n->pos[NR::X];
3484 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3485 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3486 if (n->p.other) {
3487 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3488 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3489 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3490 }
3491 }
3492 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3493 if (yp < 0) { xp = -xp; yp = -yp; }
3495 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3496 // sliding on handles, only if at least one of the handles is non-vertical
3497 // (otherwise it's the same as ctrl+drag anyway)
3499 // calculate angles of the handles
3500 if (xn == 0) {
3501 if (yn == 0) { // no handle, consider it the continuation of the other one
3502 an = 0;
3503 collinear = TRUE;
3504 }
3505 else an = 0; // vertical; set the angle to horizontal
3506 } else an = yn/xn;
3508 if (xp == 0) {
3509 if (yp == 0) { // no handle, consider it the continuation of the other one
3510 ap = an;
3511 }
3512 else ap = 0; // vertical; set the angle to horizontal
3513 } else ap = yp/xp;
3515 if (collinear) an = ap;
3517 // angles of the perpendiculars; HUGE_VAL means vertical
3518 if (an == 0) na = HUGE_VAL; else na = -1/an;
3519 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3521 // mouse point relative to the node's original pos
3522 pr = (*p) - n->origin;
3524 // distances to the four lines (two handles and two perpendiculars)
3525 d_an = point_line_distance(&pr, an);
3526 d_na = point_line_distance(&pr, na);
3527 d_ap = point_line_distance(&pr, ap);
3528 d_pa = point_line_distance(&pr, pa);
3530 // find out which line is the closest, save its closest point in c
3531 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3532 point_line_closest(&pr, an, &c);
3533 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3534 point_line_closest(&pr, ap, &c);
3535 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3536 point_line_closest(&pr, na, &c);
3537 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3538 point_line_closest(&pr, pa, &c);
3539 }
3541 // move the node to the closest point
3542 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3543 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3544 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3546 } else { // constraining to hor/vert
3548 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3549 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3550 } else { // snap to vert
3551 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3552 }
3553 }
3554 } else { // move freely
3555 if (n->is_dragging) {
3556 if (state & GDK_MOD1_MASK) { // sculpt
3557 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3558 } else {
3559 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3560 (*p)[NR::X] - n->pos[NR::X],
3561 (*p)[NR::Y] - n->pos[NR::Y],
3562 (state & GDK_SHIFT_MASK) == 0);
3563 }
3564 }
3565 }
3567 n->subpath->nodepath->desktop->scroll_to_point(p);
3569 return TRUE;
3570 }
3572 /**
3573 * Node handle clicked callback.
3574 */
3575 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3576 {
3577 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3579 if (state & GDK_CONTROL_MASK) { // "delete" handle
3580 if (n->p.knot == knot) {
3581 n->p.pos = n->pos;
3582 } else if (n->n.knot == knot) {
3583 n->n.pos = n->pos;
3584 }
3585 sp_node_update_handles(n);
3586 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3587 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3588 sp_nodepath_update_statusbar(nodepath);
3590 } else { // just select or add to selection, depending in Shift
3591 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3592 }
3593 }
3595 /**
3596 * Node handle grabbed callback.
3597 */
3598 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3599 {
3600 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3602 if (!n->selected) {
3603 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3604 }
3606 // remember the origin point of the handle
3607 if (n->p.knot == knot) {
3608 n->p.origin_radial = n->p.pos - n->pos;
3609 } else if (n->n.knot == knot) {
3610 n->n.origin_radial = n->n.pos - n->pos;
3611 } else {
3612 g_assert_not_reached();
3613 }
3615 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3616 }
3618 /**
3619 * Node handle ungrabbed callback.
3620 */
3621 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3622 {
3623 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3625 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3626 if (n->p.knot == knot) {
3627 n->p.origin_radial.a = 0;
3628 sp_knot_set_position(knot, &n->p.pos, state);
3629 } else if (n->n.knot == knot) {
3630 n->n.origin_radial.a = 0;
3631 sp_knot_set_position(knot, &n->n.pos, state);
3632 } else {
3633 g_assert_not_reached();
3634 }
3636 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3637 }
3639 /**
3640 * Node handle "request" signal callback.
3641 */
3642 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3643 {
3644 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3646 Inkscape::NodePath::NodeSide *me, *opposite;
3647 gint which;
3648 if (n->p.knot == knot) {
3649 me = &n->p;
3650 opposite = &n->n;
3651 which = -1;
3652 } else if (n->n.knot == knot) {
3653 me = &n->n;
3654 opposite = &n->p;
3655 which = 1;
3656 } else {
3657 me = opposite = NULL;
3658 which = 0;
3659 g_assert_not_reached();
3660 }
3662 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3663 Inkscape::SnappedPoint s ;
3665 Inkscape::NodePath::Node *othernode = opposite->other;
3666 if (othernode) {
3667 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3668 /* We are smooth node adjacent with line */
3669 NR::Point const delta = *p - n->pos;
3670 NR::Coord const len = NR::L2(delta);
3671 Inkscape::NodePath::Node *othernode = opposite->other;
3672 NR::Point const ndelta = n->pos - othernode->pos;
3673 NR::Coord const linelen = NR::L2(ndelta);
3674 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3675 NR::Coord const scal = dot(delta, ndelta) / linelen;
3676 (*p) = n->pos + (scal / linelen) * ndelta;
3677 }
3678 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item);
3679 } else {
3680 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item);
3681 }
3682 } else {
3683 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item);
3684 }
3686 *p = s.getPoint();
3687 if (s.getSnapped()) {
3688 n->subpath->nodepath->desktop->snapindicator->set_new_snappoint(s);
3689 }
3691 sp_node_adjust_handle(n, -which);
3693 return FALSE;
3694 }
3696 /**
3697 * Node handle moved callback.
3698 */
3699 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3700 {
3701 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3703 Inkscape::NodePath::NodeSide *me;
3704 Inkscape::NodePath::NodeSide *other;
3705 if (n->p.knot == knot) {
3706 me = &n->p;
3707 other = &n->n;
3708 } else if (n->n.knot == knot) {
3709 me = &n->n;
3710 other = &n->p;
3711 } else {
3712 me = NULL;
3713 other = NULL;
3714 g_assert_not_reached();
3715 }
3717 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3718 Radial rme(me->pos - n->pos);
3719 Radial rother(other->pos - n->pos);
3720 Radial rnew(*p - n->pos);
3722 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3723 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3724 /* 0 interpreted as "no snapping". */
3726 // 1. Snap to the closest PI/snaps angle, starting from zero.
3727 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3729 // 2. Snap to the original angle, its opposite and perpendiculars
3730 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3731 /* The closest PI/2 angle, starting from original angle */
3732 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3734 // Snap to the closest.
3735 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3736 ? a_snapped
3737 : a_ortho );
3738 }
3740 // 3. Snap to the angle of the opposite line, if any
3741 Inkscape::NodePath::Node *othernode = other->other;
3742 if (othernode) {
3743 NR::Point other_to_snap(0,0);
3744 if (sp_node_side_is_line(n, other)) {
3745 other_to_snap = othernode->pos - n->pos;
3746 } else {
3747 other_to_snap = other->pos - n->pos;
3748 }
3749 if (NR::L2(other_to_snap) > 1e-3) {
3750 Radial rother_to_snap(other_to_snap);
3751 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3752 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3754 // Snap to the closest.
3755 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3756 ? a_snapped
3757 : a_oppo );
3758 }
3759 }
3761 rnew.a = a_snapped;
3762 }
3764 if (state & GDK_MOD1_MASK) {
3765 // lock handle length
3766 rnew.r = me->origin_radial.r;
3767 }
3769 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3770 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3771 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3772 rother.a += rnew.a - rme.a;
3773 other->pos = NR::Point(rother) + n->pos;
3774 if (other->knot) {
3775 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3776 sp_knot_moveto(other->knot, &other->pos);
3777 }
3778 }
3780 me->pos = NR::Point(rnew) + n->pos;
3781 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3783 // move knot, but without emitting the signal:
3784 // we cannot emit a "moved" signal because we're now processing it
3785 sp_knot_moveto(me->knot, &(me->pos));
3787 update_object(n->subpath->nodepath);
3789 /* status text */
3790 SPDesktop *desktop = n->subpath->nodepath->desktop;
3791 if (!desktop) return;
3792 SPEventContext *ec = desktop->event_context;
3793 if (!ec) return;
3794 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3795 if (!mc) return;
3797 double degrees = 180 / M_PI * rnew.a;
3798 if (degrees > 180) degrees -= 360;
3799 if (degrees < -180) degrees += 360;
3800 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3801 degrees = angle_to_compass (degrees);
3803 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3805 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3806 _("<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);
3808 g_string_free(length, TRUE);
3809 }
3811 /**
3812 * Node handle event callback.
3813 */
3814 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3815 {
3816 gboolean ret = FALSE;
3817 switch (event->type) {
3818 case GDK_KEY_PRESS:
3819 switch (get_group0_keyval (&event->key)) {
3820 case GDK_space:
3821 if (event->key.state & GDK_BUTTON1_MASK) {
3822 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3823 stamp_repr(nodepath);
3824 ret = TRUE;
3825 }
3826 break;
3827 default:
3828 break;
3829 }
3830 break;
3831 case GDK_ENTER_NOTIFY:
3832 // we use an experimentally determined threshold that seems to work fine
3833 if (NR::L2(n->pos - knot->pos) < 0.75)
3834 Inkscape::NodePath::Path::active_node = n;
3835 break;
3836 case GDK_LEAVE_NOTIFY:
3837 // we use an experimentally determined threshold that seems to work fine
3838 if (NR::L2(n->pos - knot->pos) < 0.75)
3839 Inkscape::NodePath::Path::active_node = NULL;
3840 break;
3841 default:
3842 break;
3843 }
3845 return ret;
3846 }
3848 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3849 Radial &rme, Radial &rother, gboolean const both)
3850 {
3851 rme.a += angle;
3852 if ( both
3853 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3854 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3855 {
3856 rother.a += angle;
3857 }
3858 }
3860 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3861 Radial &rme, Radial &rother, gboolean const both)
3862 {
3863 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3865 gdouble r;
3866 if ( both
3867 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3868 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3869 {
3870 r = MAX(rme.r, rother.r);
3871 } else {
3872 r = rme.r;
3873 }
3875 gdouble const weird_angle = atan2(norm_angle, r);
3876 /* Bulia says norm_angle is just the visible distance that the
3877 * object's end must travel on the screen. Left as 'angle' for want of
3878 * a better name.*/
3880 rme.a += weird_angle;
3881 if ( both
3882 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3883 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3884 {
3885 rother.a += weird_angle;
3886 }
3887 }
3889 /**
3890 * Rotate one node.
3891 */
3892 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3893 {
3894 Inkscape::NodePath::NodeSide *me, *other;
3895 bool both = false;
3897 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3898 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3900 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3901 me = &(n->p);
3902 other = &(n->n);
3903 } else if (!n->p.other) {
3904 me = &(n->n);
3905 other = &(n->p);
3906 } else {
3907 if (which > 0) { // right handle
3908 if (xn > xp) {
3909 me = &(n->n);
3910 other = &(n->p);
3911 } else {
3912 me = &(n->p);
3913 other = &(n->n);
3914 }
3915 } else if (which < 0){ // left handle
3916 if (xn <= xp) {
3917 me = &(n->n);
3918 other = &(n->p);
3919 } else {
3920 me = &(n->p);
3921 other = &(n->n);
3922 }
3923 } else { // both handles
3924 me = &(n->n);
3925 other = &(n->p);
3926 both = true;
3927 }
3928 }
3930 Radial rme(me->pos - n->pos);
3931 Radial rother(other->pos - n->pos);
3933 if (screen) {
3934 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3935 } else {
3936 node_rotate_one_internal (*n, angle, rme, rother, both);
3937 }
3939 me->pos = n->pos + NR::Point(rme);
3941 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3942 other->pos = n->pos + NR::Point(rother);
3943 }
3945 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3946 // so here we just move all the knots without emitting move signals, for speed
3947 sp_node_update_handles(n, false);
3948 }
3950 /**
3951 * Rotate selected nodes.
3952 */
3953 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3954 {
3955 if (!nodepath || !nodepath->selected) return;
3957 if (g_list_length(nodepath->selected) == 1) {
3958 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3959 node_rotate_one (n, angle, which, screen);
3960 } else {
3961 // rotate as an object:
3963 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3964 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3965 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3966 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3967 box.expandTo (n->pos); // contain all selected nodes
3968 }
3970 gdouble rot;
3971 if (screen) {
3972 gdouble const zoom = nodepath->desktop->current_zoom();
3973 gdouble const zmove = angle / zoom;
3974 gdouble const r = NR::L2(box.max() - box.midpoint());
3975 rot = atan2(zmove, r);
3976 } else {
3977 rot = angle;
3978 }
3980 NR::Point rot_center;
3981 if (Inkscape::NodePath::Path::active_node == NULL)
3982 rot_center = box.midpoint();
3983 else
3984 rot_center = Inkscape::NodePath::Path::active_node->pos;
3986 NR::Matrix t =
3987 NR::Matrix (NR::translate(-rot_center)) *
3988 NR::Matrix (NR::rotate(rot)) *
3989 NR::Matrix (NR::translate(rot_center));
3991 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3992 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3993 n->pos *= t;
3994 n->n.pos *= t;
3995 n->p.pos *= t;
3996 sp_node_update_handles(n, false);
3997 }
3998 }
4000 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4001 }
4003 /**
4004 * Scale one node.
4005 */
4006 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4007 {
4008 bool both = false;
4009 Inkscape::NodePath::NodeSide *me, *other;
4011 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4012 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4014 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4015 me = &(n->p);
4016 other = &(n->n);
4017 n->code = NR_CURVETO;
4018 } else if (!n->p.other) {
4019 me = &(n->n);
4020 other = &(n->p);
4021 if (n->n.other)
4022 n->n.other->code = NR_CURVETO;
4023 } else {
4024 if (which > 0) { // right handle
4025 if (xn > xp) {
4026 me = &(n->n);
4027 other = &(n->p);
4028 if (n->n.other)
4029 n->n.other->code = NR_CURVETO;
4030 } else {
4031 me = &(n->p);
4032 other = &(n->n);
4033 n->code = NR_CURVETO;
4034 }
4035 } else if (which < 0){ // left handle
4036 if (xn <= xp) {
4037 me = &(n->n);
4038 other = &(n->p);
4039 if (n->n.other)
4040 n->n.other->code = NR_CURVETO;
4041 } else {
4042 me = &(n->p);
4043 other = &(n->n);
4044 n->code = NR_CURVETO;
4045 }
4046 } else { // both handles
4047 me = &(n->n);
4048 other = &(n->p);
4049 both = true;
4050 n->code = NR_CURVETO;
4051 if (n->n.other)
4052 n->n.other->code = NR_CURVETO;
4053 }
4054 }
4056 Radial rme(me->pos - n->pos);
4057 Radial rother(other->pos - n->pos);
4059 rme.r += grow;
4060 if (rme.r < 0) rme.r = 0;
4061 if (rme.a == HUGE_VAL) {
4062 if (me->other) { // if direction is unknown, initialize it towards the next node
4063 Radial rme_next(me->other->pos - n->pos);
4064 rme.a = rme_next.a;
4065 } else { // if there's no next, initialize to 0
4066 rme.a = 0;
4067 }
4068 }
4069 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4070 rother.r += grow;
4071 if (rother.r < 0) rother.r = 0;
4072 if (rother.a == HUGE_VAL) {
4073 rother.a = rme.a + M_PI;
4074 }
4075 }
4077 me->pos = n->pos + NR::Point(rme);
4079 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4080 other->pos = n->pos + NR::Point(rother);
4081 }
4083 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4084 // so here we just move all the knots without emitting move signals, for speed
4085 sp_node_update_handles(n, false);
4086 }
4088 /**
4089 * Scale selected nodes.
4090 */
4091 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4092 {
4093 if (!nodepath || !nodepath->selected) return;
4095 if (g_list_length(nodepath->selected) == 1) {
4096 // scale handles of the single selected node
4097 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4098 node_scale_one (n, grow, which);
4099 } else {
4100 // scale nodes as an "object":
4102 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4103 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4104 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4105 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4106 box.expandTo (n->pos); // contain all selected nodes
4107 }
4109 double scale = (box.maxExtent() + grow)/box.maxExtent();
4111 NR::Point scale_center;
4112 if (Inkscape::NodePath::Path::active_node == NULL)
4113 scale_center = box.midpoint();
4114 else
4115 scale_center = Inkscape::NodePath::Path::active_node->pos;
4117 NR::Matrix t =
4118 NR::Matrix (NR::translate(-scale_center)) *
4119 NR::Matrix (NR::scale(scale, scale)) *
4120 NR::Matrix (NR::translate(scale_center));
4122 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4123 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4124 n->pos *= t;
4125 n->n.pos *= t;
4126 n->p.pos *= t;
4127 sp_node_update_handles(n, false);
4128 }
4129 }
4131 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4132 }
4134 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4135 {
4136 if (!nodepath) return;
4137 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4138 }
4140 /**
4141 * Flip selected nodes horizontally/vertically.
4142 */
4143 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4144 {
4145 if (!nodepath || !nodepath->selected) return;
4147 if (g_list_length(nodepath->selected) == 1 && !center) {
4148 // flip handles of the single selected node
4149 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4150 double temp = n->p.pos[axis];
4151 n->p.pos[axis] = n->n.pos[axis];
4152 n->n.pos[axis] = temp;
4153 sp_node_update_handles(n, false);
4154 } else {
4155 // scale nodes as an "object":
4157 NR::Rect box = sp_node_selected_bbox (nodepath);
4158 if (!center) {
4159 center = box.midpoint();
4160 }
4161 NR::Matrix t =
4162 NR::Matrix (NR::translate(- *center)) *
4163 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4164 NR::Matrix (NR::translate(*center));
4166 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4167 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4168 n->pos *= t;
4169 n->n.pos *= t;
4170 n->p.pos *= t;
4171 sp_node_update_handles(n, false);
4172 }
4173 }
4175 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4176 }
4178 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4179 {
4180 g_assert (nodepath->selected);
4182 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4183 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4184 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4185 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4186 box.expandTo (n->pos); // contain all selected nodes
4187 }
4188 return box;
4189 }
4191 //-----------------------------------------------
4192 /**
4193 * Return new subpath under given nodepath.
4194 */
4195 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4196 {
4197 g_assert(nodepath);
4198 g_assert(nodepath->desktop);
4200 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4202 s->nodepath = nodepath;
4203 s->closed = FALSE;
4204 s->nodes = NULL;
4205 s->first = NULL;
4206 s->last = NULL;
4208 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4209 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4210 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4212 return s;
4213 }
4215 /**
4216 * Destroy nodes in subpath, then subpath itself.
4217 */
4218 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4219 {
4220 g_assert(subpath);
4221 g_assert(subpath->nodepath);
4222 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4224 while (subpath->nodes) {
4225 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4226 }
4228 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4230 g_free(subpath);
4231 }
4233 /**
4234 * Link head to tail in subpath.
4235 */
4236 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4237 {
4238 g_assert(!sp->closed);
4239 g_assert(sp->last != sp->first);
4240 g_assert(sp->first->code == NR_MOVETO);
4242 sp->closed = TRUE;
4244 //Link the head to the tail
4245 sp->first->p.other = sp->last;
4246 sp->last->n.other = sp->first;
4247 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4248 sp->first = sp->last;
4250 //Remove the extra end node
4251 sp_nodepath_node_destroy(sp->last->n.other);
4252 }
4254 /**
4255 * Open closed (loopy) subpath at node.
4256 */
4257 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4258 {
4259 g_assert(sp->closed);
4260 g_assert(n->subpath == sp);
4261 g_assert(sp->first == sp->last);
4263 /* We create new startpoint, current node will become last one */
4265 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4266 &n->pos, &n->pos, &n->n.pos);
4269 sp->closed = FALSE;
4271 //Unlink to make a head and tail
4272 sp->first = new_path;
4273 sp->last = n;
4274 n->n.other = NULL;
4275 new_path->p.other = NULL;
4276 }
4278 /**
4279 * Return new node in subpath with given properties.
4280 * \param pos Position of node.
4281 * \param ppos Handle position in previous direction
4282 * \param npos Handle position in previous direction
4283 */
4284 Inkscape::NodePath::Node *
4285 sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, NR::Point *ppos, NR::Point *pos, NR::Point *npos)
4286 {
4287 g_assert(sp);
4288 g_assert(sp->nodepath);
4289 g_assert(sp->nodepath->desktop);
4291 if (nodechunk == NULL)
4292 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4294 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4296 n->subpath = sp;
4298 if (type != Inkscape::NodePath::NODE_NONE) {
4299 // use the type from sodipodi:nodetypes
4300 n->type = type;
4301 } else {
4302 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4303 // points are (almost) collinear
4304 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4305 // endnode, or a node with a retracted handle
4306 n->type = Inkscape::NodePath::NODE_CUSP;
4307 } else {
4308 n->type = Inkscape::NodePath::NODE_SMOOTH;
4309 }
4310 } else {
4311 n->type = Inkscape::NodePath::NODE_CUSP;
4312 }
4313 }
4315 n->code = code;
4316 n->selected = FALSE;
4317 n->pos = *pos;
4318 n->p.pos = *ppos;
4319 n->n.pos = *npos;
4321 n->dragging_out = NULL;
4323 Inkscape::NodePath::Node *prev;
4324 if (next) {
4325 //g_assert(g_list_find(sp->nodes, next));
4326 prev = next->p.other;
4327 } else {
4328 prev = sp->last;
4329 }
4331 if (prev)
4332 prev->n.other = n;
4333 else
4334 sp->first = n;
4336 if (next)
4337 next->p.other = n;
4338 else
4339 sp->last = n;
4341 n->p.other = prev;
4342 n->n.other = next;
4344 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"));
4345 sp_knot_set_position(n->knot, pos, 0);
4347 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4348 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4349 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4350 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4351 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4352 sp_knot_update_ctrl(n->knot);
4354 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4355 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4356 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4357 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4358 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4359 sp_knot_show(n->knot);
4361 // We only create handle knots and lines on demand
4362 n->p.knot = NULL;
4363 n->p.line = NULL;
4364 n->n.knot = NULL;
4365 n->n.line = NULL;
4367 sp->nodes = g_list_prepend(sp->nodes, n);
4369 return n;
4370 }
4372 /**
4373 * Destroy node and its knots, link neighbors in subpath.
4374 */
4375 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4376 {
4377 g_assert(node);
4378 g_assert(node->subpath);
4379 g_assert(SP_IS_KNOT(node->knot));
4381 Inkscape::NodePath::SubPath *sp = node->subpath;
4383 if (node->selected) { // first, deselect
4384 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4385 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4386 }
4388 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4390 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4391 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4392 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4393 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4394 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4395 g_object_unref(G_OBJECT(node->knot));
4397 if (node->p.knot) {
4398 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4399 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4400 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4401 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4402 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4403 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4404 g_object_unref(G_OBJECT(node->p.knot));
4405 node->p.knot = NULL;
4406 }
4408 if (node->n.knot) {
4409 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4410 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4411 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4412 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4413 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4414 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4415 g_object_unref(G_OBJECT(node->n.knot));
4416 node->n.knot = NULL;
4417 }
4419 if (node->p.line)
4420 gtk_object_destroy(GTK_OBJECT(node->p.line));
4421 if (node->n.line)
4422 gtk_object_destroy(GTK_OBJECT(node->n.line));
4424 if (sp->nodes) { // there are others nodes on the subpath
4425 if (sp->closed) {
4426 if (sp->first == node) {
4427 g_assert(sp->last == node);
4428 sp->first = node->n.other;
4429 sp->last = sp->first;
4430 }
4431 node->p.other->n.other = node->n.other;
4432 node->n.other->p.other = node->p.other;
4433 } else {
4434 if (sp->first == node) {
4435 sp->first = node->n.other;
4436 sp->first->code = NR_MOVETO;
4437 }
4438 if (sp->last == node) sp->last = node->p.other;
4439 if (node->p.other) node->p.other->n.other = node->n.other;
4440 if (node->n.other) node->n.other->p.other = node->p.other;
4441 }
4442 } else { // this was the last node on subpath
4443 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4444 }
4446 g_mem_chunk_free(nodechunk, node);
4447 }
4449 /**
4450 * Returns one of the node's two sides.
4451 * \param which Indicates which side.
4452 * \return Pointer to previous node side if which==-1, next if which==1.
4453 */
4454 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4455 {
4456 g_assert(node);
4458 switch (which) {
4459 case -1:
4460 return &node->p;
4461 case 1:
4462 return &node->n;
4463 default:
4464 break;
4465 }
4467 g_assert_not_reached();
4469 return NULL;
4470 }
4472 /**
4473 * Return the other side of the node, given one of its sides.
4474 */
4475 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4476 {
4477 g_assert(node);
4479 if (me == &node->p) return &node->n;
4480 if (me == &node->n) return &node->p;
4482 g_assert_not_reached();
4484 return NULL;
4485 }
4487 /**
4488 * Return NRPathcode on the given side of the node.
4489 */
4490 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4491 {
4492 g_assert(node);
4494 if (me == &node->p) {
4495 if (node->p.other) return (NRPathcode)node->code;
4496 return NR_MOVETO;
4497 }
4499 if (me == &node->n) {
4500 if (node->n.other) return (NRPathcode)node->n.other->code;
4501 return NR_MOVETO;
4502 }
4504 g_assert_not_reached();
4506 return NR_END;
4507 }
4509 /**
4510 * Return node with the given index
4511 */
4512 Inkscape::NodePath::Node *
4513 sp_nodepath_get_node_by_index(int index)
4514 {
4515 Inkscape::NodePath::Node *e = NULL;
4517 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4518 if (!nodepath) {
4519 return e;
4520 }
4522 //find segment
4523 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4525 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4526 int n = g_list_length(sp->nodes);
4527 if (sp->closed) {
4528 n++;
4529 }
4531 //if the piece belongs to this subpath grab it
4532 //otherwise move onto the next subpath
4533 if (index < n) {
4534 e = sp->first;
4535 for (int i = 0; i < index; ++i) {
4536 e = e->n.other;
4537 }
4538 break;
4539 } else {
4540 if (sp->closed) {
4541 index -= (n+1);
4542 } else {
4543 index -= n;
4544 }
4545 }
4546 }
4548 return e;
4549 }
4551 /**
4552 * Returns plain text meaning of node type.
4553 */
4554 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4555 {
4556 unsigned retracted = 0;
4557 bool endnode = false;
4559 for (int which = -1; which <= 1; which += 2) {
4560 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4561 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4562 retracted ++;
4563 if (!side->other)
4564 endnode = true;
4565 }
4567 if (retracted == 0) {
4568 if (endnode) {
4569 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4570 return _("end node");
4571 } else {
4572 switch (node->type) {
4573 case Inkscape::NodePath::NODE_CUSP:
4574 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4575 return _("cusp");
4576 case Inkscape::NodePath::NODE_SMOOTH:
4577 // TRANSLATORS: "smooth" is an adjective here
4578 return _("smooth");
4579 case Inkscape::NodePath::NODE_SYMM:
4580 return _("symmetric");
4581 }
4582 }
4583 } else if (retracted == 1) {
4584 if (endnode) {
4585 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4586 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4587 } else {
4588 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4589 }
4590 } else {
4591 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4592 }
4594 return NULL;
4595 }
4597 /**
4598 * Handles content of statusbar as long as node tool is active.
4599 */
4600 void
4601 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4602 {
4603 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");
4604 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4606 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4607 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4608 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4609 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4611 SPDesktop *desktop = NULL;
4612 if (nodepath) {
4613 desktop = nodepath->desktop;
4614 } else {
4615 desktop = SP_ACTIVE_DESKTOP;
4616 }
4618 SPEventContext *ec = desktop->event_context;
4619 if (!ec) return;
4620 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4621 if (!mc) return;
4623 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4625 if (selected_nodes == 0) {
4626 Inkscape::Selection *sel = desktop->selection;
4627 if (!sel || sel->isEmpty()) {
4628 mc->setF(Inkscape::NORMAL_MESSAGE,
4629 _("Select a single object to edit its nodes or handles."));
4630 } else {
4631 if (nodepath) {
4632 mc->setF(Inkscape::NORMAL_MESSAGE,
4633 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.",
4634 "<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.",
4635 total_nodes),
4636 total_nodes);
4637 } else {
4638 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4639 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4640 } else {
4641 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4642 }
4643 }
4644 }
4645 } else if (nodepath && selected_nodes == 1) {
4646 mc->setF(Inkscape::NORMAL_MESSAGE,
4647 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4648 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4649 total_nodes),
4650 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4651 } else {
4652 if (selected_subpaths > 1) {
4653 mc->setF(Inkscape::NORMAL_MESSAGE,
4654 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4655 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4656 total_nodes),
4657 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4658 } else {
4659 mc->setF(Inkscape::NORMAL_MESSAGE,
4660 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4661 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4662 total_nodes),
4663 selected_nodes, total_nodes, when_selected);
4664 }
4665 }
4666 }
4668 /*
4669 * returns a *copy* of the curve of that object.
4670 */
4671 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4672 if (!object)
4673 return NULL;
4675 SPCurve *curve = NULL;
4676 if (SP_IS_PATH(object)) {
4677 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4678 curve = sp_curve_copy(curve_new);
4679 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4680 const gchar *svgd = object->repr->attribute(key);
4681 if (svgd) {
4682 NArtBpath *bpath = sp_svg_read_path(svgd);
4683 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4684 if (curve_new) {
4685 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4686 } else {
4687 g_free(bpath);
4688 }
4689 }
4690 }
4692 return curve;
4693 }
4695 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4696 if (!np || !np->object || !curve)
4697 return;
4699 if (SP_IS_PATH(np->object)) {
4700 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4701 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4702 } else {
4703 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4704 }
4705 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4706 // FIXME: this writing to string and then reading from string is bound to be slow.
4707 // create a method to convert from curve directly to 2geom...
4708 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4709 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4710 g_free(svgpath);
4712 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4713 }
4714 }
4716 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4717 np->show_helperpath = show;
4719 if (show) {
4720 SPCurve *helper_curve = sp_curve_copy(np->curve);
4721 sp_curve_transform(helper_curve, np->i2d );
4722 if (!np->helper_path) {
4723 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4724 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);
4725 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4726 sp_canvas_item_move_to_z(np->helper_path, 0);
4727 sp_canvas_item_show(np->helper_path);
4728 } else {
4729 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4730 }
4731 sp_curve_unref(helper_curve);
4732 } else {
4733 if (np->helper_path) {
4734 GtkObject *temp = np->helper_path;
4735 np->helper_path = NULL;
4736 gtk_object_destroy(temp);
4737 }
4738 }
4739 }
4741 /* sp_nodepath_make_straight_path:
4742 * Prevents user from curving the path by dragging a segment or activating handles etc.
4743 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4744 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4745 */
4746 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4747 np->straight_path = true;
4748 np->show_handles = false;
4749 g_message("add code to make the path straight.");
4750 // do sp_nodepath_convert_node_type on all nodes?
4751 // coding tip: search for this text : "Make selected segments lines"
4752 }
4755 /*
4756 Local Variables:
4757 mode:c++
4758 c-file-style:"stroustrup"
4759 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4760 indent-tabs-mode:nil
4761 fill-column:99
4762 End:
4763 */
4764 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :