Code

memleak; move deleting empty paths until after all nodes are deleted
[inkscape.git] / src / nodepath.cpp
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)
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;
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);
248 /**
249  *  Return the node count of a given NodeSubPath.
250  */
251 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
253     if (!subpath)
254         return 0;
255     gint nodeCount = g_list_length(subpath->nodes);
256     return nodeCount;
259 /**
260  *  Return the node count of a given NodePath.
261  */
262 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
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;
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)
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);
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)
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;
350 /**
351  * Convert from sodipodi:nodetypes to new style type string.
352  */
353 static gchar *parse_nodetypes(gchar const *types, gint length)
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;
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)
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);
403 /**
404  * Update XML path node with data from path object.
405  */
406 static void update_repr_internal(Inkscape::NodePath::Path *np)
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);
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)
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     }
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)
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     }
471 /**
472  * Make duplicate of path, replace corresponding XML node in tree, commit.
473  */
474 static void stamp_repr(Inkscape::NodePath::Path *np)
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);
507 /**
508  * Create curve from path.
509  */
510 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
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;
549 /**
550  * Convert path type string to sodipodi:nodetypes style.
551  */
552 static gchar *create_typestr(Inkscape::NodePath::Path *np)
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;
614 /**
615  * Returns current path in context.
616  */
617 static Inkscape::NodePath::Path *sp_nodepath_current()
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;
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)
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     }
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)
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;
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)
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     }
740 /**
741  * Duplicate node and connect to neighbours.
742  */
743 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
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
764 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
766     node->p.pos = (node->pos + (node->pos - node->n.pos));
769 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
771     node->n.pos = (node->pos + (node->pos - node->p.pos));
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)
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);
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)
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;
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)
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);
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)
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);
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)
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;
930         
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);
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)
958     if (x >= 1)
959         return 0;
960     return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
963 double
964 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
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;
972 void
973 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
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);
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)
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);
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)
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     }
1122 /**
1123  * Move node selection off screen and commit the change.
1124  */
1125 void
1126 sp_node_selected_move_screen(gdouble dx, gdouble dy)
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     }
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)
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     }
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)
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     }
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)
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);
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)
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     }
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)
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     }
1281 /**
1282  * Adds all selected nodes in nodepath to list.
1283  */
1284 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1286     StlConv<Node *>::list(l, selected);
1287 /// \todo this adds a copying, rework when the selection becomes a stl list
1290 /**
1291  * Align selected nodes on the specified axis.
1292  */
1293 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
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);
1315 /// Helper struct.
1316 struct NodeSort
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)
1329     return (a._coord < b._coord);
1332 /**
1333  * Distribute selected nodes on the specified axis.
1334  */
1335 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
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);
1378 /**
1379  * Call sp_nodepath_line_add_node() for all selected segments.
1380  */
1381 void
1382 sp_node_selected_add_node(void)
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);
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)
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);
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)
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);
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)
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);
1518 /**
1519  * Call sp_nodepath_break() for all selected segments.
1520  */
1521 void sp_node_selected_break()
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);
1546 /**
1547  * Duplicate the selected node(s).
1548  */
1549 void sp_node_selected_duplicate()
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);
1576 /**
1577  *  Join two nodes by merging them into one.
1578  */
1579 void sp_node_selected_join()
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);
1675 /**
1676  *  Join two nodes by adding a segment between them.
1677  */
1678 void sp_node_selected_join_segment()
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);
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)
1779     GSList *nodepaths = NULL;
1780     
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;
1789         
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         }
1804         
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                 }
1817                 
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);
1845             
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         }
1857        
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;
1881         // if the entire nodepath is removed, delete the selected object.
1882         if (nodepath->subpaths == NULL ||
1883             //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1884             //at least 2
1885             sp_nodepath_get_node_count(nodepath) < 2) {
1886             SPDocument *document = sp_desktop_document (nodepath->desktop);
1887             sp_nodepath_destroy(nodepath);
1888             g_list_free(nodes_to_delete);
1889             nodes_to_delete = NULL;
1890             //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1891             //delete this nodepath's object, not the entire selection! (though at this time, this
1892             //does not matter)
1893             sp_selection_delete();
1894             sp_document_done (document);
1895         } else {
1896             sp_nodepath_update_repr(nodepath);
1897             sp_nodepath_update_statusbar(nodepath);
1898         }
1899     }
1901     g_slist_free (nodepaths);
1904 /**
1905  * Delete one or more selected nodes.
1906  */
1907 void sp_node_selected_delete()
1909     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1910     if (!nodepath) return;
1911     if (!nodepath->selected) return;
1913     /** \todo fixme: do it the right way */
1914     while (nodepath->selected) {
1915        Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1916         sp_nodepath_node_destroy(node);
1917     }
1920     //clean up the nodepath (such as for trivial subpaths)
1921     sp_nodepath_cleanup(nodepath);
1923     sp_nodepath_update_handles(nodepath);
1925     // if the entire nodepath is removed, delete the selected object.
1926     if (nodepath->subpaths == NULL ||
1927         sp_nodepath_get_node_count(nodepath) < 2) {
1928         SPDocument *document = sp_desktop_document (nodepath->desktop);
1929         sp_nodepath_destroy(nodepath);
1930         sp_selection_delete();
1931         sp_document_done (document);
1932         return;
1933     }
1935     sp_nodepath_update_repr(nodepath);
1937     sp_nodepath_update_statusbar(nodepath);
1940 /**
1941  * Delete one or more segments between two selected nodes.
1942  * This is the code for 'split'.
1943  */
1944 void
1945 sp_node_selected_delete_segment(void)
1947    Inkscape::NodePath::Node *start, *end;     //Start , end nodes.  not inclusive
1948    Inkscape::NodePath::Node *curr, *next;     //Iterators
1950     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1951     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1953     if (g_list_length(nodepath->selected) != 2) {
1954         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1955                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1956         return;
1957     }
1959     //Selected nodes, not inclusive
1960    Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1961    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1963     if ( ( a==b)                       ||  //same node
1964          (a->subpath  != b->subpath )  ||  //not the same path
1965          (!a->p.other || !a->n.other)  ||  //one of a's sides does not have a segment
1966          (!b->p.other || !b->n.other) )    //one of b's sides does not have a segment
1967     {
1968         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1969                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1970         return;
1971     }
1973     //###########################################
1974     //# BEGIN EDITS
1975     //###########################################
1976     //##################################
1977     //# CLOSED PATH
1978     //##################################
1979     if (a->subpath->closed) {
1982         gboolean reversed = FALSE;
1984         //Since we can go in a circle, we need to find the shorter distance.
1985         //  a->b or b->a
1986         start = end = NULL;
1987         int distance    = 0;
1988         int minDistance = 0;
1989         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1990             if (curr==b) {
1991                 //printf("a to b:%d\n", distance);
1992                 start = a;//go from a to b
1993                 end   = b;
1994                 minDistance = distance;
1995                 //printf("A to B :\n");
1996                 break;
1997             }
1998             distance++;
1999         }
2001         //try again, the other direction
2002         distance = 0;
2003         for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2004             if (curr==a) {
2005                 //printf("b to a:%d\n", distance);
2006                 if (distance < minDistance) {
2007                     start    = b;  //we go from b to a
2008                     end      = a;
2009                     reversed = TRUE;
2010                     //printf("B to A\n");
2011                 }
2012                 break;
2013             }
2014             distance++;
2015         }
2018         //Copy everything from 'end' to 'start' to a new subpath
2019        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2020         for (curr=end ; curr ; curr=curr->n.other) {
2021             NRPathcode code = (NRPathcode) curr->code;
2022             if (curr == end)
2023                 code = NR_MOVETO;
2024             sp_nodepath_node_new(t, NULL,
2025                                  (Inkscape::NodePath::NodeType)curr->type, code,
2026                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2027             if (curr == start)
2028                 break;
2029         }
2030         sp_nodepath_subpath_destroy(a->subpath);
2033     }
2037     //##################################
2038     //# OPEN PATH
2039     //##################################
2040     else {
2042         //We need to get the direction of the list between A and B
2043         //Can we walk from a to b?
2044         start = end = NULL;
2045         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2046             if (curr==b) {
2047                 start = a;  //did it!  we go from a to b
2048                 end   = b;
2049                 //printf("A to B\n");
2050                 break;
2051             }
2052         }
2053         if (!start) {//didn't work?  let's try the other direction
2054             for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2055                 if (curr==a) {
2056                     start = b;  //did it!  we go from b to a
2057                     end   = a;
2058                     //printf("B to A\n");
2059                     break;
2060                 }
2061             }
2062         }
2063         if (!start) {
2064             nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2065                                                      _("Cannot find path between nodes."));
2066             return;
2067         }
2071         //Copy everything after 'end' to a new subpath
2072        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2073         for (curr=end ; curr ; curr=curr->n.other) {
2074             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2075                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2076         }
2078         //Now let us do our deletion.  Since the tail has been saved, go all the way to the end of the list
2079         for (curr = start->n.other ; curr  ; curr=next) {
2080             next = curr->n.other;
2081             sp_nodepath_node_destroy(curr);
2082         }
2084     }
2085     //###########################################
2086     //# END EDITS
2087     //###########################################
2089     //clean up the nodepath (such as for trivial subpaths)
2090     sp_nodepath_cleanup(nodepath);
2092     sp_nodepath_update_handles(nodepath);
2094     sp_nodepath_update_repr(nodepath);
2096     // if the entire nodepath is removed, delete the selected object.
2097     if (nodepath->subpaths == NULL ||
2098         sp_nodepath_get_node_count(nodepath) < 2) {
2099         sp_nodepath_destroy(nodepath);
2100         sp_selection_delete();
2101         return;
2102     }
2104     sp_nodepath_update_statusbar(nodepath);
2107 /**
2108  * Call sp_nodepath_set_line() for all selected segments.
2109  */
2110 void
2111 sp_node_selected_set_line_type(NRPathcode code)
2113     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2114     if (nodepath == NULL) return;
2116     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2117        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2118         g_assert(n->selected);
2119         if (n->p.other && n->p.other->selected) {
2120             sp_nodepath_set_line_type(n, code);
2121         }
2122     }
2124     sp_nodepath_update_repr(nodepath);
2127 /**
2128  * Call sp_nodepath_convert_node_type() for all selected nodes.
2129  */
2130 void
2131 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2133     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2134     if (nodepath == NULL) return;
2136     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2137         sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2138     }
2140     sp_nodepath_update_repr(nodepath);
2143 /**
2144  * Change select status of node, update its own and neighbour handles.
2145  */
2146 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2148     node->selected = selected;
2150     if (selected) {
2151         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2152         node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2153         node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2154         sp_knot_update_ctrl(node->knot);
2155     } else {
2156         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2157         node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2158         node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2159         sp_knot_update_ctrl(node->knot);
2160     }
2162     sp_node_update_handles(node);
2163     if (node->n.other) sp_node_update_handles(node->n.other);
2164     if (node->p.other) sp_node_update_handles(node->p.other);
2167 /**
2168 \brief Select a node
2169 \param node     The node to select
2170 \param incremental   If true, add to selection, otherwise deselect others
2171 \param override   If true, always select this node, otherwise toggle selected status
2172 */
2173 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2175     Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2177     if (incremental) {
2178         if (override) {
2179             if (!g_list_find(nodepath->selected, node)) {
2180                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2181             }
2182             sp_node_set_selected(node, TRUE);
2183         } else { // toggle
2184             if (node->selected) {
2185                 g_assert(g_list_find(nodepath->selected, node));
2186                 nodepath->selected = g_list_remove(nodepath->selected, node);
2187             } else {
2188                 g_assert(!g_list_find(nodepath->selected, node));
2189                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2190             }
2191             sp_node_set_selected(node, !node->selected);
2192         }
2193     } else {
2194         sp_nodepath_deselect(nodepath);
2195         nodepath->selected = g_list_prepend(nodepath->selected, node);
2196         sp_node_set_selected(node, TRUE);
2197     }
2199     sp_nodepath_update_statusbar(nodepath);
2203 /**
2204 \brief Deselect all nodes in the nodepath
2205 */
2206 void
2207 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2209     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2211     while (nodepath->selected) {
2212         sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2213         nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2214     }
2215     sp_nodepath_update_statusbar(nodepath);
2218 /**
2219 \brief Select or invert selection of all nodes in the nodepath
2220 */
2221 void
2222 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2224     if (!nodepath) return;
2226     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2227        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2228         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2229            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2230            sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2231         }
2232     }
2235 /**
2236  * If nothing selected, does the same as sp_nodepath_select_all();
2237  * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2238  * (i.e., similar to "select all in layer", with the "selected" subpaths
2239  * being treated as "layers" in the path).
2240  */
2241 void
2242 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2244     if (!nodepath) return;
2246     if (g_list_length (nodepath->selected) == 0) {
2247         sp_nodepath_select_all (nodepath, invert);
2248         return;
2249     }
2251     GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2252     GSList *subpaths = NULL;
2254     for (GList *l = copy; l != NULL; l = l->next) {
2255         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2256         Inkscape::NodePath::SubPath *subpath = n->subpath;
2257         if (!g_slist_find (subpaths, subpath))
2258             subpaths = g_slist_prepend (subpaths, subpath);
2259     }
2261     for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2262         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2263         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2264             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2265             sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2266         }
2267     }
2269     g_slist_free (subpaths);
2270     g_list_free (copy);
2273 /**
2274  * \brief Select the node after the last selected; if none is selected,
2275  * select the first within path.
2276  */
2277 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2279     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2281    Inkscape::NodePath::Node *last = NULL;
2282     if (nodepath->selected) {
2283         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2284            Inkscape::NodePath::SubPath *subpath, *subpath_next;
2285             subpath = (Inkscape::NodePath::SubPath *) spl->data;
2286             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2287                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2288                 if (node->selected) {
2289                     if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2290                         if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2291                             if (spl->next) { // there's a next subpath
2292                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2293                                 last = subpath_next->first;
2294                             } else if (spl->prev) { // there's a previous subpath
2295                                 last = NULL; // to be set later to the first node of first subpath
2296                             } else {
2297                                 last = node->n.other;
2298                             }
2299                         } else {
2300                             last = node->n.other;
2301                         }
2302                     } else {
2303                         if (node->n.other) {
2304                             last = node->n.other;
2305                         } else {
2306                             if (spl->next) { // there's a next subpath
2307                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2308                                 last = subpath_next->first;
2309                             } else if (spl->prev) { // there's a previous subpath
2310                                 last = NULL; // to be set later to the first node of first subpath
2311                             } else {
2312                                 last = (Inkscape::NodePath::Node *) subpath->first;
2313                             }
2314                         }
2315                     }
2316                 }
2317             }
2318         }
2319         sp_nodepath_deselect(nodepath);
2320     }
2322     if (last) { // there's at least one more node after selected
2323         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2324     } else { // no more nodes, select the first one in first subpath
2325        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2326         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2327     }
2330 /**
2331  * \brief Select the node before the first selected; if none is selected,
2332  * select the last within path
2333  */
2334 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2336     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2338    Inkscape::NodePath::Node *last = NULL;
2339     if (nodepath->selected) {
2340         for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2341            Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2342             for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2343                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2344                 if (node->selected) {
2345                     if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2346                         if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2347                             if (spl->prev) { // there's a prev subpath
2348                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2349                                 last = subpath_prev->last;
2350                             } else if (spl->next) { // there's a next subpath
2351                                 last = NULL; // to be set later to the last node of last subpath
2352                             } else {
2353                                 last = node->p.other;
2354                             }
2355                         } else {
2356                             last = node->p.other;
2357                         }
2358                     } else {
2359                         if (node->p.other) {
2360                             last = node->p.other;
2361                         } else {
2362                             if (spl->prev) { // there's a prev subpath
2363                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2364                                 last = subpath_prev->last;
2365                             } else if (spl->next) { // there's a next subpath
2366                                 last = NULL; // to be set later to the last node of last subpath
2367                             } else {
2368                                 last = (Inkscape::NodePath::Node *) subpath->last;
2369                             }
2370                         }
2371                     }
2372                 }
2373             }
2374         }
2375         sp_nodepath_deselect(nodepath);
2376     }
2378     if (last) { // there's at least one more node before selected
2379         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2380     } else { // no more nodes, select the last one in last subpath
2381         GList *spl = g_list_last(nodepath->subpaths);
2382        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2383         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2384     }
2387 /**
2388  * \brief Select all nodes that are within the rectangle.
2389  */
2390 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2392     if (!incremental) {
2393         sp_nodepath_deselect(nodepath);
2394     }
2396     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2397        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2398         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2399            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2401             if (b.contains(node->pos)) {
2402                 sp_nodepath_node_select(node, TRUE, TRUE);
2403             }
2404         }
2405     }
2409 /**
2410 \brief  Saves all nodes' and handles' current positions in their origin members
2411 */
2412 void
2413 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2415     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2416        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2417         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2418            Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2419            n->origin = n->pos;
2420            n->p.origin = n->p.pos;
2421            n->n.origin = n->n.pos;
2422         }
2423     }
2424
2426 /**
2427 \brief  Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2428 */
2429 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2431     if (!nodepath->selected) {
2432         return NULL;
2433     }
2435     GList *r = NULL;
2436     guint i = 0;
2437     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2438        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2439         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2440            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2441             i++;
2442             if (node->selected) {
2443                 r = g_list_append(r, GINT_TO_POINTER(i));
2444             }
2445         }
2446     }
2447     return r;
2450 /**
2451 \brief  Restores selection by selecting nodes whose positions are in the list
2452 */
2453 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2455     sp_nodepath_deselect(nodepath);
2457     guint i = 0;
2458     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2459        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2460         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2461            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2462             i++;
2463             if (g_list_find(r, GINT_TO_POINTER(i))) {
2464                 sp_nodepath_node_select(node, TRUE, TRUE);
2465             }
2466         }
2467     }
2471 /**
2472 \brief Adjusts handle according to node type and line code.
2473 */
2474 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2476     double len, otherlen, linelen;
2478     g_assert(node);
2480    Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2481    Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2483     /** \todo fixme: */
2484     if (me->other == NULL) return;
2485     if (other->other == NULL) return;
2487     /* I have line */
2489     NRPathcode mecode, ocode;
2490     if (which_adjust == 1) {
2491         mecode = (NRPathcode)me->other->code;
2492         ocode = (NRPathcode)node->code;
2493     } else {
2494         mecode = (NRPathcode)node->code;
2495         ocode = (NRPathcode)other->other->code;
2496     }
2498     if (mecode == NR_LINETO) return;
2500     /* I am curve */
2502     if (other->other == NULL) return;
2504     /* Other has line */
2506     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2508     NR::Point delta;
2509     if (ocode == NR_LINETO) {
2510         /* other is lineto, we are either smooth or symm */
2511        Inkscape::NodePath::Node *othernode = other->other;
2512         len = NR::L2(me->pos - node->pos);
2513         delta = node->pos - othernode->pos;
2514         linelen = NR::L2(delta);
2515         if (linelen < 1e-18) 
2516             return;
2517         me->pos = node->pos + (len / linelen)*delta;
2518         return;
2519     }
2521     if (node->type == Inkscape::NodePath::NODE_SYMM) {
2523         me->pos = 2 * node->pos - other->pos;
2524         return;
2525     }
2527     /* We are smooth */
2529     len = NR::L2(me->pos - node->pos);
2530     delta = other->pos - node->pos;
2531     otherlen = NR::L2(delta);
2532     if (otherlen < 1e-18) return;
2534     me->pos = node->pos - (len / otherlen) * delta;
2537 /**
2538  \brief Adjusts both handles according to node type and line code
2539  */
2540 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2542     g_assert(node);
2544     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2546     /* we are either smooth or symm */
2548     if (node->p.other == NULL) return;
2550     if (node->n.other == NULL) return;
2552     if (node->code == NR_LINETO) {
2553         if (node->n.other->code == NR_LINETO) return;
2554         sp_node_adjust_handle(node, 1);
2555         return;
2556     }
2558     if (node->n.other->code == NR_LINETO) {
2559         if (node->code == NR_LINETO) return;
2560         sp_node_adjust_handle(node, -1);
2561         return;
2562     }
2564     /* both are curves */
2565     NR::Point const delta( node->n.pos - node->p.pos );
2567     if (node->type == Inkscape::NodePath::NODE_SYMM) {
2568         node->p.pos = node->pos - delta / 2;
2569         node->n.pos = node->pos + delta / 2;
2570         return;
2571     }
2573     /* We are smooth */
2574     double plen = NR::L2(node->p.pos - node->pos);
2575     if (plen < 1e-18) return;
2576     double nlen = NR::L2(node->n.pos - node->pos);
2577     if (nlen < 1e-18) return;
2578     node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2579     node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2582 /**
2583  * Node event callback.
2584  */
2585 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2587     gboolean ret = FALSE;
2588     switch (event->type) {
2589         case GDK_ENTER_NOTIFY:
2590             active_node = n;
2591             break;
2592         case GDK_LEAVE_NOTIFY:
2593             active_node = NULL;
2594             break;
2595         case GDK_KEY_PRESS:
2596             switch (get_group0_keyval (&event->key)) {
2597                 case GDK_space:
2598                     if (event->key.state & GDK_BUTTON1_MASK) {
2599                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2600                         stamp_repr(nodepath);
2601                         ret = TRUE;
2602                     }
2603                     break;
2604                 default:
2605                     break;
2606             }
2607             break;
2608         default:
2609             break;
2610     }
2612     return ret;
2615 /**
2616  * Handle keypress on node; directly called.
2617  */
2618 gboolean node_key(GdkEvent *event)
2620     Inkscape::NodePath::Path *np;
2622     // there is no way to verify nodes so set active_node to nil when deleting!!
2623     if (active_node == NULL) return FALSE;
2625     if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2626         gint ret = FALSE;
2627         switch (get_group0_keyval (&event->key)) {
2628             /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2629             case GDK_BackSpace:
2630                 np = active_node->subpath->nodepath;
2631                 sp_nodepath_node_destroy(active_node);
2632                 sp_nodepath_update_repr(np);
2633                 active_node = NULL;
2634                 ret = TRUE;
2635                 break;
2636             case GDK_c:
2637                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2638                 ret = TRUE;
2639                 break;
2640             case GDK_s:
2641                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2642                 ret = TRUE;
2643                 break;
2644             case GDK_y:
2645                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2646                 ret = TRUE;
2647                 break;
2648             case GDK_b:
2649                 sp_nodepath_node_break(active_node);
2650                 ret = TRUE;
2651                 break;
2652         }
2653         return ret;
2654     }
2655     return FALSE;
2658 /**
2659  * Mouseclick on node callback.
2660  */
2661 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2663    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2665     if (state & GDK_CONTROL_MASK) {
2666         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2668         if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2669             if (n->type == Inkscape::NodePath::NODE_CUSP) {
2670                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2671             } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2672                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2673             } else {
2674                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2675             }
2676             sp_nodepath_update_repr(nodepath);
2677             sp_nodepath_update_statusbar(nodepath);
2679         } else { //ctrl+alt+click: delete node
2680             GList *node_to_delete = NULL;
2681             node_to_delete = g_list_append(node_to_delete, n);
2682             sp_node_delete_preserve(node_to_delete);
2683         }
2685     } else {
2686         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2687     }
2690 /**
2691  * Mouse grabbed node callback.
2692  */
2693 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2695    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2697     if (!n->selected) {
2698         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2699     }
2701     sp_nodepath_remember_origins (n->subpath->nodepath);
2704 /**
2705  * Mouse ungrabbed node callback.
2706  */
2707 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2709    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2711    n->dragging_out = NULL;
2713    sp_nodepath_update_repr(n->subpath->nodepath);
2716 /**
2717  * The point on a line, given by its angle, closest to the given point.
2718  * \param p  A point.
2719  * \param a  Angle of the line; it is assumed to go through coordinate origin.
2720  * \param closest  Pointer to the point struct where the result is stored.
2721  * \todo FIXME: use dot product perhaps?
2722  */
2723 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2725     if (a == HUGE_VAL) { // vertical
2726         *closest = NR::Point(0, (*p)[NR::Y]);
2727     } else {
2728         (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2729         (*closest)[NR::Y] = a * (*closest)[NR::X];
2730     }
2733 /**
2734  * Distance from the point to a line given by its angle.
2735  * \param p  A point.
2736  * \param a  Angle of the line; it is assumed to go through coordinate origin.
2737  */
2738 static double point_line_distance(NR::Point *p, double a)
2740     NR::Point c;
2741     point_line_closest(p, a, &c);
2742     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]));
2745 /**
2746  * Callback for node "request" signal.
2747  * \todo fixme: This goes to "moved" event? (lauris)
2748  */
2749 static gboolean
2750 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2752     double yn, xn, yp, xp;
2753     double an, ap, na, pa;
2754     double d_an, d_ap, d_na, d_pa;
2755     gboolean collinear = FALSE;
2756     NR::Point c;
2757     NR::Point pr;
2759    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2761    // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2762    if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2764        NR::Point mouse = (*p);
2766        if (!n->dragging_out) {
2767            // This is the first drag-out event; find out which handle to drag out
2768            double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2769            double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2771            if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2772                return FALSE;
2774            Inkscape::NodePath::NodeSide *opposite;
2775            if (appr_p > appr_n) { // closer to p
2776                n->dragging_out = &n->p;
2777                opposite = &n->n;
2778                n->code = NR_CURVETO;
2779            } else if (appr_p < appr_n) { // closer to n
2780                n->dragging_out = &n->n;
2781                opposite = &n->p;
2782                n->n.other->code = NR_CURVETO;
2783            } else { // p and n nodes are the same
2784                if (n->n.pos != n->pos) { // n handle already dragged, drag p
2785                    n->dragging_out = &n->p;
2786                    opposite = &n->n;
2787                    n->code = NR_CURVETO;
2788                } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2789                    n->dragging_out = &n->n;
2790                    opposite = &n->p;
2791                    n->n.other->code = NR_CURVETO;
2792                } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2793                    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);
2794                    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);
2795                    if (appr_other_p > appr_other_n) { // closer to other's p handle
2796                        n->dragging_out = &n->n;
2797                        opposite = &n->p;
2798                        n->n.other->code = NR_CURVETO;
2799                    } else { // closer to other's n handle
2800                        n->dragging_out = &n->p;
2801                        opposite = &n->n;
2802                        n->code = NR_CURVETO;
2803                    }
2804                }
2805            }
2807            // if there's another handle, make sure the one we drag out starts parallel to it
2808            if (opposite->pos != n->pos) {
2809                mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2810            }
2812            // knots might not be created yet!
2813            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2814            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2815        }
2817        // pass this on to the handle-moved callback
2818        node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2819        sp_node_update_handles(n);
2820        return TRUE;
2821    }
2823     if (state & GDK_CONTROL_MASK) { // constrained motion
2825         // calculate relative distances of handles
2826         // n handle:
2827         yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2828         xn = n->n.pos[NR::X] - n->pos[NR::X];
2829         // if there's no n handle (straight line), see if we can use the direction to the next point on path
2830         if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2831             if (n->n.other) { // if there is the next point
2832                 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2833                     yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2834                     xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2835             }
2836         }
2837         if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2838         if (yn < 0) { xn = -xn; yn = -yn; }
2840         // p handle:
2841         yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2842         xp = n->p.pos[NR::X] - n->pos[NR::X];
2843         // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2844         if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2845             if (n->p.other) {
2846                 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2847                     yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2848                     xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2849             }
2850         }
2851         if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2852         if (yp < 0) { xp = -xp; yp = -yp; }
2854         if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2855             // sliding on handles, only if at least one of the handles is non-vertical
2856             // (otherwise it's the same as ctrl+drag anyway)
2858             // calculate angles of the handles
2859             if (xn == 0) {
2860                 if (yn == 0) { // no handle, consider it the continuation of the other one
2861                     an = 0;
2862                     collinear = TRUE;
2863                 }
2864                 else an = 0; // vertical; set the angle to horizontal
2865             } else an = yn/xn;
2867             if (xp == 0) {
2868                 if (yp == 0) { // no handle, consider it the continuation of the other one
2869                     ap = an;
2870                 }
2871                 else ap = 0; // vertical; set the angle to horizontal
2872             } else  ap = yp/xp;
2874             if (collinear) an = ap;
2876             // angles of the perpendiculars; HUGE_VAL means vertical
2877             if (an == 0) na = HUGE_VAL; else na = -1/an;
2878             if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2880             // mouse point relative to the node's original pos
2881             pr = (*p) - n->origin;
2883             // distances to the four lines (two handles and two perpendiculars)
2884             d_an = point_line_distance(&pr, an);
2885             d_na = point_line_distance(&pr, na);
2886             d_ap = point_line_distance(&pr, ap);
2887             d_pa = point_line_distance(&pr, pa);
2889             // find out which line is the closest, save its closest point in c
2890             if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2891                 point_line_closest(&pr, an, &c);
2892             } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2893                 point_line_closest(&pr, ap, &c);
2894             } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2895                 point_line_closest(&pr, na, &c);
2896             } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2897                 point_line_closest(&pr, pa, &c);
2898             }
2900             // move the node to the closest point
2901             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2902                                             n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2903                                             n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2905         } else {  // constraining to hor/vert
2907             if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2908                 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2909             } else { // snap to vert
2910                 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2911             }
2912         }
2913     } else { // move freely
2914         if (state & GDK_MOD1_MASK) { // sculpt
2915             sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2916         } else {
2917             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2918                                         (*p)[NR::X] - n->pos[NR::X],
2919                                         (*p)[NR::Y] - n->pos[NR::Y],
2920                                         (state & GDK_SHIFT_MASK) == 0);
2921         }
2922     }
2924     n->subpath->nodepath->desktop->scroll_to_point(p);
2926     return TRUE;
2929 /**
2930  * Node handle clicked callback.
2931  */
2932 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2934    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2936     if (state & GDK_CONTROL_MASK) { // "delete" handle
2937         if (n->p.knot == knot) {
2938             n->p.pos = n->pos;
2939         } else if (n->n.knot == knot) {
2940             n->n.pos = n->pos;
2941         }
2942         sp_node_update_handles(n);
2943         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2944         sp_nodepath_update_repr(nodepath);
2945         sp_nodepath_update_statusbar(nodepath);
2947     } else { // just select or add to selection, depending in Shift
2948         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2949     }
2952 /**
2953  * Node handle grabbed callback.
2954  */
2955 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2957    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2959     if (!n->selected) {
2960         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2961     }
2963     // remember the origin point of the handle
2964     if (n->p.knot == knot) {
2965         n->p.origin_radial = n->p.pos - n->pos;
2966     } else if (n->n.knot == knot) {
2967         n->n.origin_radial = n->n.pos - n->pos;
2968     } else {
2969         g_assert_not_reached();
2970     }
2974 /**
2975  * Node handle ungrabbed callback.
2976  */
2977 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2979    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2981     // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2982     if (n->p.knot == knot) {
2983         n->p.origin_radial.a = 0;
2984         sp_knot_set_position(knot, &n->p.pos, state);
2985     } else if (n->n.knot == knot) {
2986         n->n.origin_radial.a = 0;
2987         sp_knot_set_position(knot, &n->n.pos, state);
2988     } else {
2989         g_assert_not_reached();
2990     }
2992     sp_nodepath_update_repr(n->subpath->nodepath);
2995 /**
2996  * Node handle "request" signal callback.
2997  */
2998 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3000     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3002     Inkscape::NodePath::NodeSide *me, *opposite;
3003     gint which;
3004     if (n->p.knot == knot) {
3005         me = &n->p;
3006         opposite = &n->n;
3007         which = -1;
3008     } else if (n->n.knot == knot) {
3009         me = &n->n;
3010         opposite = &n->p;
3011         which = 1;
3012     } else {
3013         me = opposite = NULL;
3014         which = 0;
3015         g_assert_not_reached();
3016     }
3018     NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3020     SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3022     if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3023         /* We are smooth node adjacent with line */
3024         NR::Point const delta = *p - n->pos;
3025         NR::Coord const len = NR::L2(delta);
3026         Inkscape::NodePath::Node *othernode = opposite->other;
3027         NR::Point const ndelta = n->pos - othernode->pos;
3028         NR::Coord const linelen = NR::L2(ndelta);
3029         if (len > NR_EPSILON && linelen > NR_EPSILON) {
3030             NR::Coord const scal = dot(delta, ndelta) / linelen;
3031             (*p) = n->pos + (scal / linelen) * ndelta;
3032         }
3033         *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3034     } else {
3035         *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3036     }
3038     sp_node_adjust_handle(n, -which);
3040     return FALSE;
3043 /**
3044  * Node handle moved callback.
3045  */
3046 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3048    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3050    Inkscape::NodePath::NodeSide *me;
3051    Inkscape::NodePath::NodeSide *other;
3052     if (n->p.knot == knot) {
3053         me = &n->p;
3054         other = &n->n;
3055     } else if (n->n.knot == knot) {
3056         me = &n->n;
3057         other = &n->p;
3058     } else {
3059         me = NULL;
3060         other = NULL;
3061         g_assert_not_reached();
3062     }
3064     // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3065     Radial rme(me->pos - n->pos);
3066     Radial rother(other->pos - n->pos);
3067     Radial rnew(*p - n->pos);
3069     if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3070         int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3071         /* 0 interpreted as "no snapping". */
3073         // The closest PI/snaps angle, starting from zero.
3074         double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3075         if (me->origin_radial.a == HUGE_VAL) {
3076             // ortho doesn't exist: original handle was zero length.
3077             rnew.a = a_snapped;
3078         } else {
3079             /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3080              * its opposite and perpendiculars). */
3081             double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3083             // Snap to the closest.
3084             rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3085                        ? a_snapped
3086                        : a_ortho );
3087         }
3088     }
3090     if (state & GDK_MOD1_MASK) {
3091         // lock handle length
3092         rnew.r = me->origin_radial.r;
3093     }
3095     if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3096         && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3097         // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3098         rother.a += rnew.a - rme.a;
3099         other->pos = NR::Point(rother) + n->pos;
3100         sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3101         sp_knot_set_position(other->knot, &other->pos, 0);
3102     }
3104     me->pos = NR::Point(rnew) + n->pos;
3105     sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3107     // this is what sp_knot_set_position does, but without emitting the signal:
3108     // we cannot emit a "moved" signal because we're now processing it
3109     if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3111     knot->desktop->set_coordinate_status(me->pos);
3113     update_object(n->subpath->nodepath);
3115     /* status text */
3116     SPDesktop *desktop = n->subpath->nodepath->desktop;
3117     if (!desktop) return;
3118     SPEventContext *ec = desktop->event_context;
3119     if (!ec) return;
3120     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3121     if (!mc) return;
3123     double degrees = 180 / M_PI * rnew.a;
3124     if (degrees > 180) degrees -= 360;
3125     if (degrees < -180) degrees += 360;
3126     if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3127         degrees = angle_to_compass (degrees);
3129     GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3131     mc->setF(Inkscape::NORMAL_MESSAGE,
3132          _("<b>Node handle</b>: angle %0.2f&#176;, 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);
3134     g_string_free(length, TRUE);
3137 /**
3138  * Node handle event callback.
3139  */
3140 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3142     gboolean ret = FALSE;
3143     switch (event->type) {
3144         case GDK_KEY_PRESS:
3145             switch (get_group0_keyval (&event->key)) {
3146                 case GDK_space:
3147                     if (event->key.state & GDK_BUTTON1_MASK) {
3148                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3149                         stamp_repr(nodepath);
3150                         ret = TRUE;
3151                     }
3152                     break;
3153                 default:
3154                     break;
3155             }
3156             break;
3157         default:
3158             break;
3159     }
3161     return ret;
3164 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3165                                  Radial &rme, Radial &rother, gboolean const both)
3167     rme.a += angle;
3168     if ( both
3169          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3170          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3171     {
3172         rother.a += angle;
3173     }
3176 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3177                                         Radial &rme, Radial &rother, gboolean const both)
3179     gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3181     gdouble r;
3182     if ( both
3183          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3184          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3185     {
3186         r = MAX(rme.r, rother.r);
3187     } else {
3188         r = rme.r;
3189     }
3191     gdouble const weird_angle = atan2(norm_angle, r);
3192 /* Bulia says norm_angle is just the visible distance that the
3193  * object's end must travel on the screen.  Left as 'angle' for want of
3194  * a better name.*/
3196     rme.a += weird_angle;
3197     if ( both
3198          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3199          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3200     {
3201         rother.a += weird_angle;
3202     }
3205 /**
3206  * Rotate one node.
3207  */
3208 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3210     Inkscape::NodePath::NodeSide *me, *other;
3211     bool both = false;
3213     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3214     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3216     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3217         me = &(n->p);
3218         other = &(n->n);
3219     } else if (!n->p.other) {
3220         me = &(n->n);
3221         other = &(n->p);
3222     } else {
3223         if (which > 0) { // right handle
3224             if (xn > xp) {
3225                 me = &(n->n);
3226                 other = &(n->p);
3227             } else {
3228                 me = &(n->p);
3229                 other = &(n->n);
3230             }
3231         } else if (which < 0){ // left handle
3232             if (xn <= xp) {
3233                 me = &(n->n);
3234                 other = &(n->p);
3235             } else {
3236                 me = &(n->p);
3237                 other = &(n->n);
3238             }
3239         } else { // both handles
3240             me = &(n->n);
3241             other = &(n->p);
3242             both = true;
3243         }
3244     }
3246     Radial rme(me->pos - n->pos);
3247     Radial rother(other->pos - n->pos);
3249     if (screen) {
3250         node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3251     } else {
3252         node_rotate_one_internal (*n, angle, rme, rother, both);
3253     }
3255     me->pos = n->pos + NR::Point(rme);
3257     if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3258         other->pos =  n->pos + NR::Point(rother);
3259     }
3261     // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3262     // so here we just move all the knots without emitting move signals, for speed
3263     sp_node_update_handles(n, false);
3266 /**
3267  * Rotate selected nodes.
3268  */
3269 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3271     if (!nodepath || !nodepath->selected) return;
3273     if (g_list_length(nodepath->selected) == 1) {
3274        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3275         node_rotate_one (n, angle, which, screen);
3276     } else {
3277        // rotate as an object:
3279         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3280         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3281         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3282             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3283             box.expandTo (n->pos); // contain all selected nodes
3284         }
3286         gdouble rot;
3287         if (screen) {
3288             gdouble const zoom = nodepath->desktop->current_zoom();
3289             gdouble const zmove = angle / zoom;
3290             gdouble const r = NR::L2(box.max() - box.midpoint());
3291             rot = atan2(zmove, r);
3292         } else {
3293             rot = angle;
3294         }
3296         NR::Matrix t =
3297             NR::Matrix (NR::translate(-box.midpoint())) *
3298             NR::Matrix (NR::rotate(rot)) *
3299             NR::Matrix (NR::translate(box.midpoint()));
3301         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3302             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3303             n->pos *= t;
3304             n->n.pos *= t;
3305             n->p.pos *= t;
3306             sp_node_update_handles(n, false);
3307         }
3308     }
3310     sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3313 /**
3314  * Scale one node.
3315  */
3316 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3318     bool both = false;
3319     Inkscape::NodePath::NodeSide *me, *other;
3321     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3322     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3324     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3325         me = &(n->p);
3326         other = &(n->n);
3327         n->code = NR_CURVETO;
3328     } else if (!n->p.other) {
3329         me = &(n->n);
3330         other = &(n->p);
3331         if (n->n.other)
3332             n->n.other->code = NR_CURVETO;
3333     } else {
3334         if (which > 0) { // right 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 if (which < 0){ // left handle
3346             if (xn <= xp) {
3347                 me = &(n->n);
3348                 other = &(n->p);
3349                 if (n->n.other)
3350                     n->n.other->code = NR_CURVETO;
3351             } else {
3352                 me = &(n->p);
3353                 other = &(n->n);
3354                 n->code = NR_CURVETO;
3355             }
3356         } else { // both handles
3357             me = &(n->n);
3358             other = &(n->p);
3359             both = true;
3360             n->code = NR_CURVETO;
3361             if (n->n.other)
3362                 n->n.other->code = NR_CURVETO;
3363         }
3364     }
3366     Radial rme(me->pos - n->pos);
3367     Radial rother(other->pos - n->pos);
3369     rme.r += grow;
3370     if (rme.r < 0) rme.r = 0;
3371     if (rme.a == HUGE_VAL) {
3372         if (me->other) { // if direction is unknown, initialize it towards the next node
3373             Radial rme_next(me->other->pos - n->pos);
3374             rme.a = rme_next.a;
3375         } else { // if there's no next, initialize to 0
3376             rme.a = 0;
3377         }
3378     }
3379     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3380         rother.r += grow;
3381         if (rother.r < 0) rother.r = 0;
3382         if (rother.a == HUGE_VAL) {
3383             rother.a = rme.a + M_PI;
3384         }
3385     }
3387     me->pos = n->pos + NR::Point(rme);
3389     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3390         other->pos = n->pos + NR::Point(rother);
3391     }
3393     // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3394     // so here we just move all the knots without emitting move signals, for speed
3395     sp_node_update_handles(n, false);
3398 /**
3399  * Scale selected nodes.
3400  */
3401 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3403     if (!nodepath || !nodepath->selected) return;
3405     if (g_list_length(nodepath->selected) == 1) {
3406         // scale handles of the single selected node
3407         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3408         node_scale_one (n, grow, which);
3409     } else {
3410         // scale nodes as an "object":
3412         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3413         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3414         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3415             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3416             box.expandTo (n->pos); // contain all selected nodes
3417         }
3419         double scale = (box.maxExtent() + grow)/box.maxExtent();
3421         NR::Matrix t =
3422             NR::Matrix (NR::translate(-box.midpoint())) *
3423             NR::Matrix (NR::scale(scale, scale)) *
3424             NR::Matrix (NR::translate(box.midpoint()));
3426         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3427             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3428             n->pos *= t;
3429             n->n.pos *= t;
3430             n->p.pos *= t;
3431             sp_node_update_handles(n, false);
3432         }
3433     }
3435     sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3438 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3440     if (!nodepath) return;
3441     sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3444 /**
3445  * Flip selected nodes horizontally/vertically.
3446  */
3447 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3449     if (!nodepath || !nodepath->selected) return;
3451     if (g_list_length(nodepath->selected) == 1) {
3452         // flip handles of the single selected node
3453         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3454         double temp = n->p.pos[axis];
3455         n->p.pos[axis] = n->n.pos[axis];
3456         n->n.pos[axis] = temp;
3457         sp_node_update_handles(n, false);
3458     } else {
3459         // scale nodes as an "object":
3461         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3462         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3463         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3464             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3465             box.expandTo (n->pos); // contain all selected nodes
3466         }
3468         NR::Matrix t =
3469             NR::Matrix (NR::translate(-box.midpoint())) *
3470             NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3471             NR::Matrix (NR::translate(box.midpoint()));
3473         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3474             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3475             n->pos *= t;
3476             n->n.pos *= t;
3477             n->p.pos *= t;
3478             sp_node_update_handles(n, false);
3479         }
3480     }
3482     sp_nodepath_update_repr(nodepath);
3485 //-----------------------------------------------
3486 /**
3487  * Return new subpath under given nodepath.
3488  */
3489 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3491     g_assert(nodepath);
3492     g_assert(nodepath->desktop);
3494    Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3496     s->nodepath = nodepath;
3497     s->closed = FALSE;
3498     s->nodes = NULL;
3499     s->first = NULL;
3500     s->last = NULL;
3502     // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3503     // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3504     nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3506     return s;
3509 /**
3510  * Destroy nodes in subpath, then subpath itself.
3511  */
3512 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3514     g_assert(subpath);
3515     g_assert(subpath->nodepath);
3516     g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3518     while (subpath->nodes) {
3519         sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3520     }
3522     subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3524     g_free(subpath);
3527 /**
3528  * Link head to tail in subpath.
3529  */
3530 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3532     g_assert(!sp->closed);
3533     g_assert(sp->last != sp->first);
3534     g_assert(sp->first->code == NR_MOVETO);
3536     sp->closed = TRUE;
3538     //Link the head to the tail
3539     sp->first->p.other = sp->last;
3540     sp->last->n.other  = sp->first;
3541     sp->last->n.pos    = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3542     sp->first          = sp->last;
3544     //Remove the extra end node
3545     sp_nodepath_node_destroy(sp->last->n.other);
3548 /**
3549  * Open closed (loopy) subpath at node.
3550  */
3551 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3553     g_assert(sp->closed);
3554     g_assert(n->subpath == sp);
3555     g_assert(sp->first == sp->last);
3557     /* We create new startpoint, current node will become last one */
3559    Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3560                                                 &n->pos, &n->pos, &n->n.pos);
3563     sp->closed        = FALSE;
3565     //Unlink to make a head and tail
3566     sp->first         = new_path;
3567     sp->last          = n;
3568     n->n.other        = NULL;
3569     new_path->p.other = NULL;
3572 /**
3573  * Returns area in triangle given by points; may be negative.
3574  */
3575 inline double
3576 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3578     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]);
3581 /**
3582  * Return new node in subpath with given properties.
3583  * \param pos Position of node.
3584  * \param ppos Handle position in previous direction
3585  * \param npos Handle position in previous direction
3586  */
3587 Inkscape::NodePath::Node *
3588 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)
3590     g_assert(sp);
3591     g_assert(sp->nodepath);
3592     g_assert(sp->nodepath->desktop);
3594     if (nodechunk == NULL)
3595         nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3597     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3599     n->subpath  = sp;
3601     if (type != Inkscape::NodePath::NODE_NONE) {
3602         // use the type from sodipodi:nodetypes
3603         n->type = type;
3604     } else {
3605         if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3606             // points are (almost) collinear
3607             if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3608                 // endnode, or a node with a retracted handle
3609                 n->type = Inkscape::NodePath::NODE_CUSP;
3610             } else {
3611                 n->type = Inkscape::NodePath::NODE_SMOOTH;
3612             }
3613         } else {
3614             n->type = Inkscape::NodePath::NODE_CUSP;
3615         }
3616     }
3618     n->code     = code;
3619     n->selected = FALSE;
3620     n->pos      = *pos;
3621     n->p.pos    = *ppos;
3622     n->n.pos    = *npos;
3624     n->dragging_out = NULL;
3626     Inkscape::NodePath::Node *prev;
3627     if (next) {
3628         //g_assert(g_list_find(sp->nodes, next));
3629         prev = next->p.other;
3630     } else {
3631         prev = sp->last;
3632     }
3634     if (prev)
3635         prev->n.other = n;
3636     else
3637         sp->first = n;
3639     if (next)
3640         next->p.other = n;
3641     else
3642         sp->last = n;
3644     n->p.other = prev;
3645     n->n.other = next;
3647     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"));
3648     sp_knot_set_position(n->knot, pos, 0);
3650     n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3651     n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3652     n->knot->setAnchor (GTK_ANCHOR_CENTER);
3653     n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3654     n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3655     sp_knot_update_ctrl(n->knot);
3657     g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3658     g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3659     g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3660     g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3661     g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3662     sp_knot_show(n->knot);
3664     // We only create handle knots and lines on demand
3665     n->p.knot = NULL;
3666     n->p.line = NULL;
3667     n->n.knot = NULL;
3668     n->n.line = NULL;
3670     sp->nodes = g_list_prepend(sp->nodes, n);
3672     return n;
3675 /**
3676  * Destroy node and its knots, link neighbors in subpath.
3677  */
3678 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3680     g_assert(node);
3681     g_assert(node->subpath);
3682     g_assert(SP_IS_KNOT(node->knot));
3684    Inkscape::NodePath::SubPath *sp = node->subpath;
3686     if (node->selected) { // first, deselect
3687         g_assert(g_list_find(node->subpath->nodepath->selected, node));
3688         node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3689     }
3691     node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3693     g_object_unref(G_OBJECT(node->knot));
3694     if (node->p.knot)
3695         g_object_unref(G_OBJECT(node->p.knot));
3696     if (node->n.knot)
3697         g_object_unref(G_OBJECT(node->n.knot));
3699     if (node->p.line)
3700         gtk_object_destroy(GTK_OBJECT(node->p.line));
3701     if (node->n.line)
3702         gtk_object_destroy(GTK_OBJECT(node->n.line));
3704     if (sp->nodes) { // there are others nodes on the subpath
3705         if (sp->closed) {
3706             if (sp->first == node) {
3707                 g_assert(sp->last == node);
3708                 sp->first = node->n.other;
3709                 sp->last = sp->first;
3710             }
3711             node->p.other->n.other = node->n.other;
3712             node->n.other->p.other = node->p.other;
3713         } else {
3714             if (sp->first == node) {
3715                 sp->first = node->n.other;
3716                 sp->first->code = NR_MOVETO;
3717             }
3718             if (sp->last == node) sp->last = node->p.other;
3719             if (node->p.other) node->p.other->n.other = node->n.other;
3720             if (node->n.other) node->n.other->p.other = node->p.other;
3721         }
3722     } else { // this was the last node on subpath
3723         sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3724     }
3726     g_mem_chunk_free(nodechunk, node);
3729 /**
3730  * Returns one of the node's two sides.
3731  * \param which Indicates which side.
3732  * \return Pointer to previous node side if which==-1, next if which==1.
3733  */
3734 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3736     g_assert(node);
3738     switch (which) {
3739         case -1:
3740             return &node->p;
3741         case 1:
3742             return &node->n;
3743         default:
3744             break;
3745     }
3747     g_assert_not_reached();
3749     return NULL;
3752 /**
3753  * Return the other side of the node, given one of its sides.
3754  */
3755 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3757     g_assert(node);
3759     if (me == &node->p) return &node->n;
3760     if (me == &node->n) return &node->p;
3762     g_assert_not_reached();
3764     return NULL;
3767 /**
3768  * Return NRPathcode on the given side of the node.
3769  */
3770 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3772     g_assert(node);
3774     if (me == &node->p) {
3775         if (node->p.other) return (NRPathcode)node->code;
3776         return NR_MOVETO;
3777     }
3779     if (me == &node->n) {
3780         if (node->n.other) return (NRPathcode)node->n.other->code;
3781         return NR_MOVETO;
3782     }
3784     g_assert_not_reached();
3786     return NR_END;
3789 /**
3790  * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3791  */
3792 Inkscape::NodePath::Node *
3793 sp_nodepath_get_node_by_index(int index)
3795     Inkscape::NodePath::Node *e = NULL;
3797     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3798     if (!nodepath) {
3799         return e;
3800     }
3802     //find segment
3803     for (GList *l = nodepath->subpaths; l ; l=l->next) {
3805         Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3806         int n = g_list_length(sp->nodes);
3807         if (sp->closed) {
3808             n++;
3809         }
3811         //if the piece belongs to this subpath grab it
3812         //otherwise move onto the next subpath
3813         if (index < n) {
3814             e = sp->first;
3815             for (int i = 0; i < index; ++i) {
3816                 e = e->n.other;
3817             }
3818             break;
3819         } else {
3820             if (sp->closed) {
3821                 index -= (n+1);
3822             } else {
3823                 index -= n;
3824             }
3825         }
3826     }
3828     return e;
3831 /**
3832  * Returns plain text meaning of node type.
3833  */
3834 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3836     unsigned retracted = 0;
3837     bool endnode = false;
3839     for (int which = -1; which <= 1; which += 2) {
3840         Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3841         if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3842             retracted ++;
3843         if (!side->other)
3844             endnode = true;
3845     }
3847     if (retracted == 0) {
3848         if (endnode) {
3849                 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3850                 return _("end node");
3851         } else {
3852             switch (node->type) {
3853                 case Inkscape::NodePath::NODE_CUSP:
3854                     // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3855                     return _("cusp");
3856                 case Inkscape::NodePath::NODE_SMOOTH:
3857                     // TRANSLATORS: "smooth" is an adjective here
3858                     return _("smooth");
3859                 case Inkscape::NodePath::NODE_SYMM:
3860                     return _("symmetric");
3861             }
3862         }
3863     } else if (retracted == 1) {
3864         if (endnode) {
3865             // TRANSLATORS: "end" is an adjective here (NOT a verb)
3866             return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3867         } else {
3868             return _("one handle retracted (drag with <b>Shift</b> to extend)");
3869         }
3870     } else {
3871         return _("both handles retracted (drag with <b>Shift</b> to extend)");
3872     }
3874     return NULL;
3877 /**
3878  * Handles content of statusbar as long as node tool is active.
3879  */
3880 void
3881 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3883     gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3884     gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3886     gint total = 0;
3887     gint selected = 0;
3888     SPDesktop *desktop = NULL;
3890     if (nodepath) {
3891         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3892             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3893             total += g_list_length(subpath->nodes);
3894         }
3895         selected = g_list_length(nodepath->selected);
3896         desktop = nodepath->desktop;
3897     } else {
3898         desktop = SP_ACTIVE_DESKTOP;
3899     }
3901     SPEventContext *ec = desktop->event_context;
3902     if (!ec) return;
3903     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3904     if (!mc) return;
3906     if (selected == 0) {
3907         Inkscape::Selection *sel = desktop->selection;
3908         if (!sel || sel->isEmpty()) {
3909             mc->setF(Inkscape::NORMAL_MESSAGE,
3910                      _("Select a single object to edit its nodes or handles."));
3911         } else {
3912             if (nodepath) {
3913             mc->setF(Inkscape::NORMAL_MESSAGE,
3914                      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.",
3915                               "<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.",
3916                               total),
3917                      total);
3918             } else {
3919                 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3920                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3921                 } else {
3922                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3923                 }
3924             }
3925         }
3926     } else if (nodepath && selected == 1) {
3927         mc->setF(Inkscape::NORMAL_MESSAGE,
3928                  ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3929                           "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3930                           total),
3931                  selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3932     } else {
3933         mc->setF(Inkscape::NORMAL_MESSAGE,
3934                  ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3935                           "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3936                           total),
3937                  selected, total, when_selected);
3938     }
3942 /*
3943   Local Variables:
3944   mode:c++
3945   c-file-style:"stroustrup"
3946   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3947   indent-tabs-mode:nil
3948   fill-column:99
3949   End:
3950 */
3951 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :