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