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/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include "libnr/nr-matrix-ops.h"
41 #include "splivarot.h"
42 #include "svg/svg.h"
43 #include "display/bezier-utils.h"
44 #include <vector>
45 #include <algorithm>
47 class NR::Matrix;
49 /// \todo
50 /// evil evil evil. FIXME: conflict of two different Path classes!
51 /// There is a conflict in the namespace between two classes named Path.
52 /// #include "sp-flowtext.h"
53 /// #include "sp-flowregion.h"
55 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
56 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
57 GType sp_flowregion_get_type (void);
58 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
59 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
60 GType sp_flowtext_get_type (void);
61 // end evil workaround
63 #include "helper/stlport.h"
66 /// \todo fixme: Implement these via preferences */
68 #define NODE_FILL 0xbfbfbf00
69 #define NODE_STROKE 0x000000ff
70 #define NODE_FILL_HI 0xff000000
71 #define NODE_STROKE_HI 0x000000ff
72 #define NODE_FILL_SEL 0x0000ffff
73 #define NODE_STROKE_SEL 0x000000ff
74 #define NODE_FILL_SEL_HI 0xff000000
75 #define NODE_STROKE_SEL_HI 0x000000ff
76 #define KNOT_FILL 0xffffffff
77 #define KNOT_STROKE 0x000000ff
78 #define KNOT_FILL_HI 0xff000000
79 #define KNOT_STROKE_HI 0x000000ff
81 static GMemChunk *nodechunk = NULL;
83 /* Creation from object */
85 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
86 static gchar *parse_nodetypes(gchar const *types, gint length);
88 /* Object updating */
90 static void stamp_repr(Inkscape::NodePath::Path *np);
91 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
92 static gchar *create_typestr(Inkscape::NodePath::Path *np);
94 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
96 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
98 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
100 /* Adjust handle placement, if the node or the other handle is moved */
101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
102 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
104 /* Node event callbacks */
105 static void node_clicked(SPKnot *knot, guint state, gpointer data);
106 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
107 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
108 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
110 /* Handle event callbacks */
111 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
112 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
114 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
115 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
118 /* Constructors and destructors */
120 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
121 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
122 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
123 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
124 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
125 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
126 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
128 /* Helpers */
130 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
131 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 // active_node indicates mouseover node
135 static Inkscape::NodePath::Node *active_node = NULL;
137 /**
138 * \brief Creates new nodepath from item
139 */
140 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
141 {
142 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
144 /** \todo
145 * FIXME: remove this. We don't want to edit paths inside flowtext.
146 * Instead we will build our flowtext with cloned paths, so that the
147 * real paths are outside the flowtext and thus editable as usual.
148 */
149 if (SP_IS_FLOWTEXT(item)) {
150 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
151 if SP_IS_FLOWREGION(child) {
152 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
153 if (grandchild && SP_IS_PATH(grandchild)) {
154 item = SP_ITEM(grandchild);
155 break;
156 }
157 }
158 }
159 }
161 if (!SP_IS_PATH(item))
162 return NULL;
163 SPPath *path = SP_PATH(item);
164 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
165 if (curve == NULL)
166 return NULL;
168 NArtBpath *bpath = sp_curve_first_bpath(curve);
169 gint length = curve->end;
170 if (length == 0)
171 return NULL; // prevent crash for one-node paths
173 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
174 gchar *typestr = parse_nodetypes(nodetypes, length);
176 //Create new nodepath
177 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
178 if (!np)
179 return NULL;
181 // Set defaults
182 np->desktop = desktop;
183 np->path = path;
184 np->subpaths = NULL;
185 np->selected = NULL;
186 np->nodeContext = NULL; //Let the context that makes this set it
187 np->livarot_path = NULL;
188 np->local_change = 0;
190 // we need to update item's transform from the repr here,
191 // because they may be out of sync when we respond
192 // to a change in repr by regenerating nodepath --bb
193 sp_object_read_attr(SP_OBJECT(item), "transform");
195 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
196 np->d2i = np->i2d.inverse();
197 np->repr = repr;
199 // create the subpath(s) from the bpath
200 NArtBpath *b = bpath;
201 while (b->code != NR_END) {
202 b = subpath_from_bpath(np, b, typestr + (b - bpath));
203 }
205 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
206 np->subpaths = g_list_reverse(np->subpaths);
208 g_free(typestr);
209 sp_curve_unref(curve);
211 // create the livarot representation from the same item
212 np->livarot_path = Path_for_item(item, true, true);
213 if (np->livarot_path)
214 np->livarot_path->ConvertWithBackData(0.01);
216 return np;
217 }
219 /**
220 * Destroys nodepath's subpaths, then itself, also tell context about it.
221 */
222 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
224 if (!np) //soft fail, like delete
225 return;
227 while (np->subpaths) {
228 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
229 }
231 //Inform the context that made me, if any, that I am gone.
232 if (np->nodeContext)
233 np->nodeContext->nodepath = NULL;
235 g_assert(!np->selected);
237 if (np->livarot_path) {
238 delete np->livarot_path;
239 np->livarot_path = NULL;
240 }
242 np->desktop = NULL;
244 g_free(np);
245 }
248 /**
249 * Return the node count of a given NodeSubPath.
250 */
251 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
252 {
253 if (!subpath)
254 return 0;
255 gint nodeCount = g_list_length(subpath->nodes);
256 return nodeCount;
257 }
259 /**
260 * Return the node count of a given NodePath.
261 */
262 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
263 {
264 if (!np)
265 return 0;
266 gint nodeCount = 0;
267 for (GList *item = np->subpaths ; item ; item=item->next) {
268 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
269 nodeCount += g_list_length(subpath->nodes);
270 }
271 return nodeCount;
272 }
275 /**
276 * Clean up a nodepath after editing.
277 *
278 * Currently we are deleting trivial subpaths.
279 */
280 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
281 {
282 GList *badSubPaths = NULL;
284 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
285 for (GList *l = nodepath->subpaths; l ; l=l->next) {
286 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
287 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
288 badSubPaths = g_list_append(badSubPaths, sp);
289 }
291 //Delete them. This second step is because sp_nodepath_subpath_destroy()
292 //also removes the subpath from nodepath->subpaths
293 for (GList *l = badSubPaths; l ; l=l->next) {
294 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
295 sp_nodepath_subpath_destroy(sp);
296 }
298 g_list_free(badSubPaths);
299 }
301 /**
302 * Create new nodepath from b, make it subpath of np.
303 * \param t The node type.
304 * \todo Fixme: t should be a proper type, rather than gchar
305 */
306 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
307 {
308 NR::Point ppos, pos, npos;
310 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
312 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
313 bool const closed = (b->code == NR_MOVETO);
315 pos = NR::Point(b->x3, b->y3) * np->i2d;
316 if (b[1].code == NR_CURVETO) {
317 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
318 } else {
319 npos = pos;
320 }
321 Inkscape::NodePath::Node *n;
322 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
323 g_assert(sp->first == n);
324 g_assert(sp->last == n);
326 b++;
327 t++;
328 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
329 pos = NR::Point(b->x3, b->y3) * np->i2d;
330 if (b->code == NR_CURVETO) {
331 ppos = NR::Point(b->x2, b->y2) * np->i2d;
332 } else {
333 ppos = pos;
334 }
335 if (b[1].code == NR_CURVETO) {
336 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
337 } else {
338 npos = pos;
339 }
340 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
341 b++;
342 t++;
343 }
345 if (closed) sp_nodepath_subpath_close(sp);
347 return b;
348 }
350 /**
351 * Convert from sodipodi:nodetypes to new style type string.
352 */
353 static gchar *parse_nodetypes(gchar const *types, gint length)
354 {
355 g_assert(length > 0);
357 gchar *typestr = g_new(gchar, length + 1);
359 gint pos = 0;
361 if (types) {
362 for (gint i = 0; types[i] && ( i < length ); i++) {
363 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
364 if (types[i] != '\0') {
365 switch (types[i]) {
366 case 's':
367 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
368 break;
369 case 'z':
370 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
371 break;
372 case 'c':
373 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
374 break;
375 default:
376 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
377 break;
378 }
379 }
380 }
381 }
383 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
385 return typestr;
386 }
388 /**
389 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
390 * updated but repr is not (for speed). Used during curve and node drag.
391 */
392 static void update_object(Inkscape::NodePath::Path *np)
393 {
394 g_assert(np);
396 SPCurve *curve = create_curve(np);
398 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
400 sp_curve_unref(curve);
401 }
403 /**
404 * Update XML path node with data from path object.
405 */
406 static void update_repr_internal(Inkscape::NodePath::Path *np)
407 {
408 g_assert(np);
410 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
412 SPCurve *curve = create_curve(np);
413 gchar *typestr = create_typestr(np);
414 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
416 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
417 np->local_change++;
418 repr->setAttribute("d", svgpath);
419 }
421 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
422 np->local_change++;
423 repr->setAttribute("sodipodi:nodetypes", typestr);
424 }
426 g_free(svgpath);
427 g_free(typestr);
428 sp_curve_unref(curve);
429 }
431 /**
432 * Update XML path node with data from path object, commit changes forever.
433 */
434 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
435 {
436 update_repr_internal(np);
437 sp_document_done(sp_desktop_document(np->desktop));
439 if (np->livarot_path) {
440 delete np->livarot_path;
441 np->livarot_path = NULL;
442 }
444 if (np->path && SP_IS_ITEM(np->path)) {
445 np->livarot_path = Path_for_item (np->path, true, true);
446 if (np->livarot_path)
447 np->livarot_path->ConvertWithBackData(0.01);
448 }
449 }
451 /**
452 * Update XML path node with data from path object, commit changes with undo.
453 */
454 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
455 {
456 update_repr_internal(np);
457 sp_document_maybe_done(sp_desktop_document(np->desktop), key);
459 if (np->livarot_path) {
460 delete np->livarot_path;
461 np->livarot_path = NULL;
462 }
464 if (np->path && SP_IS_ITEM(np->path)) {
465 np->livarot_path = Path_for_item (np->path, true, true);
466 if (np->livarot_path)
467 np->livarot_path->ConvertWithBackData(0.01);
468 }
469 }
471 /**
472 * Make duplicate of path, replace corresponding XML node in tree, commit.
473 */
474 static void stamp_repr(Inkscape::NodePath::Path *np)
475 {
476 g_assert(np);
478 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
479 Inkscape::XML::Node *new_repr = old_repr->duplicate();
481 // remember the position of the item
482 gint pos = old_repr->position();
483 // remember parent
484 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
486 SPCurve *curve = create_curve(np);
487 gchar *typestr = create_typestr(np);
489 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
491 new_repr->setAttribute("d", svgpath);
492 new_repr->setAttribute("sodipodi:nodetypes", typestr);
494 // add the new repr to the parent
495 parent->appendChild(new_repr);
496 // move to the saved position
497 new_repr->setPosition(pos > 0 ? pos : 0);
499 sp_document_done(sp_desktop_document(np->desktop));
501 Inkscape::GC::release(new_repr);
502 g_free(svgpath);
503 g_free(typestr);
504 sp_curve_unref(curve);
505 }
507 /**
508 * Create curve from path.
509 */
510 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
511 {
512 SPCurve *curve = sp_curve_new();
514 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
515 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
516 sp_curve_moveto(curve,
517 sp->first->pos * np->d2i);
518 Inkscape::NodePath::Node *n = sp->first->n.other;
519 while (n) {
520 NR::Point const end_pt = n->pos * np->d2i;
521 switch (n->code) {
522 case NR_LINETO:
523 sp_curve_lineto(curve, end_pt);
524 break;
525 case NR_CURVETO:
526 sp_curve_curveto(curve,
527 n->p.other->n.pos * np->d2i,
528 n->p.pos * np->d2i,
529 end_pt);
530 break;
531 default:
532 g_assert_not_reached();
533 break;
534 }
535 if (n != sp->last) {
536 n = n->n.other;
537 } else {
538 n = NULL;
539 }
540 }
541 if (sp->closed) {
542 sp_curve_closepath(curve);
543 }
544 }
546 return curve;
547 }
549 /**
550 * Convert path type string to sodipodi:nodetypes style.
551 */
552 static gchar *create_typestr(Inkscape::NodePath::Path *np)
553 {
554 gchar *typestr = g_new(gchar, 32);
555 gint len = 32;
556 gint pos = 0;
558 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
559 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
561 if (pos >= len) {
562 typestr = g_renew(gchar, typestr, len + 32);
563 len += 32;
564 }
566 typestr[pos++] = 'c';
568 Inkscape::NodePath::Node *n;
569 n = sp->first->n.other;
570 while (n) {
571 gchar code;
573 switch (n->type) {
574 case Inkscape::NodePath::NODE_CUSP:
575 code = 'c';
576 break;
577 case Inkscape::NodePath::NODE_SMOOTH:
578 code = 's';
579 break;
580 case Inkscape::NodePath::NODE_SYMM:
581 code = 'z';
582 break;
583 default:
584 g_assert_not_reached();
585 code = '\0';
586 break;
587 }
589 if (pos >= len) {
590 typestr = g_renew(gchar, typestr, len + 32);
591 len += 32;
592 }
594 typestr[pos++] = code;
596 if (n != sp->last) {
597 n = n->n.other;
598 } else {
599 n = NULL;
600 }
601 }
602 }
604 if (pos >= len) {
605 typestr = g_renew(gchar, typestr, len + 1);
606 len += 1;
607 }
609 typestr[pos++] = '\0';
611 return typestr;
612 }
614 /**
615 * Returns current path in context.
616 */
617 static Inkscape::NodePath::Path *sp_nodepath_current()
618 {
619 if (!SP_ACTIVE_DESKTOP) {
620 return NULL;
621 }
623 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
625 if (!SP_IS_NODE_CONTEXT(event_context)) {
626 return NULL;
627 }
629 return SP_NODE_CONTEXT(event_context)->nodepath;
630 }
634 /**
635 \brief Fills node and handle positions for three nodes, splitting line
636 marked by end at distance t.
637 */
638 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
639 {
640 g_assert(new_path != NULL);
641 g_assert(end != NULL);
643 g_assert(end->p.other == new_path);
644 Inkscape::NodePath::Node *start = new_path->p.other;
645 g_assert(start);
647 if (end->code == NR_LINETO) {
648 new_path->type =Inkscape::NodePath::NODE_CUSP;
649 new_path->code = NR_LINETO;
650 new_path->pos = (t * start->pos + (1 - t) * end->pos);
651 } else {
652 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
653 new_path->code = NR_CURVETO;
654 gdouble s = 1 - t;
655 for (int dim = 0; dim < 2; dim++) {
656 NR::Coord const f000 = start->pos[dim];
657 NR::Coord const f001 = start->n.pos[dim];
658 NR::Coord const f011 = end->p.pos[dim];
659 NR::Coord const f111 = end->pos[dim];
660 NR::Coord const f00t = s * f000 + t * f001;
661 NR::Coord const f01t = s * f001 + t * f011;
662 NR::Coord const f11t = s * f011 + t * f111;
663 NR::Coord const f0tt = s * f00t + t * f01t;
664 NR::Coord const f1tt = s * f01t + t * f11t;
665 NR::Coord const fttt = s * f0tt + t * f1tt;
666 start->n.pos[dim] = f00t;
667 new_path->p.pos[dim] = f0tt;
668 new_path->pos[dim] = fttt;
669 new_path->n.pos[dim] = f1tt;
670 end->p.pos[dim] = f11t;
671 }
672 }
673 }
675 /**
676 * Adds new node on direct line between two nodes, activates handles of all
677 * three nodes.
678 */
679 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
680 {
681 g_assert(end);
682 g_assert(end->subpath);
683 g_assert(g_list_find(end->subpath->nodes, end));
685 Inkscape::NodePath::Node *start = end->p.other;
686 g_assert( start->n.other == end );
687 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
688 end,
689 Inkscape::NodePath::NODE_SMOOTH,
690 (NRPathcode)end->code,
691 &start->pos, &start->pos, &start->n.pos);
692 sp_nodepath_line_midpoint(newnode, end, t);
694 sp_node_update_handles(start);
695 sp_node_update_handles(newnode);
696 sp_node_update_handles(end);
698 return newnode;
699 }
701 /**
702 \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
703 */
704 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
705 {
706 g_assert(node);
707 g_assert(node->subpath);
708 g_assert(g_list_find(node->subpath->nodes, node));
710 Inkscape::NodePath::SubPath *sp = node->subpath;
711 Inkscape::NodePath::Path *np = sp->nodepath;
713 if (sp->closed) {
714 sp_nodepath_subpath_open(sp, node);
715 return sp->first;
716 } else {
717 // no break for end nodes
718 if (node == sp->first) return NULL;
719 if (node == sp->last ) return NULL;
721 // create a new subpath
722 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
724 // duplicate the break node as start of the new subpath
725 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
727 while (node->n.other) { // copy the remaining nodes into the new subpath
728 Inkscape::NodePath::Node *n = node->n.other;
729 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);
730 if (n->selected) {
731 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
732 }
733 sp_nodepath_node_destroy(n); // remove the point on the original subpath
734 }
736 return newnode;
737 }
738 }
740 /**
741 * Duplicate node and connect to neighbours.
742 */
743 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
744 {
745 g_assert(node);
746 g_assert(node->subpath);
747 g_assert(g_list_find(node->subpath->nodes, node));
749 Inkscape::NodePath::SubPath *sp = node->subpath;
751 NRPathcode code = (NRPathcode) node->code;
752 if (code == NR_MOVETO) { // if node is the endnode,
753 node->code = NR_LINETO; // new one is inserted before it, so change that to line
754 }
756 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
758 if (!node->n.other || !node->p.other) // if node is an endnode, select it
759 return node;
760 else
761 return newnode; // otherwise select the newly created node
762 }
764 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
765 {
766 node->p.pos = (node->pos + (node->pos - node->n.pos));
767 }
769 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
770 {
771 node->n.pos = (node->pos + (node->pos - node->p.pos));
772 }
774 /**
775 * Change line type at node, with side effects on neighbours.
776 */
777 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
778 {
779 g_assert(end);
780 g_assert(end->subpath);
781 g_assert(end->p.other);
783 if (end->code == static_cast< guint > ( code ) )
784 return;
786 Inkscape::NodePath::Node *start = end->p.other;
788 end->code = code;
790 if (code == NR_LINETO) {
791 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
792 if (end->n.other) {
793 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
794 }
795 sp_node_adjust_handle(start, -1);
796 sp_node_adjust_handle(end, 1);
797 } else {
798 NR::Point delta = end->pos - start->pos;
799 start->n.pos = start->pos + delta / 3;
800 end->p.pos = end->pos - delta / 3;
801 sp_node_adjust_handle(start, 1);
802 sp_node_adjust_handle(end, -1);
803 }
805 sp_node_update_handles(start);
806 sp_node_update_handles(end);
807 }
809 /**
810 * Change node type, and its handles accordingly.
811 */
812 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
813 {
814 g_assert(node);
815 g_assert(node->subpath);
817 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
818 return node;
820 if ((node->p.other != NULL) && (node->n.other != NULL)) {
821 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
822 type =Inkscape::NodePath::NODE_CUSP;
823 }
824 }
826 node->type = type;
828 if (node->type == Inkscape::NodePath::NODE_CUSP) {
829 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
830 node->knot->setSize (node->selected? 11 : 9);
831 sp_knot_update_ctrl(node->knot);
832 } else {
833 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
834 node->knot->setSize (node->selected? 9 : 7);
835 sp_knot_update_ctrl(node->knot);
836 }
838 // if one of handles is mouseovered, preserve its position
839 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
840 sp_node_adjust_handle(node, 1);
841 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
842 sp_node_adjust_handle(node, -1);
843 } else {
844 sp_node_adjust_handles(node);
845 }
847 sp_node_update_handles(node);
849 sp_nodepath_update_statusbar(node->subpath->nodepath);
851 return node;
852 }
854 /**
855 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
856 * adjacent segments from lines to curves.
857 */
858 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
859 {
860 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
861 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
862 // convert adjacent segment BEFORE to curve
863 node->code = NR_CURVETO;
864 NR::Point delta;
865 if (node->n.other != NULL)
866 delta = node->n.other->pos - node->p.other->pos;
867 else
868 delta = node->pos - node->p.other->pos;
869 node->p.pos = node->pos - delta / 4;
870 sp_node_update_handles(node);
871 }
873 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
874 // convert adjacent segment AFTER to curve
875 node->n.other->code = NR_CURVETO;
876 NR::Point delta;
877 if (node->p.other != NULL)
878 delta = node->p.other->pos - node->n.other->pos;
879 else
880 delta = node->pos - node->n.other->pos;
881 node->n.pos = node->pos - delta / 4;
882 sp_node_update_handles(node);
883 }
884 }
886 sp_nodepath_set_node_type (node, type);
887 }
889 /**
890 * Move node to point, and adjust its and neighbouring handles.
891 */
892 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
893 {
894 NR::Point delta = p - node->pos;
895 node->pos = p;
897 node->p.pos += delta;
898 node->n.pos += delta;
900 if (node->p.other) {
901 if (node->code == NR_LINETO) {
902 sp_node_adjust_handle(node, 1);
903 sp_node_adjust_handle(node->p.other, -1);
904 }
905 }
906 if (node->n.other) {
907 if (node->n.other->code == NR_LINETO) {
908 sp_node_adjust_handle(node, -1);
909 sp_node_adjust_handle(node->n.other, 1);
910 }
911 }
913 // this function is only called from batch movers that will update display at the end
914 // themselves, so here we just move all the knots without emitting move signals, for speed
915 sp_node_update_handles(node, false);
916 }
918 /**
919 * Call sp_node_moveto() for node selection and handle possible snapping.
920 */
921 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
922 bool const snap = true)
923 {
924 NR::Coord best = NR_HUGE;
925 NR::Point delta(dx, dy);
926 NR::Point best_pt = delta;
928 if (snap) {
929 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
931 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
932 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
933 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
934 if (s.getDistance() < best) {
935 best = s.getDistance();
936 best_pt = s.getPoint() - n->pos;
937 }
938 }
939 }
941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
942 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
943 sp_node_moveto(n, n->pos + best_pt);
944 }
946 // do not update repr here so that node dragging is acceptably fast
947 update_object(nodepath);
948 }
950 /**
951 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
952 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
953 near x = 0.
954 */
955 double
956 sculpt_profile (double x, double alpha)
957 {
958 if (x >= 1)
959 return 0;
960 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
961 }
963 double
964 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
965 {
966 // extremely primitive for now, don't have time to look for the real one
967 double lower = NR::L2(b - a);
968 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
969 return (lower + upper)/2;
970 }
972 void
973 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
974 {
975 n->pos = n->origin + delta;
976 n->n.pos = n->n.origin + delta_n;
977 n->p.pos = n->p.origin + delta_p;
978 sp_node_adjust_handles(n);
979 sp_node_update_handles(n, false);
980 }
982 /**
983 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
984 * on how far they are from the dragged node n.
985 */
986 static void
987 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
988 {
989 g_assert (n);
990 g_assert (nodepath);
991 g_assert (n->subpath->nodepath == nodepath);
993 double pressure = n->knot->pressure;
994 if (pressure == 0)
995 pressure = 0.5; // default
996 pressure = CLAMP (pressure, 0.2, 0.8);
998 // map pressure to alpha = 1/5 ... 5
999 double alpha = 1 - 2 * fabs(pressure - 0.5);
1000 if (pressure > 0.5)
1001 alpha = 1/alpha;
1003 double n_sel_range = 0, p_sel_range = 0;
1004 guint n_nodes = 0, p_nodes = 0;
1005 guint n_sel_nodes = 0, p_sel_nodes = 0;
1007 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1008 {
1009 double n_range = 0, p_range = 0;
1010 bool n_going = true, p_going = true;
1011 Inkscape::NodePath::Node *n_node = n;
1012 Inkscape::NodePath::Node *p_node = n;
1013 do {
1014 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1015 if (n_node && n_going)
1016 n_node = n_node->n.other;
1017 if (n_node == NULL) {
1018 n_going = false;
1019 } else {
1020 n_nodes ++;
1021 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1022 if (n_node->selected) {
1023 n_sel_nodes ++;
1024 n_sel_range = n_range;
1025 }
1026 if (n_node == p_node) {
1027 n_going = false;
1028 p_going = false;
1029 }
1030 }
1031 if (p_node && p_going)
1032 p_node = p_node->p.other;
1033 if (p_node == NULL) {
1034 p_going = false;
1035 } else {
1036 p_nodes ++;
1037 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1038 if (p_node->selected) {
1039 p_sel_nodes ++;
1040 p_sel_range = p_range;
1041 }
1042 if (p_node == n_node) {
1043 n_going = false;
1044 p_going = false;
1045 }
1046 }
1047 } while (n_going || p_going);
1048 }
1050 // Second pass: actually move nodes
1051 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1052 {
1053 double n_range = 0, p_range = 0;
1054 bool n_going = true, p_going = true;
1055 Inkscape::NodePath::Node *n_node = n;
1056 Inkscape::NodePath::Node *p_node = n;
1057 do {
1058 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1059 if (n_node && n_going)
1060 n_node = n_node->n.other;
1061 if (n_node == NULL) {
1062 n_going = false;
1063 } else {
1064 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1065 if (n_node->selected) {
1066 sp_nodepath_move_node_and_handles (n_node,
1067 sculpt_profile (n_range / n_sel_range, alpha) * delta,
1068 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha) * delta,
1069 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha) * delta);
1070 }
1071 if (n_node == p_node) {
1072 n_going = false;
1073 p_going = false;
1074 }
1075 }
1076 if (p_node && p_going)
1077 p_node = p_node->p.other;
1078 if (p_node == NULL) {
1079 p_going = false;
1080 } else {
1081 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1082 if (p_node->selected) {
1083 sp_nodepath_move_node_and_handles (p_node,
1084 sculpt_profile (p_range / p_sel_range, alpha) * delta,
1085 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha) * delta,
1086 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha) * delta);
1087 }
1088 if (p_node == n_node) {
1089 n_going = false;
1090 p_going = false;
1091 }
1092 }
1093 } while (n_going || p_going);
1094 }
1096 // do not update repr here so that node dragging is acceptably fast
1097 update_object(nodepath);
1098 }
1101 /**
1102 * Move node selection to point, adjust its and neighbouring handles,
1103 * handle possible snapping, and commit the change with possible undo.
1104 */
1105 void
1106 sp_node_selected_move(gdouble dx, gdouble dy)
1107 {
1108 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1109 if (!nodepath) return;
1111 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1113 if (dx == 0) {
1114 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1115 } else if (dy == 0) {
1116 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1117 } else {
1118 sp_nodepath_update_repr(nodepath);
1119 }
1120 }
1122 /**
1123 * Move node selection off screen and commit the change.
1124 */
1125 void
1126 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1127 {
1128 // borrowed from sp_selection_move_screen in selection-chemistry.c
1129 // we find out the current zoom factor and divide deltas by it
1130 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1132 gdouble zoom = desktop->current_zoom();
1133 gdouble zdx = dx / zoom;
1134 gdouble zdy = dy / zoom;
1136 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1137 if (!nodepath) return;
1139 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1141 if (dx == 0) {
1142 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1143 } else if (dy == 0) {
1144 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1145 } else {
1146 sp_nodepath_update_repr(nodepath);
1147 }
1148 }
1150 /** If they don't yet exist, creates knot and line for the given side of the node */
1151 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1152 {
1153 if (!side->knot) {
1154 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"));
1156 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1157 side->knot->setSize (7);
1158 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1159 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1160 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1161 sp_knot_update_ctrl(side->knot);
1163 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1164 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1165 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1166 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1167 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1168 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1169 }
1171 if (!side->line) {
1172 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1173 SP_TYPE_CTRLLINE, NULL);
1174 }
1175 }
1177 /**
1178 * Ensure the given handle of the node is visible/invisible, update its screen position
1179 */
1180 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1181 {
1182 g_assert(node != NULL);
1184 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1185 NRPathcode code = sp_node_path_code_from_side(node, side);
1187 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1189 if (show_handle) {
1190 if (!side->knot) { // No handle knot at all
1191 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1192 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1193 side->knot->pos = side->pos;
1194 if (side->knot->item)
1195 SP_CTRL(side->knot->item)->moveto(side->pos);
1196 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1197 sp_knot_show(side->knot);
1198 } else {
1199 if (side->knot->pos != side->pos) { // only if it's really moved
1200 if (fire_move_signals) {
1201 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1202 } else {
1203 sp_knot_moveto(side->knot, &side->pos);
1204 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1205 }
1206 }
1207 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1208 sp_knot_show(side->knot);
1209 }
1210 }
1211 sp_canvas_item_show(side->line);
1212 } else {
1213 if (side->knot) {
1214 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1215 sp_knot_hide(side->knot);
1216 }
1217 }
1218 if (side->line) {
1219 sp_canvas_item_hide(side->line);
1220 }
1221 }
1222 }
1224 /**
1225 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1226 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1227 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1228 * updated; otherwise, just move the knots silently (used in batch moves).
1229 */
1230 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1231 {
1232 g_assert(node != NULL);
1234 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1235 sp_knot_show(node->knot);
1236 }
1238 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1239 if (fire_move_signals)
1240 sp_knot_set_position(node->knot, &node->pos, 0);
1241 else
1242 sp_knot_moveto(node->knot, &node->pos);
1243 }
1245 gboolean show_handles = node->selected;
1246 if (node->p.other != NULL) {
1247 if (node->p.other->selected) show_handles = TRUE;
1248 }
1249 if (node->n.other != NULL) {
1250 if (node->n.other->selected) show_handles = TRUE;
1251 }
1253 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1254 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1255 }
1257 /**
1258 * Call sp_node_update_handles() for all nodes on subpath.
1259 */
1260 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1261 {
1262 g_assert(subpath != NULL);
1264 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1265 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1266 }
1267 }
1269 /**
1270 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1271 */
1272 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1273 {
1274 g_assert(nodepath != NULL);
1276 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1277 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1278 }
1279 }
1281 /**
1282 * Adds all selected nodes in nodepath to list.
1283 */
1284 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1285 {
1286 StlConv<Node *>::list(l, selected);
1287 /// \todo this adds a copying, rework when the selection becomes a stl list
1288 }
1290 /**
1291 * Align selected nodes on the specified axis.
1292 */
1293 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1294 {
1295 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1296 return;
1297 }
1299 if ( !nodepath->selected->next ) { // only one node selected
1300 return;
1301 }
1302 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1303 NR::Point dest(pNode->pos);
1304 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1305 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1306 if (pNode) {
1307 dest[axis] = pNode->pos[axis];
1308 sp_node_moveto(pNode, dest);
1309 }
1310 }
1312 sp_nodepath_update_repr(nodepath);
1313 }
1315 /// Helper struct.
1316 struct NodeSort
1317 {
1318 Inkscape::NodePath::Node *_node;
1319 NR::Coord _coord;
1320 /// \todo use vectorof pointers instead of calling copy ctor
1321 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1322 _node(node), _coord(node->pos[axis])
1323 {}
1325 };
1327 static bool operator<(NodeSort const &a, NodeSort const &b)
1328 {
1329 return (a._coord < b._coord);
1330 }
1332 /**
1333 * Distribute selected nodes on the specified axis.
1334 */
1335 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1336 {
1337 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1338 return;
1339 }
1341 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1342 return;
1343 }
1345 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1346 std::vector<NodeSort> sorted;
1347 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1348 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1349 if (pNode) {
1350 NodeSort n(pNode, axis);
1351 sorted.push_back(n);
1352 //dest[axis] = pNode->pos[axis];
1353 //sp_node_moveto(pNode, dest);
1354 }
1355 }
1356 std::sort(sorted.begin(), sorted.end());
1357 unsigned int len = sorted.size();
1358 //overall bboxes span
1359 float dist = (sorted.back()._coord -
1360 sorted.front()._coord);
1361 //new distance between each bbox
1362 float step = (dist) / (len - 1);
1363 float pos = sorted.front()._coord;
1364 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1365 it < sorted.end();
1366 it ++ )
1367 {
1368 NR::Point dest((*it)._node->pos);
1369 dest[axis] = pos;
1370 sp_node_moveto((*it)._node, dest);
1371 pos += step;
1372 }
1374 sp_nodepath_update_repr(nodepath);
1375 }
1378 /**
1379 * Call sp_nodepath_line_add_node() for all selected segments.
1380 */
1381 void
1382 sp_node_selected_add_node(void)
1383 {
1384 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1385 if (!nodepath) {
1386 return;
1387 }
1389 GList *nl = NULL;
1391 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1392 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1393 g_assert(t->selected);
1394 if (t->p.other && t->p.other->selected) {
1395 nl = g_list_prepend(nl, t);
1396 }
1397 }
1399 while (nl) {
1400 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1401 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1402 sp_nodepath_node_select(n, TRUE, FALSE);
1403 nl = g_list_remove(nl, t);
1404 }
1406 /** \todo fixme: adjust ? */
1407 sp_nodepath_update_handles(nodepath);
1409 sp_nodepath_update_repr(nodepath);
1411 sp_nodepath_update_statusbar(nodepath);
1412 }
1414 /**
1415 * Select segment nearest to point
1416 */
1417 void
1418 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1419 {
1420 if (!nodepath) {
1421 return;
1422 }
1424 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1426 //find segment to segment
1427 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1429 gboolean force = FALSE;
1430 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1431 force = TRUE;
1432 }
1433 sp_nodepath_node_select(e, (gboolean) toggle, force);
1434 if (e->p.other)
1435 sp_nodepath_node_select(e->p.other, TRUE, force);
1437 sp_nodepath_update_handles(nodepath);
1439 sp_nodepath_update_statusbar(nodepath);
1440 }
1442 /**
1443 * Add a node nearest to point
1444 */
1445 void
1446 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1447 {
1448 if (!nodepath) {
1449 return;
1450 }
1452 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1454 //find segment to split
1455 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1457 //don't know why but t seems to flip for lines
1458 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1459 position.t = 1.0 - position.t;
1460 }
1461 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1462 sp_nodepath_node_select(n, FALSE, TRUE);
1464 /* fixme: adjust ? */
1465 sp_nodepath_update_handles(nodepath);
1467 sp_nodepath_update_repr(nodepath);
1469 sp_nodepath_update_statusbar(nodepath);
1470 }
1472 /*
1473 * Adjusts a segment so that t moves by a certain delta for dragging
1474 * converts lines to curves
1475 *
1476 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1477 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1478 */
1479 void
1480 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1481 {
1482 /* feel good is an arbitrary parameter that distributes the delta between handles
1483 * if t of the drag point is less than 1/6 distance form the endpoint only
1484 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1485 */
1486 double feel_good;
1487 if (t <= 1.0 / 6.0)
1488 feel_good = 0;
1489 else if (t <= 0.5)
1490 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1491 else if (t <= 5.0 / 6.0)
1492 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1493 else
1494 feel_good = 1;
1496 //if we're dragging a line convert it to a curve
1497 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1498 sp_nodepath_set_line_type(e, NR_CURVETO);
1499 }
1501 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1502 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1503 e->p.other->n.pos += offsetcoord0;
1504 e->p.pos += offsetcoord1;
1506 // adjust handles of adjacent nodes where necessary
1507 sp_node_adjust_handle(e,1);
1508 sp_node_adjust_handle(e->p.other,-1);
1510 sp_nodepath_update_handles(e->subpath->nodepath);
1512 update_object(e->subpath->nodepath);
1514 sp_nodepath_update_statusbar(e->subpath->nodepath);
1515 }
1518 /**
1519 * Call sp_nodepath_break() for all selected segments.
1520 */
1521 void sp_node_selected_break()
1522 {
1523 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1524 if (!nodepath) return;
1526 GList *temp = NULL;
1527 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1528 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1529 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1530 if (nn == NULL) continue; // no break, no new node
1531 temp = g_list_prepend(temp, nn);
1532 }
1534 if (temp) {
1535 sp_nodepath_deselect(nodepath);
1536 }
1537 for (GList *l = temp; l != NULL; l = l->next) {
1538 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1539 }
1541 sp_nodepath_update_handles(nodepath);
1543 sp_nodepath_update_repr(nodepath);
1544 }
1546 /**
1547 * Duplicate the selected node(s).
1548 */
1549 void sp_node_selected_duplicate()
1550 {
1551 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1552 if (!nodepath) {
1553 return;
1554 }
1556 GList *temp = NULL;
1557 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1558 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1559 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1560 if (nn == NULL) continue; // could not duplicate
1561 temp = g_list_prepend(temp, nn);
1562 }
1564 if (temp) {
1565 sp_nodepath_deselect(nodepath);
1566 }
1567 for (GList *l = temp; l != NULL; l = l->next) {
1568 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1569 }
1571 sp_nodepath_update_handles(nodepath);
1573 sp_nodepath_update_repr(nodepath);
1574 }
1576 /**
1577 * Join two nodes by merging them into one.
1578 */
1579 void sp_node_selected_join()
1580 {
1581 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1582 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1584 if (g_list_length(nodepath->selected) != 2) {
1585 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1586 return;
1587 }
1589 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1590 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1592 g_assert(a != b);
1593 g_assert(a->p.other || a->n.other);
1594 g_assert(b->p.other || b->n.other);
1596 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1597 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1598 return;
1599 }
1601 /* a and b are endpoints */
1603 NR::Point c;
1604 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1605 c = a->pos;
1606 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1607 c = b->pos;
1608 } else {
1609 c = (a->pos + b->pos) / 2;
1610 }
1612 if (a->subpath == b->subpath) {
1613 Inkscape::NodePath::SubPath *sp = a->subpath;
1614 sp_nodepath_subpath_close(sp);
1615 sp_node_moveto (sp->first, c);
1617 sp_nodepath_update_handles(sp->nodepath);
1618 sp_nodepath_update_repr(nodepath);
1619 return;
1620 }
1622 /* a and b are separate subpaths */
1623 Inkscape::NodePath::SubPath *sa = a->subpath;
1624 Inkscape::NodePath::SubPath *sb = b->subpath;
1625 NR::Point p;
1626 Inkscape::NodePath::Node *n;
1627 NRPathcode code;
1628 if (a == sa->first) {
1629 p = sa->first->n.pos;
1630 code = (NRPathcode)sa->first->n.other->code;
1631 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1632 n = sa->last;
1633 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1634 n = n->p.other;
1635 while (n) {
1636 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1637 n = n->p.other;
1638 if (n == sa->first) n = NULL;
1639 }
1640 sp_nodepath_subpath_destroy(sa);
1641 sa = t;
1642 } else if (a == sa->last) {
1643 p = sa->last->p.pos;
1644 code = (NRPathcode)sa->last->code;
1645 sp_nodepath_node_destroy(sa->last);
1646 } else {
1647 code = NR_END;
1648 g_assert_not_reached();
1649 }
1651 if (b == sb->first) {
1652 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1653 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1654 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1655 }
1656 } else if (b == sb->last) {
1657 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1658 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1659 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1660 }
1661 } else {
1662 g_assert_not_reached();
1663 }
1664 /* and now destroy sb */
1666 sp_nodepath_subpath_destroy(sb);
1668 sp_nodepath_update_handles(sa->nodepath);
1670 sp_nodepath_update_repr(nodepath);
1672 sp_nodepath_update_statusbar(nodepath);
1673 }
1675 /**
1676 * Join two nodes by adding a segment between them.
1677 */
1678 void sp_node_selected_join_segment()
1679 {
1680 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1681 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1683 if (g_list_length(nodepath->selected) != 2) {
1684 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1685 return;
1686 }
1688 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1689 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1691 g_assert(a != b);
1692 g_assert(a->p.other || a->n.other);
1693 g_assert(b->p.other || b->n.other);
1695 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1696 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1697 return;
1698 }
1700 if (a->subpath == b->subpath) {
1701 Inkscape::NodePath::SubPath *sp = a->subpath;
1703 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1704 sp->closed = TRUE;
1706 sp->first->p.other = sp->last;
1707 sp->last->n.other = sp->first;
1709 sp_node_handle_mirror_p_to_n(sp->last);
1710 sp_node_handle_mirror_n_to_p(sp->first);
1712 sp->first->code = sp->last->code;
1713 sp->first = sp->last;
1715 sp_nodepath_update_handles(sp->nodepath);
1717 sp_nodepath_update_repr(nodepath);
1719 return;
1720 }
1722 /* a and b are separate subpaths */
1723 Inkscape::NodePath::SubPath *sa = a->subpath;
1724 Inkscape::NodePath::SubPath *sb = b->subpath;
1726 Inkscape::NodePath::Node *n;
1727 NR::Point p;
1728 NRPathcode code;
1729 if (a == sa->first) {
1730 code = (NRPathcode) sa->first->n.other->code;
1731 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1732 n = sa->last;
1733 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1734 for (n = n->p.other; n != NULL; n = n->p.other) {
1735 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1736 }
1737 sp_nodepath_subpath_destroy(sa);
1738 sa = t;
1739 } else if (a == sa->last) {
1740 code = (NRPathcode)sa->last->code;
1741 } else {
1742 code = NR_END;
1743 g_assert_not_reached();
1744 }
1746 if (b == sb->first) {
1747 n = sb->first;
1748 sp_node_handle_mirror_p_to_n(sa->last);
1749 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1750 sp_node_handle_mirror_n_to_p(sa->last);
1751 for (n = n->n.other; n != NULL; n = n->n.other) {
1752 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1753 }
1754 } else if (b == sb->last) {
1755 n = sb->last;
1756 sp_node_handle_mirror_p_to_n(sa->last);
1757 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1758 sp_node_handle_mirror_n_to_p(sa->last);
1759 for (n = n->p.other; n != NULL; n = n->p.other) {
1760 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1761 }
1762 } else {
1763 g_assert_not_reached();
1764 }
1765 /* and now destroy sb */
1767 sp_nodepath_subpath_destroy(sb);
1769 sp_nodepath_update_handles(sa->nodepath);
1771 sp_nodepath_update_repr(nodepath);
1772 }
1774 /**
1775 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1776 */
1777 void sp_node_delete_preserve(GList *nodes_to_delete)
1778 {
1779 GSList *nodepaths = NULL;
1781 while (nodes_to_delete) {
1782 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1783 Inkscape::NodePath::SubPath *sp = node->subpath;
1784 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1785 Inkscape::NodePath::Node *sample_cursor = NULL;
1786 Inkscape::NodePath::Node *sample_end = NULL;
1787 Inkscape::NodePath::Node *delete_cursor = node;
1788 bool just_delete = false;
1790 //find the start of this contiguous selection
1791 //move left to the first node that is not selected
1792 //or the start of the non-closed path
1793 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1794 delete_cursor = curr;
1795 }
1797 //just delete at the beginning of an open path
1798 if (!delete_cursor->p.other) {
1799 sample_cursor = delete_cursor;
1800 just_delete = true;
1801 } else {
1802 sample_cursor = delete_cursor->p.other;
1803 }
1805 //calculate points for each segment
1806 int rate = 5;
1807 float period = 1.0 / rate;
1808 std::vector<NR::Point> data;
1809 if (!just_delete) {
1810 data.push_back(sample_cursor->pos);
1811 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1812 //just delete at the end of an open path
1813 if (!sp->closed && curr->n.other == sp->last) {
1814 just_delete = true;
1815 break;
1816 }
1818 //sample points on the contiguous selected segment
1819 NR::Point *bez;
1820 bez = new NR::Point [4];
1821 bez[0] = curr->pos;
1822 bez[1] = curr->n.pos;
1823 bez[2] = curr->n.other->p.pos;
1824 bez[3] = curr->n.other->pos;
1825 for (int i=1; i<rate; i++) {
1826 gdouble t = i * period;
1827 NR::Point p = bezier_pt(3, bez, t);
1828 data.push_back(p);
1829 }
1830 data.push_back(curr->n.other->pos);
1832 sample_end = curr->n.other;
1833 //break if we've come full circle or hit the end of the selection
1834 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1835 break;
1836 }
1837 }
1838 }
1840 if (!just_delete) {
1841 //calculate the best fitting single segment and adjust the endpoints
1842 NR::Point *adata;
1843 adata = new NR::Point [data.size()];
1844 copy(data.begin(), data.end(), adata);
1846 NR::Point *bez;
1847 bez = new NR::Point [4];
1848 //would decreasing error create a better fitting approximation?
1849 gdouble error = 1.0;
1850 gint ret;
1851 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1853 //adjust endpoints
1854 sample_cursor->n.pos = bez[1];
1855 sample_end->p.pos = bez[2];
1856 }
1858 //destroy this contiguous selection
1859 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1860 Inkscape::NodePath::Node *temp = delete_cursor;
1861 if (delete_cursor->n.other == delete_cursor) {
1862 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1863 delete_cursor = NULL;
1864 } else {
1865 delete_cursor = delete_cursor->n.other;
1866 }
1867 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1868 sp_nodepath_node_destroy(temp);
1869 }
1871 sp_nodepath_update_handles(nodepath);
1873 if (!g_slist_find(nodepaths, nodepath))
1874 nodepaths = g_slist_prepend (nodepaths, nodepath);
1875 }
1877 for (GSList *i = nodepaths; i; i = i->next) {
1878 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1879 // different nodepaths will give us one undo event per nodepath
1880 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1882 // if the entire nodepath is removed, delete the selected object.
1883 if (nodepath->subpaths == NULL ||
1884 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1885 //at least 2
1886 sp_nodepath_get_node_count(nodepath) < 2) {
1887 SPDocument *document = sp_desktop_document (nodepath->desktop);
1888 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1889 //delete this nodepath's object, not the entire selection! (though at this time, this
1890 //does not matter)
1891 sp_selection_delete();
1892 sp_document_done (document);
1893 } else {
1894 sp_nodepath_update_repr(nodepath);
1895 sp_nodepath_update_statusbar(nodepath);
1896 }
1897 }
1899 g_slist_free (nodepaths);
1900 }
1902 /**
1903 * Delete one or more selected nodes.
1904 */
1905 void sp_node_selected_delete()
1906 {
1907 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1908 if (!nodepath) return;
1909 if (!nodepath->selected) return;
1911 /** \todo fixme: do it the right way */
1912 while (nodepath->selected) {
1913 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1914 sp_nodepath_node_destroy(node);
1915 }
1918 //clean up the nodepath (such as for trivial subpaths)
1919 sp_nodepath_cleanup(nodepath);
1921 sp_nodepath_update_handles(nodepath);
1923 // if the entire nodepath is removed, delete the selected object.
1924 if (nodepath->subpaths == NULL ||
1925 sp_nodepath_get_node_count(nodepath) < 2) {
1926 SPDocument *document = sp_desktop_document (nodepath->desktop);
1927 sp_selection_delete();
1928 sp_document_done (document);
1929 return;
1930 }
1932 sp_nodepath_update_repr(nodepath);
1934 sp_nodepath_update_statusbar(nodepath);
1935 }
1937 /**
1938 * Delete one or more segments between two selected nodes.
1939 * This is the code for 'split'.
1940 */
1941 void
1942 sp_node_selected_delete_segment(void)
1943 {
1944 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1945 Inkscape::NodePath::Node *curr, *next; //Iterators
1947 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1948 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1950 if (g_list_length(nodepath->selected) != 2) {
1951 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1952 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1953 return;
1954 }
1956 //Selected nodes, not inclusive
1957 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1958 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1960 if ( ( a==b) || //same node
1961 (a->subpath != b->subpath ) || //not the same path
1962 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1963 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1964 {
1965 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1966 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1967 return;
1968 }
1970 //###########################################
1971 //# BEGIN EDITS
1972 //###########################################
1973 //##################################
1974 //# CLOSED PATH
1975 //##################################
1976 if (a->subpath->closed) {
1979 gboolean reversed = FALSE;
1981 //Since we can go in a circle, we need to find the shorter distance.
1982 // a->b or b->a
1983 start = end = NULL;
1984 int distance = 0;
1985 int minDistance = 0;
1986 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1987 if (curr==b) {
1988 //printf("a to b:%d\n", distance);
1989 start = a;//go from a to b
1990 end = b;
1991 minDistance = distance;
1992 //printf("A to B :\n");
1993 break;
1994 }
1995 distance++;
1996 }
1998 //try again, the other direction
1999 distance = 0;
2000 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2001 if (curr==a) {
2002 //printf("b to a:%d\n", distance);
2003 if (distance < minDistance) {
2004 start = b; //we go from b to a
2005 end = a;
2006 reversed = TRUE;
2007 //printf("B to A\n");
2008 }
2009 break;
2010 }
2011 distance++;
2012 }
2015 //Copy everything from 'end' to 'start' to a new subpath
2016 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2017 for (curr=end ; curr ; curr=curr->n.other) {
2018 NRPathcode code = (NRPathcode) curr->code;
2019 if (curr == end)
2020 code = NR_MOVETO;
2021 sp_nodepath_node_new(t, NULL,
2022 (Inkscape::NodePath::NodeType)curr->type, code,
2023 &curr->p.pos, &curr->pos, &curr->n.pos);
2024 if (curr == start)
2025 break;
2026 }
2027 sp_nodepath_subpath_destroy(a->subpath);
2030 }
2034 //##################################
2035 //# OPEN PATH
2036 //##################################
2037 else {
2039 //We need to get the direction of the list between A and B
2040 //Can we walk from a to b?
2041 start = end = NULL;
2042 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2043 if (curr==b) {
2044 start = a; //did it! we go from a to b
2045 end = b;
2046 //printf("A to B\n");
2047 break;
2048 }
2049 }
2050 if (!start) {//didn't work? let's try the other direction
2051 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2052 if (curr==a) {
2053 start = b; //did it! we go from b to a
2054 end = a;
2055 //printf("B to A\n");
2056 break;
2057 }
2058 }
2059 }
2060 if (!start) {
2061 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2062 _("Cannot find path between nodes."));
2063 return;
2064 }
2068 //Copy everything after 'end' to a new subpath
2069 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2070 for (curr=end ; curr ; curr=curr->n.other) {
2071 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2072 &curr->p.pos, &curr->pos, &curr->n.pos);
2073 }
2075 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2076 for (curr = start->n.other ; curr ; curr=next) {
2077 next = curr->n.other;
2078 sp_nodepath_node_destroy(curr);
2079 }
2081 }
2082 //###########################################
2083 //# END EDITS
2084 //###########################################
2086 //clean up the nodepath (such as for trivial subpaths)
2087 sp_nodepath_cleanup(nodepath);
2089 sp_nodepath_update_handles(nodepath);
2091 sp_nodepath_update_repr(nodepath);
2093 sp_nodepath_update_statusbar(nodepath);
2094 }
2096 /**
2097 * Call sp_nodepath_set_line() for all selected segments.
2098 */
2099 void
2100 sp_node_selected_set_line_type(NRPathcode code)
2101 {
2102 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2103 if (nodepath == NULL) return;
2105 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2106 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2107 g_assert(n->selected);
2108 if (n->p.other && n->p.other->selected) {
2109 sp_nodepath_set_line_type(n, code);
2110 }
2111 }
2113 sp_nodepath_update_repr(nodepath);
2114 }
2116 /**
2117 * Call sp_nodepath_convert_node_type() for all selected nodes.
2118 */
2119 void
2120 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2121 {
2122 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2123 if (nodepath == NULL) return;
2125 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2126 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2127 }
2129 sp_nodepath_update_repr(nodepath);
2130 }
2132 /**
2133 * Change select status of node, update its own and neighbour handles.
2134 */
2135 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2136 {
2137 node->selected = selected;
2139 if (selected) {
2140 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2141 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2142 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2143 sp_knot_update_ctrl(node->knot);
2144 } else {
2145 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2146 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2147 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2148 sp_knot_update_ctrl(node->knot);
2149 }
2151 sp_node_update_handles(node);
2152 if (node->n.other) sp_node_update_handles(node->n.other);
2153 if (node->p.other) sp_node_update_handles(node->p.other);
2154 }
2156 /**
2157 \brief Select a node
2158 \param node The node to select
2159 \param incremental If true, add to selection, otherwise deselect others
2160 \param override If true, always select this node, otherwise toggle selected status
2161 */
2162 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2163 {
2164 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2166 if (incremental) {
2167 if (override) {
2168 if (!g_list_find(nodepath->selected, node)) {
2169 nodepath->selected = g_list_prepend(nodepath->selected, node);
2170 }
2171 sp_node_set_selected(node, TRUE);
2172 } else { // toggle
2173 if (node->selected) {
2174 g_assert(g_list_find(nodepath->selected, node));
2175 nodepath->selected = g_list_remove(nodepath->selected, node);
2176 } else {
2177 g_assert(!g_list_find(nodepath->selected, node));
2178 nodepath->selected = g_list_prepend(nodepath->selected, node);
2179 }
2180 sp_node_set_selected(node, !node->selected);
2181 }
2182 } else {
2183 sp_nodepath_deselect(nodepath);
2184 nodepath->selected = g_list_prepend(nodepath->selected, node);
2185 sp_node_set_selected(node, TRUE);
2186 }
2188 sp_nodepath_update_statusbar(nodepath);
2189 }
2192 /**
2193 \brief Deselect all nodes in the nodepath
2194 */
2195 void
2196 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2197 {
2198 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2200 while (nodepath->selected) {
2201 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2202 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2203 }
2204 sp_nodepath_update_statusbar(nodepath);
2205 }
2207 /**
2208 \brief Select or invert selection of all nodes in the nodepath
2209 */
2210 void
2211 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2212 {
2213 if (!nodepath) return;
2215 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2216 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2217 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2218 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2219 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2220 }
2221 }
2222 }
2224 /**
2225 * If nothing selected, does the same as sp_nodepath_select_all();
2226 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2227 * (i.e., similar to "select all in layer", with the "selected" subpaths
2228 * being treated as "layers" in the path).
2229 */
2230 void
2231 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2232 {
2233 if (!nodepath) return;
2235 if (g_list_length (nodepath->selected) == 0) {
2236 sp_nodepath_select_all (nodepath, invert);
2237 return;
2238 }
2240 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2241 GSList *subpaths = NULL;
2243 for (GList *l = copy; l != NULL; l = l->next) {
2244 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2245 Inkscape::NodePath::SubPath *subpath = n->subpath;
2246 if (!g_slist_find (subpaths, subpath))
2247 subpaths = g_slist_prepend (subpaths, subpath);
2248 }
2250 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2251 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2252 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2253 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2254 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2255 }
2256 }
2258 g_slist_free (subpaths);
2259 g_list_free (copy);
2260 }
2262 /**
2263 * \brief Select the node after the last selected; if none is selected,
2264 * select the first within path.
2265 */
2266 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2267 {
2268 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2270 Inkscape::NodePath::Node *last = NULL;
2271 if (nodepath->selected) {
2272 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2273 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2274 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2275 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2276 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2277 if (node->selected) {
2278 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2279 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2280 if (spl->next) { // there's a next subpath
2281 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2282 last = subpath_next->first;
2283 } else if (spl->prev) { // there's a previous subpath
2284 last = NULL; // to be set later to the first node of first subpath
2285 } else {
2286 last = node->n.other;
2287 }
2288 } else {
2289 last = node->n.other;
2290 }
2291 } else {
2292 if (node->n.other) {
2293 last = node->n.other;
2294 } else {
2295 if (spl->next) { // there's a next subpath
2296 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2297 last = subpath_next->first;
2298 } else if (spl->prev) { // there's a previous subpath
2299 last = NULL; // to be set later to the first node of first subpath
2300 } else {
2301 last = (Inkscape::NodePath::Node *) subpath->first;
2302 }
2303 }
2304 }
2305 }
2306 }
2307 }
2308 sp_nodepath_deselect(nodepath);
2309 }
2311 if (last) { // there's at least one more node after selected
2312 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2313 } else { // no more nodes, select the first one in first subpath
2314 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2315 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2316 }
2317 }
2319 /**
2320 * \brief Select the node before the first selected; if none is selected,
2321 * select the last within path
2322 */
2323 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2324 {
2325 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2327 Inkscape::NodePath::Node *last = NULL;
2328 if (nodepath->selected) {
2329 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2330 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2331 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2332 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2333 if (node->selected) {
2334 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2335 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2336 if (spl->prev) { // there's a prev subpath
2337 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2338 last = subpath_prev->last;
2339 } else if (spl->next) { // there's a next subpath
2340 last = NULL; // to be set later to the last node of last subpath
2341 } else {
2342 last = node->p.other;
2343 }
2344 } else {
2345 last = node->p.other;
2346 }
2347 } else {
2348 if (node->p.other) {
2349 last = node->p.other;
2350 } else {
2351 if (spl->prev) { // there's a prev subpath
2352 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2353 last = subpath_prev->last;
2354 } else if (spl->next) { // there's a next subpath
2355 last = NULL; // to be set later to the last node of last subpath
2356 } else {
2357 last = (Inkscape::NodePath::Node *) subpath->last;
2358 }
2359 }
2360 }
2361 }
2362 }
2363 }
2364 sp_nodepath_deselect(nodepath);
2365 }
2367 if (last) { // there's at least one more node before selected
2368 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2369 } else { // no more nodes, select the last one in last subpath
2370 GList *spl = g_list_last(nodepath->subpaths);
2371 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2372 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2373 }
2374 }
2376 /**
2377 * \brief Select all nodes that are within the rectangle.
2378 */
2379 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2380 {
2381 if (!incremental) {
2382 sp_nodepath_deselect(nodepath);
2383 }
2385 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2386 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2387 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2388 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2390 if (b.contains(node->pos)) {
2391 sp_nodepath_node_select(node, TRUE, TRUE);
2392 }
2393 }
2394 }
2395 }
2398 /**
2399 \brief Saves all nodes' and handles' current positions in their origin members
2400 */
2401 void
2402 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2403 {
2404 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2405 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2406 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2408 n->origin = n->pos;
2409 n->p.origin = n->p.pos;
2410 n->n.origin = n->n.pos;
2411 }
2412 }
2413 }
2415 /**
2416 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2417 */
2418 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2419 {
2420 if (!nodepath->selected) {
2421 return NULL;
2422 }
2424 GList *r = NULL;
2425 guint i = 0;
2426 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2427 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2428 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2429 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2430 i++;
2431 if (node->selected) {
2432 r = g_list_append(r, GINT_TO_POINTER(i));
2433 }
2434 }
2435 }
2436 return r;
2437 }
2439 /**
2440 \brief Restores selection by selecting nodes whose positions are in the list
2441 */
2442 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2443 {
2444 sp_nodepath_deselect(nodepath);
2446 guint i = 0;
2447 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2448 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2449 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2450 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2451 i++;
2452 if (g_list_find(r, GINT_TO_POINTER(i))) {
2453 sp_nodepath_node_select(node, TRUE, TRUE);
2454 }
2455 }
2456 }
2458 }
2460 /**
2461 \brief Adjusts handle according to node type and line code.
2462 */
2463 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2464 {
2465 double len, otherlen, linelen;
2467 g_assert(node);
2469 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2470 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2472 /** \todo fixme: */
2473 if (me->other == NULL) return;
2474 if (other->other == NULL) return;
2476 /* I have line */
2478 NRPathcode mecode, ocode;
2479 if (which_adjust == 1) {
2480 mecode = (NRPathcode)me->other->code;
2481 ocode = (NRPathcode)node->code;
2482 } else {
2483 mecode = (NRPathcode)node->code;
2484 ocode = (NRPathcode)other->other->code;
2485 }
2487 if (mecode == NR_LINETO) return;
2489 /* I am curve */
2491 if (other->other == NULL) return;
2493 /* Other has line */
2495 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2497 NR::Point delta;
2498 if (ocode == NR_LINETO) {
2499 /* other is lineto, we are either smooth or symm */
2500 Inkscape::NodePath::Node *othernode = other->other;
2501 len = NR::L2(me->pos - node->pos);
2502 delta = node->pos - othernode->pos;
2503 linelen = NR::L2(delta);
2504 if (linelen < 1e-18)
2505 return;
2506 me->pos = node->pos + (len / linelen)*delta;
2507 return;
2508 }
2510 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2512 me->pos = 2 * node->pos - other->pos;
2513 return;
2514 }
2516 /* We are smooth */
2518 len = NR::L2(me->pos - node->pos);
2519 delta = other->pos - node->pos;
2520 otherlen = NR::L2(delta);
2521 if (otherlen < 1e-18) return;
2523 me->pos = node->pos - (len / otherlen) * delta;
2524 }
2526 /**
2527 \brief Adjusts both handles according to node type and line code
2528 */
2529 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2530 {
2531 g_assert(node);
2533 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2535 /* we are either smooth or symm */
2537 if (node->p.other == NULL) return;
2539 if (node->n.other == NULL) return;
2541 if (node->code == NR_LINETO) {
2542 if (node->n.other->code == NR_LINETO) return;
2543 sp_node_adjust_handle(node, 1);
2544 return;
2545 }
2547 if (node->n.other->code == NR_LINETO) {
2548 if (node->code == NR_LINETO) return;
2549 sp_node_adjust_handle(node, -1);
2550 return;
2551 }
2553 /* both are curves */
2554 NR::Point const delta( node->n.pos - node->p.pos );
2556 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2557 node->p.pos = node->pos - delta / 2;
2558 node->n.pos = node->pos + delta / 2;
2559 return;
2560 }
2562 /* We are smooth */
2563 double plen = NR::L2(node->p.pos - node->pos);
2564 if (plen < 1e-18) return;
2565 double nlen = NR::L2(node->n.pos - node->pos);
2566 if (nlen < 1e-18) return;
2567 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2568 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2569 }
2571 /**
2572 * Node event callback.
2573 */
2574 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2575 {
2576 gboolean ret = FALSE;
2577 switch (event->type) {
2578 case GDK_ENTER_NOTIFY:
2579 active_node = n;
2580 break;
2581 case GDK_LEAVE_NOTIFY:
2582 active_node = NULL;
2583 break;
2584 case GDK_KEY_PRESS:
2585 switch (get_group0_keyval (&event->key)) {
2586 case GDK_space:
2587 if (event->key.state & GDK_BUTTON1_MASK) {
2588 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2589 stamp_repr(nodepath);
2590 ret = TRUE;
2591 }
2592 break;
2593 default:
2594 break;
2595 }
2596 break;
2597 default:
2598 break;
2599 }
2601 return ret;
2602 }
2604 /**
2605 * Handle keypress on node; directly called.
2606 */
2607 gboolean node_key(GdkEvent *event)
2608 {
2609 Inkscape::NodePath::Path *np;
2611 // there is no way to verify nodes so set active_node to nil when deleting!!
2612 if (active_node == NULL) return FALSE;
2614 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2615 gint ret = FALSE;
2616 switch (get_group0_keyval (&event->key)) {
2617 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2618 case GDK_BackSpace:
2619 np = active_node->subpath->nodepath;
2620 sp_nodepath_node_destroy(active_node);
2621 sp_nodepath_update_repr(np);
2622 active_node = NULL;
2623 ret = TRUE;
2624 break;
2625 case GDK_c:
2626 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2627 ret = TRUE;
2628 break;
2629 case GDK_s:
2630 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2631 ret = TRUE;
2632 break;
2633 case GDK_y:
2634 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2635 ret = TRUE;
2636 break;
2637 case GDK_b:
2638 sp_nodepath_node_break(active_node);
2639 ret = TRUE;
2640 break;
2641 }
2642 return ret;
2643 }
2644 return FALSE;
2645 }
2647 /**
2648 * Mouseclick on node callback.
2649 */
2650 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2651 {
2652 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2654 if (state & GDK_CONTROL_MASK) {
2655 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2657 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2658 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2659 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2660 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2661 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2662 } else {
2663 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2664 }
2665 sp_nodepath_update_repr(nodepath);
2666 sp_nodepath_update_statusbar(nodepath);
2668 } else { //ctrl+alt+click: delete node
2669 GList *node_to_delete = NULL;
2670 node_to_delete = g_list_append(node_to_delete, n);
2671 sp_node_delete_preserve(node_to_delete);
2672 }
2674 } else {
2675 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2676 }
2677 }
2679 /**
2680 * Mouse grabbed node callback.
2681 */
2682 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2683 {
2684 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2686 if (!n->selected) {
2687 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2688 }
2690 sp_nodepath_remember_origins (n->subpath->nodepath);
2691 }
2693 /**
2694 * Mouse ungrabbed node callback.
2695 */
2696 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2697 {
2698 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2700 n->dragging_out = NULL;
2702 sp_nodepath_update_repr(n->subpath->nodepath);
2703 }
2705 /**
2706 * The point on a line, given by its angle, closest to the given point.
2707 * \param p A point.
2708 * \param a Angle of the line; it is assumed to go through coordinate origin.
2709 * \param closest Pointer to the point struct where the result is stored.
2710 * \todo FIXME: use dot product perhaps?
2711 */
2712 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2713 {
2714 if (a == HUGE_VAL) { // vertical
2715 *closest = NR::Point(0, (*p)[NR::Y]);
2716 } else {
2717 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2718 (*closest)[NR::Y] = a * (*closest)[NR::X];
2719 }
2720 }
2722 /**
2723 * Distance from the point to a line given by its angle.
2724 * \param p A point.
2725 * \param a Angle of the line; it is assumed to go through coordinate origin.
2726 */
2727 static double point_line_distance(NR::Point *p, double a)
2728 {
2729 NR::Point c;
2730 point_line_closest(p, a, &c);
2731 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]));
2732 }
2734 /**
2735 * Callback for node "request" signal.
2736 * \todo fixme: This goes to "moved" event? (lauris)
2737 */
2738 static gboolean
2739 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2740 {
2741 double yn, xn, yp, xp;
2742 double an, ap, na, pa;
2743 double d_an, d_ap, d_na, d_pa;
2744 gboolean collinear = FALSE;
2745 NR::Point c;
2746 NR::Point pr;
2748 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2750 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2751 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2753 NR::Point mouse = (*p);
2755 if (!n->dragging_out) {
2756 // This is the first drag-out event; find out which handle to drag out
2757 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2758 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2760 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2761 return FALSE;
2763 Inkscape::NodePath::NodeSide *opposite;
2764 if (appr_p > appr_n) { // closer to p
2765 n->dragging_out = &n->p;
2766 opposite = &n->n;
2767 n->code = NR_CURVETO;
2768 } else if (appr_p < appr_n) { // closer to n
2769 n->dragging_out = &n->n;
2770 opposite = &n->p;
2771 n->n.other->code = NR_CURVETO;
2772 } else { // p and n nodes are the same
2773 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2774 n->dragging_out = &n->p;
2775 opposite = &n->n;
2776 n->code = NR_CURVETO;
2777 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2778 n->dragging_out = &n->n;
2779 opposite = &n->p;
2780 n->n.other->code = NR_CURVETO;
2781 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2782 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);
2783 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);
2784 if (appr_other_p > appr_other_n) { // closer to other's p handle
2785 n->dragging_out = &n->n;
2786 opposite = &n->p;
2787 n->n.other->code = NR_CURVETO;
2788 } else { // closer to other's n handle
2789 n->dragging_out = &n->p;
2790 opposite = &n->n;
2791 n->code = NR_CURVETO;
2792 }
2793 }
2794 }
2796 // if there's another handle, make sure the one we drag out starts parallel to it
2797 if (opposite->pos != n->pos) {
2798 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2799 }
2801 // knots might not be created yet!
2802 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2803 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2804 }
2806 // pass this on to the handle-moved callback
2807 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2808 sp_node_update_handles(n);
2809 return TRUE;
2810 }
2812 if (state & GDK_CONTROL_MASK) { // constrained motion
2814 // calculate relative distances of handles
2815 // n handle:
2816 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2817 xn = n->n.pos[NR::X] - n->pos[NR::X];
2818 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2819 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2820 if (n->n.other) { // if there is the next point
2821 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2822 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2823 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2824 }
2825 }
2826 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2827 if (yn < 0) { xn = -xn; yn = -yn; }
2829 // p handle:
2830 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2831 xp = n->p.pos[NR::X] - n->pos[NR::X];
2832 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2833 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2834 if (n->p.other) {
2835 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2836 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2837 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2838 }
2839 }
2840 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2841 if (yp < 0) { xp = -xp; yp = -yp; }
2843 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2844 // sliding on handles, only if at least one of the handles is non-vertical
2845 // (otherwise it's the same as ctrl+drag anyway)
2847 // calculate angles of the handles
2848 if (xn == 0) {
2849 if (yn == 0) { // no handle, consider it the continuation of the other one
2850 an = 0;
2851 collinear = TRUE;
2852 }
2853 else an = 0; // vertical; set the angle to horizontal
2854 } else an = yn/xn;
2856 if (xp == 0) {
2857 if (yp == 0) { // no handle, consider it the continuation of the other one
2858 ap = an;
2859 }
2860 else ap = 0; // vertical; set the angle to horizontal
2861 } else ap = yp/xp;
2863 if (collinear) an = ap;
2865 // angles of the perpendiculars; HUGE_VAL means vertical
2866 if (an == 0) na = HUGE_VAL; else na = -1/an;
2867 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2869 // mouse point relative to the node's original pos
2870 pr = (*p) - n->origin;
2872 // distances to the four lines (two handles and two perpendiculars)
2873 d_an = point_line_distance(&pr, an);
2874 d_na = point_line_distance(&pr, na);
2875 d_ap = point_line_distance(&pr, ap);
2876 d_pa = point_line_distance(&pr, pa);
2878 // find out which line is the closest, save its closest point in c
2879 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2880 point_line_closest(&pr, an, &c);
2881 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2882 point_line_closest(&pr, ap, &c);
2883 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2884 point_line_closest(&pr, na, &c);
2885 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2886 point_line_closest(&pr, pa, &c);
2887 }
2889 // move the node to the closest point
2890 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2891 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2892 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2894 } else { // constraining to hor/vert
2896 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2897 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2898 } else { // snap to vert
2899 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2900 }
2901 }
2902 } else { // move freely
2903 if (state & GDK_MOD1_MASK) { // sculpt
2904 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2905 } else {
2906 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2907 (*p)[NR::X] - n->pos[NR::X],
2908 (*p)[NR::Y] - n->pos[NR::Y],
2909 (state & GDK_SHIFT_MASK) == 0);
2910 }
2911 }
2913 n->subpath->nodepath->desktop->scroll_to_point(p);
2915 return TRUE;
2916 }
2918 /**
2919 * Node handle clicked callback.
2920 */
2921 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2922 {
2923 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2925 if (state & GDK_CONTROL_MASK) { // "delete" handle
2926 if (n->p.knot == knot) {
2927 n->p.pos = n->pos;
2928 } else if (n->n.knot == knot) {
2929 n->n.pos = n->pos;
2930 }
2931 sp_node_update_handles(n);
2932 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2933 sp_nodepath_update_repr(nodepath);
2934 sp_nodepath_update_statusbar(nodepath);
2936 } else { // just select or add to selection, depending in Shift
2937 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2938 }
2939 }
2941 /**
2942 * Node handle grabbed callback.
2943 */
2944 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2945 {
2946 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2948 if (!n->selected) {
2949 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2950 }
2952 // remember the origin point of the handle
2953 if (n->p.knot == knot) {
2954 n->p.origin_radial = n->p.pos - n->pos;
2955 } else if (n->n.knot == knot) {
2956 n->n.origin_radial = n->n.pos - n->pos;
2957 } else {
2958 g_assert_not_reached();
2959 }
2961 }
2963 /**
2964 * Node handle ungrabbed callback.
2965 */
2966 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2967 {
2968 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2970 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2971 if (n->p.knot == knot) {
2972 n->p.origin_radial.a = 0;
2973 sp_knot_set_position(knot, &n->p.pos, state);
2974 } else if (n->n.knot == knot) {
2975 n->n.origin_radial.a = 0;
2976 sp_knot_set_position(knot, &n->n.pos, state);
2977 } else {
2978 g_assert_not_reached();
2979 }
2981 sp_nodepath_update_repr(n->subpath->nodepath);
2982 }
2984 /**
2985 * Node handle "request" signal callback.
2986 */
2987 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2988 {
2989 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2991 Inkscape::NodePath::NodeSide *me, *opposite;
2992 gint which;
2993 if (n->p.knot == knot) {
2994 me = &n->p;
2995 opposite = &n->n;
2996 which = -1;
2997 } else if (n->n.knot == knot) {
2998 me = &n->n;
2999 opposite = &n->p;
3000 which = 1;
3001 } else {
3002 me = opposite = NULL;
3003 which = 0;
3004 g_assert_not_reached();
3005 }
3007 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3009 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3011 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3012 /* We are smooth node adjacent with line */
3013 NR::Point const delta = *p - n->pos;
3014 NR::Coord const len = NR::L2(delta);
3015 Inkscape::NodePath::Node *othernode = opposite->other;
3016 NR::Point const ndelta = n->pos - othernode->pos;
3017 NR::Coord const linelen = NR::L2(ndelta);
3018 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3019 NR::Coord const scal = dot(delta, ndelta) / linelen;
3020 (*p) = n->pos + (scal / linelen) * ndelta;
3021 }
3022 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3023 } else {
3024 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3025 }
3027 sp_node_adjust_handle(n, -which);
3029 return FALSE;
3030 }
3032 /**
3033 * Node handle moved callback.
3034 */
3035 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3036 {
3037 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3039 Inkscape::NodePath::NodeSide *me;
3040 Inkscape::NodePath::NodeSide *other;
3041 if (n->p.knot == knot) {
3042 me = &n->p;
3043 other = &n->n;
3044 } else if (n->n.knot == knot) {
3045 me = &n->n;
3046 other = &n->p;
3047 } else {
3048 me = NULL;
3049 other = NULL;
3050 g_assert_not_reached();
3051 }
3053 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3054 Radial rme(me->pos - n->pos);
3055 Radial rother(other->pos - n->pos);
3056 Radial rnew(*p - n->pos);
3058 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3059 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3060 /* 0 interpreted as "no snapping". */
3062 // The closest PI/snaps angle, starting from zero.
3063 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3064 if (me->origin_radial.a == HUGE_VAL) {
3065 // ortho doesn't exist: original handle was zero length.
3066 rnew.a = a_snapped;
3067 } else {
3068 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3069 * its opposite and perpendiculars). */
3070 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3072 // Snap to the closest.
3073 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3074 ? a_snapped
3075 : a_ortho );
3076 }
3077 }
3079 if (state & GDK_MOD1_MASK) {
3080 // lock handle length
3081 rnew.r = me->origin_radial.r;
3082 }
3084 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3085 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3086 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3087 rother.a += rnew.a - rme.a;
3088 other->pos = NR::Point(rother) + n->pos;
3089 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3090 sp_knot_set_position(other->knot, &other->pos, 0);
3091 }
3093 me->pos = NR::Point(rnew) + n->pos;
3094 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3096 // this is what sp_knot_set_position does, but without emitting the signal:
3097 // we cannot emit a "moved" signal because we're now processing it
3098 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3100 knot->desktop->set_coordinate_status(me->pos);
3102 update_object(n->subpath->nodepath);
3104 /* status text */
3105 SPDesktop *desktop = n->subpath->nodepath->desktop;
3106 if (!desktop) return;
3107 SPEventContext *ec = desktop->event_context;
3108 if (!ec) return;
3109 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3110 if (!mc) return;
3112 double degrees = 180 / M_PI * rnew.a;
3113 if (degrees > 180) degrees -= 360;
3114 if (degrees < -180) degrees += 360;
3115 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3116 degrees = angle_to_compass (degrees);
3118 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3120 mc->setF(Inkscape::NORMAL_MESSAGE,
3121 _("<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);
3123 g_string_free(length, TRUE);
3124 }
3126 /**
3127 * Node handle event callback.
3128 */
3129 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3130 {
3131 gboolean ret = FALSE;
3132 switch (event->type) {
3133 case GDK_KEY_PRESS:
3134 switch (get_group0_keyval (&event->key)) {
3135 case GDK_space:
3136 if (event->key.state & GDK_BUTTON1_MASK) {
3137 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3138 stamp_repr(nodepath);
3139 ret = TRUE;
3140 }
3141 break;
3142 default:
3143 break;
3144 }
3145 break;
3146 default:
3147 break;
3148 }
3150 return ret;
3151 }
3153 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3154 Radial &rme, Radial &rother, gboolean const both)
3155 {
3156 rme.a += angle;
3157 if ( both
3158 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3159 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3160 {
3161 rother.a += angle;
3162 }
3163 }
3165 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3166 Radial &rme, Radial &rother, gboolean const both)
3167 {
3168 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3170 gdouble r;
3171 if ( both
3172 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3173 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3174 {
3175 r = MAX(rme.r, rother.r);
3176 } else {
3177 r = rme.r;
3178 }
3180 gdouble const weird_angle = atan2(norm_angle, r);
3181 /* Bulia says norm_angle is just the visible distance that the
3182 * object's end must travel on the screen. Left as 'angle' for want of
3183 * a better name.*/
3185 rme.a += weird_angle;
3186 if ( both
3187 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3188 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3189 {
3190 rother.a += weird_angle;
3191 }
3192 }
3194 /**
3195 * Rotate one node.
3196 */
3197 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3198 {
3199 Inkscape::NodePath::NodeSide *me, *other;
3200 bool both = false;
3202 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3203 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3205 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3206 me = &(n->p);
3207 other = &(n->n);
3208 } else if (!n->p.other) {
3209 me = &(n->n);
3210 other = &(n->p);
3211 } else {
3212 if (which > 0) { // right handle
3213 if (xn > xp) {
3214 me = &(n->n);
3215 other = &(n->p);
3216 } else {
3217 me = &(n->p);
3218 other = &(n->n);
3219 }
3220 } else if (which < 0){ // left handle
3221 if (xn <= xp) {
3222 me = &(n->n);
3223 other = &(n->p);
3224 } else {
3225 me = &(n->p);
3226 other = &(n->n);
3227 }
3228 } else { // both handles
3229 me = &(n->n);
3230 other = &(n->p);
3231 both = true;
3232 }
3233 }
3235 Radial rme(me->pos - n->pos);
3236 Radial rother(other->pos - n->pos);
3238 if (screen) {
3239 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3240 } else {
3241 node_rotate_one_internal (*n, angle, rme, rother, both);
3242 }
3244 me->pos = n->pos + NR::Point(rme);
3246 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3247 other->pos = n->pos + NR::Point(rother);
3248 }
3250 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3251 // so here we just move all the knots without emitting move signals, for speed
3252 sp_node_update_handles(n, false);
3253 }
3255 /**
3256 * Rotate selected nodes.
3257 */
3258 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3259 {
3260 if (!nodepath || !nodepath->selected) return;
3262 if (g_list_length(nodepath->selected) == 1) {
3263 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3264 node_rotate_one (n, angle, which, screen);
3265 } else {
3266 // rotate as an object:
3268 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3269 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3270 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3271 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3272 box.expandTo (n->pos); // contain all selected nodes
3273 }
3275 gdouble rot;
3276 if (screen) {
3277 gdouble const zoom = nodepath->desktop->current_zoom();
3278 gdouble const zmove = angle / zoom;
3279 gdouble const r = NR::L2(box.max() - box.midpoint());
3280 rot = atan2(zmove, r);
3281 } else {
3282 rot = angle;
3283 }
3285 NR::Matrix t =
3286 NR::Matrix (NR::translate(-box.midpoint())) *
3287 NR::Matrix (NR::rotate(rot)) *
3288 NR::Matrix (NR::translate(box.midpoint()));
3290 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3291 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3292 n->pos *= t;
3293 n->n.pos *= t;
3294 n->p.pos *= t;
3295 sp_node_update_handles(n, false);
3296 }
3297 }
3299 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3300 }
3302 /**
3303 * Scale one node.
3304 */
3305 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3306 {
3307 bool both = false;
3308 Inkscape::NodePath::NodeSide *me, *other;
3310 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3311 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3313 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3314 me = &(n->p);
3315 other = &(n->n);
3316 n->code = NR_CURVETO;
3317 } else if (!n->p.other) {
3318 me = &(n->n);
3319 other = &(n->p);
3320 if (n->n.other)
3321 n->n.other->code = NR_CURVETO;
3322 } else {
3323 if (which > 0) { // right handle
3324 if (xn > xp) {
3325 me = &(n->n);
3326 other = &(n->p);
3327 if (n->n.other)
3328 n->n.other->code = NR_CURVETO;
3329 } else {
3330 me = &(n->p);
3331 other = &(n->n);
3332 n->code = NR_CURVETO;
3333 }
3334 } else if (which < 0){ // left handle
3335 if (xn <= xp) {
3336 me = &(n->n);
3337 other = &(n->p);
3338 if (n->n.other)
3339 n->n.other->code = NR_CURVETO;
3340 } else {
3341 me = &(n->p);
3342 other = &(n->n);
3343 n->code = NR_CURVETO;
3344 }
3345 } else { // both handles
3346 me = &(n->n);
3347 other = &(n->p);
3348 both = true;
3349 n->code = NR_CURVETO;
3350 if (n->n.other)
3351 n->n.other->code = NR_CURVETO;
3352 }
3353 }
3355 Radial rme(me->pos - n->pos);
3356 Radial rother(other->pos - n->pos);
3358 rme.r += grow;
3359 if (rme.r < 0) rme.r = 0;
3360 if (rme.a == HUGE_VAL) {
3361 if (me->other) { // if direction is unknown, initialize it towards the next node
3362 Radial rme_next(me->other->pos - n->pos);
3363 rme.a = rme_next.a;
3364 } else { // if there's no next, initialize to 0
3365 rme.a = 0;
3366 }
3367 }
3368 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3369 rother.r += grow;
3370 if (rother.r < 0) rother.r = 0;
3371 if (rother.a == HUGE_VAL) {
3372 rother.a = rme.a + M_PI;
3373 }
3374 }
3376 me->pos = n->pos + NR::Point(rme);
3378 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3379 other->pos = n->pos + NR::Point(rother);
3380 }
3382 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3383 // so here we just move all the knots without emitting move signals, for speed
3384 sp_node_update_handles(n, false);
3385 }
3387 /**
3388 * Scale selected nodes.
3389 */
3390 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3391 {
3392 if (!nodepath || !nodepath->selected) return;
3394 if (g_list_length(nodepath->selected) == 1) {
3395 // scale handles of the single selected node
3396 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3397 node_scale_one (n, grow, which);
3398 } else {
3399 // scale nodes as an "object":
3401 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3402 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3403 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3404 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3405 box.expandTo (n->pos); // contain all selected nodes
3406 }
3408 double scale = (box.maxExtent() + grow)/box.maxExtent();
3410 NR::Matrix t =
3411 NR::Matrix (NR::translate(-box.midpoint())) *
3412 NR::Matrix (NR::scale(scale, scale)) *
3413 NR::Matrix (NR::translate(box.midpoint()));
3415 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3416 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3417 n->pos *= t;
3418 n->n.pos *= t;
3419 n->p.pos *= t;
3420 sp_node_update_handles(n, false);
3421 }
3422 }
3424 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3425 }
3427 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3428 {
3429 if (!nodepath) return;
3430 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3431 }
3433 /**
3434 * Flip selected nodes horizontally/vertically.
3435 */
3436 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3437 {
3438 if (!nodepath || !nodepath->selected) return;
3440 if (g_list_length(nodepath->selected) == 1) {
3441 // flip handles of the single selected node
3442 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3443 double temp = n->p.pos[axis];
3444 n->p.pos[axis] = n->n.pos[axis];
3445 n->n.pos[axis] = temp;
3446 sp_node_update_handles(n, false);
3447 } else {
3448 // scale nodes as an "object":
3450 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3451 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3452 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3453 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3454 box.expandTo (n->pos); // contain all selected nodes
3455 }
3457 NR::Matrix t =
3458 NR::Matrix (NR::translate(-box.midpoint())) *
3459 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3460 NR::Matrix (NR::translate(box.midpoint()));
3462 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3463 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3464 n->pos *= t;
3465 n->n.pos *= t;
3466 n->p.pos *= t;
3467 sp_node_update_handles(n, false);
3468 }
3469 }
3471 sp_nodepath_update_repr(nodepath);
3472 }
3474 //-----------------------------------------------
3475 /**
3476 * Return new subpath under given nodepath.
3477 */
3478 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3479 {
3480 g_assert(nodepath);
3481 g_assert(nodepath->desktop);
3483 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3485 s->nodepath = nodepath;
3486 s->closed = FALSE;
3487 s->nodes = NULL;
3488 s->first = NULL;
3489 s->last = NULL;
3491 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3492 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3493 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3495 return s;
3496 }
3498 /**
3499 * Destroy nodes in subpath, then subpath itself.
3500 */
3501 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3502 {
3503 g_assert(subpath);
3504 g_assert(subpath->nodepath);
3505 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3507 while (subpath->nodes) {
3508 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3509 }
3511 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3513 g_free(subpath);
3514 }
3516 /**
3517 * Link head to tail in subpath.
3518 */
3519 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3520 {
3521 g_assert(!sp->closed);
3522 g_assert(sp->last != sp->first);
3523 g_assert(sp->first->code == NR_MOVETO);
3525 sp->closed = TRUE;
3527 //Link the head to the tail
3528 sp->first->p.other = sp->last;
3529 sp->last->n.other = sp->first;
3530 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3531 sp->first = sp->last;
3533 //Remove the extra end node
3534 sp_nodepath_node_destroy(sp->last->n.other);
3535 }
3537 /**
3538 * Open closed (loopy) subpath at node.
3539 */
3540 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3541 {
3542 g_assert(sp->closed);
3543 g_assert(n->subpath == sp);
3544 g_assert(sp->first == sp->last);
3546 /* We create new startpoint, current node will become last one */
3548 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3549 &n->pos, &n->pos, &n->n.pos);
3552 sp->closed = FALSE;
3554 //Unlink to make a head and tail
3555 sp->first = new_path;
3556 sp->last = n;
3557 n->n.other = NULL;
3558 new_path->p.other = NULL;
3559 }
3561 /**
3562 * Returns area in triangle given by points; may be negative.
3563 */
3564 inline double
3565 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3566 {
3567 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]);
3568 }
3570 /**
3571 * Return new node in subpath with given properties.
3572 * \param pos Position of node.
3573 * \param ppos Handle position in previous direction
3574 * \param npos Handle position in previous direction
3575 */
3576 Inkscape::NodePath::Node *
3577 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)
3578 {
3579 g_assert(sp);
3580 g_assert(sp->nodepath);
3581 g_assert(sp->nodepath->desktop);
3583 if (nodechunk == NULL)
3584 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3586 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3588 n->subpath = sp;
3590 if (type != Inkscape::NodePath::NODE_NONE) {
3591 // use the type from sodipodi:nodetypes
3592 n->type = type;
3593 } else {
3594 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3595 // points are (almost) collinear
3596 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3597 // endnode, or a node with a retracted handle
3598 n->type = Inkscape::NodePath::NODE_CUSP;
3599 } else {
3600 n->type = Inkscape::NodePath::NODE_SMOOTH;
3601 }
3602 } else {
3603 n->type = Inkscape::NodePath::NODE_CUSP;
3604 }
3605 }
3607 n->code = code;
3608 n->selected = FALSE;
3609 n->pos = *pos;
3610 n->p.pos = *ppos;
3611 n->n.pos = *npos;
3613 n->dragging_out = NULL;
3615 Inkscape::NodePath::Node *prev;
3616 if (next) {
3617 //g_assert(g_list_find(sp->nodes, next));
3618 prev = next->p.other;
3619 } else {
3620 prev = sp->last;
3621 }
3623 if (prev)
3624 prev->n.other = n;
3625 else
3626 sp->first = n;
3628 if (next)
3629 next->p.other = n;
3630 else
3631 sp->last = n;
3633 n->p.other = prev;
3634 n->n.other = next;
3636 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"));
3637 sp_knot_set_position(n->knot, pos, 0);
3639 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3640 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3641 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3642 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3643 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3644 sp_knot_update_ctrl(n->knot);
3646 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3647 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3648 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3649 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3650 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3651 sp_knot_show(n->knot);
3653 // We only create handle knots and lines on demand
3654 n->p.knot = NULL;
3655 n->p.line = NULL;
3656 n->n.knot = NULL;
3657 n->n.line = NULL;
3659 sp->nodes = g_list_prepend(sp->nodes, n);
3661 return n;
3662 }
3664 /**
3665 * Destroy node and its knots, link neighbors in subpath.
3666 */
3667 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3668 {
3669 g_assert(node);
3670 g_assert(node->subpath);
3671 g_assert(SP_IS_KNOT(node->knot));
3673 Inkscape::NodePath::SubPath *sp = node->subpath;
3675 if (node->selected) { // first, deselect
3676 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3677 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3678 }
3680 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3682 g_object_unref(G_OBJECT(node->knot));
3683 if (node->p.knot)
3684 g_object_unref(G_OBJECT(node->p.knot));
3685 if (node->n.knot)
3686 g_object_unref(G_OBJECT(node->n.knot));
3688 if (node->p.line)
3689 gtk_object_destroy(GTK_OBJECT(node->p.line));
3690 if (node->n.line)
3691 gtk_object_destroy(GTK_OBJECT(node->n.line));
3693 if (sp->nodes) { // there are others nodes on the subpath
3694 if (sp->closed) {
3695 if (sp->first == node) {
3696 g_assert(sp->last == node);
3697 sp->first = node->n.other;
3698 sp->last = sp->first;
3699 }
3700 node->p.other->n.other = node->n.other;
3701 node->n.other->p.other = node->p.other;
3702 } else {
3703 if (sp->first == node) {
3704 sp->first = node->n.other;
3705 sp->first->code = NR_MOVETO;
3706 }
3707 if (sp->last == node) sp->last = node->p.other;
3708 if (node->p.other) node->p.other->n.other = node->n.other;
3709 if (node->n.other) node->n.other->p.other = node->p.other;
3710 }
3711 } else { // this was the last node on subpath
3712 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3713 }
3715 g_mem_chunk_free(nodechunk, node);
3716 }
3718 /**
3719 * Returns one of the node's two sides.
3720 * \param which Indicates which side.
3721 * \return Pointer to previous node side if which==-1, next if which==1.
3722 */
3723 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3724 {
3725 g_assert(node);
3727 switch (which) {
3728 case -1:
3729 return &node->p;
3730 case 1:
3731 return &node->n;
3732 default:
3733 break;
3734 }
3736 g_assert_not_reached();
3738 return NULL;
3739 }
3741 /**
3742 * Return the other side of the node, given one of its sides.
3743 */
3744 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3745 {
3746 g_assert(node);
3748 if (me == &node->p) return &node->n;
3749 if (me == &node->n) return &node->p;
3751 g_assert_not_reached();
3753 return NULL;
3754 }
3756 /**
3757 * Return NRPathcode on the given side of the node.
3758 */
3759 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3760 {
3761 g_assert(node);
3763 if (me == &node->p) {
3764 if (node->p.other) return (NRPathcode)node->code;
3765 return NR_MOVETO;
3766 }
3768 if (me == &node->n) {
3769 if (node->n.other) return (NRPathcode)node->n.other->code;
3770 return NR_MOVETO;
3771 }
3773 g_assert_not_reached();
3775 return NR_END;
3776 }
3778 /**
3779 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3780 */
3781 Inkscape::NodePath::Node *
3782 sp_nodepath_get_node_by_index(int index)
3783 {
3784 Inkscape::NodePath::Node *e = NULL;
3786 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3787 if (!nodepath) {
3788 return e;
3789 }
3791 //find segment
3792 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3794 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3795 int n = g_list_length(sp->nodes);
3796 if (sp->closed) {
3797 n++;
3798 }
3800 //if the piece belongs to this subpath grab it
3801 //otherwise move onto the next subpath
3802 if (index < n) {
3803 e = sp->first;
3804 for (int i = 0; i < index; ++i) {
3805 e = e->n.other;
3806 }
3807 break;
3808 } else {
3809 if (sp->closed) {
3810 index -= (n+1);
3811 } else {
3812 index -= n;
3813 }
3814 }
3815 }
3817 return e;
3818 }
3820 /**
3821 * Returns plain text meaning of node type.
3822 */
3823 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3824 {
3825 unsigned retracted = 0;
3826 bool endnode = false;
3828 for (int which = -1; which <= 1; which += 2) {
3829 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3830 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3831 retracted ++;
3832 if (!side->other)
3833 endnode = true;
3834 }
3836 if (retracted == 0) {
3837 if (endnode) {
3838 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3839 return _("end node");
3840 } else {
3841 switch (node->type) {
3842 case Inkscape::NodePath::NODE_CUSP:
3843 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3844 return _("cusp");
3845 case Inkscape::NodePath::NODE_SMOOTH:
3846 // TRANSLATORS: "smooth" is an adjective here
3847 return _("smooth");
3848 case Inkscape::NodePath::NODE_SYMM:
3849 return _("symmetric");
3850 }
3851 }
3852 } else if (retracted == 1) {
3853 if (endnode) {
3854 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3855 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3856 } else {
3857 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3858 }
3859 } else {
3860 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3861 }
3863 return NULL;
3864 }
3866 /**
3867 * Handles content of statusbar as long as node tool is active.
3868 */
3869 void
3870 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3871 {
3872 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3873 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3875 gint total = 0;
3876 gint selected = 0;
3877 SPDesktop *desktop = NULL;
3879 if (nodepath) {
3880 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3881 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3882 total += g_list_length(subpath->nodes);
3883 }
3884 selected = g_list_length(nodepath->selected);
3885 desktop = nodepath->desktop;
3886 } else {
3887 desktop = SP_ACTIVE_DESKTOP;
3888 }
3890 SPEventContext *ec = desktop->event_context;
3891 if (!ec) return;
3892 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3893 if (!mc) return;
3895 if (selected == 0) {
3896 Inkscape::Selection *sel = desktop->selection;
3897 if (!sel || sel->isEmpty()) {
3898 mc->setF(Inkscape::NORMAL_MESSAGE,
3899 _("Select a single object to edit its nodes or handles."));
3900 } else {
3901 if (nodepath) {
3902 mc->setF(Inkscape::NORMAL_MESSAGE,
3903 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.",
3904 "<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.",
3905 total),
3906 total);
3907 } else {
3908 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3909 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3910 } else {
3911 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3912 }
3913 }
3914 }
3915 } else if (nodepath && selected == 1) {
3916 mc->setF(Inkscape::NORMAL_MESSAGE,
3917 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3918 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3919 total),
3920 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3921 } else {
3922 mc->setF(Inkscape::NORMAL_MESSAGE,
3923 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3924 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3925 total),
3926 selected, total, when_selected);
3927 }
3928 }
3931 /*
3932 Local Variables:
3933 mode:c++
3934 c-file-style:"stroustrup"
3935 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3936 indent-tabs-mode:nil
3937 fill-column:99
3938 End:
3939 */
3940 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :