6b1c540574fe79251b327cc7d4f2104d674fd628
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 m.setup(nodepath->desktop, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1237 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1238 if (s.getDistance() < best) {
1239 best = s.getDistance();
1240 best_abs = s;
1241 best_pt = s.getPoint() - n->pos;
1242 }
1243 }
1245 if (best_abs.getSnapped()) {
1246 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1247 }
1248 }
1250 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1251 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1252 sp_node_moveto(n, n->pos + best_pt);
1253 }
1255 // do not update repr here so that node dragging is acceptably fast
1256 update_object(nodepath);
1257 }
1259 /**
1260 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1261 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1262 near x = 0.
1263 */
1264 double
1265 sculpt_profile (double x, double alpha, guint profile)
1266 {
1267 if (x >= 1)
1268 return 0;
1269 if (x <= 0)
1270 return 1;
1272 switch (profile) {
1273 case SCULPT_PROFILE_LINEAR:
1274 return 1 - x;
1275 case SCULPT_PROFILE_BELL:
1276 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1277 case SCULPT_PROFILE_ELLIPTIC:
1278 return sqrt(1 - x*x);
1279 }
1281 return 1;
1282 }
1284 double
1285 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1286 {
1287 // extremely primitive for now, don't have time to look for the real one
1288 double lower = NR::L2(b - a);
1289 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1290 return (lower + upper)/2;
1291 }
1293 void
1294 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1295 {
1296 n->pos = n->origin + delta;
1297 n->n.pos = n->n.origin + delta_n;
1298 n->p.pos = n->p.origin + delta_p;
1299 sp_node_adjust_handles(n);
1300 sp_node_update_handles(n, false);
1301 }
1303 /**
1304 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1305 * on how far they are from the dragged node n.
1306 */
1307 static void
1308 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1309 {
1310 g_assert (n);
1311 g_assert (nodepath);
1312 g_assert (n->subpath->nodepath == nodepath);
1314 double pressure = n->knot->pressure;
1315 if (pressure == 0)
1316 pressure = 0.5; // default
1317 pressure = CLAMP (pressure, 0.2, 0.8);
1319 // map pressure to alpha = 1/5 ... 5
1320 double alpha = 1 - 2 * fabs(pressure - 0.5);
1321 if (pressure > 0.5)
1322 alpha = 1/alpha;
1324 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1326 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1327 // Only one subpath has selected nodes:
1328 // use linear mode, where the distance from n to node being dragged is calculated along the path
1330 double n_sel_range = 0, p_sel_range = 0;
1331 guint n_nodes = 0, p_nodes = 0;
1332 guint n_sel_nodes = 0, p_sel_nodes = 0;
1334 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1335 {
1336 double n_range = 0, p_range = 0;
1337 bool n_going = true, p_going = true;
1338 Inkscape::NodePath::Node *n_node = n;
1339 Inkscape::NodePath::Node *p_node = n;
1340 do {
1341 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1342 if (n_node && n_going)
1343 n_node = n_node->n.other;
1344 if (n_node == NULL) {
1345 n_going = false;
1346 } else {
1347 n_nodes ++;
1348 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1349 if (n_node->selected) {
1350 n_sel_nodes ++;
1351 n_sel_range = n_range;
1352 }
1353 if (n_node == p_node) {
1354 n_going = false;
1355 p_going = false;
1356 }
1357 }
1358 if (p_node && p_going)
1359 p_node = p_node->p.other;
1360 if (p_node == NULL) {
1361 p_going = false;
1362 } else {
1363 p_nodes ++;
1364 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1365 if (p_node->selected) {
1366 p_sel_nodes ++;
1367 p_sel_range = p_range;
1368 }
1369 if (p_node == n_node) {
1370 n_going = false;
1371 p_going = false;
1372 }
1373 }
1374 } while (n_going || p_going);
1375 }
1377 // Second pass: actually move nodes in this subpath
1378 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1379 {
1380 double n_range = 0, p_range = 0;
1381 bool n_going = true, p_going = true;
1382 Inkscape::NodePath::Node *n_node = n;
1383 Inkscape::NodePath::Node *p_node = n;
1384 do {
1385 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1386 if (n_node && n_going)
1387 n_node = n_node->n.other;
1388 if (n_node == NULL) {
1389 n_going = false;
1390 } else {
1391 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1392 if (n_node->selected) {
1393 sp_nodepath_move_node_and_handles (n_node,
1394 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1395 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1396 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1397 }
1398 if (n_node == p_node) {
1399 n_going = false;
1400 p_going = false;
1401 }
1402 }
1403 if (p_node && p_going)
1404 p_node = p_node->p.other;
1405 if (p_node == NULL) {
1406 p_going = false;
1407 } else {
1408 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1409 if (p_node->selected) {
1410 sp_nodepath_move_node_and_handles (p_node,
1411 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1412 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1413 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1414 }
1415 if (p_node == n_node) {
1416 n_going = false;
1417 p_going = false;
1418 }
1419 }
1420 } while (n_going || p_going);
1421 }
1423 } else {
1424 // Multiple subpaths have selected nodes:
1425 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1426 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1427 // fix the pear-like shape when sculpting e.g. a ring
1429 // First pass: calculate range
1430 gdouble direct_range = 0;
1431 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1432 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1433 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1434 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1435 if (node->selected) {
1436 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1437 }
1438 }
1439 }
1441 // Second pass: actually move nodes
1442 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1443 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1444 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1445 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1446 if (node->selected) {
1447 if (direct_range > 1e-6) {
1448 sp_nodepath_move_node_and_handles (node,
1449 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1450 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1451 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1452 } else {
1453 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1454 }
1456 }
1457 }
1458 }
1459 }
1461 // do not update repr here so that node dragging is acceptably fast
1462 update_object(nodepath);
1463 }
1466 /**
1467 * Move node selection to point, adjust its and neighbouring handles,
1468 * handle possible snapping, and commit the change with possible undo.
1469 */
1470 void
1471 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1472 {
1473 if (!nodepath) return;
1475 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1477 if (dx == 0) {
1478 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1479 } else if (dy == 0) {
1480 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1481 } else {
1482 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1483 }
1484 }
1486 /**
1487 * Move node selection off screen and commit the change.
1488 */
1489 void
1490 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1491 {
1492 // borrowed from sp_selection_move_screen in selection-chemistry.c
1493 // we find out the current zoom factor and divide deltas by it
1494 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1496 gdouble zoom = desktop->current_zoom();
1497 gdouble zdx = dx / zoom;
1498 gdouble zdy = dy / zoom;
1500 if (!nodepath) return;
1502 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1504 if (dx == 0) {
1505 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1506 } else if (dy == 0) {
1507 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1508 } else {
1509 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1510 }
1511 }
1513 /**
1514 * Move selected nodes to the absolute position given
1515 */
1516 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1517 {
1518 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1519 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1520 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1521 sp_node_moveto(n, npos);
1522 }
1524 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1525 }
1527 /**
1528 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1529 */
1530 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1531 {
1532 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1533 g_return_val_if_fail(nodepath->selected, no_coord);
1535 // determine coordinate of first selected node
1536 GList *nsel = nodepath->selected;
1537 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1538 NR::Coord coord = n->pos[axis];
1539 bool coincide = true;
1541 // compare it to the coordinates of all the other selected nodes
1542 for (GList *l = nsel->next; l != NULL; l = l->next) {
1543 n = (Inkscape::NodePath::Node *) l->data;
1544 if (n->pos[axis] != coord) {
1545 coincide = false;
1546 }
1547 }
1548 if (coincide) {
1549 return coord;
1550 } else {
1551 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1552 // currently we return the coordinate of the bounding box midpoint because I don't know how
1553 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1554 return bbox.midpoint()[axis];
1555 }
1556 }
1558 /** If they don't yet exist, creates knot and line for the given side of the node */
1559 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1560 {
1561 if (!side->knot) {
1562 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"));
1564 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1565 side->knot->setSize (7);
1566 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1567 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1568 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1569 sp_knot_update_ctrl(side->knot);
1571 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1572 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1573 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1574 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1575 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1576 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1577 }
1579 if (!side->line) {
1580 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1581 SP_TYPE_CTRLLINE, NULL);
1582 }
1583 }
1585 /**
1586 * Ensure the given handle of the node is visible/invisible, update its screen position
1587 */
1588 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1589 {
1590 g_assert(node != NULL);
1592 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1593 NRPathcode code = sp_node_path_code_from_side(node, side);
1595 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1597 if (show_handle) {
1598 if (!side->knot) { // No handle knot at all
1599 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1600 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1601 side->knot->pos = side->pos;
1602 if (side->knot->item)
1603 SP_CTRL(side->knot->item)->moveto(side->pos);
1604 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1605 sp_knot_show(side->knot);
1606 } else {
1607 if (side->knot->pos != side->pos) { // only if it's really moved
1608 if (fire_move_signals) {
1609 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1610 } else {
1611 sp_knot_moveto(side->knot, &side->pos);
1612 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1613 }
1614 }
1615 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1616 sp_knot_show(side->knot);
1617 }
1618 }
1619 sp_canvas_item_show(side->line);
1620 } else {
1621 if (side->knot) {
1622 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1623 sp_knot_hide(side->knot);
1624 }
1625 }
1626 if (side->line) {
1627 sp_canvas_item_hide(side->line);
1628 }
1629 }
1630 }
1632 /**
1633 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1634 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1635 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1636 * updated; otherwise, just move the knots silently (used in batch moves).
1637 */
1638 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1639 {
1640 g_assert(node != NULL);
1642 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1643 sp_knot_show(node->knot);
1644 }
1646 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1647 if (fire_move_signals)
1648 sp_knot_set_position(node->knot, &node->pos, 0);
1649 else
1650 sp_knot_moveto(node->knot, &node->pos);
1651 }
1653 gboolean show_handles = node->selected;
1654 if (node->p.other != NULL) {
1655 if (node->p.other->selected) show_handles = TRUE;
1656 }
1657 if (node->n.other != NULL) {
1658 if (node->n.other->selected) show_handles = TRUE;
1659 }
1661 if (node->subpath->nodepath->show_handles == false)
1662 show_handles = FALSE;
1664 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1665 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1666 }
1668 /**
1669 * Call sp_node_update_handles() for all nodes on subpath.
1670 */
1671 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1672 {
1673 g_assert(subpath != NULL);
1675 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1676 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1677 }
1678 }
1680 /**
1681 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1682 */
1683 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1684 {
1685 g_assert(nodepath != NULL);
1687 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1688 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1689 }
1690 }
1692 void
1693 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1694 {
1695 if (nodepath == NULL) return;
1697 nodepath->show_handles = show;
1698 sp_nodepath_update_handles(nodepath);
1699 }
1701 /**
1702 * Adds all selected nodes in nodepath to list.
1703 */
1704 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1705 {
1706 StlConv<Node *>::list(l, selected);
1707 /// \todo this adds a copying, rework when the selection becomes a stl list
1708 }
1710 /**
1711 * Align selected nodes on the specified axis.
1712 */
1713 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1714 {
1715 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1716 return;
1717 }
1719 if ( !nodepath->selected->next ) { // only one node selected
1720 return;
1721 }
1722 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1723 NR::Point dest(pNode->pos);
1724 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1725 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1726 if (pNode) {
1727 dest[axis] = pNode->pos[axis];
1728 sp_node_moveto(pNode, dest);
1729 }
1730 }
1732 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1733 }
1735 /// Helper struct.
1736 struct NodeSort
1737 {
1738 Inkscape::NodePath::Node *_node;
1739 NR::Coord _coord;
1740 /// \todo use vectorof pointers instead of calling copy ctor
1741 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1742 _node(node), _coord(node->pos[axis])
1743 {}
1745 };
1747 static bool operator<(NodeSort const &a, NodeSort const &b)
1748 {
1749 return (a._coord < b._coord);
1750 }
1752 /**
1753 * Distribute selected nodes on the specified axis.
1754 */
1755 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1756 {
1757 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1758 return;
1759 }
1761 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1762 return;
1763 }
1765 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1766 std::vector<NodeSort> sorted;
1767 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1768 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1769 if (pNode) {
1770 NodeSort n(pNode, axis);
1771 sorted.push_back(n);
1772 //dest[axis] = pNode->pos[axis];
1773 //sp_node_moveto(pNode, dest);
1774 }
1775 }
1776 std::sort(sorted.begin(), sorted.end());
1777 unsigned int len = sorted.size();
1778 //overall bboxes span
1779 float dist = (sorted.back()._coord -
1780 sorted.front()._coord);
1781 //new distance between each bbox
1782 float step = (dist) / (len - 1);
1783 float pos = sorted.front()._coord;
1784 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1785 it < sorted.end();
1786 it ++ )
1787 {
1788 NR::Point dest((*it)._node->pos);
1789 dest[axis] = pos;
1790 sp_node_moveto((*it)._node, dest);
1791 pos += step;
1792 }
1794 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1795 }
1798 /**
1799 * Call sp_nodepath_line_add_node() for all selected segments.
1800 */
1801 void
1802 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1803 {
1804 if (!nodepath) {
1805 return;
1806 }
1808 GList *nl = NULL;
1810 int n_added = 0;
1812 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1813 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1814 g_assert(t->selected);
1815 if (t->p.other && t->p.other->selected) {
1816 nl = g_list_prepend(nl, t);
1817 }
1818 }
1820 while (nl) {
1821 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1822 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1823 sp_nodepath_node_select(n, TRUE, FALSE);
1824 n_added ++;
1825 nl = g_list_remove(nl, t);
1826 }
1828 /** \todo fixme: adjust ? */
1829 sp_nodepath_update_handles(nodepath);
1831 if (n_added > 1) {
1832 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1833 } else if (n_added > 0) {
1834 sp_nodepath_update_repr(nodepath, _("Add node"));
1835 }
1837 sp_nodepath_update_statusbar(nodepath);
1838 }
1840 /**
1841 * Select segment nearest to point
1842 */
1843 void
1844 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1845 {
1846 if (!nodepath) {
1847 return;
1848 }
1850 sp_nodepath_ensure_livarot_path(nodepath);
1851 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1852 if (!maybe_position) {
1853 return;
1854 }
1855 Path::cut_position position = *maybe_position;
1857 //find segment to segment
1858 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1860 //fixme: this can return NULL, so check before proceeding.
1861 g_return_if_fail(e != NULL);
1863 gboolean force = FALSE;
1864 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1865 force = TRUE;
1866 }
1867 sp_nodepath_node_select(e, (gboolean) toggle, force);
1868 if (e->p.other)
1869 sp_nodepath_node_select(e->p.other, TRUE, force);
1871 sp_nodepath_update_handles(nodepath);
1873 sp_nodepath_update_statusbar(nodepath);
1874 }
1876 /**
1877 * Add a node nearest to point
1878 */
1879 void
1880 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1881 {
1882 if (!nodepath) {
1883 return;
1884 }
1886 sp_nodepath_ensure_livarot_path(nodepath);
1887 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1888 if (!maybe_position) {
1889 return;
1890 }
1891 Path::cut_position position = *maybe_position;
1893 //find segment to split
1894 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1896 //don't know why but t seems to flip for lines
1897 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1898 position.t = 1.0 - position.t;
1899 }
1900 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1901 sp_nodepath_node_select(n, FALSE, TRUE);
1903 /* fixme: adjust ? */
1904 sp_nodepath_update_handles(nodepath);
1906 sp_nodepath_update_repr(nodepath, _("Add node"));
1908 sp_nodepath_update_statusbar(nodepath);
1909 }
1911 /*
1912 * Adjusts a segment so that t moves by a certain delta for dragging
1913 * converts lines to curves
1914 *
1915 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1916 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1917 */
1918 void
1919 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1920 {
1921 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1923 //fixme: e and e->p can be NULL, so check for those before proceeding
1924 g_return_if_fail(e != NULL);
1925 g_return_if_fail(&e->p != NULL);
1927 /* feel good is an arbitrary parameter that distributes the delta between handles
1928 * if t of the drag point is less than 1/6 distance form the endpoint only
1929 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1930 */
1931 double feel_good;
1932 if (t <= 1.0 / 6.0)
1933 feel_good = 0;
1934 else if (t <= 0.5)
1935 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1936 else if (t <= 5.0 / 6.0)
1937 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1938 else
1939 feel_good = 1;
1941 //if we're dragging a line convert it to a curve
1942 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1943 sp_nodepath_set_line_type(e, NR_CURVETO);
1944 }
1946 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1947 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1948 e->p.other->n.pos += offsetcoord0;
1949 e->p.pos += offsetcoord1;
1951 // adjust handles of adjacent nodes where necessary
1952 sp_node_adjust_handle(e,1);
1953 sp_node_adjust_handle(e->p.other,-1);
1955 sp_nodepath_update_handles(e->subpath->nodepath);
1957 update_object(e->subpath->nodepath);
1959 sp_nodepath_update_statusbar(e->subpath->nodepath);
1960 }
1963 /**
1964 * Call sp_nodepath_break() for all selected segments.
1965 */
1966 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1967 {
1968 if (!nodepath) return;
1970 GList *temp = NULL;
1971 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1972 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1973 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1974 if (nn == NULL) continue; // no break, no new node
1975 temp = g_list_prepend(temp, nn);
1976 }
1978 if (temp) {
1979 sp_nodepath_deselect(nodepath);
1980 }
1981 for (GList *l = temp; l != NULL; l = l->next) {
1982 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1983 }
1985 sp_nodepath_update_handles(nodepath);
1987 sp_nodepath_update_repr(nodepath, _("Break path"));
1988 }
1990 /**
1991 * Duplicate the selected node(s).
1992 */
1993 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1994 {
1995 if (!nodepath) {
1996 return;
1997 }
1999 GList *temp = NULL;
2000 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2001 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2002 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2003 if (nn == NULL) continue; // could not duplicate
2004 temp = g_list_prepend(temp, nn);
2005 }
2007 if (temp) {
2008 sp_nodepath_deselect(nodepath);
2009 }
2010 for (GList *l = temp; l != NULL; l = l->next) {
2011 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2012 }
2014 sp_nodepath_update_handles(nodepath);
2016 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2017 }
2019 /**
2020 * Internal function to join two nodes by merging them into one.
2021 */
2022 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2023 {
2024 /* a and b are endpoints */
2026 // if one of the two nodes is mouseovered, fix its position
2027 NR::Point c;
2028 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2029 c = a->pos;
2030 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2031 c = b->pos;
2032 } else {
2033 // otherwise, move joined node to the midpoint
2034 c = (a->pos + b->pos) / 2;
2035 }
2037 if (a->subpath == b->subpath) {
2038 Inkscape::NodePath::SubPath *sp = a->subpath;
2039 sp_nodepath_subpath_close(sp);
2040 sp_node_moveto (sp->first, c);
2042 sp_nodepath_update_handles(sp->nodepath);
2043 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2044 return;
2045 }
2047 /* a and b are separate subpaths */
2048 Inkscape::NodePath::SubPath *sa = a->subpath;
2049 Inkscape::NodePath::SubPath *sb = b->subpath;
2050 NR::Point p;
2051 Inkscape::NodePath::Node *n;
2052 NRPathcode code;
2053 if (a == sa->first) {
2054 // we will now reverse sa, so that a is its last node, not first, and drop that node
2055 p = sa->first->n.pos;
2056 code = (NRPathcode)sa->first->n.other->code;
2057 // create new subpath
2058 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2059 // create a first moveto node on it
2060 n = sa->last;
2061 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2062 n = n->p.other;
2063 if (n == sa->first) n = NULL;
2064 while (n) {
2065 // copy the rest of the nodes from sa to t, going backwards
2066 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2067 n = n->p.other;
2068 if (n == sa->first) n = NULL;
2069 }
2070 // replace sa with t
2071 sp_nodepath_subpath_destroy(sa);
2072 sa = t;
2073 } else if (a == sa->last) {
2074 // a is already last, just drop it
2075 p = sa->last->p.pos;
2076 code = (NRPathcode)sa->last->code;
2077 sp_nodepath_node_destroy(sa->last);
2078 } else {
2079 code = NR_END;
2080 g_assert_not_reached();
2081 }
2083 if (b == sb->first) {
2084 // copy all nodes from b to a, forward
2085 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2086 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2087 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2088 }
2089 } else if (b == sb->last) {
2090 // copy all nodes from b to a, backward
2091 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2092 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2093 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2094 }
2095 } else {
2096 g_assert_not_reached();
2097 }
2098 /* and now destroy sb */
2100 sp_nodepath_subpath_destroy(sb);
2102 sp_nodepath_update_handles(sa->nodepath);
2104 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2106 sp_nodepath_update_statusbar(nodepath);
2107 }
2109 /**
2110 * Internal function to join two nodes by adding a segment between them.
2111 */
2112 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2113 {
2114 if (a->subpath == b->subpath) {
2115 Inkscape::NodePath::SubPath *sp = a->subpath;
2117 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2118 sp->closed = TRUE;
2120 sp->first->p.other = sp->last;
2121 sp->last->n.other = sp->first;
2123 sp_node_handle_mirror_p_to_n(sp->last);
2124 sp_node_handle_mirror_n_to_p(sp->first);
2126 sp->first->code = sp->last->code;
2127 sp->first = sp->last;
2129 sp_nodepath_update_handles(sp->nodepath);
2131 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2133 return;
2134 }
2136 /* a and b are separate subpaths */
2137 Inkscape::NodePath::SubPath *sa = a->subpath;
2138 Inkscape::NodePath::SubPath *sb = b->subpath;
2140 Inkscape::NodePath::Node *n;
2141 NR::Point p;
2142 NRPathcode code;
2143 if (a == sa->first) {
2144 code = (NRPathcode) sa->first->n.other->code;
2145 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2146 n = sa->last;
2147 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2148 for (n = n->p.other; n != NULL; n = n->p.other) {
2149 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2150 }
2151 sp_nodepath_subpath_destroy(sa);
2152 sa = t;
2153 } else if (a == sa->last) {
2154 code = (NRPathcode)sa->last->code;
2155 } else {
2156 code = NR_END;
2157 g_assert_not_reached();
2158 }
2160 if (b == sb->first) {
2161 n = sb->first;
2162 sp_node_handle_mirror_p_to_n(sa->last);
2163 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2164 sp_node_handle_mirror_n_to_p(sa->last);
2165 for (n = n->n.other; n != NULL; n = n->n.other) {
2166 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2167 }
2168 } else if (b == sb->last) {
2169 n = sb->last;
2170 sp_node_handle_mirror_p_to_n(sa->last);
2171 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2172 sp_node_handle_mirror_n_to_p(sa->last);
2173 for (n = n->p.other; n != NULL; n = n->p.other) {
2174 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2175 }
2176 } else {
2177 g_assert_not_reached();
2178 }
2179 /* and now destroy sb */
2181 sp_nodepath_subpath_destroy(sb);
2183 sp_nodepath_update_handles(sa->nodepath);
2185 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2186 }
2188 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2190 /**
2191 * Internal function to handle joining two nodes.
2192 */
2193 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2194 {
2195 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2197 if (g_list_length(nodepath->selected) != 2) {
2198 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2199 return;
2200 }
2202 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2203 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2205 g_assert(a != b);
2206 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2207 // someone tried to join an orphan node (i.e. a single-node subpath).
2208 // this is not worth an error message, just fail silently.
2209 return;
2210 }
2212 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2213 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2214 return;
2215 }
2217 switch(mode) {
2218 case NODE_JOIN_ENDPOINTS:
2219 do_node_selected_join(nodepath, a, b);
2220 break;
2221 case NODE_JOIN_SEGMENT:
2222 do_node_selected_join_segment(nodepath, a, b);
2223 break;
2224 }
2225 }
2227 /**
2228 * Join two nodes by merging them into one.
2229 */
2230 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2231 {
2232 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2233 }
2235 /**
2236 * Join two nodes by adding a segment between them.
2237 */
2238 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2239 {
2240 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2241 }
2243 /**
2244 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2245 */
2246 void sp_node_delete_preserve(GList *nodes_to_delete)
2247 {
2248 GSList *nodepaths = NULL;
2250 while (nodes_to_delete) {
2251 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2252 Inkscape::NodePath::SubPath *sp = node->subpath;
2253 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2254 Inkscape::NodePath::Node *sample_cursor = NULL;
2255 Inkscape::NodePath::Node *sample_end = NULL;
2256 Inkscape::NodePath::Node *delete_cursor = node;
2257 bool just_delete = false;
2259 //find the start of this contiguous selection
2260 //move left to the first node that is not selected
2261 //or the start of the non-closed path
2262 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2263 delete_cursor = curr;
2264 }
2266 //just delete at the beginning of an open path
2267 if (!delete_cursor->p.other) {
2268 sample_cursor = delete_cursor;
2269 just_delete = true;
2270 } else {
2271 sample_cursor = delete_cursor->p.other;
2272 }
2274 //calculate points for each segment
2275 int rate = 5;
2276 float period = 1.0 / rate;
2277 std::vector<NR::Point> data;
2278 if (!just_delete) {
2279 data.push_back(sample_cursor->pos);
2280 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2281 //just delete at the end of an open path
2282 if (!sp->closed && curr == sp->last) {
2283 just_delete = true;
2284 break;
2285 }
2287 //sample points on the contiguous selected segment
2288 NR::Point *bez;
2289 bez = new NR::Point [4];
2290 bez[0] = curr->pos;
2291 bez[1] = curr->n.pos;
2292 bez[2] = curr->n.other->p.pos;
2293 bez[3] = curr->n.other->pos;
2294 for (int i=1; i<rate; i++) {
2295 gdouble t = i * period;
2296 NR::Point p = bezier_pt(3, bez, t);
2297 data.push_back(p);
2298 }
2299 data.push_back(curr->n.other->pos);
2301 sample_end = curr->n.other;
2302 //break if we've come full circle or hit the end of the selection
2303 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2304 break;
2305 }
2306 }
2307 }
2309 if (!just_delete) {
2310 //calculate the best fitting single segment and adjust the endpoints
2311 NR::Point *adata;
2312 adata = new NR::Point [data.size()];
2313 copy(data.begin(), data.end(), adata);
2315 NR::Point *bez;
2316 bez = new NR::Point [4];
2317 //would decreasing error create a better fitting approximation?
2318 gdouble error = 1.0;
2319 gint ret;
2320 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2322 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2323 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2324 //the resulting nodes behave as expected.
2325 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2326 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2327 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2328 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2330 //adjust endpoints
2331 sample_cursor->n.pos = bez[1];
2332 sample_end->p.pos = bez[2];
2333 }
2335 //destroy this contiguous selection
2336 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2337 Inkscape::NodePath::Node *temp = delete_cursor;
2338 if (delete_cursor->n.other == delete_cursor) {
2339 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2340 delete_cursor = NULL;
2341 } else {
2342 delete_cursor = delete_cursor->n.other;
2343 }
2344 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2345 sp_nodepath_node_destroy(temp);
2346 }
2348 sp_nodepath_update_handles(nodepath);
2350 if (!g_slist_find(nodepaths, nodepath))
2351 nodepaths = g_slist_prepend (nodepaths, nodepath);
2352 }
2354 for (GSList *i = nodepaths; i; i = i->next) {
2355 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2356 // different nodepaths will give us one undo event per nodepath
2357 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2359 // if the entire nodepath is removed, delete the selected object.
2360 if (nodepath->subpaths == NULL ||
2361 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2362 //at least 2
2363 sp_nodepath_get_node_count(nodepath) < 2) {
2364 SPDocument *document = sp_desktop_document (nodepath->desktop);
2365 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2366 //delete this nodepath's object, not the entire selection! (though at this time, this
2367 //does not matter)
2368 sp_selection_delete();
2369 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2370 _("Delete nodes"));
2371 } else {
2372 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2373 sp_nodepath_update_statusbar(nodepath);
2374 }
2375 }
2377 g_slist_free (nodepaths);
2378 }
2380 /**
2381 * Delete one or more selected nodes.
2382 */
2383 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2384 {
2385 if (!nodepath) return;
2386 if (!nodepath->selected) return;
2388 /** \todo fixme: do it the right way */
2389 while (nodepath->selected) {
2390 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2391 sp_nodepath_node_destroy(node);
2392 }
2395 //clean up the nodepath (such as for trivial subpaths)
2396 sp_nodepath_cleanup(nodepath);
2398 sp_nodepath_update_handles(nodepath);
2400 // if the entire nodepath is removed, delete the selected object.
2401 if (nodepath->subpaths == NULL ||
2402 sp_nodepath_get_node_count(nodepath) < 2) {
2403 SPDocument *document = sp_desktop_document (nodepath->desktop);
2404 sp_selection_delete();
2405 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2406 _("Delete nodes"));
2407 return;
2408 }
2410 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2412 sp_nodepath_update_statusbar(nodepath);
2413 }
2415 /**
2416 * Delete one or more segments between two selected nodes.
2417 * This is the code for 'split'.
2418 */
2419 void
2420 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2421 {
2422 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2423 Inkscape::NodePath::Node *curr, *next; //Iterators
2425 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2427 if (g_list_length(nodepath->selected) != 2) {
2428 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2429 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2430 return;
2431 }
2433 //Selected nodes, not inclusive
2434 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2435 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2437 if ( ( a==b) || //same node
2438 (a->subpath != b->subpath ) || //not the same path
2439 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2440 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2441 {
2442 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2443 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2444 return;
2445 }
2447 //###########################################
2448 //# BEGIN EDITS
2449 //###########################################
2450 //##################################
2451 //# CLOSED PATH
2452 //##################################
2453 if (a->subpath->closed) {
2456 gboolean reversed = FALSE;
2458 //Since we can go in a circle, we need to find the shorter distance.
2459 // a->b or b->a
2460 start = end = NULL;
2461 int distance = 0;
2462 int minDistance = 0;
2463 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2464 if (curr==b) {
2465 //printf("a to b:%d\n", distance);
2466 start = a;//go from a to b
2467 end = b;
2468 minDistance = distance;
2469 //printf("A to B :\n");
2470 break;
2471 }
2472 distance++;
2473 }
2475 //try again, the other direction
2476 distance = 0;
2477 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2478 if (curr==a) {
2479 //printf("b to a:%d\n", distance);
2480 if (distance < minDistance) {
2481 start = b; //we go from b to a
2482 end = a;
2483 reversed = TRUE;
2484 //printf("B to A\n");
2485 }
2486 break;
2487 }
2488 distance++;
2489 }
2492 //Copy everything from 'end' to 'start' to a new subpath
2493 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2494 for (curr=end ; curr ; curr=curr->n.other) {
2495 NRPathcode code = (NRPathcode) curr->code;
2496 if (curr == end)
2497 code = NR_MOVETO;
2498 sp_nodepath_node_new(t, NULL,
2499 (Inkscape::NodePath::NodeType)curr->type, code,
2500 &curr->p.pos, &curr->pos, &curr->n.pos);
2501 if (curr == start)
2502 break;
2503 }
2504 sp_nodepath_subpath_destroy(a->subpath);
2507 }
2511 //##################################
2512 //# OPEN PATH
2513 //##################################
2514 else {
2516 //We need to get the direction of the list between A and B
2517 //Can we walk from a to b?
2518 start = end = NULL;
2519 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2520 if (curr==b) {
2521 start = a; //did it! we go from a to b
2522 end = b;
2523 //printf("A to B\n");
2524 break;
2525 }
2526 }
2527 if (!start) {//didn't work? let's try the other direction
2528 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2529 if (curr==a) {
2530 start = b; //did it! we go from b to a
2531 end = a;
2532 //printf("B to A\n");
2533 break;
2534 }
2535 }
2536 }
2537 if (!start) {
2538 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2539 _("Cannot find path between nodes."));
2540 return;
2541 }
2545 //Copy everything after 'end' to a new subpath
2546 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2547 for (curr=end ; curr ; curr=curr->n.other) {
2548 NRPathcode code = (NRPathcode) curr->code;
2549 if (curr == end)
2550 code = NR_MOVETO;
2551 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2552 &curr->p.pos, &curr->pos, &curr->n.pos);
2553 }
2555 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2556 for (curr = start->n.other ; curr ; curr=next) {
2557 next = curr->n.other;
2558 sp_nodepath_node_destroy(curr);
2559 }
2561 }
2562 //###########################################
2563 //# END EDITS
2564 //###########################################
2566 //clean up the nodepath (such as for trivial subpaths)
2567 sp_nodepath_cleanup(nodepath);
2569 sp_nodepath_update_handles(nodepath);
2571 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2573 sp_nodepath_update_statusbar(nodepath);
2574 }
2576 /**
2577 * Call sp_nodepath_set_line() for all selected segments.
2578 */
2579 void
2580 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2581 {
2582 if (nodepath == NULL) return;
2584 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2585 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2586 g_assert(n->selected);
2587 if (n->p.other && n->p.other->selected) {
2588 sp_nodepath_set_line_type(n, code);
2589 }
2590 }
2592 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2593 }
2595 /**
2596 * Call sp_nodepath_convert_node_type() for all selected nodes.
2597 */
2598 void
2599 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2600 {
2601 if (nodepath == NULL) return;
2603 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2605 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2606 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2607 }
2609 sp_nodepath_update_repr(nodepath, _("Change node type"));
2610 }
2612 /**
2613 * Change select status of node, update its own and neighbour handles.
2614 */
2615 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2616 {
2617 node->selected = selected;
2619 if (selected) {
2620 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2621 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2622 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2623 sp_knot_update_ctrl(node->knot);
2624 } else {
2625 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2626 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2627 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2628 sp_knot_update_ctrl(node->knot);
2629 }
2631 sp_node_update_handles(node);
2632 if (node->n.other) sp_node_update_handles(node->n.other);
2633 if (node->p.other) sp_node_update_handles(node->p.other);
2634 }
2636 /**
2637 \brief Select a node
2638 \param node The node to select
2639 \param incremental If true, add to selection, otherwise deselect others
2640 \param override If true, always select this node, otherwise toggle selected status
2641 */
2642 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2643 {
2644 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2646 if (incremental) {
2647 if (override) {
2648 if (!g_list_find(nodepath->selected, node)) {
2649 nodepath->selected = g_list_prepend(nodepath->selected, node);
2650 }
2651 sp_node_set_selected(node, TRUE);
2652 } else { // toggle
2653 if (node->selected) {
2654 g_assert(g_list_find(nodepath->selected, node));
2655 nodepath->selected = g_list_remove(nodepath->selected, node);
2656 } else {
2657 g_assert(!g_list_find(nodepath->selected, node));
2658 nodepath->selected = g_list_prepend(nodepath->selected, node);
2659 }
2660 sp_node_set_selected(node, !node->selected);
2661 }
2662 } else {
2663 sp_nodepath_deselect(nodepath);
2664 nodepath->selected = g_list_prepend(nodepath->selected, node);
2665 sp_node_set_selected(node, TRUE);
2666 }
2668 sp_nodepath_update_statusbar(nodepath);
2669 }
2672 /**
2673 \brief Deselect all nodes in the nodepath
2674 */
2675 void
2676 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2677 {
2678 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2680 while (nodepath->selected) {
2681 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2682 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2683 }
2684 sp_nodepath_update_statusbar(nodepath);
2685 }
2687 /**
2688 \brief Select or invert selection of all nodes in the nodepath
2689 */
2690 void
2691 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2692 {
2693 if (!nodepath) return;
2695 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2696 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2697 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2698 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2699 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2700 }
2701 }
2702 }
2704 /**
2705 * If nothing selected, does the same as sp_nodepath_select_all();
2706 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2707 * (i.e., similar to "select all in layer", with the "selected" subpaths
2708 * being treated as "layers" in the path).
2709 */
2710 void
2711 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2712 {
2713 if (!nodepath) return;
2715 if (g_list_length (nodepath->selected) == 0) {
2716 sp_nodepath_select_all (nodepath, invert);
2717 return;
2718 }
2720 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2721 GSList *subpaths = NULL;
2723 for (GList *l = copy; l != NULL; l = l->next) {
2724 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2725 Inkscape::NodePath::SubPath *subpath = n->subpath;
2726 if (!g_slist_find (subpaths, subpath))
2727 subpaths = g_slist_prepend (subpaths, subpath);
2728 }
2730 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2731 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2732 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2733 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2734 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2735 }
2736 }
2738 g_slist_free (subpaths);
2739 g_list_free (copy);
2740 }
2742 /**
2743 * \brief Select the node after the last selected; if none is selected,
2744 * select the first within path.
2745 */
2746 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2747 {
2748 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2750 Inkscape::NodePath::Node *last = NULL;
2751 if (nodepath->selected) {
2752 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2753 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2754 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2755 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2756 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2757 if (node->selected) {
2758 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2759 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2760 if (spl->next) { // there's a next subpath
2761 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2762 last = subpath_next->first;
2763 } else if (spl->prev) { // there's a previous subpath
2764 last = NULL; // to be set later to the first node of first subpath
2765 } else {
2766 last = node->n.other;
2767 }
2768 } else {
2769 last = node->n.other;
2770 }
2771 } else {
2772 if (node->n.other) {
2773 last = node->n.other;
2774 } else {
2775 if (spl->next) { // there's a next subpath
2776 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2777 last = subpath_next->first;
2778 } else if (spl->prev) { // there's a previous subpath
2779 last = NULL; // to be set later to the first node of first subpath
2780 } else {
2781 last = (Inkscape::NodePath::Node *) subpath->first;
2782 }
2783 }
2784 }
2785 }
2786 }
2787 }
2788 sp_nodepath_deselect(nodepath);
2789 }
2791 if (last) { // there's at least one more node after selected
2792 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2793 } else { // no more nodes, select the first one in first subpath
2794 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2795 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2796 }
2797 }
2799 /**
2800 * \brief Select the node before the first selected; if none is selected,
2801 * select the last within path
2802 */
2803 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2804 {
2805 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2807 Inkscape::NodePath::Node *last = NULL;
2808 if (nodepath->selected) {
2809 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2810 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2811 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2812 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2813 if (node->selected) {
2814 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2815 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2816 if (spl->prev) { // there's a prev subpath
2817 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2818 last = subpath_prev->last;
2819 } else if (spl->next) { // there's a next subpath
2820 last = NULL; // to be set later to the last node of last subpath
2821 } else {
2822 last = node->p.other;
2823 }
2824 } else {
2825 last = node->p.other;
2826 }
2827 } else {
2828 if (node->p.other) {
2829 last = node->p.other;
2830 } else {
2831 if (spl->prev) { // there's a prev subpath
2832 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2833 last = subpath_prev->last;
2834 } else if (spl->next) { // there's a next subpath
2835 last = NULL; // to be set later to the last node of last subpath
2836 } else {
2837 last = (Inkscape::NodePath::Node *) subpath->last;
2838 }
2839 }
2840 }
2841 }
2842 }
2843 }
2844 sp_nodepath_deselect(nodepath);
2845 }
2847 if (last) { // there's at least one more node before selected
2848 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2849 } else { // no more nodes, select the last one in last subpath
2850 GList *spl = g_list_last(nodepath->subpaths);
2851 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2852 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2853 }
2854 }
2856 /**
2857 * \brief Select all nodes that are within the rectangle.
2858 */
2859 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2860 {
2861 if (!incremental) {
2862 sp_nodepath_deselect(nodepath);
2863 }
2865 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2866 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2867 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2868 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2870 if (b.contains(node->pos)) {
2871 sp_nodepath_node_select(node, TRUE, TRUE);
2872 }
2873 }
2874 }
2875 }
2878 void
2879 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2880 {
2881 g_assert (n);
2882 g_assert (nodepath);
2883 g_assert (n->subpath->nodepath == nodepath);
2885 if (g_list_length (nodepath->selected) == 0) {
2886 if (grow > 0) {
2887 sp_nodepath_node_select(n, TRUE, TRUE);
2888 }
2889 return;
2890 }
2892 if (g_list_length (nodepath->selected) == 1) {
2893 if (grow < 0) {
2894 sp_nodepath_deselect (nodepath);
2895 return;
2896 }
2897 }
2899 double n_sel_range = 0, p_sel_range = 0;
2900 Inkscape::NodePath::Node *farthest_n_node = n;
2901 Inkscape::NodePath::Node *farthest_p_node = n;
2903 // Calculate ranges
2904 {
2905 double n_range = 0, p_range = 0;
2906 bool n_going = true, p_going = true;
2907 Inkscape::NodePath::Node *n_node = n;
2908 Inkscape::NodePath::Node *p_node = n;
2909 do {
2910 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2911 if (n_node && n_going)
2912 n_node = n_node->n.other;
2913 if (n_node == NULL) {
2914 n_going = false;
2915 } else {
2916 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2917 if (n_node->selected) {
2918 n_sel_range = n_range;
2919 farthest_n_node = n_node;
2920 }
2921 if (n_node == p_node) {
2922 n_going = false;
2923 p_going = false;
2924 }
2925 }
2926 if (p_node && p_going)
2927 p_node = p_node->p.other;
2928 if (p_node == NULL) {
2929 p_going = false;
2930 } else {
2931 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2932 if (p_node->selected) {
2933 p_sel_range = p_range;
2934 farthest_p_node = p_node;
2935 }
2936 if (p_node == n_node) {
2937 n_going = false;
2938 p_going = false;
2939 }
2940 }
2941 } while (n_going || p_going);
2942 }
2944 if (grow > 0) {
2945 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2946 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2947 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2948 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2949 }
2950 } else {
2951 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2952 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2953 } else if (farthest_p_node && farthest_p_node->selected) {
2954 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2955 }
2956 }
2957 }
2959 void
2960 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2961 {
2962 g_assert (n);
2963 g_assert (nodepath);
2964 g_assert (n->subpath->nodepath == nodepath);
2966 if (g_list_length (nodepath->selected) == 0) {
2967 if (grow > 0) {
2968 sp_nodepath_node_select(n, TRUE, TRUE);
2969 }
2970 return;
2971 }
2973 if (g_list_length (nodepath->selected) == 1) {
2974 if (grow < 0) {
2975 sp_nodepath_deselect (nodepath);
2976 return;
2977 }
2978 }
2980 Inkscape::NodePath::Node *farthest_selected = NULL;
2981 double farthest_dist = 0;
2983 Inkscape::NodePath::Node *closest_unselected = NULL;
2984 double closest_dist = NR_HUGE;
2986 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2987 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2988 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2989 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2990 if (node == n)
2991 continue;
2992 if (node->selected) {
2993 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2994 farthest_dist = NR::L2(node->pos - n->pos);
2995 farthest_selected = node;
2996 }
2997 } else {
2998 if (NR::L2(node->pos - n->pos) < closest_dist) {
2999 closest_dist = NR::L2(node->pos - n->pos);
3000 closest_unselected = node;
3001 }
3002 }
3003 }
3004 }
3006 if (grow > 0) {
3007 if (closest_unselected) {
3008 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3009 }
3010 } else {
3011 if (farthest_selected) {
3012 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3013 }
3014 }
3015 }
3018 /**
3019 \brief Saves all nodes' and handles' current positions in their origin members
3020 */
3021 void
3022 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3023 {
3024 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3025 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3026 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3028 n->origin = n->pos;
3029 n->p.origin = n->p.pos;
3030 n->n.origin = n->n.pos;
3031 }
3032 }
3033 }
3035 /**
3036 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3037 */
3038 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3039 {
3040 if (!nodepath->selected) {
3041 return NULL;
3042 }
3044 GList *r = NULL;
3045 guint i = 0;
3046 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3047 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3048 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3049 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3050 i++;
3051 if (node->selected) {
3052 r = g_list_append(r, GINT_TO_POINTER(i));
3053 }
3054 }
3055 }
3056 return r;
3057 }
3059 /**
3060 \brief Restores selection by selecting nodes whose positions are in the list
3061 */
3062 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3063 {
3064 sp_nodepath_deselect(nodepath);
3066 guint i = 0;
3067 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3068 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3069 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3070 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3071 i++;
3072 if (g_list_find(r, GINT_TO_POINTER(i))) {
3073 sp_nodepath_node_select(node, TRUE, TRUE);
3074 }
3075 }
3076 }
3077 }
3080 /**
3081 \brief Adjusts handle according to node type and line code.
3082 */
3083 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3084 {
3085 g_assert(node);
3087 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3088 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3090 // nothing to do if we are an end node
3091 if (me->other == NULL) return;
3092 if (other->other == NULL) return;
3094 // nothing to do if we are a cusp node
3095 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3097 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3098 NRPathcode mecode;
3099 if (which_adjust == 1) {
3100 mecode = (NRPathcode)me->other->code;
3101 } else {
3102 mecode = (NRPathcode)node->code;
3103 }
3104 if (mecode == NR_LINETO) return;
3106 if (sp_node_side_is_line(node, other)) {
3107 // other is a line, and we are either smooth or symm
3108 Inkscape::NodePath::Node *othernode = other->other;
3109 double len = NR::L2(me->pos - node->pos);
3110 NR::Point delta = node->pos - othernode->pos;
3111 double linelen = NR::L2(delta);
3112 if (linelen < 1e-18)
3113 return;
3114 me->pos = node->pos + (len / linelen)*delta;
3115 return;
3116 }
3118 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3119 // symmetrize
3120 me->pos = 2 * node->pos - other->pos;
3121 return;
3122 } else {
3123 // smoothify
3124 double len = NR::L2(me->pos - node->pos);
3125 NR::Point delta = other->pos - node->pos;
3126 double otherlen = NR::L2(delta);
3127 if (otherlen < 1e-18) return;
3128 me->pos = node->pos - (len / otherlen) * delta;
3129 }
3130 }
3132 /**
3133 \brief Adjusts both handles according to node type and line code
3134 */
3135 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3136 {
3137 g_assert(node);
3139 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3141 /* we are either smooth or symm */
3143 if (node->p.other == NULL) return;
3144 if (node->n.other == NULL) return;
3146 if (sp_node_side_is_line(node, &node->p)) {
3147 sp_node_adjust_handle(node, 1);
3148 return;
3149 }
3151 if (sp_node_side_is_line(node, &node->n)) {
3152 sp_node_adjust_handle(node, -1);
3153 return;
3154 }
3156 /* both are curves */
3157 NR::Point const delta( node->n.pos - node->p.pos );
3159 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3160 node->p.pos = node->pos - delta / 2;
3161 node->n.pos = node->pos + delta / 2;
3162 return;
3163 }
3165 /* We are smooth */
3166 double plen = NR::L2(node->p.pos - node->pos);
3167 if (plen < 1e-18) return;
3168 double nlen = NR::L2(node->n.pos - node->pos);
3169 if (nlen < 1e-18) return;
3170 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3171 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3172 }
3174 /**
3175 * Node event callback.
3176 */
3177 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3178 {
3179 gboolean ret = FALSE;
3180 switch (event->type) {
3181 case GDK_ENTER_NOTIFY:
3182 Inkscape::NodePath::Path::active_node = n;
3183 break;
3184 case GDK_LEAVE_NOTIFY:
3185 Inkscape::NodePath::Path::active_node = NULL;
3186 break;
3187 case GDK_SCROLL:
3188 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3189 switch (event->scroll.direction) {
3190 case GDK_SCROLL_UP:
3191 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3192 break;
3193 case GDK_SCROLL_DOWN:
3194 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3195 break;
3196 default:
3197 break;
3198 }
3199 ret = TRUE;
3200 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3201 switch (event->scroll.direction) {
3202 case GDK_SCROLL_UP:
3203 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3204 break;
3205 case GDK_SCROLL_DOWN:
3206 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3207 break;
3208 default:
3209 break;
3210 }
3211 ret = TRUE;
3212 }
3213 break;
3214 case GDK_KEY_PRESS:
3215 switch (get_group0_keyval (&event->key)) {
3216 case GDK_space:
3217 if (event->key.state & GDK_BUTTON1_MASK) {
3218 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3219 stamp_repr(nodepath);
3220 ret = TRUE;
3221 }
3222 break;
3223 case GDK_Page_Up:
3224 if (event->key.state & GDK_CONTROL_MASK) {
3225 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3226 } else {
3227 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3228 }
3229 break;
3230 case GDK_Page_Down:
3231 if (event->key.state & GDK_CONTROL_MASK) {
3232 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3233 } else {
3234 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3235 }
3236 break;
3237 default:
3238 break;
3239 }
3240 break;
3241 default:
3242 break;
3243 }
3245 return ret;
3246 }
3248 /**
3249 * Handle keypress on node; directly called.
3250 */
3251 gboolean node_key(GdkEvent *event)
3252 {
3253 Inkscape::NodePath::Path *np;
3255 // there is no way to verify nodes so set active_node to nil when deleting!!
3256 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3258 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3259 gint ret = FALSE;
3260 switch (get_group0_keyval (&event->key)) {
3261 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3262 case GDK_BackSpace:
3263 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3264 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3265 sp_nodepath_update_repr(np, _("Delete node"));
3266 Inkscape::NodePath::Path::active_node = NULL;
3267 ret = TRUE;
3268 break;
3269 case GDK_c:
3270 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3271 ret = TRUE;
3272 break;
3273 case GDK_s:
3274 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3275 ret = TRUE;
3276 break;
3277 case GDK_y:
3278 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3279 ret = TRUE;
3280 break;
3281 case GDK_b:
3282 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3283 ret = TRUE;
3284 break;
3285 }
3286 return ret;
3287 }
3288 return FALSE;
3289 }
3291 /**
3292 * Mouseclick on node callback.
3293 */
3294 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3295 {
3296 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3298 if (state & GDK_CONTROL_MASK) {
3299 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3301 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3302 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3303 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3304 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3305 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3306 } else {
3307 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3308 }
3309 sp_nodepath_update_repr(nodepath, _("Change node type"));
3310 sp_nodepath_update_statusbar(nodepath);
3312 } else { //ctrl+alt+click: delete node
3313 GList *node_to_delete = NULL;
3314 node_to_delete = g_list_append(node_to_delete, n);
3315 sp_node_delete_preserve(node_to_delete);
3316 }
3318 } else {
3319 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3320 }
3321 }
3323 /**
3324 * Mouse grabbed node callback.
3325 */
3326 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3327 {
3328 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3330 if (!n->selected) {
3331 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3332 }
3334 n->is_dragging = true;
3335 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3337 sp_nodepath_remember_origins (n->subpath->nodepath);
3338 }
3340 /**
3341 * Mouse ungrabbed node callback.
3342 */
3343 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3344 {
3345 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3347 n->dragging_out = NULL;
3348 n->is_dragging = false;
3349 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3351 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3352 }
3354 /**
3355 * The point on a line, given by its angle, closest to the given point.
3356 * \param p A point.
3357 * \param a Angle of the line; it is assumed to go through coordinate origin.
3358 * \param closest Pointer to the point struct where the result is stored.
3359 * \todo FIXME: use dot product perhaps?
3360 */
3361 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3362 {
3363 if (a == HUGE_VAL) { // vertical
3364 *closest = NR::Point(0, (*p)[NR::Y]);
3365 } else {
3366 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3367 (*closest)[NR::Y] = a * (*closest)[NR::X];
3368 }
3369 }
3371 /**
3372 * Distance from the point to a line given by its angle.
3373 * \param p A point.
3374 * \param a Angle of the line; it is assumed to go through coordinate origin.
3375 */
3376 static double point_line_distance(NR::Point *p, double a)
3377 {
3378 NR::Point c;
3379 point_line_closest(p, a, &c);
3380 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]));
3381 }
3383 /**
3384 * Callback for node "request" signal.
3385 * \todo fixme: This goes to "moved" event? (lauris)
3386 */
3387 static gboolean
3388 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3389 {
3390 double yn, xn, yp, xp;
3391 double an, ap, na, pa;
3392 double d_an, d_ap, d_na, d_pa;
3393 gboolean collinear = FALSE;
3394 NR::Point c;
3395 NR::Point pr;
3397 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3399 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3401 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3402 if ( (!n->subpath->nodepath->straight_path) &&
3403 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3404 || n->dragging_out ) )
3405 {
3406 NR::Point mouse = (*p);
3408 if (!n->dragging_out) {
3409 // This is the first drag-out event; find out which handle to drag out
3410 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3411 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3413 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3414 return FALSE;
3416 Inkscape::NodePath::NodeSide *opposite;
3417 if (appr_p > appr_n) { // closer to p
3418 n->dragging_out = &n->p;
3419 opposite = &n->n;
3420 n->code = NR_CURVETO;
3421 } else if (appr_p < appr_n) { // closer to n
3422 n->dragging_out = &n->n;
3423 opposite = &n->p;
3424 n->n.other->code = NR_CURVETO;
3425 } else { // p and n nodes are the same
3426 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3427 n->dragging_out = &n->p;
3428 opposite = &n->n;
3429 n->code = NR_CURVETO;
3430 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3431 n->dragging_out = &n->n;
3432 opposite = &n->p;
3433 n->n.other->code = NR_CURVETO;
3434 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3435 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);
3436 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);
3437 if (appr_other_p > appr_other_n) { // closer to other's p handle
3438 n->dragging_out = &n->n;
3439 opposite = &n->p;
3440 n->n.other->code = NR_CURVETO;
3441 } else { // closer to other's n handle
3442 n->dragging_out = &n->p;
3443 opposite = &n->n;
3444 n->code = NR_CURVETO;
3445 }
3446 }
3447 }
3449 // if there's another handle, make sure the one we drag out starts parallel to it
3450 if (opposite->pos != n->pos) {
3451 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3452 }
3454 // knots might not be created yet!
3455 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3456 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3457 }
3459 // pass this on to the handle-moved callback
3460 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3461 sp_node_update_handles(n);
3462 return TRUE;
3463 }
3465 if (state & GDK_CONTROL_MASK) { // constrained motion
3467 // calculate relative distances of handles
3468 // n handle:
3469 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3470 xn = n->n.pos[NR::X] - n->pos[NR::X];
3471 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3472 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3473 if (n->n.other) { // if there is the next point
3474 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3475 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3476 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3477 }
3478 }
3479 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3480 if (yn < 0) { xn = -xn; yn = -yn; }
3482 // p handle:
3483 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3484 xp = n->p.pos[NR::X] - n->pos[NR::X];
3485 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3486 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3487 if (n->p.other) {
3488 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3489 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3490 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3491 }
3492 }
3493 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3494 if (yp < 0) { xp = -xp; yp = -yp; }
3496 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3497 // sliding on handles, only if at least one of the handles is non-vertical
3498 // (otherwise it's the same as ctrl+drag anyway)
3500 // calculate angles of the handles
3501 if (xn == 0) {
3502 if (yn == 0) { // no handle, consider it the continuation of the other one
3503 an = 0;
3504 collinear = TRUE;
3505 }
3506 else an = 0; // vertical; set the angle to horizontal
3507 } else an = yn/xn;
3509 if (xp == 0) {
3510 if (yp == 0) { // no handle, consider it the continuation of the other one
3511 ap = an;
3512 }
3513 else ap = 0; // vertical; set the angle to horizontal
3514 } else ap = yp/xp;
3516 if (collinear) an = ap;
3518 // angles of the perpendiculars; HUGE_VAL means vertical
3519 if (an == 0) na = HUGE_VAL; else na = -1/an;
3520 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3522 // mouse point relative to the node's original pos
3523 pr = (*p) - n->origin;
3525 // distances to the four lines (two handles and two perpendiculars)
3526 d_an = point_line_distance(&pr, an);
3527 d_na = point_line_distance(&pr, na);
3528 d_ap = point_line_distance(&pr, ap);
3529 d_pa = point_line_distance(&pr, pa);
3531 // find out which line is the closest, save its closest point in c
3532 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3533 point_line_closest(&pr, an, &c);
3534 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3535 point_line_closest(&pr, ap, &c);
3536 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3537 point_line_closest(&pr, na, &c);
3538 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3539 point_line_closest(&pr, pa, &c);
3540 }
3542 // move the node to the closest point
3543 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3544 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3545 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3547 } else { // constraining to hor/vert
3549 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3550 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3551 } else { // snap to vert
3552 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3553 }
3554 }
3555 } else { // move freely
3556 if (n->is_dragging) {
3557 if (state & GDK_MOD1_MASK) { // sculpt
3558 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3559 } else {
3560 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3561 (*p)[NR::X] - n->pos[NR::X],
3562 (*p)[NR::Y] - n->pos[NR::Y],
3563 (state & GDK_SHIFT_MASK) == 0);
3564 }
3565 }
3566 }
3568 n->subpath->nodepath->desktop->scroll_to_point(p);
3570 return TRUE;
3571 }
3573 /**
3574 * Node handle clicked callback.
3575 */
3576 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3577 {
3578 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3580 if (state & GDK_CONTROL_MASK) { // "delete" handle
3581 if (n->p.knot == knot) {
3582 n->p.pos = n->pos;
3583 } else if (n->n.knot == knot) {
3584 n->n.pos = n->pos;
3585 }
3586 sp_node_update_handles(n);
3587 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3588 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3589 sp_nodepath_update_statusbar(nodepath);
3591 } else { // just select or add to selection, depending in Shift
3592 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3593 }
3594 }
3596 /**
3597 * Node handle grabbed callback.
3598 */
3599 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3600 {
3601 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3603 if (!n->selected) {
3604 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3605 }
3607 // remember the origin point of the handle
3608 if (n->p.knot == knot) {
3609 n->p.origin_radial = n->p.pos - n->pos;
3610 } else if (n->n.knot == knot) {
3611 n->n.origin_radial = n->n.pos - n->pos;
3612 } else {
3613 g_assert_not_reached();
3614 }
3616 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3617 }
3619 /**
3620 * Node handle ungrabbed callback.
3621 */
3622 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3623 {
3624 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3626 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3627 if (n->p.knot == knot) {
3628 n->p.origin_radial.a = 0;
3629 sp_knot_set_position(knot, &n->p.pos, state);
3630 } else if (n->n.knot == knot) {
3631 n->n.origin_radial.a = 0;
3632 sp_knot_set_position(knot, &n->n.pos, state);
3633 } else {
3634 g_assert_not_reached();
3635 }
3637 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3638 }
3640 /**
3641 * Node handle "request" signal callback.
3642 */
3643 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3644 {
3645 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3647 Inkscape::NodePath::NodeSide *me, *opposite;
3648 gint which;
3649 if (n->p.knot == knot) {
3650 me = &n->p;
3651 opposite = &n->n;
3652 which = -1;
3653 } else if (n->n.knot == knot) {
3654 me = &n->n;
3655 opposite = &n->p;
3656 which = 1;
3657 } else {
3658 me = opposite = NULL;
3659 which = 0;
3660 g_assert_not_reached();
3661 }
3663 SPDesktop *desktop = n->subpath->nodepath->desktop;
3664 SnapManager &m = desktop->namedview->snap_manager;
3665 m.setup(desktop, n->subpath->nodepath->item);
3666 Inkscape::SnappedPoint s ;
3668 Inkscape::NodePath::Node *othernode = opposite->other;
3669 if (othernode) {
3670 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3671 /* We are smooth node adjacent with line */
3672 NR::Point const delta = *p - n->pos;
3673 NR::Coord const len = NR::L2(delta);
3674 Inkscape::NodePath::Node *othernode = opposite->other;
3675 NR::Point const ndelta = n->pos - othernode->pos;
3676 NR::Coord const linelen = NR::L2(ndelta);
3677 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3678 NR::Coord const scal = dot(delta, ndelta) / linelen;
3679 (*p) = n->pos + (scal / linelen) * ndelta;
3680 }
3681 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3682 } else {
3683 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3684 }
3685 }
3687 *p = s.getPoint();
3689 sp_node_adjust_handle(n, -which);
3691 return FALSE;
3692 }
3694 /**
3695 * Node handle moved callback.
3696 */
3697 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3698 {
3699 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3701 Inkscape::NodePath::NodeSide *me;
3702 Inkscape::NodePath::NodeSide *other;
3703 if (n->p.knot == knot) {
3704 me = &n->p;
3705 other = &n->n;
3706 } else if (n->n.knot == knot) {
3707 me = &n->n;
3708 other = &n->p;
3709 } else {
3710 me = NULL;
3711 other = NULL;
3712 g_assert_not_reached();
3713 }
3715 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3716 Radial rme(me->pos - n->pos);
3717 Radial rother(other->pos - n->pos);
3718 Radial rnew(*p - n->pos);
3720 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3721 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3722 /* 0 interpreted as "no snapping". */
3724 // 1. Snap to the closest PI/snaps angle, starting from zero.
3725 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3727 // 2. Snap to the original angle, its opposite and perpendiculars
3728 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3729 /* The closest PI/2 angle, starting from original angle */
3730 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3732 // Snap to the closest.
3733 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3734 ? a_snapped
3735 : a_ortho );
3736 }
3738 // 3. Snap to the angle of the opposite line, if any
3739 Inkscape::NodePath::Node *othernode = other->other;
3740 if (othernode) {
3741 NR::Point other_to_snap(0,0);
3742 if (sp_node_side_is_line(n, other)) {
3743 other_to_snap = othernode->pos - n->pos;
3744 } else {
3745 other_to_snap = other->pos - n->pos;
3746 }
3747 if (NR::L2(other_to_snap) > 1e-3) {
3748 Radial rother_to_snap(other_to_snap);
3749 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3750 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3752 // Snap to the closest.
3753 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3754 ? a_snapped
3755 : a_oppo );
3756 }
3757 }
3759 rnew.a = a_snapped;
3760 }
3762 if (state & GDK_MOD1_MASK) {
3763 // lock handle length
3764 rnew.r = me->origin_radial.r;
3765 }
3767 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3768 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3769 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3770 rother.a += rnew.a - rme.a;
3771 other->pos = NR::Point(rother) + n->pos;
3772 if (other->knot) {
3773 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3774 sp_knot_moveto(other->knot, &other->pos);
3775 }
3776 }
3778 me->pos = NR::Point(rnew) + n->pos;
3779 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3781 // move knot, but without emitting the signal:
3782 // we cannot emit a "moved" signal because we're now processing it
3783 sp_knot_moveto(me->knot, &(me->pos));
3785 update_object(n->subpath->nodepath);
3787 /* status text */
3788 SPDesktop *desktop = n->subpath->nodepath->desktop;
3789 if (!desktop) return;
3790 SPEventContext *ec = desktop->event_context;
3791 if (!ec) return;
3792 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3793 if (!mc) return;
3795 double degrees = 180 / M_PI * rnew.a;
3796 if (degrees > 180) degrees -= 360;
3797 if (degrees < -180) degrees += 360;
3798 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3799 degrees = angle_to_compass (degrees);
3801 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3803 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3804 _("<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);
3806 g_string_free(length, TRUE);
3807 }
3809 /**
3810 * Node handle event callback.
3811 */
3812 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3813 {
3814 gboolean ret = FALSE;
3815 switch (event->type) {
3816 case GDK_KEY_PRESS:
3817 switch (get_group0_keyval (&event->key)) {
3818 case GDK_space:
3819 if (event->key.state & GDK_BUTTON1_MASK) {
3820 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3821 stamp_repr(nodepath);
3822 ret = TRUE;
3823 }
3824 break;
3825 default:
3826 break;
3827 }
3828 break;
3829 case GDK_ENTER_NOTIFY:
3830 // we use an experimentally determined threshold that seems to work fine
3831 if (NR::L2(n->pos - knot->pos) < 0.75)
3832 Inkscape::NodePath::Path::active_node = n;
3833 break;
3834 case GDK_LEAVE_NOTIFY:
3835 // we use an experimentally determined threshold that seems to work fine
3836 if (NR::L2(n->pos - knot->pos) < 0.75)
3837 Inkscape::NodePath::Path::active_node = NULL;
3838 break;
3839 default:
3840 break;
3841 }
3843 return ret;
3844 }
3846 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3847 Radial &rme, Radial &rother, gboolean const both)
3848 {
3849 rme.a += angle;
3850 if ( both
3851 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3852 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3853 {
3854 rother.a += angle;
3855 }
3856 }
3858 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3859 Radial &rme, Radial &rother, gboolean const both)
3860 {
3861 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3863 gdouble r;
3864 if ( both
3865 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3866 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3867 {
3868 r = MAX(rme.r, rother.r);
3869 } else {
3870 r = rme.r;
3871 }
3873 gdouble const weird_angle = atan2(norm_angle, r);
3874 /* Bulia says norm_angle is just the visible distance that the
3875 * object's end must travel on the screen. Left as 'angle' for want of
3876 * a better name.*/
3878 rme.a += weird_angle;
3879 if ( both
3880 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3881 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3882 {
3883 rother.a += weird_angle;
3884 }
3885 }
3887 /**
3888 * Rotate one node.
3889 */
3890 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3891 {
3892 Inkscape::NodePath::NodeSide *me, *other;
3893 bool both = false;
3895 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3896 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3898 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3899 me = &(n->p);
3900 other = &(n->n);
3901 } else if (!n->p.other) {
3902 me = &(n->n);
3903 other = &(n->p);
3904 } else {
3905 if (which > 0) { // right handle
3906 if (xn > xp) {
3907 me = &(n->n);
3908 other = &(n->p);
3909 } else {
3910 me = &(n->p);
3911 other = &(n->n);
3912 }
3913 } else if (which < 0){ // left handle
3914 if (xn <= xp) {
3915 me = &(n->n);
3916 other = &(n->p);
3917 } else {
3918 me = &(n->p);
3919 other = &(n->n);
3920 }
3921 } else { // both handles
3922 me = &(n->n);
3923 other = &(n->p);
3924 both = true;
3925 }
3926 }
3928 Radial rme(me->pos - n->pos);
3929 Radial rother(other->pos - n->pos);
3931 if (screen) {
3932 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3933 } else {
3934 node_rotate_one_internal (*n, angle, rme, rother, both);
3935 }
3937 me->pos = n->pos + NR::Point(rme);
3939 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3940 other->pos = n->pos + NR::Point(rother);
3941 }
3943 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3944 // so here we just move all the knots without emitting move signals, for speed
3945 sp_node_update_handles(n, false);
3946 }
3948 /**
3949 * Rotate selected nodes.
3950 */
3951 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3952 {
3953 if (!nodepath || !nodepath->selected) return;
3955 if (g_list_length(nodepath->selected) == 1) {
3956 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3957 node_rotate_one (n, angle, which, screen);
3958 } else {
3959 // rotate as an object:
3961 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3962 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3963 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3964 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3965 box.expandTo (n->pos); // contain all selected nodes
3966 }
3968 gdouble rot;
3969 if (screen) {
3970 gdouble const zoom = nodepath->desktop->current_zoom();
3971 gdouble const zmove = angle / zoom;
3972 gdouble const r = NR::L2(box.max() - box.midpoint());
3973 rot = atan2(zmove, r);
3974 } else {
3975 rot = angle;
3976 }
3978 NR::Point rot_center;
3979 if (Inkscape::NodePath::Path::active_node == NULL)
3980 rot_center = box.midpoint();
3981 else
3982 rot_center = Inkscape::NodePath::Path::active_node->pos;
3984 NR::Matrix t =
3985 NR::Matrix (NR::translate(-rot_center)) *
3986 NR::Matrix (NR::rotate(rot)) *
3987 NR::Matrix (NR::translate(rot_center));
3989 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3990 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3991 n->pos *= t;
3992 n->n.pos *= t;
3993 n->p.pos *= t;
3994 sp_node_update_handles(n, false);
3995 }
3996 }
3998 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3999 }
4001 /**
4002 * Scale one node.
4003 */
4004 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4005 {
4006 bool both = false;
4007 Inkscape::NodePath::NodeSide *me, *other;
4009 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4010 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4012 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4013 me = &(n->p);
4014 other = &(n->n);
4015 n->code = NR_CURVETO;
4016 } else if (!n->p.other) {
4017 me = &(n->n);
4018 other = &(n->p);
4019 if (n->n.other)
4020 n->n.other->code = NR_CURVETO;
4021 } else {
4022 if (which > 0) { // right handle
4023 if (xn > xp) {
4024 me = &(n->n);
4025 other = &(n->p);
4026 if (n->n.other)
4027 n->n.other->code = NR_CURVETO;
4028 } else {
4029 me = &(n->p);
4030 other = &(n->n);
4031 n->code = NR_CURVETO;
4032 }
4033 } else if (which < 0){ // left handle
4034 if (xn <= xp) {
4035 me = &(n->n);
4036 other = &(n->p);
4037 if (n->n.other)
4038 n->n.other->code = NR_CURVETO;
4039 } else {
4040 me = &(n->p);
4041 other = &(n->n);
4042 n->code = NR_CURVETO;
4043 }
4044 } else { // both handles
4045 me = &(n->n);
4046 other = &(n->p);
4047 both = true;
4048 n->code = NR_CURVETO;
4049 if (n->n.other)
4050 n->n.other->code = NR_CURVETO;
4051 }
4052 }
4054 Radial rme(me->pos - n->pos);
4055 Radial rother(other->pos - n->pos);
4057 rme.r += grow;
4058 if (rme.r < 0) rme.r = 0;
4059 if (rme.a == HUGE_VAL) {
4060 if (me->other) { // if direction is unknown, initialize it towards the next node
4061 Radial rme_next(me->other->pos - n->pos);
4062 rme.a = rme_next.a;
4063 } else { // if there's no next, initialize to 0
4064 rme.a = 0;
4065 }
4066 }
4067 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4068 rother.r += grow;
4069 if (rother.r < 0) rother.r = 0;
4070 if (rother.a == HUGE_VAL) {
4071 rother.a = rme.a + M_PI;
4072 }
4073 }
4075 me->pos = n->pos + NR::Point(rme);
4077 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4078 other->pos = n->pos + NR::Point(rother);
4079 }
4081 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4082 // so here we just move all the knots without emitting move signals, for speed
4083 sp_node_update_handles(n, false);
4084 }
4086 /**
4087 * Scale selected nodes.
4088 */
4089 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4090 {
4091 if (!nodepath || !nodepath->selected) return;
4093 if (g_list_length(nodepath->selected) == 1) {
4094 // scale handles of the single selected node
4095 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4096 node_scale_one (n, grow, which);
4097 } else {
4098 // scale nodes as an "object":
4100 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4101 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4102 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4103 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4104 box.expandTo (n->pos); // contain all selected nodes
4105 }
4107 double scale = (box.maxExtent() + grow)/box.maxExtent();
4109 NR::Point scale_center;
4110 if (Inkscape::NodePath::Path::active_node == NULL)
4111 scale_center = box.midpoint();
4112 else
4113 scale_center = Inkscape::NodePath::Path::active_node->pos;
4115 NR::Matrix t =
4116 NR::Matrix (NR::translate(-scale_center)) *
4117 NR::Matrix (NR::scale(scale, scale)) *
4118 NR::Matrix (NR::translate(scale_center));
4120 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4121 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4122 n->pos *= t;
4123 n->n.pos *= t;
4124 n->p.pos *= t;
4125 sp_node_update_handles(n, false);
4126 }
4127 }
4129 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4130 }
4132 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4133 {
4134 if (!nodepath) return;
4135 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4136 }
4138 /**
4139 * Flip selected nodes horizontally/vertically.
4140 */
4141 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4142 {
4143 if (!nodepath || !nodepath->selected) return;
4145 if (g_list_length(nodepath->selected) == 1 && !center) {
4146 // flip handles of the single selected node
4147 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4148 double temp = n->p.pos[axis];
4149 n->p.pos[axis] = n->n.pos[axis];
4150 n->n.pos[axis] = temp;
4151 sp_node_update_handles(n, false);
4152 } else {
4153 // scale nodes as an "object":
4155 NR::Rect box = sp_node_selected_bbox (nodepath);
4156 if (!center) {
4157 center = box.midpoint();
4158 }
4159 NR::Matrix t =
4160 NR::Matrix (NR::translate(- *center)) *
4161 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4162 NR::Matrix (NR::translate(*center));
4164 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4165 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4166 n->pos *= t;
4167 n->n.pos *= t;
4168 n->p.pos *= t;
4169 sp_node_update_handles(n, false);
4170 }
4171 }
4173 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4174 }
4176 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4177 {
4178 g_assert (nodepath->selected);
4180 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4181 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4182 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4183 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4184 box.expandTo (n->pos); // contain all selected nodes
4185 }
4186 return box;
4187 }
4189 //-----------------------------------------------
4190 /**
4191 * Return new subpath under given nodepath.
4192 */
4193 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4194 {
4195 g_assert(nodepath);
4196 g_assert(nodepath->desktop);
4198 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4200 s->nodepath = nodepath;
4201 s->closed = FALSE;
4202 s->nodes = NULL;
4203 s->first = NULL;
4204 s->last = NULL;
4206 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4207 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4208 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4210 return s;
4211 }
4213 /**
4214 * Destroy nodes in subpath, then subpath itself.
4215 */
4216 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4217 {
4218 g_assert(subpath);
4219 g_assert(subpath->nodepath);
4220 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4222 while (subpath->nodes) {
4223 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4224 }
4226 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4228 g_free(subpath);
4229 }
4231 /**
4232 * Link head to tail in subpath.
4233 */
4234 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4235 {
4236 g_assert(!sp->closed);
4237 g_assert(sp->last != sp->first);
4238 g_assert(sp->first->code == NR_MOVETO);
4240 sp->closed = TRUE;
4242 //Link the head to the tail
4243 sp->first->p.other = sp->last;
4244 sp->last->n.other = sp->first;
4245 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4246 sp->first = sp->last;
4248 //Remove the extra end node
4249 sp_nodepath_node_destroy(sp->last->n.other);
4250 }
4252 /**
4253 * Open closed (loopy) subpath at node.
4254 */
4255 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4256 {
4257 g_assert(sp->closed);
4258 g_assert(n->subpath == sp);
4259 g_assert(sp->first == sp->last);
4261 /* We create new startpoint, current node will become last one */
4263 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4264 &n->pos, &n->pos, &n->n.pos);
4267 sp->closed = FALSE;
4269 //Unlink to make a head and tail
4270 sp->first = new_path;
4271 sp->last = n;
4272 n->n.other = NULL;
4273 new_path->p.other = NULL;
4274 }
4276 /**
4277 * Return new node in subpath with given properties.
4278 * \param pos Position of node.
4279 * \param ppos Handle position in previous direction
4280 * \param npos Handle position in previous direction
4281 */
4282 Inkscape::NodePath::Node *
4283 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)
4284 {
4285 g_assert(sp);
4286 g_assert(sp->nodepath);
4287 g_assert(sp->nodepath->desktop);
4289 if (nodechunk == NULL)
4290 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4292 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4294 n->subpath = sp;
4296 if (type != Inkscape::NodePath::NODE_NONE) {
4297 // use the type from sodipodi:nodetypes
4298 n->type = type;
4299 } else {
4300 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4301 // points are (almost) collinear
4302 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4303 // endnode, or a node with a retracted handle
4304 n->type = Inkscape::NodePath::NODE_CUSP;
4305 } else {
4306 n->type = Inkscape::NodePath::NODE_SMOOTH;
4307 }
4308 } else {
4309 n->type = Inkscape::NodePath::NODE_CUSP;
4310 }
4311 }
4313 n->code = code;
4314 n->selected = FALSE;
4315 n->pos = *pos;
4316 n->p.pos = *ppos;
4317 n->n.pos = *npos;
4319 n->dragging_out = NULL;
4321 Inkscape::NodePath::Node *prev;
4322 if (next) {
4323 //g_assert(g_list_find(sp->nodes, next));
4324 prev = next->p.other;
4325 } else {
4326 prev = sp->last;
4327 }
4329 if (prev)
4330 prev->n.other = n;
4331 else
4332 sp->first = n;
4334 if (next)
4335 next->p.other = n;
4336 else
4337 sp->last = n;
4339 n->p.other = prev;
4340 n->n.other = next;
4342 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"));
4343 sp_knot_set_position(n->knot, pos, 0);
4345 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4346 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4347 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4348 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4349 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4350 sp_knot_update_ctrl(n->knot);
4352 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4353 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4354 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4355 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4356 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4357 sp_knot_show(n->knot);
4359 // We only create handle knots and lines on demand
4360 n->p.knot = NULL;
4361 n->p.line = NULL;
4362 n->n.knot = NULL;
4363 n->n.line = NULL;
4365 sp->nodes = g_list_prepend(sp->nodes, n);
4367 return n;
4368 }
4370 /**
4371 * Destroy node and its knots, link neighbors in subpath.
4372 */
4373 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4374 {
4375 g_assert(node);
4376 g_assert(node->subpath);
4377 g_assert(SP_IS_KNOT(node->knot));
4379 Inkscape::NodePath::SubPath *sp = node->subpath;
4381 if (node->selected) { // first, deselect
4382 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4383 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4384 }
4386 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4388 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4389 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4390 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4391 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4392 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4393 g_object_unref(G_OBJECT(node->knot));
4395 if (node->p.knot) {
4396 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4397 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4398 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4399 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4400 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4401 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4402 g_object_unref(G_OBJECT(node->p.knot));
4403 node->p.knot = NULL;
4404 }
4406 if (node->n.knot) {
4407 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4408 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4409 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4410 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4411 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4412 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4413 g_object_unref(G_OBJECT(node->n.knot));
4414 node->n.knot = NULL;
4415 }
4417 if (node->p.line)
4418 gtk_object_destroy(GTK_OBJECT(node->p.line));
4419 if (node->n.line)
4420 gtk_object_destroy(GTK_OBJECT(node->n.line));
4422 if (sp->nodes) { // there are others nodes on the subpath
4423 if (sp->closed) {
4424 if (sp->first == node) {
4425 g_assert(sp->last == node);
4426 sp->first = node->n.other;
4427 sp->last = sp->first;
4428 }
4429 node->p.other->n.other = node->n.other;
4430 node->n.other->p.other = node->p.other;
4431 } else {
4432 if (sp->first == node) {
4433 sp->first = node->n.other;
4434 sp->first->code = NR_MOVETO;
4435 }
4436 if (sp->last == node) sp->last = node->p.other;
4437 if (node->p.other) node->p.other->n.other = node->n.other;
4438 if (node->n.other) node->n.other->p.other = node->p.other;
4439 }
4440 } else { // this was the last node on subpath
4441 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4442 }
4444 g_mem_chunk_free(nodechunk, node);
4445 }
4447 /**
4448 * Returns one of the node's two sides.
4449 * \param which Indicates which side.
4450 * \return Pointer to previous node side if which==-1, next if which==1.
4451 */
4452 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4453 {
4454 g_assert(node);
4456 switch (which) {
4457 case -1:
4458 return &node->p;
4459 case 1:
4460 return &node->n;
4461 default:
4462 break;
4463 }
4465 g_assert_not_reached();
4467 return NULL;
4468 }
4470 /**
4471 * Return the other side of the node, given one of its sides.
4472 */
4473 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4474 {
4475 g_assert(node);
4477 if (me == &node->p) return &node->n;
4478 if (me == &node->n) return &node->p;
4480 g_assert_not_reached();
4482 return NULL;
4483 }
4485 /**
4486 * Return NRPathcode on the given side of the node.
4487 */
4488 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4489 {
4490 g_assert(node);
4492 if (me == &node->p) {
4493 if (node->p.other) return (NRPathcode)node->code;
4494 return NR_MOVETO;
4495 }
4497 if (me == &node->n) {
4498 if (node->n.other) return (NRPathcode)node->n.other->code;
4499 return NR_MOVETO;
4500 }
4502 g_assert_not_reached();
4504 return NR_END;
4505 }
4507 /**
4508 * Return node with the given index
4509 */
4510 Inkscape::NodePath::Node *
4511 sp_nodepath_get_node_by_index(int index)
4512 {
4513 Inkscape::NodePath::Node *e = NULL;
4515 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4516 if (!nodepath) {
4517 return e;
4518 }
4520 //find segment
4521 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4523 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4524 int n = g_list_length(sp->nodes);
4525 if (sp->closed) {
4526 n++;
4527 }
4529 //if the piece belongs to this subpath grab it
4530 //otherwise move onto the next subpath
4531 if (index < n) {
4532 e = sp->first;
4533 for (int i = 0; i < index; ++i) {
4534 e = e->n.other;
4535 }
4536 break;
4537 } else {
4538 if (sp->closed) {
4539 index -= (n+1);
4540 } else {
4541 index -= n;
4542 }
4543 }
4544 }
4546 return e;
4547 }
4549 /**
4550 * Returns plain text meaning of node type.
4551 */
4552 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4553 {
4554 unsigned retracted = 0;
4555 bool endnode = false;
4557 for (int which = -1; which <= 1; which += 2) {
4558 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4559 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4560 retracted ++;
4561 if (!side->other)
4562 endnode = true;
4563 }
4565 if (retracted == 0) {
4566 if (endnode) {
4567 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4568 return _("end node");
4569 } else {
4570 switch (node->type) {
4571 case Inkscape::NodePath::NODE_CUSP:
4572 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4573 return _("cusp");
4574 case Inkscape::NodePath::NODE_SMOOTH:
4575 // TRANSLATORS: "smooth" is an adjective here
4576 return _("smooth");
4577 case Inkscape::NodePath::NODE_SYMM:
4578 return _("symmetric");
4579 }
4580 }
4581 } else if (retracted == 1) {
4582 if (endnode) {
4583 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4584 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4585 } else {
4586 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4587 }
4588 } else {
4589 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4590 }
4592 return NULL;
4593 }
4595 /**
4596 * Handles content of statusbar as long as node tool is active.
4597 */
4598 void
4599 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4600 {
4601 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");
4602 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4604 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4605 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4606 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4607 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4609 SPDesktop *desktop = NULL;
4610 if (nodepath) {
4611 desktop = nodepath->desktop;
4612 } else {
4613 desktop = SP_ACTIVE_DESKTOP;
4614 }
4616 SPEventContext *ec = desktop->event_context;
4617 if (!ec) return;
4618 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4619 if (!mc) return;
4621 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4623 if (selected_nodes == 0) {
4624 Inkscape::Selection *sel = desktop->selection;
4625 if (!sel || sel->isEmpty()) {
4626 mc->setF(Inkscape::NORMAL_MESSAGE,
4627 _("Select a single object to edit its nodes or handles."));
4628 } else {
4629 if (nodepath) {
4630 mc->setF(Inkscape::NORMAL_MESSAGE,
4631 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.",
4632 "<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.",
4633 total_nodes),
4634 total_nodes);
4635 } else {
4636 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4637 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4638 } else {
4639 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4640 }
4641 }
4642 }
4643 } else if (nodepath && selected_nodes == 1) {
4644 mc->setF(Inkscape::NORMAL_MESSAGE,
4645 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4646 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4647 total_nodes),
4648 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4649 } else {
4650 if (selected_subpaths > 1) {
4651 mc->setF(Inkscape::NORMAL_MESSAGE,
4652 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4653 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4654 total_nodes),
4655 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4656 } else {
4657 mc->setF(Inkscape::NORMAL_MESSAGE,
4658 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4659 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4660 total_nodes),
4661 selected_nodes, total_nodes, when_selected);
4662 }
4663 }
4664 }
4666 /*
4667 * returns a *copy* of the curve of that object.
4668 */
4669 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4670 if (!object)
4671 return NULL;
4673 SPCurve *curve = NULL;
4674 if (SP_IS_PATH(object)) {
4675 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4676 curve = sp_curve_copy(curve_new);
4677 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4678 const gchar *svgd = object->repr->attribute(key);
4679 if (svgd) {
4680 NArtBpath *bpath = sp_svg_read_path(svgd);
4681 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4682 if (curve_new) {
4683 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4684 } else {
4685 g_free(bpath);
4686 }
4687 }
4688 }
4690 return curve;
4691 }
4693 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4694 if (!np || !np->object || !curve)
4695 return;
4697 if (SP_IS_PATH(np->object)) {
4698 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4699 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4700 } else {
4701 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4702 }
4703 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4704 // FIXME: this writing to string and then reading from string is bound to be slow.
4705 // create a method to convert from curve directly to 2geom...
4706 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4707 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4708 g_free(svgpath);
4710 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4711 }
4712 }
4714 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4715 np->show_helperpath = show;
4717 if (show) {
4718 SPCurve *helper_curve = sp_curve_copy(np->curve);
4719 sp_curve_transform(helper_curve, np->i2d );
4720 if (!np->helper_path) {
4721 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4722 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);
4723 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4724 sp_canvas_item_move_to_z(np->helper_path, 0);
4725 sp_canvas_item_show(np->helper_path);
4726 } else {
4727 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4728 }
4729 sp_curve_unref(helper_curve);
4730 } else {
4731 if (np->helper_path) {
4732 GtkObject *temp = np->helper_path;
4733 np->helper_path = NULL;
4734 gtk_object_destroy(temp);
4735 }
4736 }
4737 }
4739 /* sp_nodepath_make_straight_path:
4740 * Prevents user from curving the path by dragging a segment or activating handles etc.
4741 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4742 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4743 */
4744 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4745 np->straight_path = true;
4746 np->show_handles = false;
4747 g_message("add code to make the path straight.");
4748 // do sp_nodepath_convert_node_type on all nodes?
4749 // coding tip: search for this text : "Make selected segments lines"
4750 }
4753 /*
4754 Local Variables:
4755 mode:c++
4756 c-file-style:"stroustrup"
4757 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4758 indent-tabs-mode:nil
4759 fill-column:99
4760 End:
4761 */
4762 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :