Code

2cdb37e15c6f40fb4f3c38fa1a311d4391552d6c
[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;
1882         // if the entire nodepath is removed, delete the selected object.
1883         if (nodepath->subpaths == NULL ||
1884             //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1885             //at least 2
1886             sp_nodepath_get_node_count(nodepath) < 2) {
1887             SPDocument *document = sp_desktop_document (nodepath->desktop);
1888             //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1889             //delete this nodepath's object, not the entire selection! (though at this time, this
1890             //does not matter)
1891             sp_selection_delete();
1892             sp_document_done (document);
1893         } else {
1894             sp_nodepath_update_repr(nodepath);
1895             sp_nodepath_update_statusbar(nodepath);
1896         }
1897     }
1899     g_slist_free (nodepaths);
1902 /**
1903  * Delete one or more selected nodes.
1904  */
1905 void sp_node_selected_delete()
1907     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1908     if (!nodepath) return;
1909     if (!nodepath->selected) return;
1911     /** \todo fixme: do it the right way */
1912     while (nodepath->selected) {
1913        Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1914         sp_nodepath_node_destroy(node);
1915     }
1918     //clean up the nodepath (such as for trivial subpaths)
1919     sp_nodepath_cleanup(nodepath);
1921     sp_nodepath_update_handles(nodepath);
1923     // if the entire nodepath is removed, delete the selected object.
1924     if (nodepath->subpaths == NULL ||
1925         sp_nodepath_get_node_count(nodepath) < 2) {
1926         SPDocument *document = sp_desktop_document (nodepath->desktop);
1927         sp_selection_delete();
1928         sp_document_done (document);
1929         return;
1930     }
1932     sp_nodepath_update_repr(nodepath);
1934     sp_nodepath_update_statusbar(nodepath);
1937 /**
1938  * Delete one or more segments between two selected nodes.
1939  * This is the code for 'split'.
1940  */
1941 void
1942 sp_node_selected_delete_segment(void)
1944    Inkscape::NodePath::Node *start, *end;     //Start , end nodes.  not inclusive
1945    Inkscape::NodePath::Node *curr, *next;     //Iterators
1947     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1948     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1950     if (g_list_length(nodepath->selected) != 2) {
1951         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1952                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1953         return;
1954     }
1956     //Selected nodes, not inclusive
1957    Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1958    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1960     if ( ( a==b)                       ||  //same node
1961          (a->subpath  != b->subpath )  ||  //not the same path
1962          (!a->p.other || !a->n.other)  ||  //one of a's sides does not have a segment
1963          (!b->p.other || !b->n.other) )    //one of b's sides does not have a segment
1964     {
1965         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1966                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1967         return;
1968     }
1970     //###########################################
1971     //# BEGIN EDITS
1972     //###########################################
1973     //##################################
1974     //# CLOSED PATH
1975     //##################################
1976     if (a->subpath->closed) {
1979         gboolean reversed = FALSE;
1981         //Since we can go in a circle, we need to find the shorter distance.
1982         //  a->b or b->a
1983         start = end = NULL;
1984         int distance    = 0;
1985         int minDistance = 0;
1986         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1987             if (curr==b) {
1988                 //printf("a to b:%d\n", distance);
1989                 start = a;//go from a to b
1990                 end   = b;
1991                 minDistance = distance;
1992                 //printf("A to B :\n");
1993                 break;
1994             }
1995             distance++;
1996         }
1998         //try again, the other direction
1999         distance = 0;
2000         for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2001             if (curr==a) {
2002                 //printf("b to a:%d\n", distance);
2003                 if (distance < minDistance) {
2004                     start    = b;  //we go from b to a
2005                     end      = a;
2006                     reversed = TRUE;
2007                     //printf("B to A\n");
2008                 }
2009                 break;
2010             }
2011             distance++;
2012         }
2015         //Copy everything from 'end' to 'start' to a new subpath
2016        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2017         for (curr=end ; curr ; curr=curr->n.other) {
2018             NRPathcode code = (NRPathcode) curr->code;
2019             if (curr == end)
2020                 code = NR_MOVETO;
2021             sp_nodepath_node_new(t, NULL,
2022                                  (Inkscape::NodePath::NodeType)curr->type, code,
2023                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2024             if (curr == start)
2025                 break;
2026         }
2027         sp_nodepath_subpath_destroy(a->subpath);
2030     }
2034     //##################################
2035     //# OPEN PATH
2036     //##################################
2037     else {
2039         //We need to get the direction of the list between A and B
2040         //Can we walk from a to b?
2041         start = end = NULL;
2042         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2043             if (curr==b) {
2044                 start = a;  //did it!  we go from a to b
2045                 end   = b;
2046                 //printf("A to B\n");
2047                 break;
2048             }
2049         }
2050         if (!start) {//didn't work?  let's try the other direction
2051             for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2052                 if (curr==a) {
2053                     start = b;  //did it!  we go from b to a
2054                     end   = a;
2055                     //printf("B to A\n");
2056                     break;
2057                 }
2058             }
2059         }
2060         if (!start) {
2061             nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2062                                                      _("Cannot find path between nodes."));
2063             return;
2064         }
2068         //Copy everything after 'end' to a new subpath
2069        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2070         for (curr=end ; curr ; curr=curr->n.other) {
2071             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2072                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2073         }
2075         //Now let us do our deletion.  Since the tail has been saved, go all the way to the end of the list
2076         for (curr = start->n.other ; curr  ; curr=next) {
2077             next = curr->n.other;
2078             sp_nodepath_node_destroy(curr);
2079         }
2081     }
2082     //###########################################
2083     //# END EDITS
2084     //###########################################
2086     //clean up the nodepath (such as for trivial subpaths)
2087     sp_nodepath_cleanup(nodepath);
2089     sp_nodepath_update_handles(nodepath);
2091     sp_nodepath_update_repr(nodepath);
2093     sp_nodepath_update_statusbar(nodepath);
2096 /**
2097  * Call sp_nodepath_set_line() for all selected segments.
2098  */
2099 void
2100 sp_node_selected_set_line_type(NRPathcode code)
2102     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2103     if (nodepath == NULL) return;
2105     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2106        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2107         g_assert(n->selected);
2108         if (n->p.other && n->p.other->selected) {
2109             sp_nodepath_set_line_type(n, code);
2110         }
2111     }
2113     sp_nodepath_update_repr(nodepath);
2116 /**
2117  * Call sp_nodepath_convert_node_type() for all selected nodes.
2118  */
2119 void
2120 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2122     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2123     if (nodepath == NULL) return;
2125     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2126         sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2127     }
2129     sp_nodepath_update_repr(nodepath);
2132 /**
2133  * Change select status of node, update its own and neighbour handles.
2134  */
2135 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2137     node->selected = selected;
2139     if (selected) {
2140         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2141         node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2142         node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2143         sp_knot_update_ctrl(node->knot);
2144     } else {
2145         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2146         node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2147         node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2148         sp_knot_update_ctrl(node->knot);
2149     }
2151     sp_node_update_handles(node);
2152     if (node->n.other) sp_node_update_handles(node->n.other);
2153     if (node->p.other) sp_node_update_handles(node->p.other);
2156 /**
2157 \brief Select a node
2158 \param node     The node to select
2159 \param incremental   If true, add to selection, otherwise deselect others
2160 \param override   If true, always select this node, otherwise toggle selected status
2161 */
2162 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2164     Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2166     if (incremental) {
2167         if (override) {
2168             if (!g_list_find(nodepath->selected, node)) {
2169                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2170             }
2171             sp_node_set_selected(node, TRUE);
2172         } else { // toggle
2173             if (node->selected) {
2174                 g_assert(g_list_find(nodepath->selected, node));
2175                 nodepath->selected = g_list_remove(nodepath->selected, node);
2176             } else {
2177                 g_assert(!g_list_find(nodepath->selected, node));
2178                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2179             }
2180             sp_node_set_selected(node, !node->selected);
2181         }
2182     } else {
2183         sp_nodepath_deselect(nodepath);
2184         nodepath->selected = g_list_prepend(nodepath->selected, node);
2185         sp_node_set_selected(node, TRUE);
2186     }
2188     sp_nodepath_update_statusbar(nodepath);
2192 /**
2193 \brief Deselect all nodes in the nodepath
2194 */
2195 void
2196 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2198     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2200     while (nodepath->selected) {
2201         sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2202         nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2203     }
2204     sp_nodepath_update_statusbar(nodepath);
2207 /**
2208 \brief Select or invert selection of all nodes in the nodepath
2209 */
2210 void
2211 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2213     if (!nodepath) return;
2215     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2216        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2217         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2218            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2219            sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2220         }
2221     }
2224 /**
2225  * If nothing selected, does the same as sp_nodepath_select_all();
2226  * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2227  * (i.e., similar to "select all in layer", with the "selected" subpaths
2228  * being treated as "layers" in the path).
2229  */
2230 void
2231 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2233     if (!nodepath) return;
2235     if (g_list_length (nodepath->selected) == 0) {
2236         sp_nodepath_select_all (nodepath, invert);
2237         return;
2238     }
2240     GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2241     GSList *subpaths = NULL;
2243     for (GList *l = copy; l != NULL; l = l->next) {
2244         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2245         Inkscape::NodePath::SubPath *subpath = n->subpath;
2246         if (!g_slist_find (subpaths, subpath))
2247             subpaths = g_slist_prepend (subpaths, subpath);
2248     }
2250     for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2251         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2252         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2253             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2254             sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2255         }
2256     }
2258     g_slist_free (subpaths);
2259     g_list_free (copy);
2262 /**
2263  * \brief Select the node after the last selected; if none is selected,
2264  * select the first within path.
2265  */
2266 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2268     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2270    Inkscape::NodePath::Node *last = NULL;
2271     if (nodepath->selected) {
2272         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2273            Inkscape::NodePath::SubPath *subpath, *subpath_next;
2274             subpath = (Inkscape::NodePath::SubPath *) spl->data;
2275             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2276                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2277                 if (node->selected) {
2278                     if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2279                         if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2280                             if (spl->next) { // there's a next subpath
2281                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2282                                 last = subpath_next->first;
2283                             } else if (spl->prev) { // there's a previous subpath
2284                                 last = NULL; // to be set later to the first node of first subpath
2285                             } else {
2286                                 last = node->n.other;
2287                             }
2288                         } else {
2289                             last = node->n.other;
2290                         }
2291                     } else {
2292                         if (node->n.other) {
2293                             last = node->n.other;
2294                         } else {
2295                             if (spl->next) { // there's a next subpath
2296                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2297                                 last = subpath_next->first;
2298                             } else if (spl->prev) { // there's a previous subpath
2299                                 last = NULL; // to be set later to the first node of first subpath
2300                             } else {
2301                                 last = (Inkscape::NodePath::Node *) subpath->first;
2302                             }
2303                         }
2304                     }
2305                 }
2306             }
2307         }
2308         sp_nodepath_deselect(nodepath);
2309     }
2311     if (last) { // there's at least one more node after selected
2312         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2313     } else { // no more nodes, select the first one in first subpath
2314        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2315         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2316     }
2319 /**
2320  * \brief Select the node before the first selected; if none is selected,
2321  * select the last within path
2322  */
2323 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2325     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2327    Inkscape::NodePath::Node *last = NULL;
2328     if (nodepath->selected) {
2329         for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2330            Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2331             for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2332                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2333                 if (node->selected) {
2334                     if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2335                         if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2336                             if (spl->prev) { // there's a prev subpath
2337                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2338                                 last = subpath_prev->last;
2339                             } else if (spl->next) { // there's a next subpath
2340                                 last = NULL; // to be set later to the last node of last subpath
2341                             } else {
2342                                 last = node->p.other;
2343                             }
2344                         } else {
2345                             last = node->p.other;
2346                         }
2347                     } else {
2348                         if (node->p.other) {
2349                             last = node->p.other;
2350                         } else {
2351                             if (spl->prev) { // there's a prev subpath
2352                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2353                                 last = subpath_prev->last;
2354                             } else if (spl->next) { // there's a next subpath
2355                                 last = NULL; // to be set later to the last node of last subpath
2356                             } else {
2357                                 last = (Inkscape::NodePath::Node *) subpath->last;
2358                             }
2359                         }
2360                     }
2361                 }
2362             }
2363         }
2364         sp_nodepath_deselect(nodepath);
2365     }
2367     if (last) { // there's at least one more node before selected
2368         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2369     } else { // no more nodes, select the last one in last subpath
2370         GList *spl = g_list_last(nodepath->subpaths);
2371        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2372         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2373     }
2376 /**
2377  * \brief Select all nodes that are within the rectangle.
2378  */
2379 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2381     if (!incremental) {
2382         sp_nodepath_deselect(nodepath);
2383     }
2385     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2386        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2387         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2388            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2390             if (b.contains(node->pos)) {
2391                 sp_nodepath_node_select(node, TRUE, TRUE);
2392             }
2393         }
2394     }
2398 /**
2399 \brief  Saves all nodes' and handles' current positions in their origin members
2400 */
2401 void
2402 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2404     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2405        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2406         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2407            Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2408            n->origin = n->pos;
2409            n->p.origin = n->p.pos;
2410            n->n.origin = n->n.pos;
2411         }
2412     }
2413
2415 /**
2416 \brief  Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2417 */
2418 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2420     if (!nodepath->selected) {
2421         return NULL;
2422     }
2424     GList *r = NULL;
2425     guint i = 0;
2426     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2427        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2428         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2429            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2430             i++;
2431             if (node->selected) {
2432                 r = g_list_append(r, GINT_TO_POINTER(i));
2433             }
2434         }
2435     }
2436     return r;
2439 /**
2440 \brief  Restores selection by selecting nodes whose positions are in the list
2441 */
2442 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2444     sp_nodepath_deselect(nodepath);
2446     guint i = 0;
2447     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2448        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2449         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2450            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2451             i++;
2452             if (g_list_find(r, GINT_TO_POINTER(i))) {
2453                 sp_nodepath_node_select(node, TRUE, TRUE);
2454             }
2455         }
2456     }
2460 /**
2461 \brief Adjusts handle according to node type and line code.
2462 */
2463 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2465     double len, otherlen, linelen;
2467     g_assert(node);
2469    Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2470    Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2472     /** \todo fixme: */
2473     if (me->other == NULL) return;
2474     if (other->other == NULL) return;
2476     /* I have line */
2478     NRPathcode mecode, ocode;
2479     if (which_adjust == 1) {
2480         mecode = (NRPathcode)me->other->code;
2481         ocode = (NRPathcode)node->code;
2482     } else {
2483         mecode = (NRPathcode)node->code;
2484         ocode = (NRPathcode)other->other->code;
2485     }
2487     if (mecode == NR_LINETO) return;
2489     /* I am curve */
2491     if (other->other == NULL) return;
2493     /* Other has line */
2495     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2497     NR::Point delta;
2498     if (ocode == NR_LINETO) {
2499         /* other is lineto, we are either smooth or symm */
2500        Inkscape::NodePath::Node *othernode = other->other;
2501         len = NR::L2(me->pos - node->pos);
2502         delta = node->pos - othernode->pos;
2503         linelen = NR::L2(delta);
2504         if (linelen < 1e-18) 
2505             return;
2506         me->pos = node->pos + (len / linelen)*delta;
2507         return;
2508     }
2510     if (node->type == Inkscape::NodePath::NODE_SYMM) {
2512         me->pos = 2 * node->pos - other->pos;
2513         return;
2514     }
2516     /* We are smooth */
2518     len = NR::L2(me->pos - node->pos);
2519     delta = other->pos - node->pos;
2520     otherlen = NR::L2(delta);
2521     if (otherlen < 1e-18) return;
2523     me->pos = node->pos - (len / otherlen) * delta;
2526 /**
2527  \brief Adjusts both handles according to node type and line code
2528  */
2529 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2531     g_assert(node);
2533     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2535     /* we are either smooth or symm */
2537     if (node->p.other == NULL) return;
2539     if (node->n.other == NULL) return;
2541     if (node->code == NR_LINETO) {
2542         if (node->n.other->code == NR_LINETO) return;
2543         sp_node_adjust_handle(node, 1);
2544         return;
2545     }
2547     if (node->n.other->code == NR_LINETO) {
2548         if (node->code == NR_LINETO) return;
2549         sp_node_adjust_handle(node, -1);
2550         return;
2551     }
2553     /* both are curves */
2554     NR::Point const delta( node->n.pos - node->p.pos );
2556     if (node->type == Inkscape::NodePath::NODE_SYMM) {
2557         node->p.pos = node->pos - delta / 2;
2558         node->n.pos = node->pos + delta / 2;
2559         return;
2560     }
2562     /* We are smooth */
2563     double plen = NR::L2(node->p.pos - node->pos);
2564     if (plen < 1e-18) return;
2565     double nlen = NR::L2(node->n.pos - node->pos);
2566     if (nlen < 1e-18) return;
2567     node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2568     node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2571 /**
2572  * Node event callback.
2573  */
2574 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2576     gboolean ret = FALSE;
2577     switch (event->type) {
2578         case GDK_ENTER_NOTIFY:
2579             active_node = n;
2580             break;
2581         case GDK_LEAVE_NOTIFY:
2582             active_node = NULL;
2583             break;
2584         case GDK_KEY_PRESS:
2585             switch (get_group0_keyval (&event->key)) {
2586                 case GDK_space:
2587                     if (event->key.state & GDK_BUTTON1_MASK) {
2588                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2589                         stamp_repr(nodepath);
2590                         ret = TRUE;
2591                     }
2592                     break;
2593                 default:
2594                     break;
2595             }
2596             break;
2597         default:
2598             break;
2599     }
2601     return ret;
2604 /**
2605  * Handle keypress on node; directly called.
2606  */
2607 gboolean node_key(GdkEvent *event)
2609     Inkscape::NodePath::Path *np;
2611     // there is no way to verify nodes so set active_node to nil when deleting!!
2612     if (active_node == NULL) return FALSE;
2614     if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2615         gint ret = FALSE;
2616         switch (get_group0_keyval (&event->key)) {
2617             /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2618             case GDK_BackSpace:
2619                 np = active_node->subpath->nodepath;
2620                 sp_nodepath_node_destroy(active_node);
2621                 sp_nodepath_update_repr(np);
2622                 active_node = NULL;
2623                 ret = TRUE;
2624                 break;
2625             case GDK_c:
2626                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2627                 ret = TRUE;
2628                 break;
2629             case GDK_s:
2630                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2631                 ret = TRUE;
2632                 break;
2633             case GDK_y:
2634                 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2635                 ret = TRUE;
2636                 break;
2637             case GDK_b:
2638                 sp_nodepath_node_break(active_node);
2639                 ret = TRUE;
2640                 break;
2641         }
2642         return ret;
2643     }
2644     return FALSE;
2647 /**
2648  * Mouseclick on node callback.
2649  */
2650 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2652    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2654     if (state & GDK_CONTROL_MASK) {
2655         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2657         if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2658             if (n->type == Inkscape::NodePath::NODE_CUSP) {
2659                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2660             } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2661                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2662             } else {
2663                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2664             }
2665             sp_nodepath_update_repr(nodepath);
2666             sp_nodepath_update_statusbar(nodepath);
2668         } else { //ctrl+alt+click: delete node
2669             GList *node_to_delete = NULL;
2670             node_to_delete = g_list_append(node_to_delete, n);
2671             sp_node_delete_preserve(node_to_delete);
2672         }
2674     } else {
2675         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2676     }
2679 /**
2680  * Mouse grabbed node callback.
2681  */
2682 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2684    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2686     if (!n->selected) {
2687         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2688     }
2690     sp_nodepath_remember_origins (n->subpath->nodepath);
2693 /**
2694  * Mouse ungrabbed node callback.
2695  */
2696 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2698    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2700    n->dragging_out = NULL;
2702    sp_nodepath_update_repr(n->subpath->nodepath);
2705 /**
2706  * The point on a line, given by its angle, closest to the given point.
2707  * \param p  A point.
2708  * \param a  Angle of the line; it is assumed to go through coordinate origin.
2709  * \param closest  Pointer to the point struct where the result is stored.
2710  * \todo FIXME: use dot product perhaps?
2711  */
2712 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2714     if (a == HUGE_VAL) { // vertical
2715         *closest = NR::Point(0, (*p)[NR::Y]);
2716     } else {
2717         (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2718         (*closest)[NR::Y] = a * (*closest)[NR::X];
2719     }
2722 /**
2723  * Distance from the point to a line given by its angle.
2724  * \param p  A point.
2725  * \param a  Angle of the line; it is assumed to go through coordinate origin.
2726  */
2727 static double point_line_distance(NR::Point *p, double a)
2729     NR::Point c;
2730     point_line_closest(p, a, &c);
2731     return sqrt(((*p)[NR::X] - c[NR::X])*((*p)[NR::X] - c[NR::X]) + ((*p)[NR::Y] - c[NR::Y])*((*p)[NR::Y] - c[NR::Y]));
2734 /**
2735  * Callback for node "request" signal.
2736  * \todo fixme: This goes to "moved" event? (lauris)
2737  */
2738 static gboolean
2739 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2741     double yn, xn, yp, xp;
2742     double an, ap, na, pa;
2743     double d_an, d_ap, d_na, d_pa;
2744     gboolean collinear = FALSE;
2745     NR::Point c;
2746     NR::Point pr;
2748    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2750    // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2751    if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2753        NR::Point mouse = (*p);
2755        if (!n->dragging_out) {
2756            // This is the first drag-out event; find out which handle to drag out
2757            double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2758            double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2760            if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2761                return FALSE;
2763            Inkscape::NodePath::NodeSide *opposite;
2764            if (appr_p > appr_n) { // closer to p
2765                n->dragging_out = &n->p;
2766                opposite = &n->n;
2767                n->code = NR_CURVETO;
2768            } else if (appr_p < appr_n) { // closer to n
2769                n->dragging_out = &n->n;
2770                opposite = &n->p;
2771                n->n.other->code = NR_CURVETO;
2772            } else { // p and n nodes are the same
2773                if (n->n.pos != n->pos) { // n handle already dragged, drag p
2774                    n->dragging_out = &n->p;
2775                    opposite = &n->n;
2776                    n->code = NR_CURVETO;
2777                } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2778                    n->dragging_out = &n->n;
2779                    opposite = &n->p;
2780                    n->n.other->code = NR_CURVETO;
2781                } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2782                    double appr_other_n = (n->n.other ? NR::L2(n->n.other->n.pos - n->pos) - NR::L2(n->n.other->n.pos - (*p)) : -HUGE_VAL);
2783                    double appr_other_p = (n->n.other ? NR::L2(n->n.other->p.pos - n->pos) - NR::L2(n->n.other->p.pos - (*p)) : -HUGE_VAL);
2784                    if (appr_other_p > appr_other_n) { // closer to other's p handle
2785                        n->dragging_out = &n->n;
2786                        opposite = &n->p;
2787                        n->n.other->code = NR_CURVETO;
2788                    } else { // closer to other's n handle
2789                        n->dragging_out = &n->p;
2790                        opposite = &n->n;
2791                        n->code = NR_CURVETO;
2792                    }
2793                }
2794            }
2796            // if there's another handle, make sure the one we drag out starts parallel to it
2797            if (opposite->pos != n->pos) {
2798                mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2799            }
2801            // knots might not be created yet!
2802            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2803            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2804        }
2806        // pass this on to the handle-moved callback
2807        node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2808        sp_node_update_handles(n);
2809        return TRUE;
2810    }
2812     if (state & GDK_CONTROL_MASK) { // constrained motion
2814         // calculate relative distances of handles
2815         // n handle:
2816         yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2817         xn = n->n.pos[NR::X] - n->pos[NR::X];
2818         // if there's no n handle (straight line), see if we can use the direction to the next point on path
2819         if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2820             if (n->n.other) { // if there is the next point
2821                 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2822                     yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2823                     xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2824             }
2825         }
2826         if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2827         if (yn < 0) { xn = -xn; yn = -yn; }
2829         // p handle:
2830         yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2831         xp = n->p.pos[NR::X] - n->pos[NR::X];
2832         // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2833         if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2834             if (n->p.other) {
2835                 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2836                     yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2837                     xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2838             }
2839         }
2840         if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2841         if (yp < 0) { xp = -xp; yp = -yp; }
2843         if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2844             // sliding on handles, only if at least one of the handles is non-vertical
2845             // (otherwise it's the same as ctrl+drag anyway)
2847             // calculate angles of the handles
2848             if (xn == 0) {
2849                 if (yn == 0) { // no handle, consider it the continuation of the other one
2850                     an = 0;
2851                     collinear = TRUE;
2852                 }
2853                 else an = 0; // vertical; set the angle to horizontal
2854             } else an = yn/xn;
2856             if (xp == 0) {
2857                 if (yp == 0) { // no handle, consider it the continuation of the other one
2858                     ap = an;
2859                 }
2860                 else ap = 0; // vertical; set the angle to horizontal
2861             } else  ap = yp/xp;
2863             if (collinear) an = ap;
2865             // angles of the perpendiculars; HUGE_VAL means vertical
2866             if (an == 0) na = HUGE_VAL; else na = -1/an;
2867             if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2869             // mouse point relative to the node's original pos
2870             pr = (*p) - n->origin;
2872             // distances to the four lines (two handles and two perpendiculars)
2873             d_an = point_line_distance(&pr, an);
2874             d_na = point_line_distance(&pr, na);
2875             d_ap = point_line_distance(&pr, ap);
2876             d_pa = point_line_distance(&pr, pa);
2878             // find out which line is the closest, save its closest point in c
2879             if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2880                 point_line_closest(&pr, an, &c);
2881             } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2882                 point_line_closest(&pr, ap, &c);
2883             } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2884                 point_line_closest(&pr, na, &c);
2885             } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2886                 point_line_closest(&pr, pa, &c);
2887             }
2889             // move the node to the closest point
2890             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2891                                             n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2892                                             n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2894         } else {  // constraining to hor/vert
2896             if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2897                 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2898             } else { // snap to vert
2899                 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2900             }
2901         }
2902     } else { // move freely
2903         if (state & GDK_MOD1_MASK) { // sculpt
2904             sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2905         } else {
2906             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2907                                         (*p)[NR::X] - n->pos[NR::X],
2908                                         (*p)[NR::Y] - n->pos[NR::Y],
2909                                         (state & GDK_SHIFT_MASK) == 0);
2910         }
2911     }
2913     n->subpath->nodepath->desktop->scroll_to_point(p);
2915     return TRUE;
2918 /**
2919  * Node handle clicked callback.
2920  */
2921 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2923    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2925     if (state & GDK_CONTROL_MASK) { // "delete" handle
2926         if (n->p.knot == knot) {
2927             n->p.pos = n->pos;
2928         } else if (n->n.knot == knot) {
2929             n->n.pos = n->pos;
2930         }
2931         sp_node_update_handles(n);
2932         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2933         sp_nodepath_update_repr(nodepath);
2934         sp_nodepath_update_statusbar(nodepath);
2936     } else { // just select or add to selection, depending in Shift
2937         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2938     }
2941 /**
2942  * Node handle grabbed callback.
2943  */
2944 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2946    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2948     if (!n->selected) {
2949         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2950     }
2952     // remember the origin point of the handle
2953     if (n->p.knot == knot) {
2954         n->p.origin_radial = n->p.pos - n->pos;
2955     } else if (n->n.knot == knot) {
2956         n->n.origin_radial = n->n.pos - n->pos;
2957     } else {
2958         g_assert_not_reached();
2959     }
2963 /**
2964  * Node handle ungrabbed callback.
2965  */
2966 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2968    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2970     // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2971     if (n->p.knot == knot) {
2972         n->p.origin_radial.a = 0;
2973         sp_knot_set_position(knot, &n->p.pos, state);
2974     } else if (n->n.knot == knot) {
2975         n->n.origin_radial.a = 0;
2976         sp_knot_set_position(knot, &n->n.pos, state);
2977     } else {
2978         g_assert_not_reached();
2979     }
2981     sp_nodepath_update_repr(n->subpath->nodepath);
2984 /**
2985  * Node handle "request" signal callback.
2986  */
2987 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2989     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2991     Inkscape::NodePath::NodeSide *me, *opposite;
2992     gint which;
2993     if (n->p.knot == knot) {
2994         me = &n->p;
2995         opposite = &n->n;
2996         which = -1;
2997     } else if (n->n.knot == knot) {
2998         me = &n->n;
2999         opposite = &n->p;
3000         which = 1;
3001     } else {
3002         me = opposite = NULL;
3003         which = 0;
3004         g_assert_not_reached();
3005     }
3007     NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3009     SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3011     if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3012         /* We are smooth node adjacent with line */
3013         NR::Point const delta = *p - n->pos;
3014         NR::Coord const len = NR::L2(delta);
3015         Inkscape::NodePath::Node *othernode = opposite->other;
3016         NR::Point const ndelta = n->pos - othernode->pos;
3017         NR::Coord const linelen = NR::L2(ndelta);
3018         if (len > NR_EPSILON && linelen > NR_EPSILON) {
3019             NR::Coord const scal = dot(delta, ndelta) / linelen;
3020             (*p) = n->pos + (scal / linelen) * ndelta;
3021         }
3022         *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3023     } else {
3024         *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3025     }
3027     sp_node_adjust_handle(n, -which);
3029     return FALSE;
3032 /**
3033  * Node handle moved callback.
3034  */
3035 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3037    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3039    Inkscape::NodePath::NodeSide *me;
3040    Inkscape::NodePath::NodeSide *other;
3041     if (n->p.knot == knot) {
3042         me = &n->p;
3043         other = &n->n;
3044     } else if (n->n.knot == knot) {
3045         me = &n->n;
3046         other = &n->p;
3047     } else {
3048         me = NULL;
3049         other = NULL;
3050         g_assert_not_reached();
3051     }
3053     // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3054     Radial rme(me->pos - n->pos);
3055     Radial rother(other->pos - n->pos);
3056     Radial rnew(*p - n->pos);
3058     if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3059         int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3060         /* 0 interpreted as "no snapping". */
3062         // The closest PI/snaps angle, starting from zero.
3063         double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3064         if (me->origin_radial.a == HUGE_VAL) {
3065             // ortho doesn't exist: original handle was zero length.
3066             rnew.a = a_snapped;
3067         } else {
3068             /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3069              * its opposite and perpendiculars). */
3070             double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3072             // Snap to the closest.
3073             rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3074                        ? a_snapped
3075                        : a_ortho );
3076         }
3077     }
3079     if (state & GDK_MOD1_MASK) {
3080         // lock handle length
3081         rnew.r = me->origin_radial.r;
3082     }
3084     if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3085         && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3086         // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3087         rother.a += rnew.a - rme.a;
3088         other->pos = NR::Point(rother) + n->pos;
3089         sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3090         sp_knot_set_position(other->knot, &other->pos, 0);
3091     }
3093     me->pos = NR::Point(rnew) + n->pos;
3094     sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3096     // this is what sp_knot_set_position does, but without emitting the signal:
3097     // we cannot emit a "moved" signal because we're now processing it
3098     if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3100     knot->desktop->set_coordinate_status(me->pos);
3102     update_object(n->subpath->nodepath);
3104     /* status text */
3105     SPDesktop *desktop = n->subpath->nodepath->desktop;
3106     if (!desktop) return;
3107     SPEventContext *ec = desktop->event_context;
3108     if (!ec) return;
3109     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3110     if (!mc) return;
3112     double degrees = 180 / M_PI * rnew.a;
3113     if (degrees > 180) degrees -= 360;
3114     if (degrees < -180) degrees += 360;
3115     if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3116         degrees = angle_to_compass (degrees);
3118     GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3120     mc->setF(Inkscape::NORMAL_MESSAGE,
3121          _("<b>Node handle</b>: angle %0.2f&#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);
3123     g_string_free(length, TRUE);
3126 /**
3127  * Node handle event callback.
3128  */
3129 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3131     gboolean ret = FALSE;
3132     switch (event->type) {
3133         case GDK_KEY_PRESS:
3134             switch (get_group0_keyval (&event->key)) {
3135                 case GDK_space:
3136                     if (event->key.state & GDK_BUTTON1_MASK) {
3137                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3138                         stamp_repr(nodepath);
3139                         ret = TRUE;
3140                     }
3141                     break;
3142                 default:
3143                     break;
3144             }
3145             break;
3146         default:
3147             break;
3148     }
3150     return ret;
3153 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3154                                  Radial &rme, Radial &rother, gboolean const both)
3156     rme.a += angle;
3157     if ( both
3158          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3159          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3160     {
3161         rother.a += angle;
3162     }
3165 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3166                                         Radial &rme, Radial &rother, gboolean const both)
3168     gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3170     gdouble r;
3171     if ( both
3172          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3173          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3174     {
3175         r = MAX(rme.r, rother.r);
3176     } else {
3177         r = rme.r;
3178     }
3180     gdouble const weird_angle = atan2(norm_angle, r);
3181 /* Bulia says norm_angle is just the visible distance that the
3182  * object's end must travel on the screen.  Left as 'angle' for want of
3183  * a better name.*/
3185     rme.a += weird_angle;
3186     if ( both
3187          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3188          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3189     {
3190         rother.a += weird_angle;
3191     }
3194 /**
3195  * Rotate one node.
3196  */
3197 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3199     Inkscape::NodePath::NodeSide *me, *other;
3200     bool both = false;
3202     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3203     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3205     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3206         me = &(n->p);
3207         other = &(n->n);
3208     } else if (!n->p.other) {
3209         me = &(n->n);
3210         other = &(n->p);
3211     } else {
3212         if (which > 0) { // right handle
3213             if (xn > xp) {
3214                 me = &(n->n);
3215                 other = &(n->p);
3216             } else {
3217                 me = &(n->p);
3218                 other = &(n->n);
3219             }
3220         } else if (which < 0){ // left handle
3221             if (xn <= xp) {
3222                 me = &(n->n);
3223                 other = &(n->p);
3224             } else {
3225                 me = &(n->p);
3226                 other = &(n->n);
3227             }
3228         } else { // both handles
3229             me = &(n->n);
3230             other = &(n->p);
3231             both = true;
3232         }
3233     }
3235     Radial rme(me->pos - n->pos);
3236     Radial rother(other->pos - n->pos);
3238     if (screen) {
3239         node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3240     } else {
3241         node_rotate_one_internal (*n, angle, rme, rother, both);
3242     }
3244     me->pos = n->pos + NR::Point(rme);
3246     if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3247         other->pos =  n->pos + NR::Point(rother);
3248     }
3250     // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3251     // so here we just move all the knots without emitting move signals, for speed
3252     sp_node_update_handles(n, false);
3255 /**
3256  * Rotate selected nodes.
3257  */
3258 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3260     if (!nodepath || !nodepath->selected) return;
3262     if (g_list_length(nodepath->selected) == 1) {
3263        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3264         node_rotate_one (n, angle, which, screen);
3265     } else {
3266        // rotate as an object:
3268         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3269         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3270         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3271             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3272             box.expandTo (n->pos); // contain all selected nodes
3273         }
3275         gdouble rot;
3276         if (screen) {
3277             gdouble const zoom = nodepath->desktop->current_zoom();
3278             gdouble const zmove = angle / zoom;
3279             gdouble const r = NR::L2(box.max() - box.midpoint());
3280             rot = atan2(zmove, r);
3281         } else {
3282             rot = angle;
3283         }
3285         NR::Matrix t =
3286             NR::Matrix (NR::translate(-box.midpoint())) *
3287             NR::Matrix (NR::rotate(rot)) *
3288             NR::Matrix (NR::translate(box.midpoint()));
3290         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3291             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3292             n->pos *= t;
3293             n->n.pos *= t;
3294             n->p.pos *= t;
3295             sp_node_update_handles(n, false);
3296         }
3297     }
3299     sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3302 /**
3303  * Scale one node.
3304  */
3305 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3307     bool both = false;
3308     Inkscape::NodePath::NodeSide *me, *other;
3310     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3311     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3313     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3314         me = &(n->p);
3315         other = &(n->n);
3316         n->code = NR_CURVETO;
3317     } else if (!n->p.other) {
3318         me = &(n->n);
3319         other = &(n->p);
3320         if (n->n.other)
3321             n->n.other->code = NR_CURVETO;
3322     } else {
3323         if (which > 0) { // right handle
3324             if (xn > xp) {
3325                 me = &(n->n);
3326                 other = &(n->p);
3327                 if (n->n.other)
3328                     n->n.other->code = NR_CURVETO;
3329             } else {
3330                 me = &(n->p);
3331                 other = &(n->n);
3332                 n->code = NR_CURVETO;
3333             }
3334         } else if (which < 0){ // left handle
3335             if (xn <= xp) {
3336                 me = &(n->n);
3337                 other = &(n->p);
3338                 if (n->n.other)
3339                     n->n.other->code = NR_CURVETO;
3340             } else {
3341                 me = &(n->p);
3342                 other = &(n->n);
3343                 n->code = NR_CURVETO;
3344             }
3345         } else { // both handles
3346             me = &(n->n);
3347             other = &(n->p);
3348             both = true;
3349             n->code = NR_CURVETO;
3350             if (n->n.other)
3351                 n->n.other->code = NR_CURVETO;
3352         }
3353     }
3355     Radial rme(me->pos - n->pos);
3356     Radial rother(other->pos - n->pos);
3358     rme.r += grow;
3359     if (rme.r < 0) rme.r = 0;
3360     if (rme.a == HUGE_VAL) {
3361         if (me->other) { // if direction is unknown, initialize it towards the next node
3362             Radial rme_next(me->other->pos - n->pos);
3363             rme.a = rme_next.a;
3364         } else { // if there's no next, initialize to 0
3365             rme.a = 0;
3366         }
3367     }
3368     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3369         rother.r += grow;
3370         if (rother.r < 0) rother.r = 0;
3371         if (rother.a == HUGE_VAL) {
3372             rother.a = rme.a + M_PI;
3373         }
3374     }
3376     me->pos = n->pos + NR::Point(rme);
3378     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3379         other->pos = n->pos + NR::Point(rother);
3380     }
3382     // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3383     // so here we just move all the knots without emitting move signals, for speed
3384     sp_node_update_handles(n, false);
3387 /**
3388  * Scale selected nodes.
3389  */
3390 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3392     if (!nodepath || !nodepath->selected) return;
3394     if (g_list_length(nodepath->selected) == 1) {
3395         // scale handles of the single selected node
3396         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3397         node_scale_one (n, grow, which);
3398     } else {
3399         // scale nodes as an "object":
3401         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3402         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3403         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3404             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3405             box.expandTo (n->pos); // contain all selected nodes
3406         }
3408         double scale = (box.maxExtent() + grow)/box.maxExtent();
3410         NR::Matrix t =
3411             NR::Matrix (NR::translate(-box.midpoint())) *
3412             NR::Matrix (NR::scale(scale, scale)) *
3413             NR::Matrix (NR::translate(box.midpoint()));
3415         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3416             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3417             n->pos *= t;
3418             n->n.pos *= t;
3419             n->p.pos *= t;
3420             sp_node_update_handles(n, false);
3421         }
3422     }
3424     sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3427 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3429     if (!nodepath) return;
3430     sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3433 /**
3434  * Flip selected nodes horizontally/vertically.
3435  */
3436 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3438     if (!nodepath || !nodepath->selected) return;
3440     if (g_list_length(nodepath->selected) == 1) {
3441         // flip handles of the single selected node
3442         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3443         double temp = n->p.pos[axis];
3444         n->p.pos[axis] = n->n.pos[axis];
3445         n->n.pos[axis] = temp;
3446         sp_node_update_handles(n, false);
3447     } else {
3448         // scale nodes as an "object":
3450         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3451         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3452         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3453             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3454             box.expandTo (n->pos); // contain all selected nodes
3455         }
3457         NR::Matrix t =
3458             NR::Matrix (NR::translate(-box.midpoint())) *
3459             NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3460             NR::Matrix (NR::translate(box.midpoint()));
3462         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3463             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3464             n->pos *= t;
3465             n->n.pos *= t;
3466             n->p.pos *= t;
3467             sp_node_update_handles(n, false);
3468         }
3469     }
3471     sp_nodepath_update_repr(nodepath);
3474 //-----------------------------------------------
3475 /**
3476  * Return new subpath under given nodepath.
3477  */
3478 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3480     g_assert(nodepath);
3481     g_assert(nodepath->desktop);
3483    Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3485     s->nodepath = nodepath;
3486     s->closed = FALSE;
3487     s->nodes = NULL;
3488     s->first = NULL;
3489     s->last = NULL;
3491     // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3492     // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3493     nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3495     return s;
3498 /**
3499  * Destroy nodes in subpath, then subpath itself.
3500  */
3501 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3503     g_assert(subpath);
3504     g_assert(subpath->nodepath);
3505     g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3507     while (subpath->nodes) {
3508         sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3509     }
3511     subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3513     g_free(subpath);
3516 /**
3517  * Link head to tail in subpath.
3518  */
3519 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3521     g_assert(!sp->closed);
3522     g_assert(sp->last != sp->first);
3523     g_assert(sp->first->code == NR_MOVETO);
3525     sp->closed = TRUE;
3527     //Link the head to the tail
3528     sp->first->p.other = sp->last;
3529     sp->last->n.other  = sp->first;
3530     sp->last->n.pos    = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3531     sp->first          = sp->last;
3533     //Remove the extra end node
3534     sp_nodepath_node_destroy(sp->last->n.other);
3537 /**
3538  * Open closed (loopy) subpath at node.
3539  */
3540 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3542     g_assert(sp->closed);
3543     g_assert(n->subpath == sp);
3544     g_assert(sp->first == sp->last);
3546     /* We create new startpoint, current node will become last one */
3548    Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3549                                                 &n->pos, &n->pos, &n->n.pos);
3552     sp->closed        = FALSE;
3554     //Unlink to make a head and tail
3555     sp->first         = new_path;
3556     sp->last          = n;
3557     n->n.other        = NULL;
3558     new_path->p.other = NULL;
3561 /**
3562  * Returns area in triangle given by points; may be negative.
3563  */
3564 inline double
3565 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3567     return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
3570 /**
3571  * Return new node in subpath with given properties.
3572  * \param pos Position of node.
3573  * \param ppos Handle position in previous direction
3574  * \param npos Handle position in previous direction
3575  */
3576 Inkscape::NodePath::Node *
3577 sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, NR::Point *ppos, NR::Point *pos, NR::Point *npos)
3579     g_assert(sp);
3580     g_assert(sp->nodepath);
3581     g_assert(sp->nodepath->desktop);
3583     if (nodechunk == NULL)
3584         nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3586     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3588     n->subpath  = sp;
3590     if (type != Inkscape::NodePath::NODE_NONE) {
3591         // use the type from sodipodi:nodetypes
3592         n->type = type;
3593     } else {
3594         if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3595             // points are (almost) collinear
3596             if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3597                 // endnode, or a node with a retracted handle
3598                 n->type = Inkscape::NodePath::NODE_CUSP;
3599             } else {
3600                 n->type = Inkscape::NodePath::NODE_SMOOTH;
3601             }
3602         } else {
3603             n->type = Inkscape::NodePath::NODE_CUSP;
3604         }
3605     }
3607     n->code     = code;
3608     n->selected = FALSE;
3609     n->pos      = *pos;
3610     n->p.pos    = *ppos;
3611     n->n.pos    = *npos;
3613     n->dragging_out = NULL;
3615     Inkscape::NodePath::Node *prev;
3616     if (next) {
3617         //g_assert(g_list_find(sp->nodes, next));
3618         prev = next->p.other;
3619     } else {
3620         prev = sp->last;
3621     }
3623     if (prev)
3624         prev->n.other = n;
3625     else
3626         sp->first = n;
3628     if (next)
3629         next->p.other = n;
3630     else
3631         sp->last = n;
3633     n->p.other = prev;
3634     n->n.other = next;
3636     n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions"));
3637     sp_knot_set_position(n->knot, pos, 0);
3639     n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3640     n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3641     n->knot->setAnchor (GTK_ANCHOR_CENTER);
3642     n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3643     n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3644     sp_knot_update_ctrl(n->knot);
3646     g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3647     g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3648     g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3649     g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3650     g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3651     sp_knot_show(n->knot);
3653     // We only create handle knots and lines on demand
3654     n->p.knot = NULL;
3655     n->p.line = NULL;
3656     n->n.knot = NULL;
3657     n->n.line = NULL;
3659     sp->nodes = g_list_prepend(sp->nodes, n);
3661     return n;
3664 /**
3665  * Destroy node and its knots, link neighbors in subpath.
3666  */
3667 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3669     g_assert(node);
3670     g_assert(node->subpath);
3671     g_assert(SP_IS_KNOT(node->knot));
3673    Inkscape::NodePath::SubPath *sp = node->subpath;
3675     if (node->selected) { // first, deselect
3676         g_assert(g_list_find(node->subpath->nodepath->selected, node));
3677         node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3678     }
3680     node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3682     g_object_unref(G_OBJECT(node->knot));
3683     if (node->p.knot)
3684         g_object_unref(G_OBJECT(node->p.knot));
3685     if (node->n.knot)
3686         g_object_unref(G_OBJECT(node->n.knot));
3688     if (node->p.line)
3689         gtk_object_destroy(GTK_OBJECT(node->p.line));
3690     if (node->n.line)
3691         gtk_object_destroy(GTK_OBJECT(node->n.line));
3693     if (sp->nodes) { // there are others nodes on the subpath
3694         if (sp->closed) {
3695             if (sp->first == node) {
3696                 g_assert(sp->last == node);
3697                 sp->first = node->n.other;
3698                 sp->last = sp->first;
3699             }
3700             node->p.other->n.other = node->n.other;
3701             node->n.other->p.other = node->p.other;
3702         } else {
3703             if (sp->first == node) {
3704                 sp->first = node->n.other;
3705                 sp->first->code = NR_MOVETO;
3706             }
3707             if (sp->last == node) sp->last = node->p.other;
3708             if (node->p.other) node->p.other->n.other = node->n.other;
3709             if (node->n.other) node->n.other->p.other = node->p.other;
3710         }
3711     } else { // this was the last node on subpath
3712         sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3713     }
3715     g_mem_chunk_free(nodechunk, node);
3718 /**
3719  * Returns one of the node's two sides.
3720  * \param which Indicates which side.
3721  * \return Pointer to previous node side if which==-1, next if which==1.
3722  */
3723 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3725     g_assert(node);
3727     switch (which) {
3728         case -1:
3729             return &node->p;
3730         case 1:
3731             return &node->n;
3732         default:
3733             break;
3734     }
3736     g_assert_not_reached();
3738     return NULL;
3741 /**
3742  * Return the other side of the node, given one of its sides.
3743  */
3744 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3746     g_assert(node);
3748     if (me == &node->p) return &node->n;
3749     if (me == &node->n) return &node->p;
3751     g_assert_not_reached();
3753     return NULL;
3756 /**
3757  * Return NRPathcode on the given side of the node.
3758  */
3759 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3761     g_assert(node);
3763     if (me == &node->p) {
3764         if (node->p.other) return (NRPathcode)node->code;
3765         return NR_MOVETO;
3766     }
3768     if (me == &node->n) {
3769         if (node->n.other) return (NRPathcode)node->n.other->code;
3770         return NR_MOVETO;
3771     }
3773     g_assert_not_reached();
3775     return NR_END;
3778 /**
3779  * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3780  */
3781 Inkscape::NodePath::Node *
3782 sp_nodepath_get_node_by_index(int index)
3784     Inkscape::NodePath::Node *e = NULL;
3786     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3787     if (!nodepath) {
3788         return e;
3789     }
3791     //find segment
3792     for (GList *l = nodepath->subpaths; l ; l=l->next) {
3794         Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3795         int n = g_list_length(sp->nodes);
3796         if (sp->closed) {
3797             n++;
3798         }
3800         //if the piece belongs to this subpath grab it
3801         //otherwise move onto the next subpath
3802         if (index < n) {
3803             e = sp->first;
3804             for (int i = 0; i < index; ++i) {
3805                 e = e->n.other;
3806             }
3807             break;
3808         } else {
3809             if (sp->closed) {
3810                 index -= (n+1);
3811             } else {
3812                 index -= n;
3813             }
3814         }
3815     }
3817     return e;
3820 /**
3821  * Returns plain text meaning of node type.
3822  */
3823 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3825     unsigned retracted = 0;
3826     bool endnode = false;
3828     for (int which = -1; which <= 1; which += 2) {
3829         Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3830         if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3831             retracted ++;
3832         if (!side->other)
3833             endnode = true;
3834     }
3836     if (retracted == 0) {
3837         if (endnode) {
3838                 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3839                 return _("end node");
3840         } else {
3841             switch (node->type) {
3842                 case Inkscape::NodePath::NODE_CUSP:
3843                     // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3844                     return _("cusp");
3845                 case Inkscape::NodePath::NODE_SMOOTH:
3846                     // TRANSLATORS: "smooth" is an adjective here
3847                     return _("smooth");
3848                 case Inkscape::NodePath::NODE_SYMM:
3849                     return _("symmetric");
3850             }
3851         }
3852     } else if (retracted == 1) {
3853         if (endnode) {
3854             // TRANSLATORS: "end" is an adjective here (NOT a verb)
3855             return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3856         } else {
3857             return _("one handle retracted (drag with <b>Shift</b> to extend)");
3858         }
3859     } else {
3860         return _("both handles retracted (drag with <b>Shift</b> to extend)");
3861     }
3863     return NULL;
3866 /**
3867  * Handles content of statusbar as long as node tool is active.
3868  */
3869 void
3870 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3872     gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag nodes</b> to sculpt; <b>arrow</b> keys to move nodes, <b>&lt; &gt;</b> to scale, <b>[ ]</b> to rotate");
3873     gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3875     gint total = 0;
3876     gint selected = 0;
3877     SPDesktop *desktop = NULL;
3879     if (nodepath) {
3880         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3881             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3882             total += g_list_length(subpath->nodes);
3883         }
3884         selected = g_list_length(nodepath->selected);
3885         desktop = nodepath->desktop;
3886     } else {
3887         desktop = SP_ACTIVE_DESKTOP;
3888     }
3890     SPEventContext *ec = desktop->event_context;
3891     if (!ec) return;
3892     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3893     if (!mc) return;
3895     if (selected == 0) {
3896         Inkscape::Selection *sel = desktop->selection;
3897         if (!sel || sel->isEmpty()) {
3898             mc->setF(Inkscape::NORMAL_MESSAGE,
3899                      _("Select a single object to edit its nodes or handles."));
3900         } else {
3901             if (nodepath) {
3902             mc->setF(Inkscape::NORMAL_MESSAGE,
3903                      ngettext("<b>0</b> out of <b>%i</b> node selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
3904                               "<b>0</b> out of <b>%i</b> nodes selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.",
3905                               total),
3906                      total);
3907             } else {
3908                 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3909                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3910                 } else {
3911                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3912                 }
3913             }
3914         }
3915     } else if (nodepath && selected == 1) {
3916         mc->setF(Inkscape::NORMAL_MESSAGE,
3917                  ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3918                           "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3919                           total),
3920                  selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3921     } else {
3922         mc->setF(Inkscape::NORMAL_MESSAGE,
3923                  ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3924                           "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3925                           total),
3926                  selected, total, when_selected);
3927     }
3931 /*
3932   Local Variables:
3933   mode:c++
3934   c-file-style:"stroustrup"
3935   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3936   indent-tabs-mode:nil
3937   fill-column:99
3938   End:
3939 */
3940 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :