8e2cd8e82892bee1c6c2e8ad7e1802ccfadeb38e
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 = curve->first_bpath();
184 gint length = curve->end;
185 if (length == 0) {
186 curve->unref();
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 curve->unref();
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 = curve->copy();
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 curve->unref();
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 = np->curve->copy();
269 helper_curve->transform(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 helper_curve->unref();
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 np->curve->unref();
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 curve->unref();
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 np->curve->unref();
537 np->curve = create_curve(np);
539 sp_nodepath_set_curve(np, np->curve);
541 if (np->show_helperpath) {
542 SPCurve * helper_curve = np->curve->copy();
543 helper_curve->transform(np->i2d );
544 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
545 helper_curve->unref();
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 np->curve->unref();
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 = np->curve->copy();
580 helper_curve->transform(np->i2d );
581 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
582 helper_curve->unref();
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 curve->unref();
656 }
658 /**
659 * Create curve from path.
660 */
661 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
662 {
663 SPCurve *curve = new SPCurve();
665 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
666 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
667 curve->moveto(sp->first->pos * np->d2i);
668 Inkscape::NodePath::Node *n = sp->first->n.other;
669 while (n) {
670 NR::Point const end_pt = n->pos * np->d2i;
671 switch (n->code) {
672 case NR_LINETO:
673 curve->lineto(end_pt);
674 break;
675 case NR_CURVETO:
676 curve->curveto(n->p.other->n.pos * np->d2i,
677 n->p.pos * np->d2i,
678 end_pt);
679 break;
680 default:
681 g_assert_not_reached();
682 break;
683 }
684 if (n != sp->last) {
685 n = n->n.other;
686 } else {
687 n = NULL;
688 }
689 }
690 if (sp->closed) {
691 curve->closepath();
692 }
693 }
695 return curve;
696 }
698 /**
699 * Convert path type string to sodipodi:nodetypes style.
700 */
701 static gchar *create_typestr(Inkscape::NodePath::Path *np)
702 {
703 gchar *typestr = g_new(gchar, 32);
704 gint len = 32;
705 gint pos = 0;
707 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
708 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
710 if (pos >= len) {
711 typestr = g_renew(gchar, typestr, len + 32);
712 len += 32;
713 }
715 typestr[pos++] = 'c';
717 Inkscape::NodePath::Node *n;
718 n = sp->first->n.other;
719 while (n) {
720 gchar code;
722 switch (n->type) {
723 case Inkscape::NodePath::NODE_CUSP:
724 code = 'c';
725 break;
726 case Inkscape::NodePath::NODE_SMOOTH:
727 code = 's';
728 break;
729 case Inkscape::NodePath::NODE_SYMM:
730 code = 'z';
731 break;
732 default:
733 g_assert_not_reached();
734 code = '\0';
735 break;
736 }
738 if (pos >= len) {
739 typestr = g_renew(gchar, typestr, len + 32);
740 len += 32;
741 }
743 typestr[pos++] = code;
745 if (n != sp->last) {
746 n = n->n.other;
747 } else {
748 n = NULL;
749 }
750 }
751 }
753 if (pos >= len) {
754 typestr = g_renew(gchar, typestr, len + 1);
755 len += 1;
756 }
758 typestr[pos++] = '\0';
760 return typestr;
761 }
763 /**
764 * Returns current path in context. // later eliminate this function at all!
765 */
766 static Inkscape::NodePath::Path *sp_nodepath_current()
767 {
768 if (!SP_ACTIVE_DESKTOP) {
769 return NULL;
770 }
772 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
774 if (!SP_IS_NODE_CONTEXT(event_context)) {
775 return NULL;
776 }
778 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
779 }
783 /**
784 \brief Fills node and handle positions for three nodes, splitting line
785 marked by end at distance t.
786 */
787 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
788 {
789 g_assert(new_path != NULL);
790 g_assert(end != NULL);
792 g_assert(end->p.other == new_path);
793 Inkscape::NodePath::Node *start = new_path->p.other;
794 g_assert(start);
796 if (end->code == NR_LINETO) {
797 new_path->type =Inkscape::NodePath::NODE_CUSP;
798 new_path->code = NR_LINETO;
799 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
800 } else {
801 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
802 new_path->code = NR_CURVETO;
803 gdouble s = 1 - t;
804 for (int dim = 0; dim < 2; dim++) {
805 NR::Coord const f000 = start->pos[dim];
806 NR::Coord const f001 = start->n.pos[dim];
807 NR::Coord const f011 = end->p.pos[dim];
808 NR::Coord const f111 = end->pos[dim];
809 NR::Coord const f00t = s * f000 + t * f001;
810 NR::Coord const f01t = s * f001 + t * f011;
811 NR::Coord const f11t = s * f011 + t * f111;
812 NR::Coord const f0tt = s * f00t + t * f01t;
813 NR::Coord const f1tt = s * f01t + t * f11t;
814 NR::Coord const fttt = s * f0tt + t * f1tt;
815 start->n.pos[dim] = f00t;
816 new_path->p.pos[dim] = f0tt;
817 new_path->pos[dim] = fttt;
818 new_path->n.pos[dim] = f1tt;
819 end->p.pos[dim] = f11t;
820 }
821 }
822 }
824 /**
825 * Adds new node on direct line between two nodes, activates handles of all
826 * three nodes.
827 */
828 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
829 {
830 g_assert(end);
831 g_assert(end->subpath);
832 g_assert(g_list_find(end->subpath->nodes, end));
834 Inkscape::NodePath::Node *start = end->p.other;
835 g_assert( start->n.other == end );
836 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
837 end,
838 (NRPathcode)end->code == NR_LINETO?
839 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
840 (NRPathcode)end->code,
841 &start->pos, &start->pos, &start->n.pos);
842 sp_nodepath_line_midpoint(newnode, end, t);
844 sp_node_adjust_handles(start);
845 sp_node_update_handles(start);
846 sp_node_update_handles(newnode);
847 sp_node_adjust_handles(end);
848 sp_node_update_handles(end);
850 return newnode;
851 }
853 /**
854 \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
855 */
856 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
857 {
858 g_assert(node);
859 g_assert(node->subpath);
860 g_assert(g_list_find(node->subpath->nodes, node));
862 Inkscape::NodePath::SubPath *sp = node->subpath;
863 Inkscape::NodePath::Path *np = sp->nodepath;
865 if (sp->closed) {
866 sp_nodepath_subpath_open(sp, node);
867 return sp->first;
868 } else {
869 // no break for end nodes
870 if (node == sp->first) return NULL;
871 if (node == sp->last ) return NULL;
873 // create a new subpath
874 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
876 // duplicate the break node as start of the new subpath
877 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
879 while (node->n.other) { // copy the remaining nodes into the new subpath
880 Inkscape::NodePath::Node *n = node->n.other;
881 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);
882 if (n->selected) {
883 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
884 }
885 sp_nodepath_node_destroy(n); // remove the point on the original subpath
886 }
888 return newnode;
889 }
890 }
892 /**
893 * Duplicate node and connect to neighbours.
894 */
895 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
896 {
897 g_assert(node);
898 g_assert(node->subpath);
899 g_assert(g_list_find(node->subpath->nodes, node));
901 Inkscape::NodePath::SubPath *sp = node->subpath;
903 NRPathcode code = (NRPathcode) node->code;
904 if (code == NR_MOVETO) { // if node is the endnode,
905 node->code = NR_LINETO; // new one is inserted before it, so change that to line
906 }
908 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
910 if (!node->n.other || !node->p.other) // if node is an endnode, select it
911 return node;
912 else
913 return newnode; // otherwise select the newly created node
914 }
916 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
917 {
918 node->p.pos = (node->pos + (node->pos - node->n.pos));
919 }
921 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
922 {
923 node->n.pos = (node->pos + (node->pos - node->p.pos));
924 }
926 /**
927 * Change line type at node, with side effects on neighbours.
928 */
929 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
930 {
931 g_assert(end);
932 g_assert(end->subpath);
933 g_assert(end->p.other);
935 if (end->code == static_cast< guint > ( code ) )
936 return;
938 Inkscape::NodePath::Node *start = end->p.other;
940 end->code = code;
942 if (code == NR_LINETO) {
943 if (start->code == NR_LINETO) {
944 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
945 }
946 if (end->n.other) {
947 if (end->n.other->code == NR_LINETO) {
948 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
949 }
950 }
951 } else {
952 NR::Point delta = end->pos - start->pos;
953 start->n.pos = start->pos + delta / 3;
954 end->p.pos = end->pos - delta / 3;
955 sp_node_adjust_handle(start, 1);
956 sp_node_adjust_handle(end, -1);
957 }
959 sp_node_update_handles(start);
960 sp_node_update_handles(end);
961 }
963 /**
964 * Change node type, and its handles accordingly.
965 */
966 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
967 {
968 g_assert(node);
969 g_assert(node->subpath);
971 if ((node->p.other != NULL) && (node->n.other != NULL)) {
972 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
973 type =Inkscape::NodePath::NODE_CUSP;
974 }
975 }
977 node->type = type;
979 if (node->type == Inkscape::NodePath::NODE_CUSP) {
980 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
981 node->knot->setSize (node->selected? 11 : 9);
982 sp_knot_update_ctrl(node->knot);
983 } else {
984 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
985 node->knot->setSize (node->selected? 9 : 7);
986 sp_knot_update_ctrl(node->knot);
987 }
989 // if one of handles is mouseovered, preserve its position
990 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
991 sp_node_adjust_handle(node, 1);
992 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
993 sp_node_adjust_handle(node, -1);
994 } else {
995 sp_node_adjust_handles(node);
996 }
998 sp_node_update_handles(node);
1000 sp_nodepath_update_statusbar(node->subpath->nodepath);
1002 return node;
1003 }
1005 bool
1006 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1007 {
1008 Inkscape::NodePath::Node *othernode = side->other;
1009 if (!othernode)
1010 return false;
1011 NRPathcode const code = sp_node_path_code_from_side(node, side);
1012 if (code == NR_LINETO)
1013 return true;
1014 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1015 if (&node->p == side) {
1016 other_to_me = &othernode->n;
1017 } else if (&node->n == side) {
1018 other_to_me = &othernode->p;
1019 }
1020 if (!other_to_me)
1021 return false;
1022 bool is_line =
1023 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1024 NR::L2(node->pos - side->pos) < 1e-6);
1025 return is_line;
1026 }
1028 /**
1029 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1030 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1031 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1032 * If already cusp and set to cusp, retracts handles.
1033 */
1034 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1035 {
1036 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1038 /*
1039 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1041 if (two_handles) {
1042 // do nothing, adjust_handles called via set_node_type will line them up
1043 } else if (one_handle) {
1044 if (opposite_to_handle_is_line) {
1045 if (lined_up) {
1046 // already half-smooth; pull opposite handle too making it fully smooth
1047 } else {
1048 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1049 }
1050 } else {
1051 // pull opposite handle in line with the existing one
1052 }
1053 } else if (no_handles) {
1054 if (both_segments_are_lines OR both_segments_are_curves) {
1055 //pull both handles
1056 } else {
1057 // pull the handle opposite to line segment, making node half-smooth
1058 }
1059 }
1060 */
1061 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1062 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1063 bool p_is_line = sp_node_side_is_line(node, &node->p);
1064 bool n_is_line = sp_node_side_is_line(node, &node->n);
1066 if (p_has_handle && n_has_handle) {
1067 // do nothing, adjust_handles will line them up
1068 } else if (p_has_handle || n_has_handle) {
1069 if (p_has_handle && n_is_line) {
1070 Radial line (node->n.other->pos - node->pos);
1071 Radial handle (node->pos - node->p.pos);
1072 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1073 // already half-smooth; pull opposite handle too making it fully smooth
1074 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1075 } else {
1076 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1077 }
1078 } else if (n_has_handle && p_is_line) {
1079 Radial line (node->p.other->pos - node->pos);
1080 Radial handle (node->pos - node->n.pos);
1081 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1082 // already half-smooth; pull opposite handle too making it fully smooth
1083 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1084 } else {
1085 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1086 }
1087 } else if (p_has_handle && node->n.other) {
1088 // pull n handle
1089 node->n.other->code = NR_CURVETO;
1090 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1091 NR::L2(node->p.pos - node->pos) :
1092 NR::L2(node->n.other->pos - node->pos) / 3;
1093 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1094 } else if (n_has_handle && node->p.other) {
1095 // pull p handle
1096 node->code = NR_CURVETO;
1097 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1098 NR::L2(node->n.pos - node->pos) :
1099 NR::L2(node->p.other->pos - node->pos) / 3;
1100 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1101 }
1102 } else if (!p_has_handle && !n_has_handle) {
1103 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1104 // no handles, but both segments are either lnes or curves:
1105 //pull both handles
1107 // convert both to curves:
1108 node->code = NR_CURVETO;
1109 node->n.other->code = NR_CURVETO;
1111 NR::Point leg_prev = node->pos - node->p.other->pos;
1112 NR::Point leg_next = node->pos - node->n.other->pos;
1114 double norm_leg_prev = L2(leg_prev);
1115 double norm_leg_next = L2(leg_next);
1117 NR::Point delta;
1118 if (norm_leg_next > 0.0) {
1119 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1120 (&delta)->normalize();
1121 }
1123 if (type == Inkscape::NodePath::NODE_SYMM) {
1124 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1125 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1126 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1127 } else {
1128 // length of handle is proportional to distance to adjacent node
1129 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1130 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1131 }
1133 } else {
1134 // pull the handle opposite to line segment, making it half-smooth
1135 if (p_is_line && node->n.other) {
1136 if (type != Inkscape::NodePath::NODE_SYMM) {
1137 // pull n handle
1138 node->n.other->code = NR_CURVETO;
1139 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1140 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1141 }
1142 } else if (n_is_line && node->p.other) {
1143 if (type != Inkscape::NodePath::NODE_SYMM) {
1144 // pull p handle
1145 node->code = NR_CURVETO;
1146 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1147 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1148 }
1149 }
1150 }
1151 }
1152 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1153 // cusping a cusp: retract nodes
1154 node->p.pos = node->pos;
1155 node->n.pos = node->pos;
1156 }
1158 sp_nodepath_set_node_type (node, type);
1159 }
1161 /**
1162 * Move node to point, and adjust its and neighbouring handles.
1163 */
1164 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1165 {
1166 NR::Point delta = p - node->pos;
1167 node->pos = p;
1169 node->p.pos += delta;
1170 node->n.pos += delta;
1172 Inkscape::NodePath::Node *node_p = NULL;
1173 Inkscape::NodePath::Node *node_n = NULL;
1175 if (node->p.other) {
1176 if (node->code == NR_LINETO) {
1177 sp_node_adjust_handle(node, 1);
1178 sp_node_adjust_handle(node->p.other, -1);
1179 node_p = node->p.other;
1180 }
1181 }
1182 if (node->n.other) {
1183 if (node->n.other->code == NR_LINETO) {
1184 sp_node_adjust_handle(node, -1);
1185 sp_node_adjust_handle(node->n.other, 1);
1186 node_n = node->n.other;
1187 }
1188 }
1190 // this function is only called from batch movers that will update display at the end
1191 // themselves, so here we just move all the knots without emitting move signals, for speed
1192 sp_node_update_handles(node, false);
1193 if (node_n) {
1194 sp_node_update_handles(node_n, false);
1195 }
1196 if (node_p) {
1197 sp_node_update_handles(node_p, false);
1198 }
1199 }
1201 /**
1202 * Call sp_node_moveto() for node selection and handle possible snapping.
1203 */
1204 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1205 bool const snap = true)
1206 {
1207 NR::Coord best = NR_HUGE;
1208 NR::Point delta(dx, dy);
1209 NR::Point best_pt = delta;
1210 Inkscape::SnappedPoint best_abs;
1213 if (snap) {
1214 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1215 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1216 * must provide that information. */
1218 // Build a list of the unselected nodes to which the snapper should snap
1219 std::vector<NR::Point> unselected_nodes;
1220 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1221 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1222 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1223 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1224 if (!node->selected) {
1225 unselected_nodes.push_back(node->pos);
1226 }
1227 }
1228 }
1230 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1232 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1233 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1234 m.setup(nodepath->desktop, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1235 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1236 if (s.getDistance() < best) {
1237 best = s.getDistance();
1238 best_abs = s;
1239 best_pt = s.getPoint() - n->pos;
1240 }
1241 }
1243 if (best_abs.getSnapped()) {
1244 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1245 }
1246 }
1248 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1250 sp_node_moveto(n, n->pos + best_pt);
1251 }
1253 // do not update repr here so that node dragging is acceptably fast
1254 update_object(nodepath);
1255 }
1257 /**
1258 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1259 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1260 near x = 0.
1261 */
1262 double
1263 sculpt_profile (double x, double alpha, guint profile)
1264 {
1265 if (x >= 1)
1266 return 0;
1267 if (x <= 0)
1268 return 1;
1270 switch (profile) {
1271 case SCULPT_PROFILE_LINEAR:
1272 return 1 - x;
1273 case SCULPT_PROFILE_BELL:
1274 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1275 case SCULPT_PROFILE_ELLIPTIC:
1276 return sqrt(1 - x*x);
1277 }
1279 return 1;
1280 }
1282 double
1283 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1284 {
1285 // extremely primitive for now, don't have time to look for the real one
1286 double lower = NR::L2(b - a);
1287 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1288 return (lower + upper)/2;
1289 }
1291 void
1292 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1293 {
1294 n->pos = n->origin + delta;
1295 n->n.pos = n->n.origin + delta_n;
1296 n->p.pos = n->p.origin + delta_p;
1297 sp_node_adjust_handles(n);
1298 sp_node_update_handles(n, false);
1299 }
1301 /**
1302 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1303 * on how far they are from the dragged node n.
1304 */
1305 static void
1306 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1307 {
1308 g_assert (n);
1309 g_assert (nodepath);
1310 g_assert (n->subpath->nodepath == nodepath);
1312 double pressure = n->knot->pressure;
1313 if (pressure == 0)
1314 pressure = 0.5; // default
1315 pressure = CLAMP (pressure, 0.2, 0.8);
1317 // map pressure to alpha = 1/5 ... 5
1318 double alpha = 1 - 2 * fabs(pressure - 0.5);
1319 if (pressure > 0.5)
1320 alpha = 1/alpha;
1322 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1324 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1325 // Only one subpath has selected nodes:
1326 // use linear mode, where the distance from n to node being dragged is calculated along the path
1328 double n_sel_range = 0, p_sel_range = 0;
1329 guint n_nodes = 0, p_nodes = 0;
1330 guint n_sel_nodes = 0, p_sel_nodes = 0;
1332 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1333 {
1334 double n_range = 0, p_range = 0;
1335 bool n_going = true, p_going = true;
1336 Inkscape::NodePath::Node *n_node = n;
1337 Inkscape::NodePath::Node *p_node = n;
1338 do {
1339 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1340 if (n_node && n_going)
1341 n_node = n_node->n.other;
1342 if (n_node == NULL) {
1343 n_going = false;
1344 } else {
1345 n_nodes ++;
1346 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1347 if (n_node->selected) {
1348 n_sel_nodes ++;
1349 n_sel_range = n_range;
1350 }
1351 if (n_node == p_node) {
1352 n_going = false;
1353 p_going = false;
1354 }
1355 }
1356 if (p_node && p_going)
1357 p_node = p_node->p.other;
1358 if (p_node == NULL) {
1359 p_going = false;
1360 } else {
1361 p_nodes ++;
1362 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1363 if (p_node->selected) {
1364 p_sel_nodes ++;
1365 p_sel_range = p_range;
1366 }
1367 if (p_node == n_node) {
1368 n_going = false;
1369 p_going = false;
1370 }
1371 }
1372 } while (n_going || p_going);
1373 }
1375 // Second pass: actually move nodes in this subpath
1376 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1377 {
1378 double n_range = 0, p_range = 0;
1379 bool n_going = true, p_going = true;
1380 Inkscape::NodePath::Node *n_node = n;
1381 Inkscape::NodePath::Node *p_node = n;
1382 do {
1383 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1384 if (n_node && n_going)
1385 n_node = n_node->n.other;
1386 if (n_node == NULL) {
1387 n_going = false;
1388 } else {
1389 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1390 if (n_node->selected) {
1391 sp_nodepath_move_node_and_handles (n_node,
1392 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1393 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1394 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1395 }
1396 if (n_node == p_node) {
1397 n_going = false;
1398 p_going = false;
1399 }
1400 }
1401 if (p_node && p_going)
1402 p_node = p_node->p.other;
1403 if (p_node == NULL) {
1404 p_going = false;
1405 } else {
1406 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1407 if (p_node->selected) {
1408 sp_nodepath_move_node_and_handles (p_node,
1409 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1410 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1411 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1412 }
1413 if (p_node == n_node) {
1414 n_going = false;
1415 p_going = false;
1416 }
1417 }
1418 } while (n_going || p_going);
1419 }
1421 } else {
1422 // Multiple subpaths have selected nodes:
1423 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1424 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1425 // fix the pear-like shape when sculpting e.g. a ring
1427 // First pass: calculate range
1428 gdouble direct_range = 0;
1429 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1430 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1431 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1432 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1433 if (node->selected) {
1434 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1435 }
1436 }
1437 }
1439 // Second pass: actually move nodes
1440 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1441 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1442 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1443 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1444 if (node->selected) {
1445 if (direct_range > 1e-6) {
1446 sp_nodepath_move_node_and_handles (node,
1447 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1448 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1449 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1450 } else {
1451 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1452 }
1454 }
1455 }
1456 }
1457 }
1459 // do not update repr here so that node dragging is acceptably fast
1460 update_object(nodepath);
1461 }
1464 /**
1465 * Move node selection to point, adjust its and neighbouring handles,
1466 * handle possible snapping, and commit the change with possible undo.
1467 */
1468 void
1469 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1470 {
1471 if (!nodepath) return;
1473 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1475 if (dx == 0) {
1476 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1477 } else if (dy == 0) {
1478 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1479 } else {
1480 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1481 }
1482 }
1484 /**
1485 * Move node selection off screen and commit the change.
1486 */
1487 void
1488 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1489 {
1490 // borrowed from sp_selection_move_screen in selection-chemistry.c
1491 // we find out the current zoom factor and divide deltas by it
1492 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1494 gdouble zoom = desktop->current_zoom();
1495 gdouble zdx = dx / zoom;
1496 gdouble zdy = dy / zoom;
1498 if (!nodepath) return;
1500 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1502 if (dx == 0) {
1503 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1504 } else if (dy == 0) {
1505 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1506 } else {
1507 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1508 }
1509 }
1511 /**
1512 * Move selected nodes to the absolute position given
1513 */
1514 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1515 {
1516 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1517 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1518 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1519 sp_node_moveto(n, npos);
1520 }
1522 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1523 }
1525 /**
1526 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1527 */
1528 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1529 {
1530 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1531 g_return_val_if_fail(nodepath->selected, no_coord);
1533 // determine coordinate of first selected node
1534 GList *nsel = nodepath->selected;
1535 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1536 NR::Coord coord = n->pos[axis];
1537 bool coincide = true;
1539 // compare it to the coordinates of all the other selected nodes
1540 for (GList *l = nsel->next; l != NULL; l = l->next) {
1541 n = (Inkscape::NodePath::Node *) l->data;
1542 if (n->pos[axis] != coord) {
1543 coincide = false;
1544 }
1545 }
1546 if (coincide) {
1547 return coord;
1548 } else {
1549 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1550 // currently we return the coordinate of the bounding box midpoint because I don't know how
1551 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1552 return bbox.midpoint()[axis];
1553 }
1554 }
1556 /** If they don't yet exist, creates knot and line for the given side of the node */
1557 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1558 {
1559 if (!side->knot) {
1560 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"));
1562 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1563 side->knot->setSize (7);
1564 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1565 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1566 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1567 sp_knot_update_ctrl(side->knot);
1569 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1570 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1571 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1572 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1573 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1574 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1575 }
1577 if (!side->line) {
1578 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1579 SP_TYPE_CTRLLINE, NULL);
1580 }
1581 }
1583 /**
1584 * Ensure the given handle of the node is visible/invisible, update its screen position
1585 */
1586 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1587 {
1588 g_assert(node != NULL);
1590 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1591 NRPathcode code = sp_node_path_code_from_side(node, side);
1593 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1595 if (show_handle) {
1596 if (!side->knot) { // No handle knot at all
1597 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1598 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1599 side->knot->pos = side->pos;
1600 if (side->knot->item)
1601 SP_CTRL(side->knot->item)->moveto(side->pos);
1602 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1603 sp_knot_show(side->knot);
1604 } else {
1605 if (side->knot->pos != side->pos) { // only if it's really moved
1606 if (fire_move_signals) {
1607 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1608 } else {
1609 sp_knot_moveto(side->knot, &side->pos);
1610 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1611 }
1612 }
1613 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1614 sp_knot_show(side->knot);
1615 }
1616 }
1617 sp_canvas_item_show(side->line);
1618 } else {
1619 if (side->knot) {
1620 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1621 sp_knot_hide(side->knot);
1622 }
1623 }
1624 if (side->line) {
1625 sp_canvas_item_hide(side->line);
1626 }
1627 }
1628 }
1630 /**
1631 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1632 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1633 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1634 * updated; otherwise, just move the knots silently (used in batch moves).
1635 */
1636 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1637 {
1638 g_assert(node != NULL);
1640 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1641 sp_knot_show(node->knot);
1642 }
1644 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1645 if (fire_move_signals)
1646 sp_knot_set_position(node->knot, &node->pos, 0);
1647 else
1648 sp_knot_moveto(node->knot, &node->pos);
1649 }
1651 gboolean show_handles = node->selected;
1652 if (node->p.other != NULL) {
1653 if (node->p.other->selected) show_handles = TRUE;
1654 }
1655 if (node->n.other != NULL) {
1656 if (node->n.other->selected) show_handles = TRUE;
1657 }
1659 if (node->subpath->nodepath->show_handles == false)
1660 show_handles = FALSE;
1662 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1663 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1664 }
1666 /**
1667 * Call sp_node_update_handles() for all nodes on subpath.
1668 */
1669 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1670 {
1671 g_assert(subpath != NULL);
1673 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1674 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1675 }
1676 }
1678 /**
1679 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1680 */
1681 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1682 {
1683 g_assert(nodepath != NULL);
1685 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1686 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1687 }
1688 }
1690 void
1691 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1692 {
1693 if (nodepath == NULL) return;
1695 nodepath->show_handles = show;
1696 sp_nodepath_update_handles(nodepath);
1697 }
1699 /**
1700 * Adds all selected nodes in nodepath to list.
1701 */
1702 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1703 {
1704 StlConv<Node *>::list(l, selected);
1705 /// \todo this adds a copying, rework when the selection becomes a stl list
1706 }
1708 /**
1709 * Align selected nodes on the specified axis.
1710 */
1711 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1712 {
1713 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1714 return;
1715 }
1717 if ( !nodepath->selected->next ) { // only one node selected
1718 return;
1719 }
1720 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1721 NR::Point dest(pNode->pos);
1722 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1723 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1724 if (pNode) {
1725 dest[axis] = pNode->pos[axis];
1726 sp_node_moveto(pNode, dest);
1727 }
1728 }
1730 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1731 }
1733 /// Helper struct.
1734 struct NodeSort
1735 {
1736 Inkscape::NodePath::Node *_node;
1737 NR::Coord _coord;
1738 /// \todo use vectorof pointers instead of calling copy ctor
1739 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1740 _node(node), _coord(node->pos[axis])
1741 {}
1743 };
1745 static bool operator<(NodeSort const &a, NodeSort const &b)
1746 {
1747 return (a._coord < b._coord);
1748 }
1750 /**
1751 * Distribute selected nodes on the specified axis.
1752 */
1753 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1754 {
1755 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1756 return;
1757 }
1759 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1760 return;
1761 }
1763 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1764 std::vector<NodeSort> sorted;
1765 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1766 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1767 if (pNode) {
1768 NodeSort n(pNode, axis);
1769 sorted.push_back(n);
1770 //dest[axis] = pNode->pos[axis];
1771 //sp_node_moveto(pNode, dest);
1772 }
1773 }
1774 std::sort(sorted.begin(), sorted.end());
1775 unsigned int len = sorted.size();
1776 //overall bboxes span
1777 float dist = (sorted.back()._coord -
1778 sorted.front()._coord);
1779 //new distance between each bbox
1780 float step = (dist) / (len - 1);
1781 float pos = sorted.front()._coord;
1782 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1783 it < sorted.end();
1784 it ++ )
1785 {
1786 NR::Point dest((*it)._node->pos);
1787 dest[axis] = pos;
1788 sp_node_moveto((*it)._node, dest);
1789 pos += step;
1790 }
1792 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1793 }
1796 /**
1797 * Call sp_nodepath_line_add_node() for all selected segments.
1798 */
1799 void
1800 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1801 {
1802 if (!nodepath) {
1803 return;
1804 }
1806 GList *nl = NULL;
1808 int n_added = 0;
1810 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1811 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1812 g_assert(t->selected);
1813 if (t->p.other && t->p.other->selected) {
1814 nl = g_list_prepend(nl, t);
1815 }
1816 }
1818 while (nl) {
1819 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1820 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1821 sp_nodepath_node_select(n, TRUE, FALSE);
1822 n_added ++;
1823 nl = g_list_remove(nl, t);
1824 }
1826 /** \todo fixme: adjust ? */
1827 sp_nodepath_update_handles(nodepath);
1829 if (n_added > 1) {
1830 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1831 } else if (n_added > 0) {
1832 sp_nodepath_update_repr(nodepath, _("Add node"));
1833 }
1835 sp_nodepath_update_statusbar(nodepath);
1836 }
1838 /**
1839 * Select segment nearest to point
1840 */
1841 void
1842 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1843 {
1844 if (!nodepath) {
1845 return;
1846 }
1848 sp_nodepath_ensure_livarot_path(nodepath);
1849 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1850 if (!maybe_position) {
1851 return;
1852 }
1853 Path::cut_position position = *maybe_position;
1855 //find segment to segment
1856 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1858 //fixme: this can return NULL, so check before proceeding.
1859 g_return_if_fail(e != NULL);
1861 gboolean force = FALSE;
1862 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1863 force = TRUE;
1864 }
1865 sp_nodepath_node_select(e, (gboolean) toggle, force);
1866 if (e->p.other)
1867 sp_nodepath_node_select(e->p.other, TRUE, force);
1869 sp_nodepath_update_handles(nodepath);
1871 sp_nodepath_update_statusbar(nodepath);
1872 }
1874 /**
1875 * Add a node nearest to point
1876 */
1877 void
1878 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1879 {
1880 if (!nodepath) {
1881 return;
1882 }
1884 sp_nodepath_ensure_livarot_path(nodepath);
1885 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1886 if (!maybe_position) {
1887 return;
1888 }
1889 Path::cut_position position = *maybe_position;
1891 //find segment to split
1892 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1894 //don't know why but t seems to flip for lines
1895 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1896 position.t = 1.0 - position.t;
1897 }
1898 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1899 sp_nodepath_node_select(n, FALSE, TRUE);
1901 /* fixme: adjust ? */
1902 sp_nodepath_update_handles(nodepath);
1904 sp_nodepath_update_repr(nodepath, _("Add node"));
1906 sp_nodepath_update_statusbar(nodepath);
1907 }
1909 /*
1910 * Adjusts a segment so that t moves by a certain delta for dragging
1911 * converts lines to curves
1912 *
1913 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1914 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1915 */
1916 void
1917 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1918 {
1919 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1921 //fixme: e and e->p can be NULL, so check for those before proceeding
1922 g_return_if_fail(e != NULL);
1923 g_return_if_fail(&e->p != NULL);
1925 /* feel good is an arbitrary parameter that distributes the delta between handles
1926 * if t of the drag point is less than 1/6 distance form the endpoint only
1927 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1928 */
1929 double feel_good;
1930 if (t <= 1.0 / 6.0)
1931 feel_good = 0;
1932 else if (t <= 0.5)
1933 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1934 else if (t <= 5.0 / 6.0)
1935 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1936 else
1937 feel_good = 1;
1939 //if we're dragging a line convert it to a curve
1940 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1941 sp_nodepath_set_line_type(e, NR_CURVETO);
1942 }
1944 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1945 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1946 e->p.other->n.pos += offsetcoord0;
1947 e->p.pos += offsetcoord1;
1949 // adjust handles of adjacent nodes where necessary
1950 sp_node_adjust_handle(e,1);
1951 sp_node_adjust_handle(e->p.other,-1);
1953 sp_nodepath_update_handles(e->subpath->nodepath);
1955 update_object(e->subpath->nodepath);
1957 sp_nodepath_update_statusbar(e->subpath->nodepath);
1958 }
1961 /**
1962 * Call sp_nodepath_break() for all selected segments.
1963 */
1964 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1965 {
1966 if (!nodepath) return;
1968 GList *temp = NULL;
1969 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1970 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1971 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1972 if (nn == NULL) continue; // no break, no new node
1973 temp = g_list_prepend(temp, nn);
1974 }
1976 if (temp) {
1977 sp_nodepath_deselect(nodepath);
1978 }
1979 for (GList *l = temp; l != NULL; l = l->next) {
1980 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1981 }
1983 sp_nodepath_update_handles(nodepath);
1985 sp_nodepath_update_repr(nodepath, _("Break path"));
1986 }
1988 /**
1989 * Duplicate the selected node(s).
1990 */
1991 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1992 {
1993 if (!nodepath) {
1994 return;
1995 }
1997 GList *temp = NULL;
1998 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1999 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2000 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2001 if (nn == NULL) continue; // could not duplicate
2002 temp = g_list_prepend(temp, nn);
2003 }
2005 if (temp) {
2006 sp_nodepath_deselect(nodepath);
2007 }
2008 for (GList *l = temp; l != NULL; l = l->next) {
2009 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2010 }
2012 sp_nodepath_update_handles(nodepath);
2014 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2015 }
2017 /**
2018 * Internal function to join two nodes by merging them into one.
2019 */
2020 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2021 {
2022 /* a and b are endpoints */
2024 // if one of the two nodes is mouseovered, fix its position
2025 NR::Point c;
2026 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2027 c = a->pos;
2028 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2029 c = b->pos;
2030 } else {
2031 // otherwise, move joined node to the midpoint
2032 c = (a->pos + b->pos) / 2;
2033 }
2035 if (a->subpath == b->subpath) {
2036 Inkscape::NodePath::SubPath *sp = a->subpath;
2037 sp_nodepath_subpath_close(sp);
2038 sp_node_moveto (sp->first, c);
2040 sp_nodepath_update_handles(sp->nodepath);
2041 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2042 return;
2043 }
2045 /* a and b are separate subpaths */
2046 Inkscape::NodePath::SubPath *sa = a->subpath;
2047 Inkscape::NodePath::SubPath *sb = b->subpath;
2048 NR::Point p;
2049 Inkscape::NodePath::Node *n;
2050 NRPathcode code;
2051 if (a == sa->first) {
2052 // we will now reverse sa, so that a is its last node, not first, and drop that node
2053 p = sa->first->n.pos;
2054 code = (NRPathcode)sa->first->n.other->code;
2055 // create new subpath
2056 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2057 // create a first moveto node on it
2058 n = sa->last;
2059 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2060 n = n->p.other;
2061 if (n == sa->first) n = NULL;
2062 while (n) {
2063 // copy the rest of the nodes from sa to t, going backwards
2064 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2065 n = n->p.other;
2066 if (n == sa->first) n = NULL;
2067 }
2068 // replace sa with t
2069 sp_nodepath_subpath_destroy(sa);
2070 sa = t;
2071 } else if (a == sa->last) {
2072 // a is already last, just drop it
2073 p = sa->last->p.pos;
2074 code = (NRPathcode)sa->last->code;
2075 sp_nodepath_node_destroy(sa->last);
2076 } else {
2077 code = NR_END;
2078 g_assert_not_reached();
2079 }
2081 if (b == sb->first) {
2082 // copy all nodes from b to a, forward
2083 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2084 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2085 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2086 }
2087 } else if (b == sb->last) {
2088 // copy all nodes from b to a, backward
2089 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2090 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2091 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2092 }
2093 } else {
2094 g_assert_not_reached();
2095 }
2096 /* and now destroy sb */
2098 sp_nodepath_subpath_destroy(sb);
2100 sp_nodepath_update_handles(sa->nodepath);
2102 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2104 sp_nodepath_update_statusbar(nodepath);
2105 }
2107 /**
2108 * Internal function to join two nodes by adding a segment between them.
2109 */
2110 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2111 {
2112 if (a->subpath == b->subpath) {
2113 Inkscape::NodePath::SubPath *sp = a->subpath;
2115 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2116 sp->closed = TRUE;
2118 sp->first->p.other = sp->last;
2119 sp->last->n.other = sp->first;
2121 sp_node_handle_mirror_p_to_n(sp->last);
2122 sp_node_handle_mirror_n_to_p(sp->first);
2124 sp->first->code = sp->last->code;
2125 sp->first = sp->last;
2127 sp_nodepath_update_handles(sp->nodepath);
2129 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2131 return;
2132 }
2134 /* a and b are separate subpaths */
2135 Inkscape::NodePath::SubPath *sa = a->subpath;
2136 Inkscape::NodePath::SubPath *sb = b->subpath;
2138 Inkscape::NodePath::Node *n;
2139 NR::Point p;
2140 NRPathcode code;
2141 if (a == sa->first) {
2142 code = (NRPathcode) sa->first->n.other->code;
2143 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2144 n = sa->last;
2145 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2146 for (n = n->p.other; n != NULL; n = n->p.other) {
2147 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2148 }
2149 sp_nodepath_subpath_destroy(sa);
2150 sa = t;
2151 } else if (a == sa->last) {
2152 code = (NRPathcode)sa->last->code;
2153 } else {
2154 code = NR_END;
2155 g_assert_not_reached();
2156 }
2158 if (b == sb->first) {
2159 n = sb->first;
2160 sp_node_handle_mirror_p_to_n(sa->last);
2161 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2162 sp_node_handle_mirror_n_to_p(sa->last);
2163 for (n = n->n.other; n != NULL; n = n->n.other) {
2164 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2165 }
2166 } else if (b == sb->last) {
2167 n = sb->last;
2168 sp_node_handle_mirror_p_to_n(sa->last);
2169 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2170 sp_node_handle_mirror_n_to_p(sa->last);
2171 for (n = n->p.other; n != NULL; n = n->p.other) {
2172 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2173 }
2174 } else {
2175 g_assert_not_reached();
2176 }
2177 /* and now destroy sb */
2179 sp_nodepath_subpath_destroy(sb);
2181 sp_nodepath_update_handles(sa->nodepath);
2183 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2184 }
2186 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2188 /**
2189 * Internal function to handle joining two nodes.
2190 */
2191 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2192 {
2193 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2195 if (g_list_length(nodepath->selected) != 2) {
2196 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2197 return;
2198 }
2200 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2201 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2203 g_assert(a != b);
2204 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2205 // someone tried to join an orphan node (i.e. a single-node subpath).
2206 // this is not worth an error message, just fail silently.
2207 return;
2208 }
2210 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2211 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2212 return;
2213 }
2215 switch(mode) {
2216 case NODE_JOIN_ENDPOINTS:
2217 do_node_selected_join(nodepath, a, b);
2218 break;
2219 case NODE_JOIN_SEGMENT:
2220 do_node_selected_join_segment(nodepath, a, b);
2221 break;
2222 }
2223 }
2225 /**
2226 * Join two nodes by merging them into one.
2227 */
2228 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2229 {
2230 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2231 }
2233 /**
2234 * Join two nodes by adding a segment between them.
2235 */
2236 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2237 {
2238 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2239 }
2241 /**
2242 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2243 */
2244 void sp_node_delete_preserve(GList *nodes_to_delete)
2245 {
2246 GSList *nodepaths = NULL;
2248 while (nodes_to_delete) {
2249 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2250 Inkscape::NodePath::SubPath *sp = node->subpath;
2251 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2252 Inkscape::NodePath::Node *sample_cursor = NULL;
2253 Inkscape::NodePath::Node *sample_end = NULL;
2254 Inkscape::NodePath::Node *delete_cursor = node;
2255 bool just_delete = false;
2257 //find the start of this contiguous selection
2258 //move left to the first node that is not selected
2259 //or the start of the non-closed path
2260 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2261 delete_cursor = curr;
2262 }
2264 //just delete at the beginning of an open path
2265 if (!delete_cursor->p.other) {
2266 sample_cursor = delete_cursor;
2267 just_delete = true;
2268 } else {
2269 sample_cursor = delete_cursor->p.other;
2270 }
2272 //calculate points for each segment
2273 int rate = 5;
2274 float period = 1.0 / rate;
2275 std::vector<NR::Point> data;
2276 if (!just_delete) {
2277 data.push_back(sample_cursor->pos);
2278 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2279 //just delete at the end of an open path
2280 if (!sp->closed && curr == sp->last) {
2281 just_delete = true;
2282 break;
2283 }
2285 //sample points on the contiguous selected segment
2286 NR::Point *bez;
2287 bez = new NR::Point [4];
2288 bez[0] = curr->pos;
2289 bez[1] = curr->n.pos;
2290 bez[2] = curr->n.other->p.pos;
2291 bez[3] = curr->n.other->pos;
2292 for (int i=1; i<rate; i++) {
2293 gdouble t = i * period;
2294 NR::Point p = bezier_pt(3, bez, t);
2295 data.push_back(p);
2296 }
2297 data.push_back(curr->n.other->pos);
2299 sample_end = curr->n.other;
2300 //break if we've come full circle or hit the end of the selection
2301 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2302 break;
2303 }
2304 }
2305 }
2307 if (!just_delete) {
2308 //calculate the best fitting single segment and adjust the endpoints
2309 NR::Point *adata;
2310 adata = new NR::Point [data.size()];
2311 copy(data.begin(), data.end(), adata);
2313 NR::Point *bez;
2314 bez = new NR::Point [4];
2315 //would decreasing error create a better fitting approximation?
2316 gdouble error = 1.0;
2317 gint ret;
2318 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2320 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2321 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2322 //the resulting nodes behave as expected.
2323 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2324 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2325 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2326 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2328 //adjust endpoints
2329 sample_cursor->n.pos = bez[1];
2330 sample_end->p.pos = bez[2];
2331 }
2333 //destroy this contiguous selection
2334 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2335 Inkscape::NodePath::Node *temp = delete_cursor;
2336 if (delete_cursor->n.other == delete_cursor) {
2337 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2338 delete_cursor = NULL;
2339 } else {
2340 delete_cursor = delete_cursor->n.other;
2341 }
2342 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2343 sp_nodepath_node_destroy(temp);
2344 }
2346 sp_nodepath_update_handles(nodepath);
2348 if (!g_slist_find(nodepaths, nodepath))
2349 nodepaths = g_slist_prepend (nodepaths, nodepath);
2350 }
2352 for (GSList *i = nodepaths; i; i = i->next) {
2353 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2354 // different nodepaths will give us one undo event per nodepath
2355 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2357 // if the entire nodepath is removed, delete the selected object.
2358 if (nodepath->subpaths == NULL ||
2359 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2360 //at least 2
2361 sp_nodepath_get_node_count(nodepath) < 2) {
2362 SPDocument *document = sp_desktop_document (nodepath->desktop);
2363 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2364 //delete this nodepath's object, not the entire selection! (though at this time, this
2365 //does not matter)
2366 sp_selection_delete();
2367 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2368 _("Delete nodes"));
2369 } else {
2370 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2371 sp_nodepath_update_statusbar(nodepath);
2372 }
2373 }
2375 g_slist_free (nodepaths);
2376 }
2378 /**
2379 * Delete one or more selected nodes.
2380 */
2381 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2382 {
2383 if (!nodepath) return;
2384 if (!nodepath->selected) return;
2386 /** \todo fixme: do it the right way */
2387 while (nodepath->selected) {
2388 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2389 sp_nodepath_node_destroy(node);
2390 }
2393 //clean up the nodepath (such as for trivial subpaths)
2394 sp_nodepath_cleanup(nodepath);
2396 sp_nodepath_update_handles(nodepath);
2398 // if the entire nodepath is removed, delete the selected object.
2399 if (nodepath->subpaths == NULL ||
2400 sp_nodepath_get_node_count(nodepath) < 2) {
2401 SPDocument *document = sp_desktop_document (nodepath->desktop);
2402 sp_selection_delete();
2403 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2404 _("Delete nodes"));
2405 return;
2406 }
2408 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2410 sp_nodepath_update_statusbar(nodepath);
2411 }
2413 /**
2414 * Delete one or more segments between two selected nodes.
2415 * This is the code for 'split'.
2416 */
2417 void
2418 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2419 {
2420 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2421 Inkscape::NodePath::Node *curr, *next; //Iterators
2423 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2425 if (g_list_length(nodepath->selected) != 2) {
2426 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2427 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2428 return;
2429 }
2431 //Selected nodes, not inclusive
2432 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2433 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2435 if ( ( a==b) || //same node
2436 (a->subpath != b->subpath ) || //not the same path
2437 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2438 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2439 {
2440 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2441 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2442 return;
2443 }
2445 //###########################################
2446 //# BEGIN EDITS
2447 //###########################################
2448 //##################################
2449 //# CLOSED PATH
2450 //##################################
2451 if (a->subpath->closed) {
2454 gboolean reversed = FALSE;
2456 //Since we can go in a circle, we need to find the shorter distance.
2457 // a->b or b->a
2458 start = end = NULL;
2459 int distance = 0;
2460 int minDistance = 0;
2461 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2462 if (curr==b) {
2463 //printf("a to b:%d\n", distance);
2464 start = a;//go from a to b
2465 end = b;
2466 minDistance = distance;
2467 //printf("A to B :\n");
2468 break;
2469 }
2470 distance++;
2471 }
2473 //try again, the other direction
2474 distance = 0;
2475 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2476 if (curr==a) {
2477 //printf("b to a:%d\n", distance);
2478 if (distance < minDistance) {
2479 start = b; //we go from b to a
2480 end = a;
2481 reversed = TRUE;
2482 //printf("B to A\n");
2483 }
2484 break;
2485 }
2486 distance++;
2487 }
2490 //Copy everything from 'end' to 'start' to a new subpath
2491 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2492 for (curr=end ; curr ; curr=curr->n.other) {
2493 NRPathcode code = (NRPathcode) curr->code;
2494 if (curr == end)
2495 code = NR_MOVETO;
2496 sp_nodepath_node_new(t, NULL,
2497 (Inkscape::NodePath::NodeType)curr->type, code,
2498 &curr->p.pos, &curr->pos, &curr->n.pos);
2499 if (curr == start)
2500 break;
2501 }
2502 sp_nodepath_subpath_destroy(a->subpath);
2505 }
2509 //##################################
2510 //# OPEN PATH
2511 //##################################
2512 else {
2514 //We need to get the direction of the list between A and B
2515 //Can we walk from a to b?
2516 start = end = NULL;
2517 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2518 if (curr==b) {
2519 start = a; //did it! we go from a to b
2520 end = b;
2521 //printf("A to B\n");
2522 break;
2523 }
2524 }
2525 if (!start) {//didn't work? let's try the other direction
2526 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2527 if (curr==a) {
2528 start = b; //did it! we go from b to a
2529 end = a;
2530 //printf("B to A\n");
2531 break;
2532 }
2533 }
2534 }
2535 if (!start) {
2536 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2537 _("Cannot find path between nodes."));
2538 return;
2539 }
2543 //Copy everything after 'end' to a new subpath
2544 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2545 for (curr=end ; curr ; curr=curr->n.other) {
2546 NRPathcode code = (NRPathcode) curr->code;
2547 if (curr == end)
2548 code = NR_MOVETO;
2549 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2550 &curr->p.pos, &curr->pos, &curr->n.pos);
2551 }
2553 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2554 for (curr = start->n.other ; curr ; curr=next) {
2555 next = curr->n.other;
2556 sp_nodepath_node_destroy(curr);
2557 }
2559 }
2560 //###########################################
2561 //# END EDITS
2562 //###########################################
2564 //clean up the nodepath (such as for trivial subpaths)
2565 sp_nodepath_cleanup(nodepath);
2567 sp_nodepath_update_handles(nodepath);
2569 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2571 sp_nodepath_update_statusbar(nodepath);
2572 }
2574 /**
2575 * Call sp_nodepath_set_line() for all selected segments.
2576 */
2577 void
2578 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2579 {
2580 if (nodepath == NULL) return;
2582 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2583 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2584 g_assert(n->selected);
2585 if (n->p.other && n->p.other->selected) {
2586 sp_nodepath_set_line_type(n, code);
2587 }
2588 }
2590 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2591 }
2593 /**
2594 * Call sp_nodepath_convert_node_type() for all selected nodes.
2595 */
2596 void
2597 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2598 {
2599 if (nodepath == NULL) return;
2601 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2603 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2604 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2605 }
2607 sp_nodepath_update_repr(nodepath, _("Change node type"));
2608 }
2610 /**
2611 * Change select status of node, update its own and neighbour handles.
2612 */
2613 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2614 {
2615 node->selected = selected;
2617 if (selected) {
2618 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2619 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2620 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2621 sp_knot_update_ctrl(node->knot);
2622 } else {
2623 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2624 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2625 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2626 sp_knot_update_ctrl(node->knot);
2627 }
2629 sp_node_update_handles(node);
2630 if (node->n.other) sp_node_update_handles(node->n.other);
2631 if (node->p.other) sp_node_update_handles(node->p.other);
2632 }
2634 /**
2635 \brief Select a node
2636 \param node The node to select
2637 \param incremental If true, add to selection, otherwise deselect others
2638 \param override If true, always select this node, otherwise toggle selected status
2639 */
2640 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2641 {
2642 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2644 if (incremental) {
2645 if (override) {
2646 if (!g_list_find(nodepath->selected, node)) {
2647 nodepath->selected = g_list_prepend(nodepath->selected, node);
2648 }
2649 sp_node_set_selected(node, TRUE);
2650 } else { // toggle
2651 if (node->selected) {
2652 g_assert(g_list_find(nodepath->selected, node));
2653 nodepath->selected = g_list_remove(nodepath->selected, node);
2654 } else {
2655 g_assert(!g_list_find(nodepath->selected, node));
2656 nodepath->selected = g_list_prepend(nodepath->selected, node);
2657 }
2658 sp_node_set_selected(node, !node->selected);
2659 }
2660 } else {
2661 sp_nodepath_deselect(nodepath);
2662 nodepath->selected = g_list_prepend(nodepath->selected, node);
2663 sp_node_set_selected(node, TRUE);
2664 }
2666 sp_nodepath_update_statusbar(nodepath);
2667 }
2670 /**
2671 \brief Deselect all nodes in the nodepath
2672 */
2673 void
2674 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2675 {
2676 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2678 while (nodepath->selected) {
2679 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2680 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2681 }
2682 sp_nodepath_update_statusbar(nodepath);
2683 }
2685 /**
2686 \brief Select or invert selection of all nodes in the nodepath
2687 */
2688 void
2689 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2690 {
2691 if (!nodepath) return;
2693 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2694 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2695 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2696 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2697 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2698 }
2699 }
2700 }
2702 /**
2703 * If nothing selected, does the same as sp_nodepath_select_all();
2704 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2705 * (i.e., similar to "select all in layer", with the "selected" subpaths
2706 * being treated as "layers" in the path).
2707 */
2708 void
2709 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2710 {
2711 if (!nodepath) return;
2713 if (g_list_length (nodepath->selected) == 0) {
2714 sp_nodepath_select_all (nodepath, invert);
2715 return;
2716 }
2718 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2719 GSList *subpaths = NULL;
2721 for (GList *l = copy; l != NULL; l = l->next) {
2722 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2723 Inkscape::NodePath::SubPath *subpath = n->subpath;
2724 if (!g_slist_find (subpaths, subpath))
2725 subpaths = g_slist_prepend (subpaths, subpath);
2726 }
2728 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2729 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2730 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2731 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2732 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2733 }
2734 }
2736 g_slist_free (subpaths);
2737 g_list_free (copy);
2738 }
2740 /**
2741 * \brief Select the node after the last selected; if none is selected,
2742 * select the first within path.
2743 */
2744 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2745 {
2746 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2748 Inkscape::NodePath::Node *last = NULL;
2749 if (nodepath->selected) {
2750 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2751 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2752 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2753 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2754 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2755 if (node->selected) {
2756 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2757 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2758 if (spl->next) { // there's a next subpath
2759 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2760 last = subpath_next->first;
2761 } else if (spl->prev) { // there's a previous subpath
2762 last = NULL; // to be set later to the first node of first subpath
2763 } else {
2764 last = node->n.other;
2765 }
2766 } else {
2767 last = node->n.other;
2768 }
2769 } else {
2770 if (node->n.other) {
2771 last = node->n.other;
2772 } else {
2773 if (spl->next) { // there's a next subpath
2774 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2775 last = subpath_next->first;
2776 } else if (spl->prev) { // there's a previous subpath
2777 last = NULL; // to be set later to the first node of first subpath
2778 } else {
2779 last = (Inkscape::NodePath::Node *) subpath->first;
2780 }
2781 }
2782 }
2783 }
2784 }
2785 }
2786 sp_nodepath_deselect(nodepath);
2787 }
2789 if (last) { // there's at least one more node after selected
2790 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2791 } else { // no more nodes, select the first one in first subpath
2792 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2793 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2794 }
2795 }
2797 /**
2798 * \brief Select the node before the first selected; if none is selected,
2799 * select the last within path
2800 */
2801 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2802 {
2803 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2805 Inkscape::NodePath::Node *last = NULL;
2806 if (nodepath->selected) {
2807 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2808 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2809 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2810 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2811 if (node->selected) {
2812 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2813 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2814 if (spl->prev) { // there's a prev subpath
2815 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2816 last = subpath_prev->last;
2817 } else if (spl->next) { // there's a next subpath
2818 last = NULL; // to be set later to the last node of last subpath
2819 } else {
2820 last = node->p.other;
2821 }
2822 } else {
2823 last = node->p.other;
2824 }
2825 } else {
2826 if (node->p.other) {
2827 last = node->p.other;
2828 } else {
2829 if (spl->prev) { // there's a prev subpath
2830 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2831 last = subpath_prev->last;
2832 } else if (spl->next) { // there's a next subpath
2833 last = NULL; // to be set later to the last node of last subpath
2834 } else {
2835 last = (Inkscape::NodePath::Node *) subpath->last;
2836 }
2837 }
2838 }
2839 }
2840 }
2841 }
2842 sp_nodepath_deselect(nodepath);
2843 }
2845 if (last) { // there's at least one more node before selected
2846 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2847 } else { // no more nodes, select the last one in last subpath
2848 GList *spl = g_list_last(nodepath->subpaths);
2849 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2850 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2851 }
2852 }
2854 /**
2855 * \brief Select all nodes that are within the rectangle.
2856 */
2857 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2858 {
2859 if (!incremental) {
2860 sp_nodepath_deselect(nodepath);
2861 }
2863 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2864 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2865 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2866 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2868 if (b.contains(node->pos)) {
2869 sp_nodepath_node_select(node, TRUE, TRUE);
2870 }
2871 }
2872 }
2873 }
2876 void
2877 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2878 {
2879 g_assert (n);
2880 g_assert (nodepath);
2881 g_assert (n->subpath->nodepath == nodepath);
2883 if (g_list_length (nodepath->selected) == 0) {
2884 if (grow > 0) {
2885 sp_nodepath_node_select(n, TRUE, TRUE);
2886 }
2887 return;
2888 }
2890 if (g_list_length (nodepath->selected) == 1) {
2891 if (grow < 0) {
2892 sp_nodepath_deselect (nodepath);
2893 return;
2894 }
2895 }
2897 double n_sel_range = 0, p_sel_range = 0;
2898 Inkscape::NodePath::Node *farthest_n_node = n;
2899 Inkscape::NodePath::Node *farthest_p_node = n;
2901 // Calculate ranges
2902 {
2903 double n_range = 0, p_range = 0;
2904 bool n_going = true, p_going = true;
2905 Inkscape::NodePath::Node *n_node = n;
2906 Inkscape::NodePath::Node *p_node = n;
2907 do {
2908 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2909 if (n_node && n_going)
2910 n_node = n_node->n.other;
2911 if (n_node == NULL) {
2912 n_going = false;
2913 } else {
2914 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2915 if (n_node->selected) {
2916 n_sel_range = n_range;
2917 farthest_n_node = n_node;
2918 }
2919 if (n_node == p_node) {
2920 n_going = false;
2921 p_going = false;
2922 }
2923 }
2924 if (p_node && p_going)
2925 p_node = p_node->p.other;
2926 if (p_node == NULL) {
2927 p_going = false;
2928 } else {
2929 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2930 if (p_node->selected) {
2931 p_sel_range = p_range;
2932 farthest_p_node = p_node;
2933 }
2934 if (p_node == n_node) {
2935 n_going = false;
2936 p_going = false;
2937 }
2938 }
2939 } while (n_going || p_going);
2940 }
2942 if (grow > 0) {
2943 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2944 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2945 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2946 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2947 }
2948 } else {
2949 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2950 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2951 } else if (farthest_p_node && farthest_p_node->selected) {
2952 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2953 }
2954 }
2955 }
2957 void
2958 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2959 {
2960 g_assert (n);
2961 g_assert (nodepath);
2962 g_assert (n->subpath->nodepath == nodepath);
2964 if (g_list_length (nodepath->selected) == 0) {
2965 if (grow > 0) {
2966 sp_nodepath_node_select(n, TRUE, TRUE);
2967 }
2968 return;
2969 }
2971 if (g_list_length (nodepath->selected) == 1) {
2972 if (grow < 0) {
2973 sp_nodepath_deselect (nodepath);
2974 return;
2975 }
2976 }
2978 Inkscape::NodePath::Node *farthest_selected = NULL;
2979 double farthest_dist = 0;
2981 Inkscape::NodePath::Node *closest_unselected = NULL;
2982 double closest_dist = NR_HUGE;
2984 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2985 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2986 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2987 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2988 if (node == n)
2989 continue;
2990 if (node->selected) {
2991 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2992 farthest_dist = NR::L2(node->pos - n->pos);
2993 farthest_selected = node;
2994 }
2995 } else {
2996 if (NR::L2(node->pos - n->pos) < closest_dist) {
2997 closest_dist = NR::L2(node->pos - n->pos);
2998 closest_unselected = node;
2999 }
3000 }
3001 }
3002 }
3004 if (grow > 0) {
3005 if (closest_unselected) {
3006 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3007 }
3008 } else {
3009 if (farthest_selected) {
3010 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3011 }
3012 }
3013 }
3016 /**
3017 \brief Saves all nodes' and handles' current positions in their origin members
3018 */
3019 void
3020 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3021 {
3022 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3023 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3024 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3025 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3026 n->origin = n->pos;
3027 n->p.origin = n->p.pos;
3028 n->n.origin = n->n.pos;
3029 }
3030 }
3031 }
3033 /**
3034 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3035 */
3036 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3037 {
3038 if (!nodepath->selected) {
3039 return NULL;
3040 }
3042 GList *r = NULL;
3043 guint i = 0;
3044 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3045 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3046 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3047 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3048 i++;
3049 if (node->selected) {
3050 r = g_list_append(r, GINT_TO_POINTER(i));
3051 }
3052 }
3053 }
3054 return r;
3055 }
3057 /**
3058 \brief Restores selection by selecting nodes whose positions are in the list
3059 */
3060 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3061 {
3062 sp_nodepath_deselect(nodepath);
3064 guint i = 0;
3065 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3066 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3067 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3068 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3069 i++;
3070 if (g_list_find(r, GINT_TO_POINTER(i))) {
3071 sp_nodepath_node_select(node, TRUE, TRUE);
3072 }
3073 }
3074 }
3075 }
3078 /**
3079 \brief Adjusts handle according to node type and line code.
3080 */
3081 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3082 {
3083 g_assert(node);
3085 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3086 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3088 // nothing to do if we are an end node
3089 if (me->other == NULL) return;
3090 if (other->other == NULL) return;
3092 // nothing to do if we are a cusp node
3093 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3095 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3096 NRPathcode mecode;
3097 if (which_adjust == 1) {
3098 mecode = (NRPathcode)me->other->code;
3099 } else {
3100 mecode = (NRPathcode)node->code;
3101 }
3102 if (mecode == NR_LINETO) return;
3104 if (sp_node_side_is_line(node, other)) {
3105 // other is a line, and we are either smooth or symm
3106 Inkscape::NodePath::Node *othernode = other->other;
3107 double len = NR::L2(me->pos - node->pos);
3108 NR::Point delta = node->pos - othernode->pos;
3109 double linelen = NR::L2(delta);
3110 if (linelen < 1e-18)
3111 return;
3112 me->pos = node->pos + (len / linelen)*delta;
3113 return;
3114 }
3116 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3117 // symmetrize
3118 me->pos = 2 * node->pos - other->pos;
3119 return;
3120 } else {
3121 // smoothify
3122 double len = NR::L2(me->pos - node->pos);
3123 NR::Point delta = other->pos - node->pos;
3124 double otherlen = NR::L2(delta);
3125 if (otherlen < 1e-18) return;
3126 me->pos = node->pos - (len / otherlen) * delta;
3127 }
3128 }
3130 /**
3131 \brief Adjusts both handles according to node type and line code
3132 */
3133 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3134 {
3135 g_assert(node);
3137 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3139 /* we are either smooth or symm */
3141 if (node->p.other == NULL) return;
3142 if (node->n.other == NULL) return;
3144 if (sp_node_side_is_line(node, &node->p)) {
3145 sp_node_adjust_handle(node, 1);
3146 return;
3147 }
3149 if (sp_node_side_is_line(node, &node->n)) {
3150 sp_node_adjust_handle(node, -1);
3151 return;
3152 }
3154 /* both are curves */
3155 NR::Point const delta( node->n.pos - node->p.pos );
3157 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3158 node->p.pos = node->pos - delta / 2;
3159 node->n.pos = node->pos + delta / 2;
3160 return;
3161 }
3163 /* We are smooth */
3164 double plen = NR::L2(node->p.pos - node->pos);
3165 if (plen < 1e-18) return;
3166 double nlen = NR::L2(node->n.pos - node->pos);
3167 if (nlen < 1e-18) return;
3168 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3169 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3170 }
3172 /**
3173 * Node event callback.
3174 */
3175 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3176 {
3177 gboolean ret = FALSE;
3178 switch (event->type) {
3179 case GDK_ENTER_NOTIFY:
3180 Inkscape::NodePath::Path::active_node = n;
3181 break;
3182 case GDK_LEAVE_NOTIFY:
3183 Inkscape::NodePath::Path::active_node = NULL;
3184 break;
3185 case GDK_SCROLL:
3186 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3187 switch (event->scroll.direction) {
3188 case GDK_SCROLL_UP:
3189 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3190 break;
3191 case GDK_SCROLL_DOWN:
3192 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3193 break;
3194 default:
3195 break;
3196 }
3197 ret = TRUE;
3198 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3199 switch (event->scroll.direction) {
3200 case GDK_SCROLL_UP:
3201 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3202 break;
3203 case GDK_SCROLL_DOWN:
3204 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3205 break;
3206 default:
3207 break;
3208 }
3209 ret = TRUE;
3210 }
3211 break;
3212 case GDK_KEY_PRESS:
3213 switch (get_group0_keyval (&event->key)) {
3214 case GDK_space:
3215 if (event->key.state & GDK_BUTTON1_MASK) {
3216 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3217 stamp_repr(nodepath);
3218 ret = TRUE;
3219 }
3220 break;
3221 case GDK_Page_Up:
3222 if (event->key.state & GDK_CONTROL_MASK) {
3223 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3224 } else {
3225 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3226 }
3227 break;
3228 case GDK_Page_Down:
3229 if (event->key.state & GDK_CONTROL_MASK) {
3230 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3231 } else {
3232 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3233 }
3234 break;
3235 default:
3236 break;
3237 }
3238 break;
3239 default:
3240 break;
3241 }
3243 return ret;
3244 }
3246 /**
3247 * Handle keypress on node; directly called.
3248 */
3249 gboolean node_key(GdkEvent *event)
3250 {
3251 Inkscape::NodePath::Path *np;
3253 // there is no way to verify nodes so set active_node to nil when deleting!!
3254 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3256 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3257 gint ret = FALSE;
3258 switch (get_group0_keyval (&event->key)) {
3259 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3260 case GDK_BackSpace:
3261 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3262 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3263 sp_nodepath_update_repr(np, _("Delete node"));
3264 Inkscape::NodePath::Path::active_node = NULL;
3265 ret = TRUE;
3266 break;
3267 case GDK_c:
3268 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3269 ret = TRUE;
3270 break;
3271 case GDK_s:
3272 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3273 ret = TRUE;
3274 break;
3275 case GDK_y:
3276 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3277 ret = TRUE;
3278 break;
3279 case GDK_b:
3280 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3281 ret = TRUE;
3282 break;
3283 }
3284 return ret;
3285 }
3286 return FALSE;
3287 }
3289 /**
3290 * Mouseclick on node callback.
3291 */
3292 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3293 {
3294 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3296 if (state & GDK_CONTROL_MASK) {
3297 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3299 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3300 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3301 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3302 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3303 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3304 } else {
3305 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3306 }
3307 sp_nodepath_update_repr(nodepath, _("Change node type"));
3308 sp_nodepath_update_statusbar(nodepath);
3310 } else { //ctrl+alt+click: delete node
3311 GList *node_to_delete = NULL;
3312 node_to_delete = g_list_append(node_to_delete, n);
3313 sp_node_delete_preserve(node_to_delete);
3314 }
3316 } else {
3317 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3318 }
3319 }
3321 /**
3322 * Mouse grabbed node callback.
3323 */
3324 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3325 {
3326 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3328 if (!n->selected) {
3329 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3330 }
3332 n->is_dragging = true;
3333 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3335 sp_nodepath_remember_origins (n->subpath->nodepath);
3336 }
3338 /**
3339 * Mouse ungrabbed node callback.
3340 */
3341 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3342 {
3343 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3345 n->dragging_out = NULL;
3346 n->is_dragging = false;
3347 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3349 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3350 }
3352 /**
3353 * The point on a line, given by its angle, closest to the given point.
3354 * \param p A point.
3355 * \param a Angle of the line; it is assumed to go through coordinate origin.
3356 * \param closest Pointer to the point struct where the result is stored.
3357 * \todo FIXME: use dot product perhaps?
3358 */
3359 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3360 {
3361 if (a == HUGE_VAL) { // vertical
3362 *closest = NR::Point(0, (*p)[NR::Y]);
3363 } else {
3364 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3365 (*closest)[NR::Y] = a * (*closest)[NR::X];
3366 }
3367 }
3369 /**
3370 * Distance from the point to a line given by its angle.
3371 * \param p A point.
3372 * \param a Angle of the line; it is assumed to go through coordinate origin.
3373 */
3374 static double point_line_distance(NR::Point *p, double a)
3375 {
3376 NR::Point c;
3377 point_line_closest(p, a, &c);
3378 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]));
3379 }
3381 /**
3382 * Callback for node "request" signal.
3383 * \todo fixme: This goes to "moved" event? (lauris)
3384 */
3385 static gboolean
3386 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3387 {
3388 double yn, xn, yp, xp;
3389 double an, ap, na, pa;
3390 double d_an, d_ap, d_na, d_pa;
3391 gboolean collinear = FALSE;
3392 NR::Point c;
3393 NR::Point pr;
3395 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3397 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3399 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3400 if ( (!n->subpath->nodepath->straight_path) &&
3401 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3402 || n->dragging_out ) )
3403 {
3404 NR::Point mouse = (*p);
3406 if (!n->dragging_out) {
3407 // This is the first drag-out event; find out which handle to drag out
3408 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3409 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3411 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3412 return FALSE;
3414 Inkscape::NodePath::NodeSide *opposite;
3415 if (appr_p > appr_n) { // closer to p
3416 n->dragging_out = &n->p;
3417 opposite = &n->n;
3418 n->code = NR_CURVETO;
3419 } else if (appr_p < appr_n) { // closer to n
3420 n->dragging_out = &n->n;
3421 opposite = &n->p;
3422 n->n.other->code = NR_CURVETO;
3423 } else { // p and n nodes are the same
3424 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3425 n->dragging_out = &n->p;
3426 opposite = &n->n;
3427 n->code = NR_CURVETO;
3428 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3429 n->dragging_out = &n->n;
3430 opposite = &n->p;
3431 n->n.other->code = NR_CURVETO;
3432 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3433 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);
3434 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);
3435 if (appr_other_p > appr_other_n) { // closer to other's p handle
3436 n->dragging_out = &n->n;
3437 opposite = &n->p;
3438 n->n.other->code = NR_CURVETO;
3439 } else { // closer to other's n handle
3440 n->dragging_out = &n->p;
3441 opposite = &n->n;
3442 n->code = NR_CURVETO;
3443 }
3444 }
3445 }
3447 // if there's another handle, make sure the one we drag out starts parallel to it
3448 if (opposite->pos != n->pos) {
3449 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3450 }
3452 // knots might not be created yet!
3453 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3454 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3455 }
3457 // pass this on to the handle-moved callback
3458 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3459 sp_node_update_handles(n);
3460 return TRUE;
3461 }
3463 if (state & GDK_CONTROL_MASK) { // constrained motion
3465 // calculate relative distances of handles
3466 // n handle:
3467 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3468 xn = n->n.pos[NR::X] - n->pos[NR::X];
3469 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3470 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3471 if (n->n.other) { // if there is the next point
3472 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3473 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3474 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3475 }
3476 }
3477 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3478 if (yn < 0) { xn = -xn; yn = -yn; }
3480 // p handle:
3481 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3482 xp = n->p.pos[NR::X] - n->pos[NR::X];
3483 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3484 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3485 if (n->p.other) {
3486 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3487 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3488 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3489 }
3490 }
3491 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3492 if (yp < 0) { xp = -xp; yp = -yp; }
3494 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3495 // sliding on handles, only if at least one of the handles is non-vertical
3496 // (otherwise it's the same as ctrl+drag anyway)
3498 // calculate angles of the handles
3499 if (xn == 0) {
3500 if (yn == 0) { // no handle, consider it the continuation of the other one
3501 an = 0;
3502 collinear = TRUE;
3503 }
3504 else an = 0; // vertical; set the angle to horizontal
3505 } else an = yn/xn;
3507 if (xp == 0) {
3508 if (yp == 0) { // no handle, consider it the continuation of the other one
3509 ap = an;
3510 }
3511 else ap = 0; // vertical; set the angle to horizontal
3512 } else ap = yp/xp;
3514 if (collinear) an = ap;
3516 // angles of the perpendiculars; HUGE_VAL means vertical
3517 if (an == 0) na = HUGE_VAL; else na = -1/an;
3518 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3520 // mouse point relative to the node's original pos
3521 pr = (*p) - n->origin;
3523 // distances to the four lines (two handles and two perpendiculars)
3524 d_an = point_line_distance(&pr, an);
3525 d_na = point_line_distance(&pr, na);
3526 d_ap = point_line_distance(&pr, ap);
3527 d_pa = point_line_distance(&pr, pa);
3529 // find out which line is the closest, save its closest point in c
3530 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3531 point_line_closest(&pr, an, &c);
3532 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3533 point_line_closest(&pr, ap, &c);
3534 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3535 point_line_closest(&pr, na, &c);
3536 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3537 point_line_closest(&pr, pa, &c);
3538 }
3540 // move the node to the closest point
3541 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3542 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3543 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3545 } else { // constraining to hor/vert
3547 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3548 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3549 } else { // snap to vert
3550 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3551 }
3552 }
3553 } else { // move freely
3554 if (n->is_dragging) {
3555 if (state & GDK_MOD1_MASK) { // sculpt
3556 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3557 } else {
3558 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3559 (*p)[NR::X] - n->pos[NR::X],
3560 (*p)[NR::Y] - n->pos[NR::Y],
3561 (state & GDK_SHIFT_MASK) == 0);
3562 }
3563 }
3564 }
3566 n->subpath->nodepath->desktop->scroll_to_point(p);
3568 return TRUE;
3569 }
3571 /**
3572 * Node handle clicked callback.
3573 */
3574 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3575 {
3576 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3578 if (state & GDK_CONTROL_MASK) { // "delete" handle
3579 if (n->p.knot == knot) {
3580 n->p.pos = n->pos;
3581 } else if (n->n.knot == knot) {
3582 n->n.pos = n->pos;
3583 }
3584 sp_node_update_handles(n);
3585 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3586 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3587 sp_nodepath_update_statusbar(nodepath);
3589 } else { // just select or add to selection, depending in Shift
3590 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3591 }
3592 }
3594 /**
3595 * Node handle grabbed callback.
3596 */
3597 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3598 {
3599 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3601 if (!n->selected) {
3602 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3603 }
3605 // remember the origin point of the handle
3606 if (n->p.knot == knot) {
3607 n->p.origin_radial = n->p.pos - n->pos;
3608 } else if (n->n.knot == knot) {
3609 n->n.origin_radial = n->n.pos - n->pos;
3610 } else {
3611 g_assert_not_reached();
3612 }
3614 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3615 }
3617 /**
3618 * Node handle ungrabbed callback.
3619 */
3620 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3621 {
3622 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3624 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3625 if (n->p.knot == knot) {
3626 n->p.origin_radial.a = 0;
3627 sp_knot_set_position(knot, &n->p.pos, state);
3628 } else if (n->n.knot == knot) {
3629 n->n.origin_radial.a = 0;
3630 sp_knot_set_position(knot, &n->n.pos, state);
3631 } else {
3632 g_assert_not_reached();
3633 }
3635 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3636 }
3638 /**
3639 * Node handle "request" signal callback.
3640 */
3641 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3642 {
3643 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3645 Inkscape::NodePath::NodeSide *me, *opposite;
3646 gint which;
3647 if (n->p.knot == knot) {
3648 me = &n->p;
3649 opposite = &n->n;
3650 which = -1;
3651 } else if (n->n.knot == knot) {
3652 me = &n->n;
3653 opposite = &n->p;
3654 which = 1;
3655 } else {
3656 me = opposite = NULL;
3657 which = 0;
3658 g_assert_not_reached();
3659 }
3661 SPDesktop *desktop = n->subpath->nodepath->desktop;
3662 SnapManager &m = desktop->namedview->snap_manager;
3663 m.setup(desktop, n->subpath->nodepath->item);
3664 Inkscape::SnappedPoint s ;
3666 Inkscape::NodePath::Node *othernode = opposite->other;
3667 if (othernode) {
3668 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3669 /* We are smooth node adjacent with line */
3670 NR::Point const delta = *p - n->pos;
3671 NR::Coord const len = NR::L2(delta);
3672 Inkscape::NodePath::Node *othernode = opposite->other;
3673 NR::Point const ndelta = n->pos - othernode->pos;
3674 NR::Coord const linelen = NR::L2(ndelta);
3675 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3676 NR::Coord const scal = dot(delta, ndelta) / linelen;
3677 (*p) = n->pos + (scal / linelen) * ndelta;
3678 }
3679 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3680 } else {
3681 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3682 }
3683 }
3685 *p = s.getPoint();
3687 sp_node_adjust_handle(n, -which);
3689 return FALSE;
3690 }
3692 /**
3693 * Node handle moved callback.
3694 */
3695 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3696 {
3697 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3699 Inkscape::NodePath::NodeSide *me;
3700 Inkscape::NodePath::NodeSide *other;
3701 if (n->p.knot == knot) {
3702 me = &n->p;
3703 other = &n->n;
3704 } else if (n->n.knot == knot) {
3705 me = &n->n;
3706 other = &n->p;
3707 } else {
3708 me = NULL;
3709 other = NULL;
3710 g_assert_not_reached();
3711 }
3713 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3714 Radial rme(me->pos - n->pos);
3715 Radial rother(other->pos - n->pos);
3716 Radial rnew(*p - n->pos);
3718 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3719 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3720 /* 0 interpreted as "no snapping". */
3722 // 1. Snap to the closest PI/snaps angle, starting from zero.
3723 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3725 // 2. Snap to the original angle, its opposite and perpendiculars
3726 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3727 /* The closest PI/2 angle, starting from original angle */
3728 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3730 // Snap to the closest.
3731 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3732 ? a_snapped
3733 : a_ortho );
3734 }
3736 // 3. Snap to the angle of the opposite line, if any
3737 Inkscape::NodePath::Node *othernode = other->other;
3738 if (othernode) {
3739 NR::Point other_to_snap(0,0);
3740 if (sp_node_side_is_line(n, other)) {
3741 other_to_snap = othernode->pos - n->pos;
3742 } else {
3743 other_to_snap = other->pos - n->pos;
3744 }
3745 if (NR::L2(other_to_snap) > 1e-3) {
3746 Radial rother_to_snap(other_to_snap);
3747 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3748 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3750 // Snap to the closest.
3751 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3752 ? a_snapped
3753 : a_oppo );
3754 }
3755 }
3757 rnew.a = a_snapped;
3758 }
3760 if (state & GDK_MOD1_MASK) {
3761 // lock handle length
3762 rnew.r = me->origin_radial.r;
3763 }
3765 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3766 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3767 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3768 rother.a += rnew.a - rme.a;
3769 other->pos = NR::Point(rother) + n->pos;
3770 if (other->knot) {
3771 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3772 sp_knot_moveto(other->knot, &other->pos);
3773 }
3774 }
3776 me->pos = NR::Point(rnew) + n->pos;
3777 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3779 // move knot, but without emitting the signal:
3780 // we cannot emit a "moved" signal because we're now processing it
3781 sp_knot_moveto(me->knot, &(me->pos));
3783 update_object(n->subpath->nodepath);
3785 /* status text */
3786 SPDesktop *desktop = n->subpath->nodepath->desktop;
3787 if (!desktop) return;
3788 SPEventContext *ec = desktop->event_context;
3789 if (!ec) return;
3790 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3791 if (!mc) return;
3793 double degrees = 180 / M_PI * rnew.a;
3794 if (degrees > 180) degrees -= 360;
3795 if (degrees < -180) degrees += 360;
3796 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3797 degrees = angle_to_compass (degrees);
3799 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3801 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3802 _("<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);
3804 g_string_free(length, TRUE);
3805 }
3807 /**
3808 * Node handle event callback.
3809 */
3810 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3811 {
3812 gboolean ret = FALSE;
3813 switch (event->type) {
3814 case GDK_KEY_PRESS:
3815 switch (get_group0_keyval (&event->key)) {
3816 case GDK_space:
3817 if (event->key.state & GDK_BUTTON1_MASK) {
3818 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3819 stamp_repr(nodepath);
3820 ret = TRUE;
3821 }
3822 break;
3823 default:
3824 break;
3825 }
3826 break;
3827 case GDK_ENTER_NOTIFY:
3828 // we use an experimentally determined threshold that seems to work fine
3829 if (NR::L2(n->pos - knot->pos) < 0.75)
3830 Inkscape::NodePath::Path::active_node = n;
3831 break;
3832 case GDK_LEAVE_NOTIFY:
3833 // we use an experimentally determined threshold that seems to work fine
3834 if (NR::L2(n->pos - knot->pos) < 0.75)
3835 Inkscape::NodePath::Path::active_node = NULL;
3836 break;
3837 default:
3838 break;
3839 }
3841 return ret;
3842 }
3844 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3845 Radial &rme, Radial &rother, gboolean const both)
3846 {
3847 rme.a += angle;
3848 if ( both
3849 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3850 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3851 {
3852 rother.a += angle;
3853 }
3854 }
3856 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3857 Radial &rme, Radial &rother, gboolean const both)
3858 {
3859 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3861 gdouble r;
3862 if ( both
3863 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3864 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3865 {
3866 r = MAX(rme.r, rother.r);
3867 } else {
3868 r = rme.r;
3869 }
3871 gdouble const weird_angle = atan2(norm_angle, r);
3872 /* Bulia says norm_angle is just the visible distance that the
3873 * object's end must travel on the screen. Left as 'angle' for want of
3874 * a better name.*/
3876 rme.a += weird_angle;
3877 if ( both
3878 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3879 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3880 {
3881 rother.a += weird_angle;
3882 }
3883 }
3885 /**
3886 * Rotate one node.
3887 */
3888 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3889 {
3890 Inkscape::NodePath::NodeSide *me, *other;
3891 bool both = false;
3893 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3894 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3896 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3897 me = &(n->p);
3898 other = &(n->n);
3899 } else if (!n->p.other) {
3900 me = &(n->n);
3901 other = &(n->p);
3902 } else {
3903 if (which > 0) { // right handle
3904 if (xn > xp) {
3905 me = &(n->n);
3906 other = &(n->p);
3907 } else {
3908 me = &(n->p);
3909 other = &(n->n);
3910 }
3911 } else if (which < 0){ // left handle
3912 if (xn <= xp) {
3913 me = &(n->n);
3914 other = &(n->p);
3915 } else {
3916 me = &(n->p);
3917 other = &(n->n);
3918 }
3919 } else { // both handles
3920 me = &(n->n);
3921 other = &(n->p);
3922 both = true;
3923 }
3924 }
3926 Radial rme(me->pos - n->pos);
3927 Radial rother(other->pos - n->pos);
3929 if (screen) {
3930 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3931 } else {
3932 node_rotate_one_internal (*n, angle, rme, rother, both);
3933 }
3935 me->pos = n->pos + NR::Point(rme);
3937 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3938 other->pos = n->pos + NR::Point(rother);
3939 }
3941 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3942 // so here we just move all the knots without emitting move signals, for speed
3943 sp_node_update_handles(n, false);
3944 }
3946 /**
3947 * Rotate selected nodes.
3948 */
3949 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3950 {
3951 if (!nodepath || !nodepath->selected) return;
3953 if (g_list_length(nodepath->selected) == 1) {
3954 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3955 node_rotate_one (n, angle, which, screen);
3956 } else {
3957 // rotate as an object:
3959 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3960 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3961 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3962 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3963 box.expandTo (n->pos); // contain all selected nodes
3964 }
3966 gdouble rot;
3967 if (screen) {
3968 gdouble const zoom = nodepath->desktop->current_zoom();
3969 gdouble const zmove = angle / zoom;
3970 gdouble const r = NR::L2(box.max() - box.midpoint());
3971 rot = atan2(zmove, r);
3972 } else {
3973 rot = angle;
3974 }
3976 NR::Point rot_center;
3977 if (Inkscape::NodePath::Path::active_node == NULL)
3978 rot_center = box.midpoint();
3979 else
3980 rot_center = Inkscape::NodePath::Path::active_node->pos;
3982 NR::Matrix t =
3983 NR::Matrix (NR::translate(-rot_center)) *
3984 NR::Matrix (NR::rotate(rot)) *
3985 NR::Matrix (NR::translate(rot_center));
3987 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3988 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3989 n->pos *= t;
3990 n->n.pos *= t;
3991 n->p.pos *= t;
3992 sp_node_update_handles(n, false);
3993 }
3994 }
3996 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3997 }
3999 /**
4000 * Scale one node.
4001 */
4002 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4003 {
4004 bool both = false;
4005 Inkscape::NodePath::NodeSide *me, *other;
4007 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4008 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4010 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4011 me = &(n->p);
4012 other = &(n->n);
4013 n->code = NR_CURVETO;
4014 } else if (!n->p.other) {
4015 me = &(n->n);
4016 other = &(n->p);
4017 if (n->n.other)
4018 n->n.other->code = NR_CURVETO;
4019 } else {
4020 if (which > 0) { // right handle
4021 if (xn > xp) {
4022 me = &(n->n);
4023 other = &(n->p);
4024 if (n->n.other)
4025 n->n.other->code = NR_CURVETO;
4026 } else {
4027 me = &(n->p);
4028 other = &(n->n);
4029 n->code = NR_CURVETO;
4030 }
4031 } else if (which < 0){ // left handle
4032 if (xn <= xp) {
4033 me = &(n->n);
4034 other = &(n->p);
4035 if (n->n.other)
4036 n->n.other->code = NR_CURVETO;
4037 } else {
4038 me = &(n->p);
4039 other = &(n->n);
4040 n->code = NR_CURVETO;
4041 }
4042 } else { // both handles
4043 me = &(n->n);
4044 other = &(n->p);
4045 both = true;
4046 n->code = NR_CURVETO;
4047 if (n->n.other)
4048 n->n.other->code = NR_CURVETO;
4049 }
4050 }
4052 Radial rme(me->pos - n->pos);
4053 Radial rother(other->pos - n->pos);
4055 rme.r += grow;
4056 if (rme.r < 0) rme.r = 0;
4057 if (rme.a == HUGE_VAL) {
4058 if (me->other) { // if direction is unknown, initialize it towards the next node
4059 Radial rme_next(me->other->pos - n->pos);
4060 rme.a = rme_next.a;
4061 } else { // if there's no next, initialize to 0
4062 rme.a = 0;
4063 }
4064 }
4065 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4066 rother.r += grow;
4067 if (rother.r < 0) rother.r = 0;
4068 if (rother.a == HUGE_VAL) {
4069 rother.a = rme.a + M_PI;
4070 }
4071 }
4073 me->pos = n->pos + NR::Point(rme);
4075 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4076 other->pos = n->pos + NR::Point(rother);
4077 }
4079 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4080 // so here we just move all the knots without emitting move signals, for speed
4081 sp_node_update_handles(n, false);
4082 }
4084 /**
4085 * Scale selected nodes.
4086 */
4087 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4088 {
4089 if (!nodepath || !nodepath->selected) return;
4091 if (g_list_length(nodepath->selected) == 1) {
4092 // scale handles of the single selected node
4093 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4094 node_scale_one (n, grow, which);
4095 } else {
4096 // scale nodes as an "object":
4098 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4099 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4100 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4101 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4102 box.expandTo (n->pos); // contain all selected nodes
4103 }
4105 double scale = (box.maxExtent() + grow)/box.maxExtent();
4107 NR::Point scale_center;
4108 if (Inkscape::NodePath::Path::active_node == NULL)
4109 scale_center = box.midpoint();
4110 else
4111 scale_center = Inkscape::NodePath::Path::active_node->pos;
4113 NR::Matrix t =
4114 NR::Matrix (NR::translate(-scale_center)) *
4115 NR::Matrix (NR::scale(scale, scale)) *
4116 NR::Matrix (NR::translate(scale_center));
4118 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4119 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4120 n->pos *= t;
4121 n->n.pos *= t;
4122 n->p.pos *= t;
4123 sp_node_update_handles(n, false);
4124 }
4125 }
4127 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4128 }
4130 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4131 {
4132 if (!nodepath) return;
4133 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4134 }
4136 /**
4137 * Flip selected nodes horizontally/vertically.
4138 */
4139 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4140 {
4141 if (!nodepath || !nodepath->selected) return;
4143 if (g_list_length(nodepath->selected) == 1 && !center) {
4144 // flip handles of the single selected node
4145 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4146 double temp = n->p.pos[axis];
4147 n->p.pos[axis] = n->n.pos[axis];
4148 n->n.pos[axis] = temp;
4149 sp_node_update_handles(n, false);
4150 } else {
4151 // scale nodes as an "object":
4153 NR::Rect box = sp_node_selected_bbox (nodepath);
4154 if (!center) {
4155 center = box.midpoint();
4156 }
4157 NR::Matrix t =
4158 NR::Matrix (NR::translate(- *center)) *
4159 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4160 NR::Matrix (NR::translate(*center));
4162 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4163 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4164 n->pos *= t;
4165 n->n.pos *= t;
4166 n->p.pos *= t;
4167 sp_node_update_handles(n, false);
4168 }
4169 }
4171 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4172 }
4174 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4175 {
4176 g_assert (nodepath->selected);
4178 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4179 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4180 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4181 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4182 box.expandTo (n->pos); // contain all selected nodes
4183 }
4184 return box;
4185 }
4187 //-----------------------------------------------
4188 /**
4189 * Return new subpath under given nodepath.
4190 */
4191 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4192 {
4193 g_assert(nodepath);
4194 g_assert(nodepath->desktop);
4196 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4198 s->nodepath = nodepath;
4199 s->closed = FALSE;
4200 s->nodes = NULL;
4201 s->first = NULL;
4202 s->last = NULL;
4204 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4205 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4206 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4208 return s;
4209 }
4211 /**
4212 * Destroy nodes in subpath, then subpath itself.
4213 */
4214 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4215 {
4216 g_assert(subpath);
4217 g_assert(subpath->nodepath);
4218 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4220 while (subpath->nodes) {
4221 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4222 }
4224 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4226 g_free(subpath);
4227 }
4229 /**
4230 * Link head to tail in subpath.
4231 */
4232 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4233 {
4234 g_assert(!sp->closed);
4235 g_assert(sp->last != sp->first);
4236 g_assert(sp->first->code == NR_MOVETO);
4238 sp->closed = TRUE;
4240 //Link the head to the tail
4241 sp->first->p.other = sp->last;
4242 sp->last->n.other = sp->first;
4243 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4244 sp->first = sp->last;
4246 //Remove the extra end node
4247 sp_nodepath_node_destroy(sp->last->n.other);
4248 }
4250 /**
4251 * Open closed (loopy) subpath at node.
4252 */
4253 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4254 {
4255 g_assert(sp->closed);
4256 g_assert(n->subpath == sp);
4257 g_assert(sp->first == sp->last);
4259 /* We create new startpoint, current node will become last one */
4261 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4262 &n->pos, &n->pos, &n->n.pos);
4265 sp->closed = FALSE;
4267 //Unlink to make a head and tail
4268 sp->first = new_path;
4269 sp->last = n;
4270 n->n.other = NULL;
4271 new_path->p.other = NULL;
4272 }
4274 /**
4275 * Return new node in subpath with given properties.
4276 * \param pos Position of node.
4277 * \param ppos Handle position in previous direction
4278 * \param npos Handle position in previous direction
4279 */
4280 Inkscape::NodePath::Node *
4281 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)
4282 {
4283 g_assert(sp);
4284 g_assert(sp->nodepath);
4285 g_assert(sp->nodepath->desktop);
4287 if (nodechunk == NULL)
4288 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4290 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4292 n->subpath = sp;
4294 if (type != Inkscape::NodePath::NODE_NONE) {
4295 // use the type from sodipodi:nodetypes
4296 n->type = type;
4297 } else {
4298 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4299 // points are (almost) collinear
4300 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4301 // endnode, or a node with a retracted handle
4302 n->type = Inkscape::NodePath::NODE_CUSP;
4303 } else {
4304 n->type = Inkscape::NodePath::NODE_SMOOTH;
4305 }
4306 } else {
4307 n->type = Inkscape::NodePath::NODE_CUSP;
4308 }
4309 }
4311 n->code = code;
4312 n->selected = FALSE;
4313 n->pos = *pos;
4314 n->p.pos = *ppos;
4315 n->n.pos = *npos;
4317 n->dragging_out = NULL;
4319 Inkscape::NodePath::Node *prev;
4320 if (next) {
4321 //g_assert(g_list_find(sp->nodes, next));
4322 prev = next->p.other;
4323 } else {
4324 prev = sp->last;
4325 }
4327 if (prev)
4328 prev->n.other = n;
4329 else
4330 sp->first = n;
4332 if (next)
4333 next->p.other = n;
4334 else
4335 sp->last = n;
4337 n->p.other = prev;
4338 n->n.other = next;
4340 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"));
4341 sp_knot_set_position(n->knot, pos, 0);
4343 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4344 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4345 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4346 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4347 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4348 sp_knot_update_ctrl(n->knot);
4350 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4351 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4352 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4353 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4354 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4355 sp_knot_show(n->knot);
4357 // We only create handle knots and lines on demand
4358 n->p.knot = NULL;
4359 n->p.line = NULL;
4360 n->n.knot = NULL;
4361 n->n.line = NULL;
4363 sp->nodes = g_list_prepend(sp->nodes, n);
4365 return n;
4366 }
4368 /**
4369 * Destroy node and its knots, link neighbors in subpath.
4370 */
4371 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4372 {
4373 g_assert(node);
4374 g_assert(node->subpath);
4375 g_assert(SP_IS_KNOT(node->knot));
4377 Inkscape::NodePath::SubPath *sp = node->subpath;
4379 if (node->selected) { // first, deselect
4380 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4381 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4382 }
4384 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4386 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4387 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4388 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4389 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4390 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4391 g_object_unref(G_OBJECT(node->knot));
4393 if (node->p.knot) {
4394 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4395 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4396 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4397 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4398 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4399 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4400 g_object_unref(G_OBJECT(node->p.knot));
4401 node->p.knot = NULL;
4402 }
4404 if (node->n.knot) {
4405 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4406 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4407 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4408 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4409 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4410 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4411 g_object_unref(G_OBJECT(node->n.knot));
4412 node->n.knot = NULL;
4413 }
4415 if (node->p.line)
4416 gtk_object_destroy(GTK_OBJECT(node->p.line));
4417 if (node->n.line)
4418 gtk_object_destroy(GTK_OBJECT(node->n.line));
4420 if (sp->nodes) { // there are others nodes on the subpath
4421 if (sp->closed) {
4422 if (sp->first == node) {
4423 g_assert(sp->last == node);
4424 sp->first = node->n.other;
4425 sp->last = sp->first;
4426 }
4427 node->p.other->n.other = node->n.other;
4428 node->n.other->p.other = node->p.other;
4429 } else {
4430 if (sp->first == node) {
4431 sp->first = node->n.other;
4432 sp->first->code = NR_MOVETO;
4433 }
4434 if (sp->last == node) sp->last = node->p.other;
4435 if (node->p.other) node->p.other->n.other = node->n.other;
4436 if (node->n.other) node->n.other->p.other = node->p.other;
4437 }
4438 } else { // this was the last node on subpath
4439 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4440 }
4442 g_mem_chunk_free(nodechunk, node);
4443 }
4445 /**
4446 * Returns one of the node's two sides.
4447 * \param which Indicates which side.
4448 * \return Pointer to previous node side if which==-1, next if which==1.
4449 */
4450 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4451 {
4452 g_assert(node);
4454 switch (which) {
4455 case -1:
4456 return &node->p;
4457 case 1:
4458 return &node->n;
4459 default:
4460 break;
4461 }
4463 g_assert_not_reached();
4465 return NULL;
4466 }
4468 /**
4469 * Return the other side of the node, given one of its sides.
4470 */
4471 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4472 {
4473 g_assert(node);
4475 if (me == &node->p) return &node->n;
4476 if (me == &node->n) return &node->p;
4478 g_assert_not_reached();
4480 return NULL;
4481 }
4483 /**
4484 * Return NRPathcode on the given side of the node.
4485 */
4486 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4487 {
4488 g_assert(node);
4490 if (me == &node->p) {
4491 if (node->p.other) return (NRPathcode)node->code;
4492 return NR_MOVETO;
4493 }
4495 if (me == &node->n) {
4496 if (node->n.other) return (NRPathcode)node->n.other->code;
4497 return NR_MOVETO;
4498 }
4500 g_assert_not_reached();
4502 return NR_END;
4503 }
4505 /**
4506 * Return node with the given index
4507 */
4508 Inkscape::NodePath::Node *
4509 sp_nodepath_get_node_by_index(int index)
4510 {
4511 Inkscape::NodePath::Node *e = NULL;
4513 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4514 if (!nodepath) {
4515 return e;
4516 }
4518 //find segment
4519 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4521 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4522 int n = g_list_length(sp->nodes);
4523 if (sp->closed) {
4524 n++;
4525 }
4527 //if the piece belongs to this subpath grab it
4528 //otherwise move onto the next subpath
4529 if (index < n) {
4530 e = sp->first;
4531 for (int i = 0; i < index; ++i) {
4532 e = e->n.other;
4533 }
4534 break;
4535 } else {
4536 if (sp->closed) {
4537 index -= (n+1);
4538 } else {
4539 index -= n;
4540 }
4541 }
4542 }
4544 return e;
4545 }
4547 /**
4548 * Returns plain text meaning of node type.
4549 */
4550 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4551 {
4552 unsigned retracted = 0;
4553 bool endnode = false;
4555 for (int which = -1; which <= 1; which += 2) {
4556 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4557 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4558 retracted ++;
4559 if (!side->other)
4560 endnode = true;
4561 }
4563 if (retracted == 0) {
4564 if (endnode) {
4565 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4566 return _("end node");
4567 } else {
4568 switch (node->type) {
4569 case Inkscape::NodePath::NODE_CUSP:
4570 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4571 return _("cusp");
4572 case Inkscape::NodePath::NODE_SMOOTH:
4573 // TRANSLATORS: "smooth" is an adjective here
4574 return _("smooth");
4575 case Inkscape::NodePath::NODE_SYMM:
4576 return _("symmetric");
4577 }
4578 }
4579 } else if (retracted == 1) {
4580 if (endnode) {
4581 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4582 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4583 } else {
4584 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4585 }
4586 } else {
4587 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4588 }
4590 return NULL;
4591 }
4593 /**
4594 * Handles content of statusbar as long as node tool is active.
4595 */
4596 void
4597 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4598 {
4599 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");
4600 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4602 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4603 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4604 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4605 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4607 SPDesktop *desktop = NULL;
4608 if (nodepath) {
4609 desktop = nodepath->desktop;
4610 } else {
4611 desktop = SP_ACTIVE_DESKTOP;
4612 }
4614 SPEventContext *ec = desktop->event_context;
4615 if (!ec) return;
4616 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4617 if (!mc) return;
4619 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4621 if (selected_nodes == 0) {
4622 Inkscape::Selection *sel = desktop->selection;
4623 if (!sel || sel->isEmpty()) {
4624 mc->setF(Inkscape::NORMAL_MESSAGE,
4625 _("Select a single object to edit its nodes or handles."));
4626 } else {
4627 if (nodepath) {
4628 mc->setF(Inkscape::NORMAL_MESSAGE,
4629 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.",
4630 "<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.",
4631 total_nodes),
4632 total_nodes);
4633 } else {
4634 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4635 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4636 } else {
4637 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4638 }
4639 }
4640 }
4641 } else if (nodepath && selected_nodes == 1) {
4642 mc->setF(Inkscape::NORMAL_MESSAGE,
4643 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4644 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4645 total_nodes),
4646 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4647 } else {
4648 if (selected_subpaths > 1) {
4649 mc->setF(Inkscape::NORMAL_MESSAGE,
4650 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4651 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4652 total_nodes),
4653 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4654 } else {
4655 mc->setF(Inkscape::NORMAL_MESSAGE,
4656 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4657 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4658 total_nodes),
4659 selected_nodes, total_nodes, when_selected);
4660 }
4661 }
4662 }
4664 /*
4665 * returns a *copy* of the curve of that object.
4666 */
4667 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4668 if (!object)
4669 return NULL;
4671 SPCurve *curve = NULL;
4672 if (SP_IS_PATH(object)) {
4673 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4674 curve = curve_new->copy();
4675 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4676 const gchar *svgd = object->repr->attribute(key);
4677 if (svgd) {
4678 NArtBpath *bpath = sp_svg_read_path(svgd);
4679 SPCurve *curve_new = SPCurve::new_from_bpath(bpath);
4680 if (curve_new) {
4681 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4682 } else {
4683 g_free(bpath);
4684 }
4685 }
4686 }
4688 return curve;
4689 }
4691 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4692 if (!np || !np->object || !curve)
4693 return;
4695 if (SP_IS_PATH(np->object)) {
4696 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4697 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4698 } else {
4699 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4700 }
4701 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4702 // FIXME: this writing to string and then reading from string is bound to be slow.
4703 // create a method to convert from curve directly to 2geom...
4704 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4705 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4706 g_free(svgpath);
4708 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4709 }
4710 }
4712 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4713 np->show_helperpath = show;
4715 if (show) {
4716 SPCurve *helper_curve = np->curve->copy();
4717 helper_curve->transform(np->i2d );
4718 if (!np->helper_path) {
4719 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4720 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);
4721 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4722 sp_canvas_item_move_to_z(np->helper_path, 0);
4723 sp_canvas_item_show(np->helper_path);
4724 } else {
4725 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4726 }
4727 helper_curve->unref();
4728 } else {
4729 if (np->helper_path) {
4730 GtkObject *temp = np->helper_path;
4731 np->helper_path = NULL;
4732 gtk_object_destroy(temp);
4733 }
4734 }
4735 }
4737 /* sp_nodepath_make_straight_path:
4738 * Prevents user from curving the path by dragging a segment or activating handles etc.
4739 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4740 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4741 */
4742 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4743 np->straight_path = true;
4744 np->show_handles = false;
4745 g_message("add code to make the path straight.");
4746 // do sp_nodepath_convert_node_type on all nodes?
4747 // coding tip: search for this text : "Make selected segments lines"
4748 }
4751 /*
4752 Local Variables:
4753 mode:c++
4754 c-file-style:"stroustrup"
4755 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4756 indent-tabs-mode:nil
4757 fill-column:99
4758 End:
4759 */
4760 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :