f48a83b4f254e9e7e73b046c5338959494b66b53
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 <glibmm/i18n.h>
23 #include "libnr/n-art-bpath.h"
24 #include "libnr/nr-path.h"
25 #include "helper/units.h"
26 #include "knot.h"
27 #include "inkscape.h"
28 #include "document.h"
29 #include "sp-namedview.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "snap.h"
33 #include "message-stack.h"
34 #include "message-context.h"
35 #include "node-context.h"
36 #include "shape-editor.h"
37 #include "selection-chemistry.h"
38 #include "selection.h"
39 #include "xml/repr.h"
40 #include "prefs-utils.h"
41 #include "sp-metrics.h"
42 #include "sp-path.h"
43 #include "libnr/nr-matrix-ops.h"
44 #include "splivarot.h"
45 #include "svg/svg.h"
46 #include "verbs.h"
47 #include "display/bezier-utils.h"
48 #include <vector>
49 #include <algorithm>
50 #include "live_effects/lpeobject.h"
51 #include "live_effects/parameter/parameter.h"
53 class NR::Matrix;
55 /// \todo
56 /// evil evil evil. FIXME: conflict of two different Path classes!
57 /// There is a conflict in the namespace between two classes named Path.
58 /// #include "sp-flowtext.h"
59 /// #include "sp-flowregion.h"
61 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
62 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
63 GType sp_flowregion_get_type (void);
64 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
65 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
66 GType sp_flowtext_get_type (void);
67 // end evil workaround
69 #include "helper/stlport.h"
72 /// \todo fixme: Implement these via preferences */
74 #define NODE_FILL 0xbfbfbf00
75 #define NODE_STROKE 0x000000ff
76 #define NODE_FILL_HI 0xff000000
77 #define NODE_STROKE_HI 0x000000ff
78 #define NODE_FILL_SEL 0x0000ffff
79 #define NODE_STROKE_SEL 0x000000ff
80 #define NODE_FILL_SEL_HI 0xff000000
81 #define NODE_STROKE_SEL_HI 0x000000ff
82 #define KNOT_FILL 0xffffffff
83 #define KNOT_STROKE 0x000000ff
84 #define KNOT_FILL_HI 0xff000000
85 #define KNOT_STROKE_HI 0x000000ff
87 static GMemChunk *nodechunk = NULL;
89 /* Creation from object */
91 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
92 static gchar *parse_nodetypes(gchar const *types, gint length);
94 /* Object updating */
96 static void stamp_repr(Inkscape::NodePath::Path *np);
97 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
98 static gchar *create_typestr(Inkscape::NodePath::Path *np);
100 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
102 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
104 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
106 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
108 /* Adjust handle placement, if the node or the other handle is moved */
109 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
110 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
112 /* Node event callbacks */
113 static void node_clicked(SPKnot *knot, guint state, gpointer data);
114 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
115 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
116 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
118 /* Handle event callbacks */
119 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
120 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
121 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
122 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
123 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
124 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
126 /* Constructors and destructors */
128 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
129 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
130 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
131 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
132 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
133 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
134 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
136 /* Helpers */
138 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
139 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
140 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
142 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
143 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
145 // active_node indicates mouseover node
146 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
148 /**
149 * \brief Creates new nodepath from item
150 */
151 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
152 {
153 Inkscape::XML::Node *repr = object->repr;
155 /** \todo
156 * FIXME: remove this. We don't want to edit paths inside flowtext.
157 * Instead we will build our flowtext with cloned paths, so that the
158 * real paths are outside the flowtext and thus editable as usual.
159 */
160 if (SP_IS_FLOWTEXT(object)) {
161 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
162 if SP_IS_FLOWREGION(child) {
163 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
164 if (grandchild && SP_IS_PATH(grandchild)) {
165 object = SP_ITEM(grandchild);
166 break;
167 }
168 }
169 }
170 }
172 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
174 if (curve == NULL)
175 return NULL;
177 NArtBpath *bpath = sp_curve_first_bpath(curve);
178 gint length = curve->end;
179 if (length == 0) {
180 sp_curve_unref(curve);
181 return NULL; // prevent crash for one-node paths
182 }
184 //Create new nodepath
185 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
186 if (!np) {
187 sp_curve_unref(curve);
188 return NULL;
189 }
191 // Set defaults
192 np->desktop = desktop;
193 np->object = object;
194 np->subpaths = NULL;
195 np->selected = NULL;
196 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
197 np->livarot_path = NULL;
198 np->local_change = 0;
199 np->show_handles = show_handles;
200 np->helper_path = NULL;
201 np->curve = sp_curve_copy(curve);
202 np->show_helperpath = false;
203 np->straight_path = false;
204 if (IS_LIVEPATHEFFECT(object) && item) {
205 np->item = item;
206 } else {
207 np->item = SP_ITEM(object);
208 }
210 // we need to update item's transform from the repr here,
211 // because they may be out of sync when we respond
212 // to a change in repr by regenerating nodepath --bb
213 sp_object_read_attr(SP_OBJECT(np->item), "transform");
215 np->i2d = sp_item_i2d_affine(np->item);
216 np->d2i = np->i2d.inverse();
218 np->repr = repr;
219 if (repr_key_in) { // apparantly the object is an LPEObject
220 np->repr_key = g_strdup(repr_key_in);
221 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
222 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
223 if (lpeparam) {
224 lpeparam->param_setup_notepath(np);
225 }
226 } else {
227 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
228 if ( SP_SHAPE(np->object)->path_effect_href ) {
229 np->repr_key = g_strdup("inkscape:original-d");
231 LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(SP_SHAPE(np->object));
232 if (lpeobj && lpeobj->lpe) {
233 lpeobj->lpe->setup_notepath(np);
234 }
235 } else {
236 np->repr_key = g_strdup("d");
237 }
238 }
240 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
241 gchar *typestr = parse_nodetypes(nodetypes, length);
243 // create the subpath(s) from the bpath
244 NArtBpath *b = bpath;
245 while (b->code != NR_END) {
246 b = subpath_from_bpath(np, b, typestr + (b - bpath));
247 }
249 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
250 np->subpaths = g_list_reverse(np->subpaths);
252 g_free(typestr);
253 sp_curve_unref(curve);
255 // create the livarot representation from the same item
256 sp_nodepath_ensure_livarot_path(np);
258 // Draw helper curve
259 if (np->show_helperpath) {
260 SPCurve *helper_curve = sp_curve_copy(np->curve);
261 sp_curve_transform(helper_curve, np->i2d );
262 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
263 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);
264 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
265 sp_canvas_item_show(np->helper_path);
266 sp_curve_unref(helper_curve);
267 }
269 return np;
270 }
272 /**
273 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
274 */
275 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
277 if (!np) //soft fail, like delete
278 return;
280 while (np->subpaths) {
281 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
282 }
284 //Inform the ShapeEditor that made me, if any, that I am gone.
285 if (np->shape_editor)
286 np->shape_editor->nodepath_destroyed();
288 g_assert(!np->selected);
290 if (np->livarot_path) {
291 delete np->livarot_path;
292 np->livarot_path = NULL;
293 }
295 if (np->helper_path) {
296 GtkObject *temp = np->helper_path;
297 np->helper_path = NULL;
298 gtk_object_destroy(temp);
299 }
300 if (np->curve) {
301 sp_curve_unref(np->curve);
302 np->curve = NULL;
303 }
305 if (np->repr_key) {
306 g_free(np->repr_key);
307 np->repr_key = NULL;
308 }
309 if (np->repr_nodetypes_key) {
310 g_free(np->repr_nodetypes_key);
311 np->repr_nodetypes_key = NULL;
312 }
314 np->desktop = NULL;
316 g_free(np);
317 }
320 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
321 {
322 if (np && np->livarot_path == NULL) {
323 SPCurve *curve = create_curve(np);
324 NArtBpath *bpath = SP_CURVE_BPATH(curve);
325 np->livarot_path = bpath_to_Path(bpath);
327 if (np->livarot_path)
328 np->livarot_path->ConvertWithBackData(0.01);
330 sp_curve_unref(curve);
331 }
332 }
335 /**
336 * Return the node count of a given NodeSubPath.
337 */
338 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
339 {
340 if (!subpath)
341 return 0;
342 gint nodeCount = g_list_length(subpath->nodes);
343 return nodeCount;
344 }
346 /**
347 * Return the node count of a given NodePath.
348 */
349 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
350 {
351 if (!np)
352 return 0;
353 gint nodeCount = 0;
354 for (GList *item = np->subpaths ; item ; item=item->next) {
355 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
356 nodeCount += g_list_length(subpath->nodes);
357 }
358 return nodeCount;
359 }
361 /**
362 * Return the subpath count of a given NodePath.
363 */
364 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
365 {
366 if (!np)
367 return 0;
368 return g_list_length (np->subpaths);
369 }
371 /**
372 * Return the selected node count of a given NodePath.
373 */
374 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
375 {
376 if (!np)
377 return 0;
378 return g_list_length (np->selected);
379 }
381 /**
382 * Return the number of subpaths where nodes are selected in a given NodePath.
383 */
384 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
385 {
386 if (!np)
387 return 0;
388 if (!np->selected)
389 return 0;
390 if (!np->selected->next)
391 return 1;
392 gint count = 0;
393 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
394 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
395 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
396 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
397 if (node->selected) {
398 count ++;
399 break;
400 }
401 }
402 }
403 return count;
404 }
406 /**
407 * Clean up a nodepath after editing.
408 *
409 * Currently we are deleting trivial subpaths.
410 */
411 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
412 {
413 GList *badSubPaths = NULL;
415 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
416 for (GList *l = nodepath->subpaths; l ; l=l->next) {
417 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
418 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
419 badSubPaths = g_list_append(badSubPaths, sp);
420 }
422 //Delete them. This second step is because sp_nodepath_subpath_destroy()
423 //also removes the subpath from nodepath->subpaths
424 for (GList *l = badSubPaths; l ; l=l->next) {
425 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
426 sp_nodepath_subpath_destroy(sp);
427 }
429 g_list_free(badSubPaths);
430 }
432 /**
433 * Create new nodepath from b, make it subpath of np.
434 * \param t The node type.
435 * \todo Fixme: t should be a proper type, rather than gchar
436 */
437 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
438 {
439 NR::Point ppos, pos, npos;
441 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
443 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
444 bool const closed = (b->code == NR_MOVETO);
446 pos = NR::Point(b->x3, b->y3) * np->i2d;
447 if (b[1].code == NR_CURVETO) {
448 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
449 } else {
450 npos = pos;
451 }
452 Inkscape::NodePath::Node *n;
453 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
454 g_assert(sp->first == n);
455 g_assert(sp->last == n);
457 b++;
458 t++;
459 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
460 pos = NR::Point(b->x3, b->y3) * np->i2d;
461 if (b->code == NR_CURVETO) {
462 ppos = NR::Point(b->x2, b->y2) * np->i2d;
463 } else {
464 ppos = pos;
465 }
466 if (b[1].code == NR_CURVETO) {
467 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
468 } else {
469 npos = pos;
470 }
471 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
472 b++;
473 t++;
474 }
476 if (closed) sp_nodepath_subpath_close(sp);
478 return b;
479 }
481 /**
482 * Convert from sodipodi:nodetypes to new style type string.
483 */
484 static gchar *parse_nodetypes(gchar const *types, gint length)
485 {
486 g_assert(length > 0);
488 gchar *typestr = g_new(gchar, length + 1);
490 gint pos = 0;
492 if (types) {
493 for (gint i = 0; types[i] && ( i < length ); i++) {
494 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
495 if (types[i] != '\0') {
496 switch (types[i]) {
497 case 's':
498 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
499 break;
500 case 'z':
501 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
502 break;
503 case 'c':
504 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
505 break;
506 default:
507 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
508 break;
509 }
510 }
511 }
512 }
514 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
516 return typestr;
517 }
519 /**
520 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
521 * updated but repr is not (for speed). Used during curve and node drag.
522 */
523 static void update_object(Inkscape::NodePath::Path *np)
524 {
525 g_assert(np);
527 sp_curve_unref(np->curve);
528 np->curve = create_curve(np);
530 sp_nodepath_set_curve(np, np->curve);
532 if (np->show_helperpath) {
533 SPCurve * helper_curve = sp_curve_copy(np->curve);
534 sp_curve_transform(helper_curve, np->i2d );
535 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
536 sp_curve_unref(helper_curve);
537 }
538 }
540 /**
541 * Update XML path node with data from path object.
542 */
543 static void update_repr_internal(Inkscape::NodePath::Path *np)
544 {
545 g_assert(np);
547 Inkscape::XML::Node *repr = np->object->repr;
549 sp_curve_unref(np->curve);
550 np->curve = create_curve(np);
552 gchar *typestr = create_typestr(np);
553 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
555 // determine if path has an effect applied and write to correct "d" attribute.
556 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
557 np->local_change++;
558 repr->setAttribute(np->repr_key, svgpath);
559 }
561 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
562 np->local_change++;
563 repr->setAttribute(np->repr_nodetypes_key, typestr);
564 }
566 g_free(svgpath);
567 g_free(typestr);
569 if (np->show_helperpath) {
570 SPCurve * helper_curve = sp_curve_copy(np->curve);
571 sp_curve_transform(helper_curve, np->i2d );
572 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
573 sp_curve_unref(helper_curve);
574 }
575 }
577 /**
578 * Update XML path node with data from path object, commit changes forever.
579 */
580 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
581 {
582 //fixme: np can be NULL, so check before proceeding
583 g_return_if_fail(np != NULL);
585 if (np->livarot_path) {
586 delete np->livarot_path;
587 np->livarot_path = NULL;
588 }
590 update_repr_internal(np);
591 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
593 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
594 annotation);
595 }
597 /**
598 * Update XML path node with data from path object, commit changes with undo.
599 */
600 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
601 {
602 if (np->livarot_path) {
603 delete np->livarot_path;
604 np->livarot_path = NULL;
605 }
607 update_repr_internal(np);
608 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
609 annotation);
610 }
612 /**
613 * Make duplicate of path, replace corresponding XML node in tree, commit.
614 */
615 static void stamp_repr(Inkscape::NodePath::Path *np)
616 {
617 g_assert(np);
619 Inkscape::XML::Node *old_repr = np->object->repr;
620 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
622 // remember the position of the item
623 gint pos = old_repr->position();
624 // remember parent
625 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
627 SPCurve *curve = create_curve(np);
628 gchar *typestr = create_typestr(np);
630 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
632 new_repr->setAttribute(np->repr_key, svgpath);
633 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
635 // add the new repr to the parent
636 parent->appendChild(new_repr);
637 // move to the saved position
638 new_repr->setPosition(pos > 0 ? pos : 0);
640 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
641 _("Stamp"));
643 Inkscape::GC::release(new_repr);
644 g_free(svgpath);
645 g_free(typestr);
646 sp_curve_unref(curve);
647 }
649 /**
650 * Create curve from path.
651 */
652 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
653 {
654 SPCurve *curve = sp_curve_new();
656 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
657 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
658 sp_curve_moveto(curve,
659 sp->first->pos * np->d2i);
660 Inkscape::NodePath::Node *n = sp->first->n.other;
661 while (n) {
662 NR::Point const end_pt = n->pos * np->d2i;
663 switch (n->code) {
664 case NR_LINETO:
665 sp_curve_lineto(curve, end_pt);
666 break;
667 case NR_CURVETO:
668 sp_curve_curveto(curve,
669 n->p.other->n.pos * np->d2i,
670 n->p.pos * np->d2i,
671 end_pt);
672 break;
673 default:
674 g_assert_not_reached();
675 break;
676 }
677 if (n != sp->last) {
678 n = n->n.other;
679 } else {
680 n = NULL;
681 }
682 }
683 if (sp->closed) {
684 sp_curve_closepath(curve);
685 }
686 }
688 return curve;
689 }
691 /**
692 * Convert path type string to sodipodi:nodetypes style.
693 */
694 static gchar *create_typestr(Inkscape::NodePath::Path *np)
695 {
696 gchar *typestr = g_new(gchar, 32);
697 gint len = 32;
698 gint pos = 0;
700 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
701 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
703 if (pos >= len) {
704 typestr = g_renew(gchar, typestr, len + 32);
705 len += 32;
706 }
708 typestr[pos++] = 'c';
710 Inkscape::NodePath::Node *n;
711 n = sp->first->n.other;
712 while (n) {
713 gchar code;
715 switch (n->type) {
716 case Inkscape::NodePath::NODE_CUSP:
717 code = 'c';
718 break;
719 case Inkscape::NodePath::NODE_SMOOTH:
720 code = 's';
721 break;
722 case Inkscape::NodePath::NODE_SYMM:
723 code = 'z';
724 break;
725 default:
726 g_assert_not_reached();
727 code = '\0';
728 break;
729 }
731 if (pos >= len) {
732 typestr = g_renew(gchar, typestr, len + 32);
733 len += 32;
734 }
736 typestr[pos++] = code;
738 if (n != sp->last) {
739 n = n->n.other;
740 } else {
741 n = NULL;
742 }
743 }
744 }
746 if (pos >= len) {
747 typestr = g_renew(gchar, typestr, len + 1);
748 len += 1;
749 }
751 typestr[pos++] = '\0';
753 return typestr;
754 }
756 /**
757 * Returns current path in context. // later eliminate this function at all!
758 */
759 static Inkscape::NodePath::Path *sp_nodepath_current()
760 {
761 if (!SP_ACTIVE_DESKTOP) {
762 return NULL;
763 }
765 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
767 if (!SP_IS_NODE_CONTEXT(event_context)) {
768 return NULL;
769 }
771 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
772 }
776 /**
777 \brief Fills node and handle positions for three nodes, splitting line
778 marked by end at distance t.
779 */
780 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
781 {
782 g_assert(new_path != NULL);
783 g_assert(end != NULL);
785 g_assert(end->p.other == new_path);
786 Inkscape::NodePath::Node *start = new_path->p.other;
787 g_assert(start);
789 if (end->code == NR_LINETO) {
790 new_path->type =Inkscape::NodePath::NODE_CUSP;
791 new_path->code = NR_LINETO;
792 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
793 } else {
794 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
795 new_path->code = NR_CURVETO;
796 gdouble s = 1 - t;
797 for (int dim = 0; dim < 2; dim++) {
798 NR::Coord const f000 = start->pos[dim];
799 NR::Coord const f001 = start->n.pos[dim];
800 NR::Coord const f011 = end->p.pos[dim];
801 NR::Coord const f111 = end->pos[dim];
802 NR::Coord const f00t = s * f000 + t * f001;
803 NR::Coord const f01t = s * f001 + t * f011;
804 NR::Coord const f11t = s * f011 + t * f111;
805 NR::Coord const f0tt = s * f00t + t * f01t;
806 NR::Coord const f1tt = s * f01t + t * f11t;
807 NR::Coord const fttt = s * f0tt + t * f1tt;
808 start->n.pos[dim] = f00t;
809 new_path->p.pos[dim] = f0tt;
810 new_path->pos[dim] = fttt;
811 new_path->n.pos[dim] = f1tt;
812 end->p.pos[dim] = f11t;
813 }
814 }
815 }
817 /**
818 * Adds new node on direct line between two nodes, activates handles of all
819 * three nodes.
820 */
821 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
822 {
823 g_assert(end);
824 g_assert(end->subpath);
825 g_assert(g_list_find(end->subpath->nodes, end));
827 Inkscape::NodePath::Node *start = end->p.other;
828 g_assert( start->n.other == end );
829 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
830 end,
831 (NRPathcode)end->code == NR_LINETO?
832 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
833 (NRPathcode)end->code,
834 &start->pos, &start->pos, &start->n.pos);
835 sp_nodepath_line_midpoint(newnode, end, t);
837 sp_node_adjust_handles(start);
838 sp_node_update_handles(start);
839 sp_node_update_handles(newnode);
840 sp_node_adjust_handles(end);
841 sp_node_update_handles(end);
843 return newnode;
844 }
846 /**
847 \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
848 */
849 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
850 {
851 g_assert(node);
852 g_assert(node->subpath);
853 g_assert(g_list_find(node->subpath->nodes, node));
855 Inkscape::NodePath::SubPath *sp = node->subpath;
856 Inkscape::NodePath::Path *np = sp->nodepath;
858 if (sp->closed) {
859 sp_nodepath_subpath_open(sp, node);
860 return sp->first;
861 } else {
862 // no break for end nodes
863 if (node == sp->first) return NULL;
864 if (node == sp->last ) return NULL;
866 // create a new subpath
867 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
869 // duplicate the break node as start of the new subpath
870 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
872 while (node->n.other) { // copy the remaining nodes into the new subpath
873 Inkscape::NodePath::Node *n = node->n.other;
874 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);
875 if (n->selected) {
876 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
877 }
878 sp_nodepath_node_destroy(n); // remove the point on the original subpath
879 }
881 return newnode;
882 }
883 }
885 /**
886 * Duplicate node and connect to neighbours.
887 */
888 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
889 {
890 g_assert(node);
891 g_assert(node->subpath);
892 g_assert(g_list_find(node->subpath->nodes, node));
894 Inkscape::NodePath::SubPath *sp = node->subpath;
896 NRPathcode code = (NRPathcode) node->code;
897 if (code == NR_MOVETO) { // if node is the endnode,
898 node->code = NR_LINETO; // new one is inserted before it, so change that to line
899 }
901 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
903 if (!node->n.other || !node->p.other) // if node is an endnode, select it
904 return node;
905 else
906 return newnode; // otherwise select the newly created node
907 }
909 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
910 {
911 node->p.pos = (node->pos + (node->pos - node->n.pos));
912 }
914 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
915 {
916 node->n.pos = (node->pos + (node->pos - node->p.pos));
917 }
919 /**
920 * Change line type at node, with side effects on neighbours.
921 */
922 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
923 {
924 g_assert(end);
925 g_assert(end->subpath);
926 g_assert(end->p.other);
928 if (end->code == static_cast< guint > ( code ) )
929 return;
931 Inkscape::NodePath::Node *start = end->p.other;
933 end->code = code;
935 if (code == NR_LINETO) {
936 if (start->code == NR_LINETO) {
937 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
938 }
939 if (end->n.other) {
940 if (end->n.other->code == NR_LINETO) {
941 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
942 }
943 }
944 } else {
945 NR::Point delta = end->pos - start->pos;
946 start->n.pos = start->pos + delta / 3;
947 end->p.pos = end->pos - delta / 3;
948 sp_node_adjust_handle(start, 1);
949 sp_node_adjust_handle(end, -1);
950 }
952 sp_node_update_handles(start);
953 sp_node_update_handles(end);
954 }
956 /**
957 * Change node type, and its handles accordingly.
958 */
959 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
960 {
961 g_assert(node);
962 g_assert(node->subpath);
964 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
965 return node;
967 if ((node->p.other != NULL) && (node->n.other != NULL)) {
968 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
969 type =Inkscape::NodePath::NODE_CUSP;
970 }
971 }
973 node->type = type;
975 if (node->type == Inkscape::NodePath::NODE_CUSP) {
976 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
977 node->knot->setSize (node->selected? 11 : 9);
978 sp_knot_update_ctrl(node->knot);
979 } else {
980 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
981 node->knot->setSize (node->selected? 9 : 7);
982 sp_knot_update_ctrl(node->knot);
983 }
985 // if one of handles is mouseovered, preserve its position
986 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
987 sp_node_adjust_handle(node, 1);
988 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
989 sp_node_adjust_handle(node, -1);
990 } else {
991 sp_node_adjust_handles(node);
992 }
994 sp_node_update_handles(node);
996 sp_nodepath_update_statusbar(node->subpath->nodepath);
998 return node;
999 }
1001 /**
1002 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
1003 * adjacent segments from lines to curves.
1004 */
1005 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1006 {
1007 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1008 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1010 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1011 if (p_line && n_line) {
1012 // only if both adjacent segments are lines,
1013 // convert both to curves:
1015 node->code = NR_CURVETO;
1016 node->n.other->code = NR_CURVETO;
1018 NR::Point leg_prev = node->pos - node->p.other->pos;
1019 NR::Point leg_next = node->pos - node->n.other->pos;
1021 double norm_leg_prev = L2(leg_prev);
1022 double norm_leg_next = L2(leg_next);
1024 // delta has length 1 and is orthogonal to bisecting line
1025 NR::Point delta;
1026 if (norm_leg_next > 0.0) {
1027 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1028 (&delta)->normalize();
1029 }
1031 if (type == Inkscape::NodePath::NODE_SYMM) {
1032 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1033 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1034 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1035 } else {
1036 // length of handle is proportional to distance to adjacent node
1037 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1038 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1039 }
1041 sp_node_update_handles(node);
1042 }
1043 }
1045 sp_nodepath_set_node_type (node, type);
1046 }
1048 /**
1049 * Move node to point, and adjust its and neighbouring handles.
1050 */
1051 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1052 {
1053 NR::Point delta = p - node->pos;
1054 node->pos = p;
1056 node->p.pos += delta;
1057 node->n.pos += delta;
1059 Inkscape::NodePath::Node *node_p = NULL;
1060 Inkscape::NodePath::Node *node_n = NULL;
1062 if (node->p.other) {
1063 if (node->code == NR_LINETO) {
1064 sp_node_adjust_handle(node, 1);
1065 sp_node_adjust_handle(node->p.other, -1);
1066 node_p = node->p.other;
1067 }
1068 }
1069 if (node->n.other) {
1070 if (node->n.other->code == NR_LINETO) {
1071 sp_node_adjust_handle(node, -1);
1072 sp_node_adjust_handle(node->n.other, 1);
1073 node_n = node->n.other;
1074 }
1075 }
1077 // this function is only called from batch movers that will update display at the end
1078 // themselves, so here we just move all the knots without emitting move signals, for speed
1079 sp_node_update_handles(node, false);
1080 if (node_n) {
1081 sp_node_update_handles(node_n, false);
1082 }
1083 if (node_p) {
1084 sp_node_update_handles(node_p, false);
1085 }
1086 }
1088 /**
1089 * Call sp_node_moveto() for node selection and handle possible snapping.
1090 */
1091 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1092 bool const snap = true)
1093 {
1094 NR::Coord best = NR_HUGE;
1095 NR::Point delta(dx, dy);
1096 NR::Point best_pt = delta;
1098 if (snap) {
1099 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1101 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1102 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1103 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item));
1104 if (s.getDistance() < best) {
1105 best = s.getDistance();
1106 best_pt = s.getPoint() - n->pos;
1107 }
1108 }
1109 }
1111 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1112 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1113 sp_node_moveto(n, n->pos + best_pt);
1114 }
1116 // do not update repr here so that node dragging is acceptably fast
1117 update_object(nodepath);
1118 }
1120 /**
1121 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1122 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1123 near x = 0.
1124 */
1125 double
1126 sculpt_profile (double x, double alpha, guint profile)
1127 {
1128 if (x >= 1)
1129 return 0;
1130 if (x <= 0)
1131 return 1;
1133 switch (profile) {
1134 case SCULPT_PROFILE_LINEAR:
1135 return 1 - x;
1136 case SCULPT_PROFILE_BELL:
1137 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1138 case SCULPT_PROFILE_ELLIPTIC:
1139 return sqrt(1 - x*x);
1140 }
1142 return 1;
1143 }
1145 double
1146 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1147 {
1148 // extremely primitive for now, don't have time to look for the real one
1149 double lower = NR::L2(b - a);
1150 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1151 return (lower + upper)/2;
1152 }
1154 void
1155 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1156 {
1157 n->pos = n->origin + delta;
1158 n->n.pos = n->n.origin + delta_n;
1159 n->p.pos = n->p.origin + delta_p;
1160 sp_node_adjust_handles(n);
1161 sp_node_update_handles(n, false);
1162 }
1164 /**
1165 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1166 * on how far they are from the dragged node n.
1167 */
1168 static void
1169 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1170 {
1171 g_assert (n);
1172 g_assert (nodepath);
1173 g_assert (n->subpath->nodepath == nodepath);
1175 double pressure = n->knot->pressure;
1176 if (pressure == 0)
1177 pressure = 0.5; // default
1178 pressure = CLAMP (pressure, 0.2, 0.8);
1180 // map pressure to alpha = 1/5 ... 5
1181 double alpha = 1 - 2 * fabs(pressure - 0.5);
1182 if (pressure > 0.5)
1183 alpha = 1/alpha;
1185 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1187 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1188 // Only one subpath has selected nodes:
1189 // use linear mode, where the distance from n to node being dragged is calculated along the path
1191 double n_sel_range = 0, p_sel_range = 0;
1192 guint n_nodes = 0, p_nodes = 0;
1193 guint n_sel_nodes = 0, p_sel_nodes = 0;
1195 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1196 {
1197 double n_range = 0, p_range = 0;
1198 bool n_going = true, p_going = true;
1199 Inkscape::NodePath::Node *n_node = n;
1200 Inkscape::NodePath::Node *p_node = n;
1201 do {
1202 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1203 if (n_node && n_going)
1204 n_node = n_node->n.other;
1205 if (n_node == NULL) {
1206 n_going = false;
1207 } else {
1208 n_nodes ++;
1209 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1210 if (n_node->selected) {
1211 n_sel_nodes ++;
1212 n_sel_range = n_range;
1213 }
1214 if (n_node == p_node) {
1215 n_going = false;
1216 p_going = false;
1217 }
1218 }
1219 if (p_node && p_going)
1220 p_node = p_node->p.other;
1221 if (p_node == NULL) {
1222 p_going = false;
1223 } else {
1224 p_nodes ++;
1225 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1226 if (p_node->selected) {
1227 p_sel_nodes ++;
1228 p_sel_range = p_range;
1229 }
1230 if (p_node == n_node) {
1231 n_going = false;
1232 p_going = false;
1233 }
1234 }
1235 } while (n_going || p_going);
1236 }
1238 // Second pass: actually move nodes in this subpath
1239 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1240 {
1241 double n_range = 0, p_range = 0;
1242 bool n_going = true, p_going = true;
1243 Inkscape::NodePath::Node *n_node = n;
1244 Inkscape::NodePath::Node *p_node = n;
1245 do {
1246 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1247 if (n_node && n_going)
1248 n_node = n_node->n.other;
1249 if (n_node == NULL) {
1250 n_going = false;
1251 } else {
1252 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1253 if (n_node->selected) {
1254 sp_nodepath_move_node_and_handles (n_node,
1255 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1256 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1257 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1258 }
1259 if (n_node == p_node) {
1260 n_going = false;
1261 p_going = false;
1262 }
1263 }
1264 if (p_node && p_going)
1265 p_node = p_node->p.other;
1266 if (p_node == NULL) {
1267 p_going = false;
1268 } else {
1269 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1270 if (p_node->selected) {
1271 sp_nodepath_move_node_and_handles (p_node,
1272 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1273 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1274 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1275 }
1276 if (p_node == n_node) {
1277 n_going = false;
1278 p_going = false;
1279 }
1280 }
1281 } while (n_going || p_going);
1282 }
1284 } else {
1285 // Multiple subpaths have selected nodes:
1286 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1287 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1288 // fix the pear-like shape when sculpting e.g. a ring
1290 // First pass: calculate range
1291 gdouble direct_range = 0;
1292 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1293 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1294 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1295 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1296 if (node->selected) {
1297 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1298 }
1299 }
1300 }
1302 // Second pass: actually move nodes
1303 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1304 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1305 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1306 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1307 if (node->selected) {
1308 if (direct_range > 1e-6) {
1309 sp_nodepath_move_node_and_handles (node,
1310 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1311 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1312 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1313 } else {
1314 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1315 }
1317 }
1318 }
1319 }
1320 }
1322 // do not update repr here so that node dragging is acceptably fast
1323 update_object(nodepath);
1324 }
1327 /**
1328 * Move node selection to point, adjust its and neighbouring handles,
1329 * handle possible snapping, and commit the change with possible undo.
1330 */
1331 void
1332 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1333 {
1334 if (!nodepath) return;
1336 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1338 if (dx == 0) {
1339 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1340 } else if (dy == 0) {
1341 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1342 } else {
1343 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1344 }
1345 }
1347 /**
1348 * Move node selection off screen and commit the change.
1349 */
1350 void
1351 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1352 {
1353 // borrowed from sp_selection_move_screen in selection-chemistry.c
1354 // we find out the current zoom factor and divide deltas by it
1355 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1357 gdouble zoom = desktop->current_zoom();
1358 gdouble zdx = dx / zoom;
1359 gdouble zdy = dy / zoom;
1361 if (!nodepath) return;
1363 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1365 if (dx == 0) {
1366 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1367 } else if (dy == 0) {
1368 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1369 } else {
1370 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1371 }
1372 }
1374 /** If they don't yet exist, creates knot and line for the given side of the node */
1375 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1376 {
1377 if (!side->knot) {
1378 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"));
1380 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1381 side->knot->setSize (7);
1382 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1383 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1384 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1385 sp_knot_update_ctrl(side->knot);
1387 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1388 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1389 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1390 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1391 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1392 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1393 }
1395 if (!side->line) {
1396 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1397 SP_TYPE_CTRLLINE, NULL);
1398 }
1399 }
1401 /**
1402 * Ensure the given handle of the node is visible/invisible, update its screen position
1403 */
1404 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1405 {
1406 g_assert(node != NULL);
1408 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1409 NRPathcode code = sp_node_path_code_from_side(node, side);
1411 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1413 if (show_handle) {
1414 if (!side->knot) { // No handle knot at all
1415 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1416 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1417 side->knot->pos = side->pos;
1418 if (side->knot->item)
1419 SP_CTRL(side->knot->item)->moveto(side->pos);
1420 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1421 sp_knot_show(side->knot);
1422 } else {
1423 if (side->knot->pos != side->pos) { // only if it's really moved
1424 if (fire_move_signals) {
1425 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1426 } else {
1427 sp_knot_moveto(side->knot, &side->pos);
1428 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1429 }
1430 }
1431 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1432 sp_knot_show(side->knot);
1433 }
1434 }
1435 sp_canvas_item_show(side->line);
1436 } else {
1437 if (side->knot) {
1438 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1439 sp_knot_hide(side->knot);
1440 }
1441 }
1442 if (side->line) {
1443 sp_canvas_item_hide(side->line);
1444 }
1445 }
1446 }
1448 /**
1449 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1450 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1451 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1452 * updated; otherwise, just move the knots silently (used in batch moves).
1453 */
1454 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1455 {
1456 g_assert(node != NULL);
1458 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1459 sp_knot_show(node->knot);
1460 }
1462 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1463 if (fire_move_signals)
1464 sp_knot_set_position(node->knot, &node->pos, 0);
1465 else
1466 sp_knot_moveto(node->knot, &node->pos);
1467 }
1469 gboolean show_handles = node->selected;
1470 if (node->p.other != NULL) {
1471 if (node->p.other->selected) show_handles = TRUE;
1472 }
1473 if (node->n.other != NULL) {
1474 if (node->n.other->selected) show_handles = TRUE;
1475 }
1477 if (node->subpath->nodepath->show_handles == false)
1478 show_handles = FALSE;
1480 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1481 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1482 }
1484 /**
1485 * Call sp_node_update_handles() for all nodes on subpath.
1486 */
1487 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1488 {
1489 g_assert(subpath != NULL);
1491 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1492 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1493 }
1494 }
1496 /**
1497 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1498 */
1499 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1500 {
1501 g_assert(nodepath != NULL);
1503 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1504 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1505 }
1506 }
1508 void
1509 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1510 {
1511 if (nodepath == NULL) return;
1513 nodepath->show_handles = show;
1514 sp_nodepath_update_handles(nodepath);
1515 }
1517 /**
1518 * Adds all selected nodes in nodepath to list.
1519 */
1520 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1521 {
1522 StlConv<Node *>::list(l, selected);
1523 /// \todo this adds a copying, rework when the selection becomes a stl list
1524 }
1526 /**
1527 * Align selected nodes on the specified axis.
1528 */
1529 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1530 {
1531 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1532 return;
1533 }
1535 if ( !nodepath->selected->next ) { // only one node selected
1536 return;
1537 }
1538 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1539 NR::Point dest(pNode->pos);
1540 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1541 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1542 if (pNode) {
1543 dest[axis] = pNode->pos[axis];
1544 sp_node_moveto(pNode, dest);
1545 }
1546 }
1548 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1549 }
1551 /// Helper struct.
1552 struct NodeSort
1553 {
1554 Inkscape::NodePath::Node *_node;
1555 NR::Coord _coord;
1556 /// \todo use vectorof pointers instead of calling copy ctor
1557 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1558 _node(node), _coord(node->pos[axis])
1559 {}
1561 };
1563 static bool operator<(NodeSort const &a, NodeSort const &b)
1564 {
1565 return (a._coord < b._coord);
1566 }
1568 /**
1569 * Distribute selected nodes on the specified axis.
1570 */
1571 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1572 {
1573 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1574 return;
1575 }
1577 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1578 return;
1579 }
1581 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1582 std::vector<NodeSort> sorted;
1583 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1584 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1585 if (pNode) {
1586 NodeSort n(pNode, axis);
1587 sorted.push_back(n);
1588 //dest[axis] = pNode->pos[axis];
1589 //sp_node_moveto(pNode, dest);
1590 }
1591 }
1592 std::sort(sorted.begin(), sorted.end());
1593 unsigned int len = sorted.size();
1594 //overall bboxes span
1595 float dist = (sorted.back()._coord -
1596 sorted.front()._coord);
1597 //new distance between each bbox
1598 float step = (dist) / (len - 1);
1599 float pos = sorted.front()._coord;
1600 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1601 it < sorted.end();
1602 it ++ )
1603 {
1604 NR::Point dest((*it)._node->pos);
1605 dest[axis] = pos;
1606 sp_node_moveto((*it)._node, dest);
1607 pos += step;
1608 }
1610 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1611 }
1614 /**
1615 * Call sp_nodepath_line_add_node() for all selected segments.
1616 */
1617 void
1618 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1619 {
1620 if (!nodepath) {
1621 return;
1622 }
1624 GList *nl = NULL;
1626 int n_added = 0;
1628 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1629 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1630 g_assert(t->selected);
1631 if (t->p.other && t->p.other->selected) {
1632 nl = g_list_prepend(nl, t);
1633 }
1634 }
1636 while (nl) {
1637 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1638 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1639 sp_nodepath_node_select(n, TRUE, FALSE);
1640 n_added ++;
1641 nl = g_list_remove(nl, t);
1642 }
1644 /** \todo fixme: adjust ? */
1645 sp_nodepath_update_handles(nodepath);
1647 if (n_added > 1) {
1648 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1649 } else if (n_added > 0) {
1650 sp_nodepath_update_repr(nodepath, _("Add node"));
1651 }
1653 sp_nodepath_update_statusbar(nodepath);
1654 }
1656 /**
1657 * Select segment nearest to point
1658 */
1659 void
1660 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1661 {
1662 if (!nodepath) {
1663 return;
1664 }
1666 sp_nodepath_ensure_livarot_path(nodepath);
1667 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1668 if (!maybe_position) {
1669 return;
1670 }
1671 Path::cut_position position = *maybe_position;
1673 //find segment to segment
1674 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1676 //fixme: this can return NULL, so check before proceeding.
1677 g_return_if_fail(e != NULL);
1679 gboolean force = FALSE;
1680 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1681 force = TRUE;
1682 }
1683 sp_nodepath_node_select(e, (gboolean) toggle, force);
1684 if (e->p.other)
1685 sp_nodepath_node_select(e->p.other, TRUE, force);
1687 sp_nodepath_update_handles(nodepath);
1689 sp_nodepath_update_statusbar(nodepath);
1690 }
1692 /**
1693 * Add a node nearest to point
1694 */
1695 void
1696 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1697 {
1698 if (!nodepath) {
1699 return;
1700 }
1702 sp_nodepath_ensure_livarot_path(nodepath);
1703 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1704 if (!maybe_position) {
1705 return;
1706 }
1707 Path::cut_position position = *maybe_position;
1709 //find segment to split
1710 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1712 //don't know why but t seems to flip for lines
1713 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1714 position.t = 1.0 - position.t;
1715 }
1716 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1717 sp_nodepath_node_select(n, FALSE, TRUE);
1719 /* fixme: adjust ? */
1720 sp_nodepath_update_handles(nodepath);
1722 sp_nodepath_update_repr(nodepath, _("Add node"));
1724 sp_nodepath_update_statusbar(nodepath);
1725 }
1727 /*
1728 * Adjusts a segment so that t moves by a certain delta for dragging
1729 * converts lines to curves
1730 *
1731 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1732 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1733 */
1734 void
1735 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1736 {
1737 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1739 //fixme: e and e->p can be NULL, so check for those before proceeding
1740 g_return_if_fail(e != NULL);
1741 g_return_if_fail(&e->p != NULL);
1743 /* feel good is an arbitrary parameter that distributes the delta between handles
1744 * if t of the drag point is less than 1/6 distance form the endpoint only
1745 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1746 */
1747 double feel_good;
1748 if (t <= 1.0 / 6.0)
1749 feel_good = 0;
1750 else if (t <= 0.5)
1751 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1752 else if (t <= 5.0 / 6.0)
1753 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1754 else
1755 feel_good = 1;
1757 //if we're dragging a line convert it to a curve
1758 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1759 sp_nodepath_set_line_type(e, NR_CURVETO);
1760 }
1762 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1763 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1764 e->p.other->n.pos += offsetcoord0;
1765 e->p.pos += offsetcoord1;
1767 // adjust handles of adjacent nodes where necessary
1768 sp_node_adjust_handle(e,1);
1769 sp_node_adjust_handle(e->p.other,-1);
1771 sp_nodepath_update_handles(e->subpath->nodepath);
1773 update_object(e->subpath->nodepath);
1775 sp_nodepath_update_statusbar(e->subpath->nodepath);
1776 }
1779 /**
1780 * Call sp_nodepath_break() for all selected segments.
1781 */
1782 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1783 {
1784 if (!nodepath) return;
1786 GList *temp = NULL;
1787 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1788 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1789 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1790 if (nn == NULL) continue; // no break, no new node
1791 temp = g_list_prepend(temp, nn);
1792 }
1794 if (temp) {
1795 sp_nodepath_deselect(nodepath);
1796 }
1797 for (GList *l = temp; l != NULL; l = l->next) {
1798 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1799 }
1801 sp_nodepath_update_handles(nodepath);
1803 sp_nodepath_update_repr(nodepath, _("Break path"));
1804 }
1806 /**
1807 * Duplicate the selected node(s).
1808 */
1809 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1810 {
1811 if (!nodepath) {
1812 return;
1813 }
1815 GList *temp = NULL;
1816 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1817 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1818 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1819 if (nn == NULL) continue; // could not duplicate
1820 temp = g_list_prepend(temp, nn);
1821 }
1823 if (temp) {
1824 sp_nodepath_deselect(nodepath);
1825 }
1826 for (GList *l = temp; l != NULL; l = l->next) {
1827 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1828 }
1830 sp_nodepath_update_handles(nodepath);
1832 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1833 }
1835 /**
1836 * Join two nodes by merging them into one.
1837 */
1838 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1839 {
1840 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1842 if (g_list_length(nodepath->selected) != 2) {
1843 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1844 return;
1845 }
1847 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1848 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1850 g_assert(a != b);
1851 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1852 // someone tried to join an orphan node (i.e. a single-node subpath).
1853 // this is not worth an error message, just fail silently.
1854 return;
1855 }
1857 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1858 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1859 return;
1860 }
1862 /* a and b are endpoints */
1864 NR::Point c;
1865 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1866 c = a->pos;
1867 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1868 c = b->pos;
1869 } else {
1870 c = (a->pos + b->pos) / 2;
1871 }
1873 if (a->subpath == b->subpath) {
1874 Inkscape::NodePath::SubPath *sp = a->subpath;
1875 sp_nodepath_subpath_close(sp);
1876 sp_node_moveto (sp->first, c);
1878 sp_nodepath_update_handles(sp->nodepath);
1879 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1880 return;
1881 }
1883 /* a and b are separate subpaths */
1884 Inkscape::NodePath::SubPath *sa = a->subpath;
1885 Inkscape::NodePath::SubPath *sb = b->subpath;
1886 NR::Point p;
1887 Inkscape::NodePath::Node *n;
1888 NRPathcode code;
1889 if (a == sa->first) {
1890 p = sa->first->n.pos;
1891 code = (NRPathcode)sa->first->n.other->code;
1892 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1893 n = sa->last;
1894 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1895 n = n->p.other;
1896 while (n) {
1897 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1898 n = n->p.other;
1899 if (n == sa->first) n = NULL;
1900 }
1901 sp_nodepath_subpath_destroy(sa);
1902 sa = t;
1903 } else if (a == sa->last) {
1904 p = sa->last->p.pos;
1905 code = (NRPathcode)sa->last->code;
1906 sp_nodepath_node_destroy(sa->last);
1907 } else {
1908 code = NR_END;
1909 g_assert_not_reached();
1910 }
1912 if (b == sb->first) {
1913 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1914 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1915 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1916 }
1917 } else if (b == sb->last) {
1918 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1919 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1920 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1921 }
1922 } else {
1923 g_assert_not_reached();
1924 }
1925 /* and now destroy sb */
1927 sp_nodepath_subpath_destroy(sb);
1929 sp_nodepath_update_handles(sa->nodepath);
1931 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1933 sp_nodepath_update_statusbar(nodepath);
1934 }
1936 /**
1937 * Join two nodes by adding a segment between them.
1938 */
1939 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1940 {
1941 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1943 if (g_list_length(nodepath->selected) != 2) {
1944 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1945 return;
1946 }
1948 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1949 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1951 g_assert(a != b);
1952 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1953 // someone tried to join an orphan node (i.e. a single-node subpath).
1954 // this is not worth an error message, just fail silently.
1955 return;
1956 }
1958 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1959 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1960 return;
1961 }
1963 if (a->subpath == b->subpath) {
1964 Inkscape::NodePath::SubPath *sp = a->subpath;
1966 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1967 sp->closed = TRUE;
1969 sp->first->p.other = sp->last;
1970 sp->last->n.other = sp->first;
1972 sp_node_handle_mirror_p_to_n(sp->last);
1973 sp_node_handle_mirror_n_to_p(sp->first);
1975 sp->first->code = sp->last->code;
1976 sp->first = sp->last;
1978 sp_nodepath_update_handles(sp->nodepath);
1980 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1982 return;
1983 }
1985 /* a and b are separate subpaths */
1986 Inkscape::NodePath::SubPath *sa = a->subpath;
1987 Inkscape::NodePath::SubPath *sb = b->subpath;
1989 Inkscape::NodePath::Node *n;
1990 NR::Point p;
1991 NRPathcode code;
1992 if (a == sa->first) {
1993 code = (NRPathcode) sa->first->n.other->code;
1994 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1995 n = sa->last;
1996 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1997 for (n = n->p.other; n != NULL; n = n->p.other) {
1998 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1999 }
2000 sp_nodepath_subpath_destroy(sa);
2001 sa = t;
2002 } else if (a == sa->last) {
2003 code = (NRPathcode)sa->last->code;
2004 } else {
2005 code = NR_END;
2006 g_assert_not_reached();
2007 }
2009 if (b == sb->first) {
2010 n = sb->first;
2011 sp_node_handle_mirror_p_to_n(sa->last);
2012 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2013 sp_node_handle_mirror_n_to_p(sa->last);
2014 for (n = n->n.other; n != NULL; n = n->n.other) {
2015 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2016 }
2017 } else if (b == sb->last) {
2018 n = sb->last;
2019 sp_node_handle_mirror_p_to_n(sa->last);
2020 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2021 sp_node_handle_mirror_n_to_p(sa->last);
2022 for (n = n->p.other; n != NULL; n = n->p.other) {
2023 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2024 }
2025 } else {
2026 g_assert_not_reached();
2027 }
2028 /* and now destroy sb */
2030 sp_nodepath_subpath_destroy(sb);
2032 sp_nodepath_update_handles(sa->nodepath);
2034 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2035 }
2037 /**
2038 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2039 */
2040 void sp_node_delete_preserve(GList *nodes_to_delete)
2041 {
2042 GSList *nodepaths = NULL;
2044 while (nodes_to_delete) {
2045 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2046 Inkscape::NodePath::SubPath *sp = node->subpath;
2047 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2048 Inkscape::NodePath::Node *sample_cursor = NULL;
2049 Inkscape::NodePath::Node *sample_end = NULL;
2050 Inkscape::NodePath::Node *delete_cursor = node;
2051 bool just_delete = false;
2053 //find the start of this contiguous selection
2054 //move left to the first node that is not selected
2055 //or the start of the non-closed path
2056 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2057 delete_cursor = curr;
2058 }
2060 //just delete at the beginning of an open path
2061 if (!delete_cursor->p.other) {
2062 sample_cursor = delete_cursor;
2063 just_delete = true;
2064 } else {
2065 sample_cursor = delete_cursor->p.other;
2066 }
2068 //calculate points for each segment
2069 int rate = 5;
2070 float period = 1.0 / rate;
2071 std::vector<NR::Point> data;
2072 if (!just_delete) {
2073 data.push_back(sample_cursor->pos);
2074 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2075 //just delete at the end of an open path
2076 if (!sp->closed && curr == sp->last) {
2077 just_delete = true;
2078 break;
2079 }
2081 //sample points on the contiguous selected segment
2082 NR::Point *bez;
2083 bez = new NR::Point [4];
2084 bez[0] = curr->pos;
2085 bez[1] = curr->n.pos;
2086 bez[2] = curr->n.other->p.pos;
2087 bez[3] = curr->n.other->pos;
2088 for (int i=1; i<rate; i++) {
2089 gdouble t = i * period;
2090 NR::Point p = bezier_pt(3, bez, t);
2091 data.push_back(p);
2092 }
2093 data.push_back(curr->n.other->pos);
2095 sample_end = curr->n.other;
2096 //break if we've come full circle or hit the end of the selection
2097 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2098 break;
2099 }
2100 }
2101 }
2103 if (!just_delete) {
2104 //calculate the best fitting single segment and adjust the endpoints
2105 NR::Point *adata;
2106 adata = new NR::Point [data.size()];
2107 copy(data.begin(), data.end(), adata);
2109 NR::Point *bez;
2110 bez = new NR::Point [4];
2111 //would decreasing error create a better fitting approximation?
2112 gdouble error = 1.0;
2113 gint ret;
2114 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2116 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2117 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2118 //the resulting nodes behave as expected.
2119 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2120 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2122 //adjust endpoints
2123 sample_cursor->n.pos = bez[1];
2124 sample_end->p.pos = bez[2];
2125 }
2127 //destroy this contiguous selection
2128 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2129 Inkscape::NodePath::Node *temp = delete_cursor;
2130 if (delete_cursor->n.other == delete_cursor) {
2131 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2132 delete_cursor = NULL;
2133 } else {
2134 delete_cursor = delete_cursor->n.other;
2135 }
2136 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2137 sp_nodepath_node_destroy(temp);
2138 }
2140 sp_nodepath_update_handles(nodepath);
2142 if (!g_slist_find(nodepaths, nodepath))
2143 nodepaths = g_slist_prepend (nodepaths, nodepath);
2144 }
2146 for (GSList *i = nodepaths; i; i = i->next) {
2147 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2148 // different nodepaths will give us one undo event per nodepath
2149 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2151 // if the entire nodepath is removed, delete the selected object.
2152 if (nodepath->subpaths == NULL ||
2153 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2154 //at least 2
2155 sp_nodepath_get_node_count(nodepath) < 2) {
2156 SPDocument *document = sp_desktop_document (nodepath->desktop);
2157 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2158 //delete this nodepath's object, not the entire selection! (though at this time, this
2159 //does not matter)
2160 sp_selection_delete();
2161 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2162 _("Delete nodes"));
2163 } else {
2164 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2165 sp_nodepath_update_statusbar(nodepath);
2166 }
2167 }
2169 g_slist_free (nodepaths);
2170 }
2172 /**
2173 * Delete one or more selected nodes.
2174 */
2175 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2176 {
2177 if (!nodepath) return;
2178 if (!nodepath->selected) return;
2180 /** \todo fixme: do it the right way */
2181 while (nodepath->selected) {
2182 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2183 sp_nodepath_node_destroy(node);
2184 }
2187 //clean up the nodepath (such as for trivial subpaths)
2188 sp_nodepath_cleanup(nodepath);
2190 sp_nodepath_update_handles(nodepath);
2192 // if the entire nodepath is removed, delete the selected object.
2193 if (nodepath->subpaths == NULL ||
2194 sp_nodepath_get_node_count(nodepath) < 2) {
2195 SPDocument *document = sp_desktop_document (nodepath->desktop);
2196 sp_selection_delete();
2197 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2198 _("Delete nodes"));
2199 return;
2200 }
2202 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2204 sp_nodepath_update_statusbar(nodepath);
2205 }
2207 /**
2208 * Delete one or more segments between two selected nodes.
2209 * This is the code for 'split'.
2210 */
2211 void
2212 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2213 {
2214 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2215 Inkscape::NodePath::Node *curr, *next; //Iterators
2217 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2219 if (g_list_length(nodepath->selected) != 2) {
2220 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2221 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2222 return;
2223 }
2225 //Selected nodes, not inclusive
2226 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2227 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2229 if ( ( a==b) || //same node
2230 (a->subpath != b->subpath ) || //not the same path
2231 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2232 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2233 {
2234 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2235 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2236 return;
2237 }
2239 //###########################################
2240 //# BEGIN EDITS
2241 //###########################################
2242 //##################################
2243 //# CLOSED PATH
2244 //##################################
2245 if (a->subpath->closed) {
2248 gboolean reversed = FALSE;
2250 //Since we can go in a circle, we need to find the shorter distance.
2251 // a->b or b->a
2252 start = end = NULL;
2253 int distance = 0;
2254 int minDistance = 0;
2255 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2256 if (curr==b) {
2257 //printf("a to b:%d\n", distance);
2258 start = a;//go from a to b
2259 end = b;
2260 minDistance = distance;
2261 //printf("A to B :\n");
2262 break;
2263 }
2264 distance++;
2265 }
2267 //try again, the other direction
2268 distance = 0;
2269 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2270 if (curr==a) {
2271 //printf("b to a:%d\n", distance);
2272 if (distance < minDistance) {
2273 start = b; //we go from b to a
2274 end = a;
2275 reversed = TRUE;
2276 //printf("B to A\n");
2277 }
2278 break;
2279 }
2280 distance++;
2281 }
2284 //Copy everything from 'end' to 'start' to a new subpath
2285 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2286 for (curr=end ; curr ; curr=curr->n.other) {
2287 NRPathcode code = (NRPathcode) curr->code;
2288 if (curr == end)
2289 code = NR_MOVETO;
2290 sp_nodepath_node_new(t, NULL,
2291 (Inkscape::NodePath::NodeType)curr->type, code,
2292 &curr->p.pos, &curr->pos, &curr->n.pos);
2293 if (curr == start)
2294 break;
2295 }
2296 sp_nodepath_subpath_destroy(a->subpath);
2299 }
2303 //##################################
2304 //# OPEN PATH
2305 //##################################
2306 else {
2308 //We need to get the direction of the list between A and B
2309 //Can we walk from a to b?
2310 start = end = NULL;
2311 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2312 if (curr==b) {
2313 start = a; //did it! we go from a to b
2314 end = b;
2315 //printf("A to B\n");
2316 break;
2317 }
2318 }
2319 if (!start) {//didn't work? let's try the other direction
2320 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2321 if (curr==a) {
2322 start = b; //did it! we go from b to a
2323 end = a;
2324 //printf("B to A\n");
2325 break;
2326 }
2327 }
2328 }
2329 if (!start) {
2330 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2331 _("Cannot find path between nodes."));
2332 return;
2333 }
2337 //Copy everything after 'end' to a new subpath
2338 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2339 for (curr=end ; curr ; curr=curr->n.other) {
2340 NRPathcode code = (NRPathcode) curr->code;
2341 if (curr == end)
2342 code = NR_MOVETO;
2343 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2344 &curr->p.pos, &curr->pos, &curr->n.pos);
2345 }
2347 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2348 for (curr = start->n.other ; curr ; curr=next) {
2349 next = curr->n.other;
2350 sp_nodepath_node_destroy(curr);
2351 }
2353 }
2354 //###########################################
2355 //# END EDITS
2356 //###########################################
2358 //clean up the nodepath (such as for trivial subpaths)
2359 sp_nodepath_cleanup(nodepath);
2361 sp_nodepath_update_handles(nodepath);
2363 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2365 sp_nodepath_update_statusbar(nodepath);
2366 }
2368 /**
2369 * Call sp_nodepath_set_line() for all selected segments.
2370 */
2371 void
2372 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2373 {
2374 if (nodepath == NULL) return;
2376 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2377 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2378 g_assert(n->selected);
2379 if (n->p.other && n->p.other->selected) {
2380 sp_nodepath_set_line_type(n, code);
2381 }
2382 }
2384 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2385 }
2387 /**
2388 * Call sp_nodepath_convert_node_type() for all selected nodes.
2389 */
2390 void
2391 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2392 {
2393 if (nodepath == NULL) return;
2395 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2397 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2398 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2399 }
2401 sp_nodepath_update_repr(nodepath, _("Change node type"));
2402 }
2404 /**
2405 * Change select status of node, update its own and neighbour handles.
2406 */
2407 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2408 {
2409 node->selected = selected;
2411 if (selected) {
2412 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2413 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2414 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2415 sp_knot_update_ctrl(node->knot);
2416 } else {
2417 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2418 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2419 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2420 sp_knot_update_ctrl(node->knot);
2421 }
2423 sp_node_update_handles(node);
2424 if (node->n.other) sp_node_update_handles(node->n.other);
2425 if (node->p.other) sp_node_update_handles(node->p.other);
2426 }
2428 /**
2429 \brief Select a node
2430 \param node The node to select
2431 \param incremental If true, add to selection, otherwise deselect others
2432 \param override If true, always select this node, otherwise toggle selected status
2433 */
2434 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2435 {
2436 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2438 if (incremental) {
2439 if (override) {
2440 if (!g_list_find(nodepath->selected, node)) {
2441 nodepath->selected = g_list_prepend(nodepath->selected, node);
2442 }
2443 sp_node_set_selected(node, TRUE);
2444 } else { // toggle
2445 if (node->selected) {
2446 g_assert(g_list_find(nodepath->selected, node));
2447 nodepath->selected = g_list_remove(nodepath->selected, node);
2448 } else {
2449 g_assert(!g_list_find(nodepath->selected, node));
2450 nodepath->selected = g_list_prepend(nodepath->selected, node);
2451 }
2452 sp_node_set_selected(node, !node->selected);
2453 }
2454 } else {
2455 sp_nodepath_deselect(nodepath);
2456 nodepath->selected = g_list_prepend(nodepath->selected, node);
2457 sp_node_set_selected(node, TRUE);
2458 }
2460 sp_nodepath_update_statusbar(nodepath);
2461 }
2464 /**
2465 \brief Deselect all nodes in the nodepath
2466 */
2467 void
2468 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2469 {
2470 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2472 while (nodepath->selected) {
2473 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2474 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2475 }
2476 sp_nodepath_update_statusbar(nodepath);
2477 }
2479 /**
2480 \brief Select or invert selection of all nodes in the nodepath
2481 */
2482 void
2483 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2484 {
2485 if (!nodepath) return;
2487 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2488 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2489 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2490 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2491 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2492 }
2493 }
2494 }
2496 /**
2497 * If nothing selected, does the same as sp_nodepath_select_all();
2498 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2499 * (i.e., similar to "select all in layer", with the "selected" subpaths
2500 * being treated as "layers" in the path).
2501 */
2502 void
2503 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2504 {
2505 if (!nodepath) return;
2507 if (g_list_length (nodepath->selected) == 0) {
2508 sp_nodepath_select_all (nodepath, invert);
2509 return;
2510 }
2512 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2513 GSList *subpaths = NULL;
2515 for (GList *l = copy; l != NULL; l = l->next) {
2516 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2517 Inkscape::NodePath::SubPath *subpath = n->subpath;
2518 if (!g_slist_find (subpaths, subpath))
2519 subpaths = g_slist_prepend (subpaths, subpath);
2520 }
2522 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2523 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2524 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2525 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2526 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2527 }
2528 }
2530 g_slist_free (subpaths);
2531 g_list_free (copy);
2532 }
2534 /**
2535 * \brief Select the node after the last selected; if none is selected,
2536 * select the first within path.
2537 */
2538 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2539 {
2540 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2542 Inkscape::NodePath::Node *last = NULL;
2543 if (nodepath->selected) {
2544 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2545 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2546 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2547 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2548 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2549 if (node->selected) {
2550 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2551 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2552 if (spl->next) { // there's a next subpath
2553 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2554 last = subpath_next->first;
2555 } else if (spl->prev) { // there's a previous subpath
2556 last = NULL; // to be set later to the first node of first subpath
2557 } else {
2558 last = node->n.other;
2559 }
2560 } else {
2561 last = node->n.other;
2562 }
2563 } else {
2564 if (node->n.other) {
2565 last = node->n.other;
2566 } else {
2567 if (spl->next) { // there's a next subpath
2568 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2569 last = subpath_next->first;
2570 } else if (spl->prev) { // there's a previous subpath
2571 last = NULL; // to be set later to the first node of first subpath
2572 } else {
2573 last = (Inkscape::NodePath::Node *) subpath->first;
2574 }
2575 }
2576 }
2577 }
2578 }
2579 }
2580 sp_nodepath_deselect(nodepath);
2581 }
2583 if (last) { // there's at least one more node after selected
2584 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2585 } else { // no more nodes, select the first one in first subpath
2586 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2587 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2588 }
2589 }
2591 /**
2592 * \brief Select the node before the first selected; if none is selected,
2593 * select the last within path
2594 */
2595 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2596 {
2597 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2599 Inkscape::NodePath::Node *last = NULL;
2600 if (nodepath->selected) {
2601 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2602 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2603 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2604 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2605 if (node->selected) {
2606 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2607 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2608 if (spl->prev) { // there's a prev subpath
2609 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2610 last = subpath_prev->last;
2611 } else if (spl->next) { // there's a next subpath
2612 last = NULL; // to be set later to the last node of last subpath
2613 } else {
2614 last = node->p.other;
2615 }
2616 } else {
2617 last = node->p.other;
2618 }
2619 } else {
2620 if (node->p.other) {
2621 last = node->p.other;
2622 } else {
2623 if (spl->prev) { // there's a prev subpath
2624 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2625 last = subpath_prev->last;
2626 } else if (spl->next) { // there's a next subpath
2627 last = NULL; // to be set later to the last node of last subpath
2628 } else {
2629 last = (Inkscape::NodePath::Node *) subpath->last;
2630 }
2631 }
2632 }
2633 }
2634 }
2635 }
2636 sp_nodepath_deselect(nodepath);
2637 }
2639 if (last) { // there's at least one more node before selected
2640 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2641 } else { // no more nodes, select the last one in last subpath
2642 GList *spl = g_list_last(nodepath->subpaths);
2643 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2644 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2645 }
2646 }
2648 /**
2649 * \brief Select all nodes that are within the rectangle.
2650 */
2651 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2652 {
2653 if (!incremental) {
2654 sp_nodepath_deselect(nodepath);
2655 }
2657 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2658 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2659 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2660 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2662 if (b.contains(node->pos)) {
2663 sp_nodepath_node_select(node, TRUE, TRUE);
2664 }
2665 }
2666 }
2667 }
2670 void
2671 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2672 {
2673 g_assert (n);
2674 g_assert (nodepath);
2675 g_assert (n->subpath->nodepath == nodepath);
2677 if (g_list_length (nodepath->selected) == 0) {
2678 if (grow > 0) {
2679 sp_nodepath_node_select(n, TRUE, TRUE);
2680 }
2681 return;
2682 }
2684 if (g_list_length (nodepath->selected) == 1) {
2685 if (grow < 0) {
2686 sp_nodepath_deselect (nodepath);
2687 return;
2688 }
2689 }
2691 double n_sel_range = 0, p_sel_range = 0;
2692 Inkscape::NodePath::Node *farthest_n_node = n;
2693 Inkscape::NodePath::Node *farthest_p_node = n;
2695 // Calculate ranges
2696 {
2697 double n_range = 0, p_range = 0;
2698 bool n_going = true, p_going = true;
2699 Inkscape::NodePath::Node *n_node = n;
2700 Inkscape::NodePath::Node *p_node = n;
2701 do {
2702 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2703 if (n_node && n_going)
2704 n_node = n_node->n.other;
2705 if (n_node == NULL) {
2706 n_going = false;
2707 } else {
2708 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2709 if (n_node->selected) {
2710 n_sel_range = n_range;
2711 farthest_n_node = n_node;
2712 }
2713 if (n_node == p_node) {
2714 n_going = false;
2715 p_going = false;
2716 }
2717 }
2718 if (p_node && p_going)
2719 p_node = p_node->p.other;
2720 if (p_node == NULL) {
2721 p_going = false;
2722 } else {
2723 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2724 if (p_node->selected) {
2725 p_sel_range = p_range;
2726 farthest_p_node = p_node;
2727 }
2728 if (p_node == n_node) {
2729 n_going = false;
2730 p_going = false;
2731 }
2732 }
2733 } while (n_going || p_going);
2734 }
2736 if (grow > 0) {
2737 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2738 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2739 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2740 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2741 }
2742 } else {
2743 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2744 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2745 } else if (farthest_p_node && farthest_p_node->selected) {
2746 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2747 }
2748 }
2749 }
2751 void
2752 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2753 {
2754 g_assert (n);
2755 g_assert (nodepath);
2756 g_assert (n->subpath->nodepath == nodepath);
2758 if (g_list_length (nodepath->selected) == 0) {
2759 if (grow > 0) {
2760 sp_nodepath_node_select(n, TRUE, TRUE);
2761 }
2762 return;
2763 }
2765 if (g_list_length (nodepath->selected) == 1) {
2766 if (grow < 0) {
2767 sp_nodepath_deselect (nodepath);
2768 return;
2769 }
2770 }
2772 Inkscape::NodePath::Node *farthest_selected = NULL;
2773 double farthest_dist = 0;
2775 Inkscape::NodePath::Node *closest_unselected = NULL;
2776 double closest_dist = NR_HUGE;
2778 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2779 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2780 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2781 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2782 if (node == n)
2783 continue;
2784 if (node->selected) {
2785 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2786 farthest_dist = NR::L2(node->pos - n->pos);
2787 farthest_selected = node;
2788 }
2789 } else {
2790 if (NR::L2(node->pos - n->pos) < closest_dist) {
2791 closest_dist = NR::L2(node->pos - n->pos);
2792 closest_unselected = node;
2793 }
2794 }
2795 }
2796 }
2798 if (grow > 0) {
2799 if (closest_unselected) {
2800 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2801 }
2802 } else {
2803 if (farthest_selected) {
2804 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2805 }
2806 }
2807 }
2810 /**
2811 \brief Saves all nodes' and handles' current positions in their origin members
2812 */
2813 void
2814 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2815 {
2816 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2817 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2818 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2819 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2820 n->origin = n->pos;
2821 n->p.origin = n->p.pos;
2822 n->n.origin = n->n.pos;
2823 }
2824 }
2825 }
2827 /**
2828 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2829 */
2830 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2831 {
2832 if (!nodepath->selected) {
2833 return NULL;
2834 }
2836 GList *r = NULL;
2837 guint i = 0;
2838 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2839 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2840 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2841 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2842 i++;
2843 if (node->selected) {
2844 r = g_list_append(r, GINT_TO_POINTER(i));
2845 }
2846 }
2847 }
2848 return r;
2849 }
2851 /**
2852 \brief Restores selection by selecting nodes whose positions are in the list
2853 */
2854 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2855 {
2856 sp_nodepath_deselect(nodepath);
2858 guint i = 0;
2859 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2860 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2861 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2862 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2863 i++;
2864 if (g_list_find(r, GINT_TO_POINTER(i))) {
2865 sp_nodepath_node_select(node, TRUE, TRUE);
2866 }
2867 }
2868 }
2870 }
2872 /**
2873 \brief Adjusts handle according to node type and line code.
2874 */
2875 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2876 {
2877 double len, otherlen, linelen;
2879 g_assert(node);
2881 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2882 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2884 /** \todo fixme: */
2885 if (me->other == NULL) return;
2886 if (other->other == NULL) return;
2888 /* I have line */
2890 NRPathcode mecode, ocode;
2891 if (which_adjust == 1) {
2892 mecode = (NRPathcode)me->other->code;
2893 ocode = (NRPathcode)node->code;
2894 } else {
2895 mecode = (NRPathcode)node->code;
2896 ocode = (NRPathcode)other->other->code;
2897 }
2899 if (mecode == NR_LINETO) return;
2901 /* I am curve */
2903 if (other->other == NULL) return;
2905 /* Other has line */
2907 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2909 NR::Point delta;
2910 if (ocode == NR_LINETO) {
2911 /* other is lineto, we are either smooth or symm */
2912 Inkscape::NodePath::Node *othernode = other->other;
2913 len = NR::L2(me->pos - node->pos);
2914 delta = node->pos - othernode->pos;
2915 linelen = NR::L2(delta);
2916 if (linelen < 1e-18)
2917 return;
2918 me->pos = node->pos + (len / linelen)*delta;
2919 return;
2920 }
2922 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2924 me->pos = 2 * node->pos - other->pos;
2925 return;
2926 }
2928 /* We are smooth */
2930 len = NR::L2(me->pos - node->pos);
2931 delta = other->pos - node->pos;
2932 otherlen = NR::L2(delta);
2933 if (otherlen < 1e-18) return;
2935 me->pos = node->pos - (len / otherlen) * delta;
2936 }
2938 /**
2939 \brief Adjusts both handles according to node type and line code
2940 */
2941 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2942 {
2943 g_assert(node);
2945 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2947 /* we are either smooth or symm */
2949 if (node->p.other == NULL) return;
2951 if (node->n.other == NULL) return;
2953 if (node->code == NR_LINETO) {
2954 if (node->n.other->code == NR_LINETO) return;
2955 sp_node_adjust_handle(node, 1);
2956 return;
2957 }
2959 if (node->n.other->code == NR_LINETO) {
2960 if (node->code == NR_LINETO) return;
2961 sp_node_adjust_handle(node, -1);
2962 return;
2963 }
2965 /* both are curves */
2966 NR::Point const delta( node->n.pos - node->p.pos );
2968 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2969 node->p.pos = node->pos - delta / 2;
2970 node->n.pos = node->pos + delta / 2;
2971 return;
2972 }
2974 /* We are smooth */
2975 double plen = NR::L2(node->p.pos - node->pos);
2976 if (plen < 1e-18) return;
2977 double nlen = NR::L2(node->n.pos - node->pos);
2978 if (nlen < 1e-18) return;
2979 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2980 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2981 }
2983 /**
2984 * Node event callback.
2985 */
2986 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
2987 {
2988 gboolean ret = FALSE;
2989 switch (event->type) {
2990 case GDK_ENTER_NOTIFY:
2991 Inkscape::NodePath::Path::active_node = n;
2992 break;
2993 case GDK_LEAVE_NOTIFY:
2994 Inkscape::NodePath::Path::active_node = NULL;
2995 break;
2996 case GDK_SCROLL:
2997 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2998 switch (event->scroll.direction) {
2999 case GDK_SCROLL_UP:
3000 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3001 break;
3002 case GDK_SCROLL_DOWN:
3003 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3004 break;
3005 default:
3006 break;
3007 }
3008 ret = TRUE;
3009 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3010 switch (event->scroll.direction) {
3011 case GDK_SCROLL_UP:
3012 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3013 break;
3014 case GDK_SCROLL_DOWN:
3015 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3016 break;
3017 default:
3018 break;
3019 }
3020 ret = TRUE;
3021 }
3022 break;
3023 case GDK_KEY_PRESS:
3024 switch (get_group0_keyval (&event->key)) {
3025 case GDK_space:
3026 if (event->key.state & GDK_BUTTON1_MASK) {
3027 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3028 stamp_repr(nodepath);
3029 ret = TRUE;
3030 }
3031 break;
3032 case GDK_Page_Up:
3033 if (event->key.state & GDK_CONTROL_MASK) {
3034 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3035 } else {
3036 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3037 }
3038 break;
3039 case GDK_Page_Down:
3040 if (event->key.state & GDK_CONTROL_MASK) {
3041 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3042 } else {
3043 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3044 }
3045 break;
3046 default:
3047 break;
3048 }
3049 break;
3050 default:
3051 break;
3052 }
3054 return ret;
3055 }
3057 /**
3058 * Handle keypress on node; directly called.
3059 */
3060 gboolean node_key(GdkEvent *event)
3061 {
3062 Inkscape::NodePath::Path *np;
3064 // there is no way to verify nodes so set active_node to nil when deleting!!
3065 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3067 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3068 gint ret = FALSE;
3069 switch (get_group0_keyval (&event->key)) {
3070 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3071 case GDK_BackSpace:
3072 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3073 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3074 sp_nodepath_update_repr(np, _("Delete node"));
3075 Inkscape::NodePath::Path::active_node = NULL;
3076 ret = TRUE;
3077 break;
3078 case GDK_c:
3079 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3080 ret = TRUE;
3081 break;
3082 case GDK_s:
3083 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3084 ret = TRUE;
3085 break;
3086 case GDK_y:
3087 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3088 ret = TRUE;
3089 break;
3090 case GDK_b:
3091 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3092 ret = TRUE;
3093 break;
3094 }
3095 return ret;
3096 }
3097 return FALSE;
3098 }
3100 /**
3101 * Mouseclick on node callback.
3102 */
3103 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3104 {
3105 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3107 if (state & GDK_CONTROL_MASK) {
3108 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3110 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3111 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3112 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3113 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3114 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3115 } else {
3116 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3117 }
3118 sp_nodepath_update_repr(nodepath, _("Change node type"));
3119 sp_nodepath_update_statusbar(nodepath);
3121 } else { //ctrl+alt+click: delete node
3122 GList *node_to_delete = NULL;
3123 node_to_delete = g_list_append(node_to_delete, n);
3124 sp_node_delete_preserve(node_to_delete);
3125 }
3127 } else {
3128 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3129 }
3130 }
3132 /**
3133 * Mouse grabbed node callback.
3134 */
3135 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3136 {
3137 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3139 if (!n->selected) {
3140 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3141 }
3143 n->is_dragging = true;
3144 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3146 sp_nodepath_remember_origins (n->subpath->nodepath);
3147 }
3149 /**
3150 * Mouse ungrabbed node callback.
3151 */
3152 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3153 {
3154 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3156 n->dragging_out = NULL;
3157 n->is_dragging = false;
3158 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3160 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3161 }
3163 /**
3164 * The point on a line, given by its angle, closest to the given point.
3165 * \param p A point.
3166 * \param a Angle of the line; it is assumed to go through coordinate origin.
3167 * \param closest Pointer to the point struct where the result is stored.
3168 * \todo FIXME: use dot product perhaps?
3169 */
3170 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3171 {
3172 if (a == HUGE_VAL) { // vertical
3173 *closest = NR::Point(0, (*p)[NR::Y]);
3174 } else {
3175 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3176 (*closest)[NR::Y] = a * (*closest)[NR::X];
3177 }
3178 }
3180 /**
3181 * Distance from the point to a line given by its angle.
3182 * \param p A point.
3183 * \param a Angle of the line; it is assumed to go through coordinate origin.
3184 */
3185 static double point_line_distance(NR::Point *p, double a)
3186 {
3187 NR::Point c;
3188 point_line_closest(p, a, &c);
3189 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]));
3190 }
3192 /**
3193 * Callback for node "request" signal.
3194 * \todo fixme: This goes to "moved" event? (lauris)
3195 */
3196 static gboolean
3197 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3198 {
3199 double yn, xn, yp, xp;
3200 double an, ap, na, pa;
3201 double d_an, d_ap, d_na, d_pa;
3202 gboolean collinear = FALSE;
3203 NR::Point c;
3204 NR::Point pr;
3206 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3208 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3209 if ( (!n->subpath->nodepath->straight_path) &&
3210 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3211 || n->dragging_out ) )
3212 {
3213 NR::Point mouse = (*p);
3215 if (!n->dragging_out) {
3216 // This is the first drag-out event; find out which handle to drag out
3217 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3218 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3220 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3221 return FALSE;
3223 Inkscape::NodePath::NodeSide *opposite;
3224 if (appr_p > appr_n) { // closer to p
3225 n->dragging_out = &n->p;
3226 opposite = &n->n;
3227 n->code = NR_CURVETO;
3228 } else if (appr_p < appr_n) { // closer to n
3229 n->dragging_out = &n->n;
3230 opposite = &n->p;
3231 n->n.other->code = NR_CURVETO;
3232 } else { // p and n nodes are the same
3233 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3234 n->dragging_out = &n->p;
3235 opposite = &n->n;
3236 n->code = NR_CURVETO;
3237 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3238 n->dragging_out = &n->n;
3239 opposite = &n->p;
3240 n->n.other->code = NR_CURVETO;
3241 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3242 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);
3243 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);
3244 if (appr_other_p > appr_other_n) { // closer to other's p handle
3245 n->dragging_out = &n->n;
3246 opposite = &n->p;
3247 n->n.other->code = NR_CURVETO;
3248 } else { // closer to other's n handle
3249 n->dragging_out = &n->p;
3250 opposite = &n->n;
3251 n->code = NR_CURVETO;
3252 }
3253 }
3254 }
3256 // if there's another handle, make sure the one we drag out starts parallel to it
3257 if (opposite->pos != n->pos) {
3258 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3259 }
3261 // knots might not be created yet!
3262 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3263 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3264 }
3266 // pass this on to the handle-moved callback
3267 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3268 sp_node_update_handles(n);
3269 return TRUE;
3270 }
3272 if (state & GDK_CONTROL_MASK) { // constrained motion
3274 // calculate relative distances of handles
3275 // n handle:
3276 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3277 xn = n->n.pos[NR::X] - n->pos[NR::X];
3278 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3279 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3280 if (n->n.other) { // if there is the next point
3281 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3282 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3283 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3284 }
3285 }
3286 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3287 if (yn < 0) { xn = -xn; yn = -yn; }
3289 // p handle:
3290 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3291 xp = n->p.pos[NR::X] - n->pos[NR::X];
3292 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3293 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3294 if (n->p.other) {
3295 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3296 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3297 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3298 }
3299 }
3300 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3301 if (yp < 0) { xp = -xp; yp = -yp; }
3303 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3304 // sliding on handles, only if at least one of the handles is non-vertical
3305 // (otherwise it's the same as ctrl+drag anyway)
3307 // calculate angles of the handles
3308 if (xn == 0) {
3309 if (yn == 0) { // no handle, consider it the continuation of the other one
3310 an = 0;
3311 collinear = TRUE;
3312 }
3313 else an = 0; // vertical; set the angle to horizontal
3314 } else an = yn/xn;
3316 if (xp == 0) {
3317 if (yp == 0) { // no handle, consider it the continuation of the other one
3318 ap = an;
3319 }
3320 else ap = 0; // vertical; set the angle to horizontal
3321 } else ap = yp/xp;
3323 if (collinear) an = ap;
3325 // angles of the perpendiculars; HUGE_VAL means vertical
3326 if (an == 0) na = HUGE_VAL; else na = -1/an;
3327 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3329 // mouse point relative to the node's original pos
3330 pr = (*p) - n->origin;
3332 // distances to the four lines (two handles and two perpendiculars)
3333 d_an = point_line_distance(&pr, an);
3334 d_na = point_line_distance(&pr, na);
3335 d_ap = point_line_distance(&pr, ap);
3336 d_pa = point_line_distance(&pr, pa);
3338 // find out which line is the closest, save its closest point in c
3339 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3340 point_line_closest(&pr, an, &c);
3341 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3342 point_line_closest(&pr, ap, &c);
3343 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3344 point_line_closest(&pr, na, &c);
3345 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3346 point_line_closest(&pr, pa, &c);
3347 }
3349 // move the node to the closest point
3350 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3351 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3352 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3354 } else { // constraining to hor/vert
3356 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3357 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3358 } else { // snap to vert
3359 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3360 }
3361 }
3362 } else { // move freely
3363 if (n->is_dragging) {
3364 if (state & GDK_MOD1_MASK) { // sculpt
3365 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3366 } else {
3367 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3368 (*p)[NR::X] - n->pos[NR::X],
3369 (*p)[NR::Y] - n->pos[NR::Y],
3370 (state & GDK_SHIFT_MASK) == 0);
3371 }
3372 }
3373 }
3375 n->subpath->nodepath->desktop->scroll_to_point(p);
3377 return TRUE;
3378 }
3380 /**
3381 * Node handle clicked callback.
3382 */
3383 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3384 {
3385 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3387 if (state & GDK_CONTROL_MASK) { // "delete" handle
3388 if (n->p.knot == knot) {
3389 n->p.pos = n->pos;
3390 } else if (n->n.knot == knot) {
3391 n->n.pos = n->pos;
3392 }
3393 sp_node_update_handles(n);
3394 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3395 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3396 sp_nodepath_update_statusbar(nodepath);
3398 } else { // just select or add to selection, depending in Shift
3399 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3400 }
3401 }
3403 /**
3404 * Node handle grabbed callback.
3405 */
3406 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3407 {
3408 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3410 if (!n->selected) {
3411 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3412 }
3414 // remember the origin point of the handle
3415 if (n->p.knot == knot) {
3416 n->p.origin_radial = n->p.pos - n->pos;
3417 } else if (n->n.knot == knot) {
3418 n->n.origin_radial = n->n.pos - n->pos;
3419 } else {
3420 g_assert_not_reached();
3421 }
3423 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3424 }
3426 /**
3427 * Node handle ungrabbed callback.
3428 */
3429 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3430 {
3431 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3433 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3434 if (n->p.knot == knot) {
3435 n->p.origin_radial.a = 0;
3436 sp_knot_set_position(knot, &n->p.pos, state);
3437 } else if (n->n.knot == knot) {
3438 n->n.origin_radial.a = 0;
3439 sp_knot_set_position(knot, &n->n.pos, state);
3440 } else {
3441 g_assert_not_reached();
3442 }
3444 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3445 }
3447 /**
3448 * Node handle "request" signal callback.
3449 */
3450 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3451 {
3452 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3454 Inkscape::NodePath::NodeSide *me, *opposite;
3455 gint which;
3456 if (n->p.knot == knot) {
3457 me = &n->p;
3458 opposite = &n->n;
3459 which = -1;
3460 } else if (n->n.knot == knot) {
3461 me = &n->n;
3462 opposite = &n->p;
3463 which = 1;
3464 } else {
3465 me = opposite = NULL;
3466 which = 0;
3467 g_assert_not_reached();
3468 }
3470 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3472 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3474 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3475 /* We are smooth node adjacent with line */
3476 NR::Point const delta = *p - n->pos;
3477 NR::Coord const len = NR::L2(delta);
3478 Inkscape::NodePath::Node *othernode = opposite->other;
3479 NR::Point const ndelta = n->pos - othernode->pos;
3480 NR::Coord const linelen = NR::L2(ndelta);
3481 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3482 NR::Coord const scal = dot(delta, ndelta) / linelen;
3483 (*p) = n->pos + (scal / linelen) * ndelta;
3484 }
3485 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item).getPoint();
3486 } else {
3487 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item).getPoint();
3488 }
3490 sp_node_adjust_handle(n, -which);
3492 return FALSE;
3493 }
3495 /**
3496 * Node handle moved callback.
3497 */
3498 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3499 {
3500 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3502 Inkscape::NodePath::NodeSide *me;
3503 Inkscape::NodePath::NodeSide *other;
3504 if (n->p.knot == knot) {
3505 me = &n->p;
3506 other = &n->n;
3507 } else if (n->n.knot == knot) {
3508 me = &n->n;
3509 other = &n->p;
3510 } else {
3511 me = NULL;
3512 other = NULL;
3513 g_assert_not_reached();
3514 }
3516 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3517 Radial rme(me->pos - n->pos);
3518 Radial rother(other->pos - n->pos);
3519 Radial rnew(*p - n->pos);
3521 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3522 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3523 /* 0 interpreted as "no snapping". */
3525 // The closest PI/snaps angle, starting from zero.
3526 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3527 if (me->origin_radial.a == HUGE_VAL) {
3528 // ortho doesn't exist: original handle was zero length.
3529 rnew.a = a_snapped;
3530 } else {
3531 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3532 * its opposite and perpendiculars). */
3533 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3535 // Snap to the closest.
3536 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3537 ? a_snapped
3538 : a_ortho );
3539 }
3540 }
3542 if (state & GDK_MOD1_MASK) {
3543 // lock handle length
3544 rnew.r = me->origin_radial.r;
3545 }
3547 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3548 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3549 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3550 rother.a += rnew.a - rme.a;
3551 other->pos = NR::Point(rother) + n->pos;
3552 if (other->knot) {
3553 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3554 sp_knot_moveto(other->knot, &other->pos);
3555 }
3556 }
3558 me->pos = NR::Point(rnew) + n->pos;
3559 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3561 // move knot, but without emitting the signal:
3562 // we cannot emit a "moved" signal because we're now processing it
3563 sp_knot_moveto(me->knot, &(me->pos));
3565 update_object(n->subpath->nodepath);
3567 /* status text */
3568 SPDesktop *desktop = n->subpath->nodepath->desktop;
3569 if (!desktop) return;
3570 SPEventContext *ec = desktop->event_context;
3571 if (!ec) return;
3572 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3573 if (!mc) return;
3575 double degrees = 180 / M_PI * rnew.a;
3576 if (degrees > 180) degrees -= 360;
3577 if (degrees < -180) degrees += 360;
3578 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3579 degrees = angle_to_compass (degrees);
3581 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3583 mc->setF(Inkscape::NORMAL_MESSAGE,
3584 _("<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);
3586 g_string_free(length, TRUE);
3587 }
3589 /**
3590 * Node handle event callback.
3591 */
3592 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3593 {
3594 gboolean ret = FALSE;
3595 switch (event->type) {
3596 case GDK_KEY_PRESS:
3597 switch (get_group0_keyval (&event->key)) {
3598 case GDK_space:
3599 if (event->key.state & GDK_BUTTON1_MASK) {
3600 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3601 stamp_repr(nodepath);
3602 ret = TRUE;
3603 }
3604 break;
3605 default:
3606 break;
3607 }
3608 break;
3609 case GDK_ENTER_NOTIFY:
3610 // we use an experimentally determined threshold that seems to work fine
3611 if (NR::L2(n->pos - knot->pos) < 0.75)
3612 Inkscape::NodePath::Path::active_node = n;
3613 break;
3614 case GDK_LEAVE_NOTIFY:
3615 // we use an experimentally determined threshold that seems to work fine
3616 if (NR::L2(n->pos - knot->pos) < 0.75)
3617 Inkscape::NodePath::Path::active_node = NULL;
3618 break;
3619 default:
3620 break;
3621 }
3623 return ret;
3624 }
3626 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3627 Radial &rme, Radial &rother, gboolean const both)
3628 {
3629 rme.a += angle;
3630 if ( both
3631 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3632 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3633 {
3634 rother.a += angle;
3635 }
3636 }
3638 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3639 Radial &rme, Radial &rother, gboolean const both)
3640 {
3641 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3643 gdouble r;
3644 if ( both
3645 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3646 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3647 {
3648 r = MAX(rme.r, rother.r);
3649 } else {
3650 r = rme.r;
3651 }
3653 gdouble const weird_angle = atan2(norm_angle, r);
3654 /* Bulia says norm_angle is just the visible distance that the
3655 * object's end must travel on the screen. Left as 'angle' for want of
3656 * a better name.*/
3658 rme.a += weird_angle;
3659 if ( both
3660 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3661 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3662 {
3663 rother.a += weird_angle;
3664 }
3665 }
3667 /**
3668 * Rotate one node.
3669 */
3670 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3671 {
3672 Inkscape::NodePath::NodeSide *me, *other;
3673 bool both = false;
3675 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3676 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3678 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3679 me = &(n->p);
3680 other = &(n->n);
3681 } else if (!n->p.other) {
3682 me = &(n->n);
3683 other = &(n->p);
3684 } else {
3685 if (which > 0) { // right handle
3686 if (xn > xp) {
3687 me = &(n->n);
3688 other = &(n->p);
3689 } else {
3690 me = &(n->p);
3691 other = &(n->n);
3692 }
3693 } else if (which < 0){ // left handle
3694 if (xn <= xp) {
3695 me = &(n->n);
3696 other = &(n->p);
3697 } else {
3698 me = &(n->p);
3699 other = &(n->n);
3700 }
3701 } else { // both handles
3702 me = &(n->n);
3703 other = &(n->p);
3704 both = true;
3705 }
3706 }
3708 Radial rme(me->pos - n->pos);
3709 Radial rother(other->pos - n->pos);
3711 if (screen) {
3712 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3713 } else {
3714 node_rotate_one_internal (*n, angle, rme, rother, both);
3715 }
3717 me->pos = n->pos + NR::Point(rme);
3719 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3720 other->pos = n->pos + NR::Point(rother);
3721 }
3723 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3724 // so here we just move all the knots without emitting move signals, for speed
3725 sp_node_update_handles(n, false);
3726 }
3728 /**
3729 * Rotate selected nodes.
3730 */
3731 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3732 {
3733 if (!nodepath || !nodepath->selected) return;
3735 if (g_list_length(nodepath->selected) == 1) {
3736 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3737 node_rotate_one (n, angle, which, screen);
3738 } else {
3739 // rotate as an object:
3741 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3742 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3743 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3744 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3745 box.expandTo (n->pos); // contain all selected nodes
3746 }
3748 gdouble rot;
3749 if (screen) {
3750 gdouble const zoom = nodepath->desktop->current_zoom();
3751 gdouble const zmove = angle / zoom;
3752 gdouble const r = NR::L2(box.max() - box.midpoint());
3753 rot = atan2(zmove, r);
3754 } else {
3755 rot = angle;
3756 }
3758 NR::Point rot_center;
3759 if (Inkscape::NodePath::Path::active_node == NULL)
3760 rot_center = box.midpoint();
3761 else
3762 rot_center = Inkscape::NodePath::Path::active_node->pos;
3764 NR::Matrix t =
3765 NR::Matrix (NR::translate(-rot_center)) *
3766 NR::Matrix (NR::rotate(rot)) *
3767 NR::Matrix (NR::translate(rot_center));
3769 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3770 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3771 n->pos *= t;
3772 n->n.pos *= t;
3773 n->p.pos *= t;
3774 sp_node_update_handles(n, false);
3775 }
3776 }
3778 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3779 }
3781 /**
3782 * Scale one node.
3783 */
3784 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3785 {
3786 bool both = false;
3787 Inkscape::NodePath::NodeSide *me, *other;
3789 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3790 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3792 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3793 me = &(n->p);
3794 other = &(n->n);
3795 n->code = NR_CURVETO;
3796 } else if (!n->p.other) {
3797 me = &(n->n);
3798 other = &(n->p);
3799 if (n->n.other)
3800 n->n.other->code = NR_CURVETO;
3801 } else {
3802 if (which > 0) { // right handle
3803 if (xn > xp) {
3804 me = &(n->n);
3805 other = &(n->p);
3806 if (n->n.other)
3807 n->n.other->code = NR_CURVETO;
3808 } else {
3809 me = &(n->p);
3810 other = &(n->n);
3811 n->code = NR_CURVETO;
3812 }
3813 } else if (which < 0){ // left handle
3814 if (xn <= xp) {
3815 me = &(n->n);
3816 other = &(n->p);
3817 if (n->n.other)
3818 n->n.other->code = NR_CURVETO;
3819 } else {
3820 me = &(n->p);
3821 other = &(n->n);
3822 n->code = NR_CURVETO;
3823 }
3824 } else { // both handles
3825 me = &(n->n);
3826 other = &(n->p);
3827 both = true;
3828 n->code = NR_CURVETO;
3829 if (n->n.other)
3830 n->n.other->code = NR_CURVETO;
3831 }
3832 }
3834 Radial rme(me->pos - n->pos);
3835 Radial rother(other->pos - n->pos);
3837 rme.r += grow;
3838 if (rme.r < 0) rme.r = 0;
3839 if (rme.a == HUGE_VAL) {
3840 if (me->other) { // if direction is unknown, initialize it towards the next node
3841 Radial rme_next(me->other->pos - n->pos);
3842 rme.a = rme_next.a;
3843 } else { // if there's no next, initialize to 0
3844 rme.a = 0;
3845 }
3846 }
3847 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3848 rother.r += grow;
3849 if (rother.r < 0) rother.r = 0;
3850 if (rother.a == HUGE_VAL) {
3851 rother.a = rme.a + M_PI;
3852 }
3853 }
3855 me->pos = n->pos + NR::Point(rme);
3857 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3858 other->pos = n->pos + NR::Point(rother);
3859 }
3861 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3862 // so here we just move all the knots without emitting move signals, for speed
3863 sp_node_update_handles(n, false);
3864 }
3866 /**
3867 * Scale selected nodes.
3868 */
3869 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3870 {
3871 if (!nodepath || !nodepath->selected) return;
3873 if (g_list_length(nodepath->selected) == 1) {
3874 // scale handles of the single selected node
3875 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3876 node_scale_one (n, grow, which);
3877 } else {
3878 // scale nodes as an "object":
3880 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3881 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3882 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3883 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3884 box.expandTo (n->pos); // contain all selected nodes
3885 }
3887 double scale = (box.maxExtent() + grow)/box.maxExtent();
3889 NR::Point scale_center;
3890 if (Inkscape::NodePath::Path::active_node == NULL)
3891 scale_center = box.midpoint();
3892 else
3893 scale_center = Inkscape::NodePath::Path::active_node->pos;
3895 NR::Matrix t =
3896 NR::Matrix (NR::translate(-scale_center)) *
3897 NR::Matrix (NR::scale(scale, scale)) *
3898 NR::Matrix (NR::translate(scale_center));
3900 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3901 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3902 n->pos *= t;
3903 n->n.pos *= t;
3904 n->p.pos *= t;
3905 sp_node_update_handles(n, false);
3906 }
3907 }
3909 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3910 }
3912 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3913 {
3914 if (!nodepath) return;
3915 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3916 }
3918 /**
3919 * Flip selected nodes horizontally/vertically.
3920 */
3921 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3922 {
3923 if (!nodepath || !nodepath->selected) return;
3925 if (g_list_length(nodepath->selected) == 1 && !center) {
3926 // flip handles of the single selected node
3927 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3928 double temp = n->p.pos[axis];
3929 n->p.pos[axis] = n->n.pos[axis];
3930 n->n.pos[axis] = temp;
3931 sp_node_update_handles(n, false);
3932 } else {
3933 // scale nodes as an "object":
3935 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3936 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3937 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3938 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3939 box.expandTo (n->pos); // contain all selected nodes
3940 }
3942 if (!center) {
3943 center = box.midpoint();
3944 }
3945 NR::Matrix t =
3946 NR::Matrix (NR::translate(- *center)) *
3947 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3948 NR::Matrix (NR::translate(*center));
3950 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3951 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3952 n->pos *= t;
3953 n->n.pos *= t;
3954 n->p.pos *= t;
3955 sp_node_update_handles(n, false);
3956 }
3957 }
3959 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3960 }
3962 //-----------------------------------------------
3963 /**
3964 * Return new subpath under given nodepath.
3965 */
3966 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3967 {
3968 g_assert(nodepath);
3969 g_assert(nodepath->desktop);
3971 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3973 s->nodepath = nodepath;
3974 s->closed = FALSE;
3975 s->nodes = NULL;
3976 s->first = NULL;
3977 s->last = NULL;
3979 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3980 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3981 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3983 return s;
3984 }
3986 /**
3987 * Destroy nodes in subpath, then subpath itself.
3988 */
3989 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3990 {
3991 g_assert(subpath);
3992 g_assert(subpath->nodepath);
3993 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3995 while (subpath->nodes) {
3996 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3997 }
3999 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4001 g_free(subpath);
4002 }
4004 /**
4005 * Link head to tail in subpath.
4006 */
4007 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4008 {
4009 g_assert(!sp->closed);
4010 g_assert(sp->last != sp->first);
4011 g_assert(sp->first->code == NR_MOVETO);
4013 sp->closed = TRUE;
4015 //Link the head to the tail
4016 sp->first->p.other = sp->last;
4017 sp->last->n.other = sp->first;
4018 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4019 sp->first = sp->last;
4021 //Remove the extra end node
4022 sp_nodepath_node_destroy(sp->last->n.other);
4023 }
4025 /**
4026 * Open closed (loopy) subpath at node.
4027 */
4028 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4029 {
4030 g_assert(sp->closed);
4031 g_assert(n->subpath == sp);
4032 g_assert(sp->first == sp->last);
4034 /* We create new startpoint, current node will become last one */
4036 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4037 &n->pos, &n->pos, &n->n.pos);
4040 sp->closed = FALSE;
4042 //Unlink to make a head and tail
4043 sp->first = new_path;
4044 sp->last = n;
4045 n->n.other = NULL;
4046 new_path->p.other = NULL;
4047 }
4049 /**
4050 * Returns area in triangle given by points; may be negative.
4051 */
4052 inline double
4053 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
4054 {
4055 return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
4056 }
4058 /**
4059 * Return new node in subpath with given properties.
4060 * \param pos Position of node.
4061 * \param ppos Handle position in previous direction
4062 * \param npos Handle position in previous direction
4063 */
4064 Inkscape::NodePath::Node *
4065 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)
4066 {
4067 g_assert(sp);
4068 g_assert(sp->nodepath);
4069 g_assert(sp->nodepath->desktop);
4071 if (nodechunk == NULL)
4072 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4074 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4076 n->subpath = sp;
4078 if (type != Inkscape::NodePath::NODE_NONE) {
4079 // use the type from sodipodi:nodetypes
4080 n->type = type;
4081 } else {
4082 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4083 // points are (almost) collinear
4084 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4085 // endnode, or a node with a retracted handle
4086 n->type = Inkscape::NodePath::NODE_CUSP;
4087 } else {
4088 n->type = Inkscape::NodePath::NODE_SMOOTH;
4089 }
4090 } else {
4091 n->type = Inkscape::NodePath::NODE_CUSP;
4092 }
4093 }
4095 n->code = code;
4096 n->selected = FALSE;
4097 n->pos = *pos;
4098 n->p.pos = *ppos;
4099 n->n.pos = *npos;
4101 n->dragging_out = NULL;
4103 Inkscape::NodePath::Node *prev;
4104 if (next) {
4105 //g_assert(g_list_find(sp->nodes, next));
4106 prev = next->p.other;
4107 } else {
4108 prev = sp->last;
4109 }
4111 if (prev)
4112 prev->n.other = n;
4113 else
4114 sp->first = n;
4116 if (next)
4117 next->p.other = n;
4118 else
4119 sp->last = n;
4121 n->p.other = prev;
4122 n->n.other = next;
4124 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"));
4125 sp_knot_set_position(n->knot, pos, 0);
4127 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4128 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4129 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4130 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4131 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4132 sp_knot_update_ctrl(n->knot);
4134 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4135 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4136 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4137 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4138 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4139 sp_knot_show(n->knot);
4141 // We only create handle knots and lines on demand
4142 n->p.knot = NULL;
4143 n->p.line = NULL;
4144 n->n.knot = NULL;
4145 n->n.line = NULL;
4147 sp->nodes = g_list_prepend(sp->nodes, n);
4149 return n;
4150 }
4152 /**
4153 * Destroy node and its knots, link neighbors in subpath.
4154 */
4155 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4156 {
4157 g_assert(node);
4158 g_assert(node->subpath);
4159 g_assert(SP_IS_KNOT(node->knot));
4161 Inkscape::NodePath::SubPath *sp = node->subpath;
4163 if (node->selected) { // first, deselect
4164 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4165 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4166 }
4168 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4170 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4171 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4172 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4173 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4174 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4175 g_object_unref(G_OBJECT(node->knot));
4177 if (node->p.knot) {
4178 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4179 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4180 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4181 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4182 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4183 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4184 g_object_unref(G_OBJECT(node->p.knot));
4185 node->p.knot = NULL;
4186 }
4188 if (node->n.knot) {
4189 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4190 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4191 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4192 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4193 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4194 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4195 g_object_unref(G_OBJECT(node->n.knot));
4196 node->n.knot = NULL;
4197 }
4199 if (node->p.line)
4200 gtk_object_destroy(GTK_OBJECT(node->p.line));
4201 if (node->n.line)
4202 gtk_object_destroy(GTK_OBJECT(node->n.line));
4204 if (sp->nodes) { // there are others nodes on the subpath
4205 if (sp->closed) {
4206 if (sp->first == node) {
4207 g_assert(sp->last == node);
4208 sp->first = node->n.other;
4209 sp->last = sp->first;
4210 }
4211 node->p.other->n.other = node->n.other;
4212 node->n.other->p.other = node->p.other;
4213 } else {
4214 if (sp->first == node) {
4215 sp->first = node->n.other;
4216 sp->first->code = NR_MOVETO;
4217 }
4218 if (sp->last == node) sp->last = node->p.other;
4219 if (node->p.other) node->p.other->n.other = node->n.other;
4220 if (node->n.other) node->n.other->p.other = node->p.other;
4221 }
4222 } else { // this was the last node on subpath
4223 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4224 }
4226 g_mem_chunk_free(nodechunk, node);
4227 }
4229 /**
4230 * Returns one of the node's two sides.
4231 * \param which Indicates which side.
4232 * \return Pointer to previous node side if which==-1, next if which==1.
4233 */
4234 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4235 {
4236 g_assert(node);
4238 switch (which) {
4239 case -1:
4240 return &node->p;
4241 case 1:
4242 return &node->n;
4243 default:
4244 break;
4245 }
4247 g_assert_not_reached();
4249 return NULL;
4250 }
4252 /**
4253 * Return the other side of the node, given one of its sides.
4254 */
4255 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4256 {
4257 g_assert(node);
4259 if (me == &node->p) return &node->n;
4260 if (me == &node->n) return &node->p;
4262 g_assert_not_reached();
4264 return NULL;
4265 }
4267 /**
4268 * Return NRPathcode on the given side of the node.
4269 */
4270 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4271 {
4272 g_assert(node);
4274 if (me == &node->p) {
4275 if (node->p.other) return (NRPathcode)node->code;
4276 return NR_MOVETO;
4277 }
4279 if (me == &node->n) {
4280 if (node->n.other) return (NRPathcode)node->n.other->code;
4281 return NR_MOVETO;
4282 }
4284 g_assert_not_reached();
4286 return NR_END;
4287 }
4289 /**
4290 * Return node with the given index
4291 */
4292 Inkscape::NodePath::Node *
4293 sp_nodepath_get_node_by_index(int index)
4294 {
4295 Inkscape::NodePath::Node *e = NULL;
4297 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4298 if (!nodepath) {
4299 return e;
4300 }
4302 //find segment
4303 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4305 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4306 int n = g_list_length(sp->nodes);
4307 if (sp->closed) {
4308 n++;
4309 }
4311 //if the piece belongs to this subpath grab it
4312 //otherwise move onto the next subpath
4313 if (index < n) {
4314 e = sp->first;
4315 for (int i = 0; i < index; ++i) {
4316 e = e->n.other;
4317 }
4318 break;
4319 } else {
4320 if (sp->closed) {
4321 index -= (n+1);
4322 } else {
4323 index -= n;
4324 }
4325 }
4326 }
4328 return e;
4329 }
4331 /**
4332 * Returns plain text meaning of node type.
4333 */
4334 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4335 {
4336 unsigned retracted = 0;
4337 bool endnode = false;
4339 for (int which = -1; which <= 1; which += 2) {
4340 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4341 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4342 retracted ++;
4343 if (!side->other)
4344 endnode = true;
4345 }
4347 if (retracted == 0) {
4348 if (endnode) {
4349 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4350 return _("end node");
4351 } else {
4352 switch (node->type) {
4353 case Inkscape::NodePath::NODE_CUSP:
4354 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4355 return _("cusp");
4356 case Inkscape::NodePath::NODE_SMOOTH:
4357 // TRANSLATORS: "smooth" is an adjective here
4358 return _("smooth");
4359 case Inkscape::NodePath::NODE_SYMM:
4360 return _("symmetric");
4361 }
4362 }
4363 } else if (retracted == 1) {
4364 if (endnode) {
4365 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4366 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4367 } else {
4368 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4369 }
4370 } else {
4371 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4372 }
4374 return NULL;
4375 }
4377 /**
4378 * Handles content of statusbar as long as node tool is active.
4379 */
4380 void
4381 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4382 {
4383 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");
4384 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4386 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4387 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4388 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4389 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4391 SPDesktop *desktop = NULL;
4392 if (nodepath) {
4393 desktop = nodepath->desktop;
4394 } else {
4395 desktop = SP_ACTIVE_DESKTOP;
4396 }
4398 SPEventContext *ec = desktop->event_context;
4399 if (!ec) return;
4400 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4401 if (!mc) return;
4403 if (selected_nodes == 0) {
4404 Inkscape::Selection *sel = desktop->selection;
4405 if (!sel || sel->isEmpty()) {
4406 mc->setF(Inkscape::NORMAL_MESSAGE,
4407 _("Select a single object to edit its nodes or handles."));
4408 } else {
4409 if (nodepath) {
4410 mc->setF(Inkscape::NORMAL_MESSAGE,
4411 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.",
4412 "<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.",
4413 total_nodes),
4414 total_nodes);
4415 } else {
4416 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4417 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4418 } else {
4419 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4420 }
4421 }
4422 }
4423 } else if (nodepath && selected_nodes == 1) {
4424 mc->setF(Inkscape::NORMAL_MESSAGE,
4425 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4426 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4427 total_nodes),
4428 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4429 } else {
4430 if (selected_subpaths > 1) {
4431 mc->setF(Inkscape::NORMAL_MESSAGE,
4432 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4433 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4434 total_nodes),
4435 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4436 } else {
4437 mc->setF(Inkscape::NORMAL_MESSAGE,
4438 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4439 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4440 total_nodes),
4441 selected_nodes, total_nodes, when_selected);
4442 }
4443 }
4444 }
4446 /*
4447 * returns a *copy* of the curve of that object.
4448 */
4449 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4450 if (!object)
4451 return NULL;
4453 SPCurve *curve = NULL;
4454 if (SP_IS_PATH(object)) {
4455 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4456 curve = sp_curve_copy(curve_new);
4457 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4458 const gchar *svgd = object->repr->attribute(key);
4459 if (svgd) {
4460 NArtBpath *bpath = sp_svg_read_path(svgd);
4461 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4462 if (curve_new) {
4463 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4464 } else {
4465 g_free(bpath);
4466 }
4467 }
4468 }
4470 return curve;
4471 }
4473 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4474 if (!np || !np->object || !curve)
4475 return;
4477 if (SP_IS_PATH(np->object)) {
4478 if (SP_SHAPE(np->object)->path_effect_href) {
4479 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4480 } else {
4481 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4482 }
4483 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4484 // FIXME: this writing to string and then reading from string is bound to be slow.
4485 // create a method to convert from curve directly to 2geom...
4486 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4487 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4488 g_free(svgpath);
4490 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4491 }
4492 }
4494 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4495 np->show_helperpath = show;
4496 }
4498 /* this function does not work yet */
4499 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4500 np->straight_path = true;
4501 np->show_handles = false;
4502 g_message("add code to make the path straight.");
4503 // do sp_nodepath_convert_node_type on all nodes?
4504 // search for this text !!! "Make selected segments lines"
4505 }
4508 /*
4509 Local Variables:
4510 mode:c++
4511 c-file-style:"stroustrup"
4512 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4513 indent-tabs-mode:nil
4514 fill-column:99
4515 End:
4516 */
4517 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :