Code

Enable simultaneous knotholder and nodepath
[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/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include "libnr/n-art-bpath.h"
25 #include "libnr/nr-path.h"
26 #include <2geom/pathvector.h>
27 #include <2geom/sbasis-to-bezier.h>
28 #include "helper/units.h"
29 #include "knot.h"
30 #include "inkscape.h"
31 #include "document.h"
32 #include "sp-namedview.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "snap.h"
36 #include "message-stack.h"
37 #include "message-context.h"
38 #include "node-context.h"
39 #include "shape-editor.h"
40 #include "selection-chemistry.h"
41 #include "selection.h"
42 #include "xml/repr.h"
43 #include "prefs-utils.h"
44 #include "sp-metrics.h"
45 #include "sp-path.h"
46 #include "libnr/nr-matrix-ops.h"
47 #include "splivarot.h"
48 #include "svg/svg.h"
49 #include "verbs.h"
50 #include "display/bezier-utils.h"
51 #include <vector>
52 #include <algorithm>
53 #include <cstring>
54 #include <string>
55 #include "live_effects/lpeobject.h"
56 #include "live_effects/effect.h"
57 #include "live_effects/parameter/parameter.h"
58 #include "util/mathfns.h"
59 #include "display/snap-indicator.h"
60 #include "snapped-point.h"
62 class NR::Matrix;
64 /// \todo
65 /// evil evil evil. FIXME: conflict of two different Path classes!
66 /// There is a conflict in the namespace between two classes named Path.
67 /// #include "sp-flowtext.h"
68 /// #include "sp-flowregion.h"
70 #define SP_TYPE_FLOWREGION            (sp_flowregion_get_type ())
71 #define SP_IS_FLOWREGION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
72 GType sp_flowregion_get_type (void);
73 #define SP_TYPE_FLOWTEXT            (sp_flowtext_get_type ())
74 #define SP_IS_FLOWTEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
75 GType sp_flowtext_get_type (void);
76 // end evil workaround
78 #include "helper/stlport.h"
81 /// \todo fixme: Implement these via preferences */
83 #define NODE_FILL          0xbfbfbf00
84 #define NODE_STROKE        0x000000ff
85 #define NODE_FILL_HI       0xff000000
86 #define NODE_STROKE_HI     0x000000ff
87 #define NODE_FILL_SEL      0x0000ffff
88 #define NODE_STROKE_SEL    0x000000ff
89 #define NODE_FILL_SEL_HI   0xff000000
90 #define NODE_STROKE_SEL_HI 0x000000ff
91 #define KNOT_FILL          0xffffffff
92 #define KNOT_STROKE        0x000000ff
93 #define KNOT_FILL_HI       0xff000000
94 #define KNOT_STROKE_HI     0x000000ff
96 static GMemChunk *nodechunk = NULL;
98 /* Creation from object */
100 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, Inkscape::NodePath::NodeType const *t);
101 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
102 static void add_curve_to_subpath( Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c,
103                                   Inkscape::NodePath::NodeType const *t, guint & i, NR::Point & ppos, NRPathcode & pcode  );
104 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, gint length);
106 /* Object updating */
108 static void stamp_repr(Inkscape::NodePath::Path *np);
109 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
110 static gchar *create_typestr(Inkscape::NodePath::Path *np);
112 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
114 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
116 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
118 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
120 /* Adjust handle placement, if the node or the other handle is moved */
121 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
122 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
124 /* Node event callbacks */
125 static void node_clicked(SPKnot *knot, guint state, gpointer data);
126 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
127 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
128 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
130 /* Handle event callbacks */
131 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
132 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
133 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
134 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
135 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
136 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
138 /* Constructors and destructors */
140 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
141 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
142 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
143 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
144 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
145                                          NR::Point *ppos, NR::Point *pos, NR::Point *npos);
146 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
148 /* Helpers */
150 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
151 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
152 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
154 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
155 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
157 // active_node indicates mouseover node
158 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
160 static void sp_nodepath_draw_helper_curve(Inkscape::NodePath::Path *np, SPDesktop *desktop) {
161     // Draw helper curve
162     if (np->show_helperpath) {
163         SPCurve *helper_curve = np->curve->copy();
164         helper_curve->transform(np->i2d );
165         np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
166         sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
167         sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
168         sp_canvas_item_move_to_z(np->helper_path, 0);
169         sp_canvas_item_show(np->helper_path);
170         helper_curve->unref();
171     }
174 /**
175  * \brief Creates new nodepath from item
176  */
177 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
179     Inkscape::XML::Node *repr = object->repr;
181     /** \todo
182      * FIXME: remove this. We don't want to edit paths inside flowtext.
183      * Instead we will build our flowtext with cloned paths, so that the
184      * real paths are outside the flowtext and thus editable as usual.
185      */
186     if (SP_IS_FLOWTEXT(object)) {
187         for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
188             if SP_IS_FLOWREGION(child) {
189                 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
190                 if (grandchild && SP_IS_PATH(grandchild)) {
191                     object = SP_ITEM(grandchild);
192                     break;
193                 }
194             }
195         }
196     }
198     SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
200     if (curve == NULL)
201         return NULL;
203     gint length = curve->get_length();
204     if (length == 0) {
205         curve->unref();
206         return NULL; // prevent crash for one-node paths
207     }
209     //Create new nodepath
210     Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
211     if (!np) {
212         curve->unref();
213         return NULL;
214     }
216     // Set defaults
217     np->desktop     = desktop;
218     np->object      = object;
219     np->subpaths    = NULL;
220     np->selected    = NULL;
221     np->shape_editor = NULL; //Let the shapeeditor that makes this set it
222     np->livarot_path = NULL;
223     np->local_change = 0;
224     np->show_handles = show_handles;
225     np->helper_path = NULL;
226     np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
227     np->helperpath_width = 1.0;
228     np->curve = curve->copy();
229     np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath",  0) == 1);
230     if (SP_IS_LPE_ITEM(object)) {
231         Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
232         if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
233             np->show_helperpath = true;
234         }            
235     }
236     np->straight_path = false;
237     if (IS_LIVEPATHEFFECT(object) && item) {
238         np->item = item;
239     } else {
240         np->item = SP_ITEM(object);
241     }
243     // we need to update item's transform from the repr here,
244     // because they may be out of sync when we respond
245     // to a change in repr by regenerating nodepath     --bb
246     sp_object_read_attr(SP_OBJECT(np->item), "transform");
248     np->i2d  = from_2geom(sp_item_i2d_affine(np->item));
249     np->d2i  = np->i2d.inverse();
251     np->repr = repr;
252     if (repr_key_in) { // apparantly the object is an LPEObject
253         np->repr_key = g_strdup(repr_key_in);
254         np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
255         Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
256         if (lpeparam) {
257             lpeparam->param_setup_nodepath(np);
258         }
259     } else {
260         np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
261         if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
262             np->repr_key = g_strdup("inkscape:original-d");
264             Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
265             if (lpe) {
266                 lpe->setup_nodepath(np);
267             }
268         } else {
269             np->repr_key = g_strdup("d");
270         }
271     }
273     gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
274     Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
276     // create the subpath(s) from the bpath
277 //    NArtBpath const *bpath = curve->get_bpath();
278 //    NArtBpath const *b = bpath;
279 //    while (b->code != NR_END) {
280 //        b = subpath_from_bpath(np, b, typestr + (b - bpath));
281 //    }
282     subpaths_from_pathvector(np, curve->get_pathvector(), typestr);
284     // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
285     np->subpaths = g_list_reverse(np->subpaths);
287     delete[] typestr;
288     curve->unref();
290     // create the livarot representation from the same item
291     sp_nodepath_ensure_livarot_path(np);
293     sp_nodepath_draw_helper_curve(np, desktop);
295     return np;
298 /**
299  * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
300  */
301 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
303     if (!np)  //soft fail, like delete
304         return;
306     while (np->subpaths) {
307         sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
308     }
310     //Inform the ShapeEditor that made me, if any, that I am gone.
311     if (np->shape_editor)
312         np->shape_editor->nodepath_destroyed();
314     g_assert(!np->selected);
316     if (np->livarot_path) {
317         delete np->livarot_path;
318         np->livarot_path = NULL;
319     }
321     if (np->helper_path) {
322         GtkObject *temp = np->helper_path;
323         np->helper_path = NULL;
324         gtk_object_destroy(temp);
325     }
326     if (np->curve) {
327         np->curve->unref();
328         np->curve = NULL;
329     }
331     if (np->repr_key) {
332         g_free(np->repr_key);
333         np->repr_key = NULL;
334     }
335     if (np->repr_nodetypes_key) {
336         g_free(np->repr_nodetypes_key);
337         np->repr_nodetypes_key = NULL;
338     }
340     np->desktop = NULL;
342     g_free(np);
346 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
348     if (np && np->livarot_path == NULL) {
349         SPCurve *curve = create_curve(np);
350         np->livarot_path = new Path;
351         np->livarot_path->LoadPathVector(curve->get_pathvector());
353         if (np->livarot_path)
354             np->livarot_path->ConvertWithBackData(0.01);
356         curve->unref();
357     }
361 /**
362  *  Return the node count of a given NodeSubPath.
363  */
364 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
366     if (!subpath)
367         return 0;
368     gint nodeCount = g_list_length(subpath->nodes);
369     return nodeCount;
372 /**
373  *  Return the node count of a given NodePath.
374  */
375 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
377     if (!np)
378         return 0;
379     gint nodeCount = 0;
380     for (GList *item = np->subpaths ; item ; item=item->next) {
381        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
382         nodeCount += g_list_length(subpath->nodes);
383     }
384     return nodeCount;
387 /**
388  *  Return the subpath count of a given NodePath.
389  */
390 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
392     if (!np)
393         return 0;
394     return g_list_length (np->subpaths);
397 /**
398  *  Return the selected node count of a given NodePath.
399  */
400 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
402     if (!np)
403         return 0;
404     return g_list_length (np->selected);
407 /**
408  *  Return the number of subpaths where nodes are selected in a given NodePath.
409  */
410 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
412     if (!np)
413         return 0;
414     if (!np->selected)
415         return 0;
416     if (!np->selected->next)
417         return 1;
418     gint count = 0;
419     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
420         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
421         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
422             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
423             if (node->selected) {
424                 count ++;
425                 break;
426             }
427         }
428     }
429     return count;
432 /**
433  * Clean up a nodepath after editing.
434  *
435  * Currently we are deleting trivial subpaths.
436  */
437 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
439     GList *badSubPaths = NULL;
441     //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
442     for (GList *l = nodepath->subpaths; l ; l=l->next) {
443        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
444        if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
445             badSubPaths = g_list_append(badSubPaths, sp);
446     }
448     //Delete them.  This second step is because sp_nodepath_subpath_destroy()
449     //also removes the subpath from nodepath->subpaths
450     for (GList *l = badSubPaths; l ; l=l->next) {
451        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
452         sp_nodepath_subpath_destroy(sp);
453     }
455     g_list_free(badSubPaths);
458 /**
459  * Create new nodepath from b, make it subpath of np.
460  * \param t The node type.
461  */
462 static NArtBpath const * subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath const *b, Inkscape::NodePath::NodeType const *t)
464     NR::Point ppos, pos, npos;
466     g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
468     Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
469     bool const closed = (b->code == NR_MOVETO);
471     pos = NR::Point(b->x3, b->y3) * np->i2d;
472     if (b[1].code == NR_CURVETO) {
473         npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
474     } else {
475         npos = pos;
476     }
477     Inkscape::NodePath::Node *n;
478     n = sp_nodepath_node_new(sp, NULL, *t, NR_MOVETO, &pos, &pos, &npos);
479     g_assert(sp->first == n);
480     g_assert(sp->last  == n);
482     b++;
483     t++;
484     while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
485         pos = NR::Point(b->x3, b->y3) * np->i2d;
486         if (b->code == NR_CURVETO) {
487             ppos = NR::Point(b->x2, b->y2) * np->i2d;
488         } else {
489             ppos = pos;
490         }
491         if (b[1].code == NR_CURVETO) {
492             npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
493         } else {
494             npos = pos;
495         }
496         n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
497         b++;
498         t++;
499     }
501     if (closed) sp_nodepath_subpath_close(sp);
503     return b;
506 /**
507  * Create new nodepaths from pathvector, make it subpaths of np.
508  * \param t The node type array.
509  */
510 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
512     guint i = 0;  // index into node type array
513     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
514         if (pit->empty())
515             continue;  // don't add single knot paths
517         Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
519         NR::Point ppos = from_2geom(pit->initialPoint()) * np->i2d;
520         NRPathcode pcode = NR_MOVETO;
522         for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
523             add_curve_to_subpath(np, sp, *cit, t, i, ppos, pcode);
524         }
526         if (pit->closed()) {
527             // Add last knot (because sp_nodepath_subpath_close kills the last knot)
528             /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
529              * If the length is zero, don't add it to the nodepath. */
530             Geom::Curve const &closing_seg = pit->back_closed();
531             if ( ! closing_seg.isDegenerate() ) {
532                 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
533                 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
534             }
536             sp_nodepath_subpath_close(sp);
537         }
538     }
540 // should add initial point of curve with type of previous curve:
541 static void add_curve_to_subpath(Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c, Inkscape::NodePath::NodeType const *t, guint & i,
542                                  NR::Point & ppos, NRPathcode & pcode)
544     if( dynamic_cast<Geom::LineSegment const*>(&c) ||
545         dynamic_cast<Geom::HLineSegment const*>(&c) ||
546         dynamic_cast<Geom::VLineSegment const*>(&c) )
547     {
548         NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
549         sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
550         ppos = from_2geom(c.finalPoint());
551         pcode = NR_LINETO;
552     }
553     else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
554         std::vector<Geom::Point> points = cubic_bezier->points();
555         NR::Point pos = from_2geom(points[0]) * np->i2d;
556         NR::Point npos = from_2geom(points[1]) * np->i2d;
557         sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
558         ppos = from_2geom(points[2]) * np->i2d;
559         pcode = NR_CURVETO;
560     }
561     else {
562         //this case handles sbasis as well as all other curve types
563         Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
565         for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
566             add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
567         }
568     }
572 /**
573  * Convert from sodipodi:nodetypes to new style type array.
574  */
575 static
576 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, gint length)
578     g_assert(length > 0);
580     Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
582     gint pos = 0;
584     if (types) {
585         for (gint i = 0; types[i] && ( i < length ); i++) {
586             while ((types[i] > '\0') && (types[i] <= ' ')) i++;
587             if (types[i] != '\0') {
588                 switch (types[i]) {
589                     case 's':
590                         typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
591                         break;
592                     case 'z':
593                         typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
594                         break;
595                     case 'c':
596                         typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
597                         break;
598                     default:
599                         typestr[pos++] =Inkscape::NodePath::NODE_NONE;
600                         break;
601                 }
602             }
603         }
604     }
606     while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
608     return typestr;
611 /**
612  * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
613  * updated but repr is not (for speed). Used during curve and node drag.
614  */
615 static void update_object(Inkscape::NodePath::Path *np)
617     g_assert(np);
619     np->curve->unref();
620     np->curve = create_curve(np);
622     sp_nodepath_set_curve(np, np->curve);
624     if (np->show_helperpath) {
625         SPCurve * helper_curve = np->curve->copy();
626         helper_curve->transform(np->i2d );
627         sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
628         helper_curve->unref();
629     }
631     // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
632     // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
633     np->shape_editor->update_knotholder();
636 /**
637  * Update XML path node with data from path object.
638  */
639 static void update_repr_internal(Inkscape::NodePath::Path *np)
641     g_assert(np);
643     Inkscape::XML::Node *repr = np->object->repr;
645     np->curve->unref();
646     np->curve = create_curve(np);
648     gchar *typestr = create_typestr(np);
649     gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
651     // determine if path has an effect applied and write to correct "d" attribute.
652     if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
653         np->local_change++;
654         repr->setAttribute(np->repr_key, svgpath);
655     }
657     if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
658         np->local_change++;
659         repr->setAttribute(np->repr_nodetypes_key, typestr);
660     }
662     g_free(svgpath);
663     g_free(typestr);
665     if (np->show_helperpath) {
666         SPCurve * helper_curve = np->curve->copy();
667         helper_curve->transform(np->i2d );
668         sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
669         helper_curve->unref();
670     }
671  }
673 /**
674  * Update XML path node with data from path object, commit changes forever.
675  */
676 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
678     //fixme: np can be NULL, so check before proceeding
679     g_return_if_fail(np != NULL);
681     if (np->livarot_path) {
682         delete np->livarot_path;
683         np->livarot_path = NULL;
684     }
686     update_repr_internal(np);
687     sp_canvas_end_forced_full_redraws(np->desktop->canvas);
689     sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
690                      annotation);
693 /**
694  * Update XML path node with data from path object, commit changes with undo.
695  */
696 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
698     if (np->livarot_path) {
699         delete np->livarot_path;
700         np->livarot_path = NULL;
701     }
703     update_repr_internal(np);
704     sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
705                            annotation);
708 /**
709  * Make duplicate of path, replace corresponding XML node in tree, commit.
710  */
711 static void stamp_repr(Inkscape::NodePath::Path *np)
713     g_assert(np);
715     Inkscape::XML::Node *old_repr = np->object->repr;
716     Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
718     // remember the position of the item
719     gint pos = old_repr->position();
720     // remember parent
721     Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
723     SPCurve *curve = create_curve(np);
724     gchar *typestr = create_typestr(np);
726     gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
728     new_repr->setAttribute(np->repr_key, svgpath);
729     new_repr->setAttribute(np->repr_nodetypes_key, typestr);
731     // add the new repr to the parent
732     parent->appendChild(new_repr);
733     // move to the saved position
734     new_repr->setPosition(pos > 0 ? pos : 0);
736     sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
737                      _("Stamp"));
739     Inkscape::GC::release(new_repr);
740     g_free(svgpath);
741     g_free(typestr);
742     curve->unref();
745 /**
746  * Create curve from path.
747  */
748 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
750     SPCurve *curve = new SPCurve();
752     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
753        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
754         curve->moveto(sp->first->pos * np->d2i);
755        Inkscape::NodePath::Node *n = sp->first->n.other;
756         while (n) {
757             NR::Point const end_pt = n->pos * np->d2i;
758             switch (n->code) {
759                 case NR_LINETO:
760                     curve->lineto(end_pt);
761                     break;
762                 case NR_CURVETO:
763                     curve->curveto(n->p.other->n.pos * np->d2i,
764                                      n->p.pos * np->d2i,
765                                      end_pt);
766                     break;
767                 default:
768                     g_assert_not_reached();
769                     break;
770             }
771             if (n != sp->last) {
772                 n = n->n.other;
773             } else {
774                 n = NULL;
775             }
776         }
777         if (sp->closed) {
778             curve->closepath();
779         }
780     }
782     return curve;
785 /**
786  * Convert path type string to sodipodi:nodetypes style.
787  */
788 static gchar *create_typestr(Inkscape::NodePath::Path *np)
790     gchar *typestr = g_new(gchar, 32);
791     gint len = 32;
792     gint pos = 0;
794     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
795        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
797         if (pos >= len) {
798             typestr = g_renew(gchar, typestr, len + 32);
799             len += 32;
800         }
802         typestr[pos++] = 'c';
804        Inkscape::NodePath::Node *n;
805         n = sp->first->n.other;
806         while (n) {
807             gchar code;
809             switch (n->type) {
810                 case Inkscape::NodePath::NODE_CUSP:
811                     code = 'c';
812                     break;
813                 case Inkscape::NodePath::NODE_SMOOTH:
814                     code = 's';
815                     break;
816                 case Inkscape::NodePath::NODE_SYMM:
817                     code = 'z';
818                     break;
819                 default:
820                     g_assert_not_reached();
821                     code = '\0';
822                     break;
823             }
825             if (pos >= len) {
826                 typestr = g_renew(gchar, typestr, len + 32);
827                 len += 32;
828             }
830             typestr[pos++] = code;
832             if (n != sp->last) {
833                 n = n->n.other;
834             } else {
835                 n = NULL;
836             }
837         }
838     }
840     if (pos >= len) {
841         typestr = g_renew(gchar, typestr, len + 1);
842         len += 1;
843     }
845     typestr[pos++] = '\0';
847     return typestr;
850 /**
851  * Returns current path in context. // later eliminate this function at all!
852  */
853 static Inkscape::NodePath::Path *sp_nodepath_current()
855     if (!SP_ACTIVE_DESKTOP) {
856         return NULL;
857     }
859     SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
861     if (!SP_IS_NODE_CONTEXT(event_context)) {
862         return NULL;
863     }
865     return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
870 /**
871  \brief Fills node and handle positions for three nodes, splitting line
872   marked by end at distance t.
873  */
874 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
876     g_assert(new_path != NULL);
877     g_assert(end      != NULL);
879     g_assert(end->p.other == new_path);
880    Inkscape::NodePath::Node *start = new_path->p.other;
881     g_assert(start);
883     if (end->code == NR_LINETO) {
884         new_path->type =Inkscape::NodePath::NODE_CUSP;
885         new_path->code = NR_LINETO;
886         new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
887     } else {
888         new_path->type =Inkscape::NodePath::NODE_SMOOTH;
889         new_path->code = NR_CURVETO;
890         gdouble s      = 1 - t;
891         for (int dim = 0; dim < 2; dim++) {
892             NR::Coord const f000 = start->pos[dim];
893             NR::Coord const f001 = start->n.pos[dim];
894             NR::Coord const f011 = end->p.pos[dim];
895             NR::Coord const f111 = end->pos[dim];
896             NR::Coord const f00t = s * f000 + t * f001;
897             NR::Coord const f01t = s * f001 + t * f011;
898             NR::Coord const f11t = s * f011 + t * f111;
899             NR::Coord const f0tt = s * f00t + t * f01t;
900             NR::Coord const f1tt = s * f01t + t * f11t;
901             NR::Coord const fttt = s * f0tt + t * f1tt;
902             start->n.pos[dim]    = f00t;
903             new_path->p.pos[dim] = f0tt;
904             new_path->pos[dim]   = fttt;
905             new_path->n.pos[dim] = f1tt;
906             end->p.pos[dim]      = f11t;
907         }
908     }
911 /**
912  * Adds new node on direct line between two nodes, activates handles of all
913  * three nodes.
914  */
915 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
917     g_assert(end);
918     g_assert(end->subpath);
919     g_assert(g_list_find(end->subpath->nodes, end));
921    Inkscape::NodePath::Node *start = end->p.other;
922     g_assert( start->n.other == end );
923    Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
924                                                end,
925                                                (NRPathcode)end->code == NR_LINETO?
926                                                   Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
927                                                (NRPathcode)end->code,
928                                                &start->pos, &start->pos, &start->n.pos);
929     sp_nodepath_line_midpoint(newnode, end, t);
931     sp_node_adjust_handles(start);
932     sp_node_update_handles(start);
933     sp_node_update_handles(newnode);
934     sp_node_adjust_handles(end);
935     sp_node_update_handles(end);
937     return newnode;
940 /**
941 \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
942 */
943 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
945     g_assert(node);
946     g_assert(node->subpath);
947     g_assert(g_list_find(node->subpath->nodes, node));
949    Inkscape::NodePath::SubPath *sp = node->subpath;
950     Inkscape::NodePath::Path *np    = sp->nodepath;
952     if (sp->closed) {
953         sp_nodepath_subpath_open(sp, node);
954         return sp->first;
955     } else {
956         // no break for end nodes
957         if (node == sp->first) return NULL;
958         if (node == sp->last ) return NULL;
960         // create a new subpath
961        Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
963         // duplicate the break node as start of the new subpath
964         Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
966         // attach rest of curve to new node
967         g_assert(node->n.other);
968         newnode->n.other = node->n.other; node->n.other = NULL;
969         newnode->n.other->p.other = newnode;
970         newsubpath->last = sp->last;
971         sp->last = node;
972         node = newnode;
973         while (node->n.other) {
974             node = node->n.other;
975             node->subpath = newsubpath;
976             sp->nodes = g_list_remove(sp->nodes, node);
977             newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
978         }
981         return newnode;
982     }
985 /**
986  * Duplicate node and connect to neighbours.
987  */
988 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
990     g_assert(node);
991     g_assert(node->subpath);
992     g_assert(g_list_find(node->subpath->nodes, node));
994    Inkscape::NodePath::SubPath *sp = node->subpath;
996     NRPathcode code = (NRPathcode) node->code;
997     if (code == NR_MOVETO) { // if node is the endnode,
998         node->code = NR_LINETO; // new one is inserted before it, so change that to line
999     }
1001     Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1003     if (!node->n.other || !node->p.other) // if node is an endnode, select it
1004         return node;
1005     else
1006         return newnode; // otherwise select the newly created node
1009 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1011     node->p.pos = (node->pos + (node->pos - node->n.pos));
1014 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1016     node->n.pos = (node->pos + (node->pos - node->p.pos));
1019 /**
1020  * Change line type at node, with side effects on neighbours.
1021  */
1022 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1024     g_assert(end);
1025     g_assert(end->subpath);
1026     g_assert(end->p.other);
1028     if (end->code == static_cast< guint > ( code ) )
1029         return;
1031    Inkscape::NodePath::Node *start = end->p.other;
1033     end->code = code;
1035     if (code == NR_LINETO) {
1036         if (start->code == NR_LINETO) {
1037             sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1038         }
1039         if (end->n.other) {
1040             if (end->n.other->code == NR_LINETO) {
1041                 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1042             }
1043         }
1044     } else {
1045         NR::Point delta = end->pos - start->pos;
1046         start->n.pos = start->pos + delta / 3;
1047         end->p.pos = end->pos - delta / 3;
1048         sp_node_adjust_handle(start, 1);
1049         sp_node_adjust_handle(end, -1);
1050     }
1052     sp_node_update_handles(start);
1053     sp_node_update_handles(end);
1056 /**
1057  * Change node type, and its handles accordingly.
1058  */
1059 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1061     g_assert(node);
1062     g_assert(node->subpath);
1064     if ((node->p.other != NULL) && (node->n.other != NULL)) {
1065         if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1066             type =Inkscape::NodePath::NODE_CUSP;
1067         }
1068     }
1070     node->type = type;
1072     if (node->type == Inkscape::NodePath::NODE_CUSP) {
1073         node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1074         node->knot->setSize (node->selected? 11 : 9);
1075         sp_knot_update_ctrl(node->knot);
1076     } else {
1077         node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1078         node->knot->setSize (node->selected? 9 : 7);
1079         sp_knot_update_ctrl(node->knot);
1080     }
1082     // if one of handles is mouseovered, preserve its position
1083     if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1084         sp_node_adjust_handle(node, 1);
1085     } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1086         sp_node_adjust_handle(node, -1);
1087     } else {
1088         sp_node_adjust_handles(node);
1089     }
1091     sp_node_update_handles(node);
1093     sp_nodepath_update_statusbar(node->subpath->nodepath);
1095     return node;
1098 bool
1099 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1101         Inkscape::NodePath::Node *othernode = side->other;
1102         if (!othernode)
1103             return false;
1104         NRPathcode const code = sp_node_path_code_from_side(node, side);
1105         if (code == NR_LINETO)
1106             return true;
1107         Inkscape::NodePath::NodeSide *other_to_me = NULL;
1108         if (&node->p == side) {
1109             other_to_me = &othernode->n;
1110         } else if (&node->n == side) {
1111             other_to_me = &othernode->p;
1112         } 
1113         if (!other_to_me)
1114             return false;
1115         bool is_line = 
1116              (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1117               NR::L2(node->pos - side->pos) < 1e-6);
1118         return is_line;
1121 /**
1122  * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1123  * lines to curves.  If adjacent to one line segment, pulls out or rotates opposite handle to align
1124  * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too. 
1125  * If already cusp and set to cusp, retracts handles.
1126 */
1127 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1129     if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1131 /* 
1132   Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1133  
1134         if (two_handles) {
1135             // do nothing, adjust_handles called via set_node_type will line them up
1136         } else if (one_handle) {
1137             if (opposite_to_handle_is_line) {
1138                 if (lined_up) {
1139                     // already half-smooth; pull opposite handle too making it fully smooth
1140                 } else {
1141                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1142                 }
1143             } else {
1144                 // pull opposite handle in line with the existing one
1145             }
1146         } else if (no_handles) {
1147             if (both_segments_are_lines OR both_segments_are_curves) {
1148                 //pull both handles
1149             } else {
1150                 // pull the handle opposite to line segment, making node half-smooth
1151             }
1152         }
1153 */
1154         bool p_has_handle = (NR::L2(node->pos  - node->p.pos) > 1e-6);
1155         bool n_has_handle = (NR::L2(node->pos  - node->n.pos) > 1e-6);
1156         bool p_is_line = sp_node_side_is_line(node, &node->p);
1157         bool n_is_line = sp_node_side_is_line(node, &node->n);
1159         if (p_has_handle && n_has_handle) {
1160             // do nothing, adjust_handles will line them up
1161         } else if (p_has_handle || n_has_handle) {
1162             if (p_has_handle && n_is_line) {
1163                 Radial line (node->n.other->pos - node->pos);
1164                 Radial handle (node->pos - node->p.pos);
1165                 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1166                     // already half-smooth; pull opposite handle too making it fully smooth
1167                     node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1168                 } else {
1169                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1170                 }
1171             } else if (n_has_handle && p_is_line) {
1172                 Radial line (node->p.other->pos - node->pos);
1173                 Radial handle (node->pos - node->n.pos);
1174                 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1175                     // already half-smooth; pull opposite handle too making it fully smooth
1176                     node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1177                 } else {
1178                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1179                 }
1180             } else if (p_has_handle && node->n.other) {
1181                 // pull n handle
1182                 node->n.other->code = NR_CURVETO;
1183                 double len =  (type == Inkscape::NodePath::NODE_SYMM)?
1184                     NR::L2(node->p.pos - node->pos) :
1185                     NR::L2(node->n.other->pos - node->pos) / 3;
1186                 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1187             } else if (n_has_handle && node->p.other) {
1188                 // pull p handle
1189                 node->code = NR_CURVETO;
1190                 double len =  (type == Inkscape::NodePath::NODE_SYMM)?
1191                     NR::L2(node->n.pos - node->pos) :
1192                     NR::L2(node->p.other->pos - node->pos) / 3;
1193                 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1194             }
1195         } else if (!p_has_handle && !n_has_handle) {
1196             if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1197                 // no handles, but both segments are either lnes or curves:
1198                 //pull both handles
1200                 // convert both to curves:
1201                 node->code = NR_CURVETO;
1202                 node->n.other->code = NR_CURVETO;
1204                 NR::Point leg_prev = node->pos - node->p.other->pos;
1205                 NR::Point leg_next = node->pos - node->n.other->pos;
1207                 double norm_leg_prev = L2(leg_prev);
1208                 double norm_leg_next = L2(leg_next);
1210                 NR::Point delta;
1211                 if (norm_leg_next > 0.0) {
1212                     delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1213                     (&delta)->normalize();
1214                 }
1216                 if (type == Inkscape::NodePath::NODE_SYMM) {
1217                     double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1218                     node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1219                     node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1220                 } else {
1221                     // length of handle is proportional to distance to adjacent node
1222                     node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1223                     node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1224                 }
1226             } else {
1227                 // pull the handle opposite to line segment, making it half-smooth
1228                 if (p_is_line && node->n.other) {
1229                     if (type != Inkscape::NodePath::NODE_SYMM) {
1230                         // pull n handle
1231                         node->n.other->code = NR_CURVETO;
1232                         double len =  NR::L2(node->n.other->pos - node->pos) / 3;
1233                         node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1234                     }
1235                 } else if (n_is_line && node->p.other) {
1236                     if (type != Inkscape::NodePath::NODE_SYMM) {
1237                         // pull p handle
1238                         node->code = NR_CURVETO;
1239                         double len =  NR::L2(node->p.other->pos - node->pos) / 3;
1240                         node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1241                     }
1242                 }
1243             }
1244         }
1245     } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1246         // cusping a cusp: retract nodes
1247         node->p.pos = node->pos;
1248         node->n.pos = node->pos;
1249     }
1251     sp_nodepath_set_node_type (node, type);
1254 /**
1255  * Move node to point, and adjust its and neighbouring handles.
1256  */
1257 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1259     NR::Point delta = p - node->pos;
1260     node->pos = p;
1262     node->p.pos += delta;
1263     node->n.pos += delta;
1265     Inkscape::NodePath::Node *node_p = NULL;
1266     Inkscape::NodePath::Node *node_n = NULL;
1268     if (node->p.other) {
1269         if (node->code == NR_LINETO) {
1270             sp_node_adjust_handle(node, 1);
1271             sp_node_adjust_handle(node->p.other, -1);
1272             node_p = node->p.other;
1273         }
1274     }
1275     if (node->n.other) {
1276         if (node->n.other->code == NR_LINETO) {
1277             sp_node_adjust_handle(node, -1);
1278             sp_node_adjust_handle(node->n.other, 1);
1279             node_n = node->n.other;
1280         }
1281     }
1283     // this function is only called from batch movers that will update display at the end
1284     // themselves, so here we just move all the knots without emitting move signals, for speed
1285     sp_node_update_handles(node, false);
1286     if (node_n) {
1287         sp_node_update_handles(node_n, false);
1288     }
1289     if (node_p) {
1290         sp_node_update_handles(node_p, false);
1291     }
1294 /**
1295  * Call sp_node_moveto() for node selection and handle possible snapping.
1296  */
1297 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1298                                             bool const snap, bool constrained = false, 
1299                                             Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1301     NR::Coord best = NR_HUGE;
1302     NR::Point delta(dx, dy);
1303     NR::Point best_pt = delta;
1304     Inkscape::SnappedPoint best_abs;
1305     
1306     if (snap) {    
1307         /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1308          * not to itself. The snapper however can not tell which nodes are selected and which are not, so we 
1309          * must provide that information. */
1310           
1311         // Build a list of the unselected nodes to which the snapper should snap 
1312         std::vector<NR::Point> unselected_nodes;
1313         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1314             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1315             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1316                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1317                 if (!node->selected) {
1318                     unselected_nodes.push_back(node->pos);
1319                 }    
1320             }
1321         }        
1322         
1323         SnapManager &m = nodepath->desktop->namedview->snap_manager;
1324         
1325         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1326             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1327             m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1328             Inkscape::SnappedPoint s;
1329             if (constrained) {
1330                 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1331                 dedicated_constraint.setPoint(n->pos);
1332                 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1333             } else {
1334                 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1335             }            
1336             if (s.getSnapped() && (s.getDistance() < best)) {
1337                 best = s.getDistance();
1338                 best_abs = s;
1339                 best_pt = s.getPoint() - n->pos;
1340             }
1341         }
1342                         
1343         if (best_abs.getSnapped()) {
1344             nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1345         } else {
1346             nodepath->desktop->snapindicator->remove_snappoint();    
1347         }
1348     }
1350     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1351         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1352         sp_node_moveto(n, n->pos + best_pt);
1353     }
1355     // do not update repr here so that node dragging is acceptably fast
1356     update_object(nodepath);
1359 /**
1360 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1361 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1362 near x = 0.
1363  */
1364 double
1365 sculpt_profile (double x, double alpha, guint profile)
1367     if (x >= 1)
1368         return 0;
1369     if (x <= 0)
1370         return 1;
1372     switch (profile) {
1373         case SCULPT_PROFILE_LINEAR:
1374         return 1 - x;
1375         case SCULPT_PROFILE_BELL:
1376         return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1377         case SCULPT_PROFILE_ELLIPTIC:
1378         return sqrt(1 - x*x);
1379     }
1381     return 1;
1384 double
1385 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1387     // extremely primitive for now, don't have time to look for the real one
1388     double lower = NR::L2(b - a);
1389     double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1390     return (lower + upper)/2;
1393 void
1394 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1396     n->pos = n->origin + delta;
1397     n->n.pos = n->n.origin + delta_n;
1398     n->p.pos = n->p.origin + delta_p;
1399     sp_node_adjust_handles(n);
1400     sp_node_update_handles(n, false);
1403 /**
1404  * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1405  * on how far they are from the dragged node n.
1406  */
1407 static void
1408 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1410     g_assert (n);
1411     g_assert (nodepath);
1412     g_assert (n->subpath->nodepath == nodepath);
1414     double pressure = n->knot->pressure;
1415     if (pressure == 0)
1416         pressure = 0.5; // default
1417     pressure = CLAMP (pressure, 0.2, 0.8);
1419     // map pressure to alpha = 1/5 ... 5
1420     double alpha = 1 - 2 * fabs(pressure - 0.5);
1421     if (pressure > 0.5)
1422         alpha = 1/alpha;
1424     guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1426     if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1427         // Only one subpath has selected nodes:
1428         // use linear mode, where the distance from n to node being dragged is calculated along the path
1430         double n_sel_range = 0, p_sel_range = 0;
1431         guint n_nodes = 0, p_nodes = 0;
1432         guint n_sel_nodes = 0, p_sel_nodes = 0;
1434         // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1435         {
1436             double n_range = 0, p_range = 0;
1437             bool n_going = true, p_going = true;
1438             Inkscape::NodePath::Node *n_node = n;
1439             Inkscape::NodePath::Node *p_node = n;
1440             do {
1441                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1442                 if (n_node && n_going)
1443                     n_node = n_node->n.other;
1444                 if (n_node == NULL) {
1445                     n_going = false;
1446                 } else {
1447                     n_nodes ++;
1448                     n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1449                     if (n_node->selected) {
1450                         n_sel_nodes ++;
1451                         n_sel_range = n_range;
1452                     }
1453                     if (n_node == p_node) {
1454                         n_going = false;
1455                         p_going = false;
1456                     }
1457                 }
1458                 if (p_node && p_going)
1459                     p_node = p_node->p.other;
1460                 if (p_node == NULL) {
1461                     p_going = false;
1462                 } else {
1463                     p_nodes ++;
1464                     p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1465                     if (p_node->selected) {
1466                         p_sel_nodes ++;
1467                         p_sel_range = p_range;
1468                     }
1469                     if (p_node == n_node) {
1470                         n_going = false;
1471                         p_going = false;
1472                     }
1473                 }
1474             } while (n_going || p_going);
1475         }
1477         // Second pass: actually move nodes in this subpath
1478         sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1479         {
1480             double n_range = 0, p_range = 0;
1481             bool n_going = true, p_going = true;
1482             Inkscape::NodePath::Node *n_node = n;
1483             Inkscape::NodePath::Node *p_node = n;
1484             do {
1485                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1486                 if (n_node && n_going)
1487                     n_node = n_node->n.other;
1488                 if (n_node == NULL) {
1489                     n_going = false;
1490                 } else {
1491                     n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1492                     if (n_node->selected) {
1493                         sp_nodepath_move_node_and_handles (n_node,
1494                                                            sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1495                                                            sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1496                                                            sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1497                     }
1498                     if (n_node == p_node) {
1499                         n_going = false;
1500                         p_going = false;
1501                     }
1502                 }
1503                 if (p_node && p_going)
1504                     p_node = p_node->p.other;
1505                 if (p_node == NULL) {
1506                     p_going = false;
1507                 } else {
1508                     p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1509                     if (p_node->selected) {
1510                         sp_nodepath_move_node_and_handles (p_node,
1511                                                            sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1512                                                            sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1513                                                            sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1514                     }
1515                     if (p_node == n_node) {
1516                         n_going = false;
1517                         p_going = false;
1518                     }
1519                 }
1520             } while (n_going || p_going);
1521         }
1523     } else {
1524         // Multiple subpaths have selected nodes:
1525         // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1526         // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1527         // fix the pear-like shape when sculpting e.g. a ring
1529         // First pass: calculate range
1530         gdouble direct_range = 0;
1531         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1532             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1533             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1534                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1535                 if (node->selected) {
1536                     direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1537                 }
1538             }
1539         }
1541         // Second pass: actually move nodes
1542         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1543             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1544             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1545                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1546                 if (node->selected) {
1547                     if (direct_range > 1e-6) {
1548                         sp_nodepath_move_node_and_handles (node,
1549                                                        sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1550                                                        sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1551                                                        sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1552                     } else {
1553                         sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1554                     }
1556                 }
1557             }
1558         }
1559     }
1561     // do not update repr here so that node dragging is acceptably fast
1562     update_object(nodepath);
1566 /**
1567  * Move node selection to point, adjust its and neighbouring handles,
1568  * handle possible snapping, and commit the change with possible undo.
1569  */
1570 void
1571 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1573     if (!nodepath) return;
1575     sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1577     if (dx == 0) {
1578         sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1579     } else if (dy == 0) {
1580         sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1581     } else {
1582         sp_nodepath_update_repr(nodepath, _("Move nodes"));
1583     }
1586 /**
1587  * Move node selection off screen and commit the change.
1588  */
1589 void
1590 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1592     // borrowed from sp_selection_move_screen in selection-chemistry.c
1593     // we find out the current zoom factor and divide deltas by it
1594     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1596     gdouble zoom = desktop->current_zoom();
1597     gdouble zdx = dx / zoom;
1598     gdouble zdy = dy / zoom;
1600     if (!nodepath) return;
1602     sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1604     if (dx == 0) {
1605         sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1606     } else if (dy == 0) {
1607         sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1608     } else {
1609         sp_nodepath_update_repr(nodepath, _("Move nodes"));
1610     }
1613 /**
1614  * Move selected nodes to the absolute position given
1615  */
1616 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1618     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1619         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1620         NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1621         sp_node_moveto(n, npos);
1622     }
1624     sp_nodepath_update_repr(nodepath, _("Move nodes"));
1627 /**
1628  * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1629  */
1630 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1632     NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1633     g_return_val_if_fail(nodepath->selected, no_coord);
1635     // determine coordinate of first selected node
1636     GList *nsel = nodepath->selected;
1637     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1638     NR::Coord coord = n->pos[axis];
1639     bool coincide = true;
1641     // compare it to the coordinates of all the other selected nodes
1642     for (GList *l = nsel->next; l != NULL; l = l->next) {
1643         n = (Inkscape::NodePath::Node *) l->data;
1644         if (n->pos[axis] != coord) {
1645             coincide = false;
1646         }
1647     }
1648     if (coincide) {
1649         return coord;
1650     } else {
1651         NR::Rect bbox = sp_node_selected_bbox(nodepath);
1652         // currently we return the coordinate of the bounding box midpoint because I don't know how
1653         // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1654         return bbox.midpoint()[axis];
1655     }
1658 /** If they don't yet exist, creates knot and line for the given side of the node */
1659 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1661     if (!side->knot) {
1662         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"));
1664         side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1665         side->knot->setSize (7);
1666         side->knot->setAnchor (GTK_ANCHOR_CENTER);
1667         side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1668         side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1669         sp_knot_update_ctrl(side->knot);
1671         g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1672         g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1673         g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1674         g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1675         g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1676         g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1677     }
1679     if (!side->line) {
1680         side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1681                                         SP_TYPE_CTRLLINE, NULL);
1682     }
1685 /**
1686  * Ensure the given handle of the node is visible/invisible, update its screen position
1687  */
1688 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1690     g_assert(node != NULL);
1692    Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1693     NRPathcode code = sp_node_path_code_from_side(node, side);
1695     show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1697     if (show_handle) {
1698         if (!side->knot) { // No handle knot at all
1699             sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1700             // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1701             side->knot->pos = side->pos;
1702             if (side->knot->item)
1703                 SP_CTRL(side->knot->item)->moveto(side->pos);
1704             sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1705             sp_knot_show(side->knot);
1706         } else {
1707             if (side->knot->pos != side->pos) { // only if it's really moved
1708                 if (fire_move_signals) {
1709                     sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1710                 } else {
1711                     sp_knot_moveto(side->knot, &side->pos);
1712                     sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1713                 }
1714             }
1715             if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1716                 sp_knot_show(side->knot);
1717             }
1718         }
1719         sp_canvas_item_show(side->line);
1720     } else {
1721         if (side->knot) {
1722             if (SP_KNOT_IS_VISIBLE(side->knot)) {
1723                 sp_knot_hide(side->knot);
1724             }
1725         }
1726         if (side->line) {
1727             sp_canvas_item_hide(side->line);
1728         }
1729     }
1732 /**
1733  * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1734  * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1735  * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1736  * updated; otherwise, just move the knots silently (used in batch moves).
1737  */
1738 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1740     g_assert(node != NULL);
1742     if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1743         sp_knot_show(node->knot);
1744     }
1746     if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1747         if (fire_move_signals)
1748             sp_knot_set_position(node->knot, &node->pos, 0);
1749         else
1750             sp_knot_moveto(node->knot, &node->pos);
1751     }
1753     gboolean show_handles = node->selected;
1754     if (node->p.other != NULL) {
1755         if (node->p.other->selected) show_handles = TRUE;
1756     }
1757     if (node->n.other != NULL) {
1758         if (node->n.other->selected) show_handles = TRUE;
1759     }
1761     if (node->subpath->nodepath->show_handles == false)
1762         show_handles = FALSE;
1764     sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1765     sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1768 /**
1769  * Call sp_node_update_handles() for all nodes on subpath.
1770  */
1771 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1773     g_assert(subpath != NULL);
1775     for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1776         sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1777     }
1780 /**
1781  * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1782  */
1783 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1785     g_assert(nodepath != NULL);
1787     for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1788         sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1789     }
1792 void
1793 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1795     if (nodepath == NULL) return;
1797     nodepath->show_handles = show;
1798     sp_nodepath_update_handles(nodepath);
1801 /**
1802  * Adds all selected nodes in nodepath to list.
1803  */
1804 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1806     StlConv<Node *>::list(l, selected);
1807 /// \todo this adds a copying, rework when the selection becomes a stl list
1810 /**
1811  * Align selected nodes on the specified axis.
1812  */
1813 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1815     if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1816         return;
1817     }
1819     if ( !nodepath->selected->next ) { // only one node selected
1820         return;
1821     }
1822    Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1823     NR::Point dest(pNode->pos);
1824     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1825         pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1826         if (pNode) {
1827             dest[axis] = pNode->pos[axis];
1828             sp_node_moveto(pNode, dest);
1829         }
1830     }
1832     sp_nodepath_update_repr(nodepath, _("Align nodes"));
1835 /// Helper struct.
1836 struct NodeSort
1838    Inkscape::NodePath::Node *_node;
1839     NR::Coord _coord;
1840     /// \todo use vectorof pointers instead of calling copy ctor
1841     NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1842         _node(node), _coord(node->pos[axis])
1843     {}
1845 };
1847 static bool operator<(NodeSort const &a, NodeSort const &b)
1849     return (a._coord < b._coord);
1852 /**
1853  * Distribute selected nodes on the specified axis.
1854  */
1855 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1857     if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1858         return;
1859     }
1861     if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1862         return;
1863     }
1865    Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1866     std::vector<NodeSort> sorted;
1867     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1868         pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1869         if (pNode) {
1870             NodeSort n(pNode, axis);
1871             sorted.push_back(n);
1872             //dest[axis] = pNode->pos[axis];
1873             //sp_node_moveto(pNode, dest);
1874         }
1875     }
1876     std::sort(sorted.begin(), sorted.end());
1877     unsigned int len = sorted.size();
1878     //overall bboxes span
1879     float dist = (sorted.back()._coord -
1880                   sorted.front()._coord);
1881     //new distance between each bbox
1882     float step = (dist) / (len - 1);
1883     float pos = sorted.front()._coord;
1884     for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1885           it < sorted.end();
1886           it ++ )
1887     {
1888         NR::Point dest((*it)._node->pos);
1889         dest[axis] = pos;
1890         sp_node_moveto((*it)._node, dest);
1891         pos += step;
1892     }
1894     sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1898 /**
1899  * Call sp_nodepath_line_add_node() for all selected segments.
1900  */
1901 void
1902 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1904     if (!nodepath) {
1905         return;
1906     }
1908     GList *nl = NULL;
1910     int n_added = 0;
1912     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1913        Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1914         g_assert(t->selected);
1915         if (t->p.other && t->p.other->selected) {
1916             nl = g_list_prepend(nl, t);
1917         }
1918     }
1920     while (nl) {
1921        Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1922        Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1923        sp_nodepath_node_select(n, TRUE, FALSE);
1924        n_added ++;
1925        nl = g_list_remove(nl, t);
1926     }
1928     /** \todo fixme: adjust ? */
1929     sp_nodepath_update_handles(nodepath);
1931     if (n_added > 1) {
1932         sp_nodepath_update_repr(nodepath, _("Add nodes"));
1933     } else if (n_added > 0) {
1934         sp_nodepath_update_repr(nodepath, _("Add node"));
1935     }
1937     sp_nodepath_update_statusbar(nodepath);
1940 /**
1941  * Select segment nearest to point
1942  */
1943 void
1944 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1946     if (!nodepath) {
1947         return;
1948     }
1950     sp_nodepath_ensure_livarot_path(nodepath);
1951     NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1952     if (!maybe_position) {
1953         return;
1954     }
1955     Path::cut_position position = *maybe_position;
1957     //find segment to segment
1958     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1960     //fixme: this can return NULL, so check before proceeding.
1961     g_return_if_fail(e != NULL);
1963     gboolean force = FALSE;
1964     if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1965         force = TRUE;
1966     }
1967     sp_nodepath_node_select(e, (gboolean) toggle, force);
1968     if (e->p.other)
1969         sp_nodepath_node_select(e->p.other, TRUE, force);
1971     sp_nodepath_update_handles(nodepath);
1973     sp_nodepath_update_statusbar(nodepath);
1976 /**
1977  * Add a node nearest to point
1978  */
1979 void
1980 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1982     if (!nodepath) {
1983         return;
1984     }
1986     sp_nodepath_ensure_livarot_path(nodepath);
1987     NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1988     if (!maybe_position) {
1989         return;
1990     }
1991     Path::cut_position position = *maybe_position;
1993     //find segment to split
1994     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1996     //don't know why but t seems to flip for lines
1997     if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1998         position.t = 1.0 - position.t;
1999     }
2000     Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
2001     sp_nodepath_node_select(n, FALSE, TRUE);
2003     /* fixme: adjust ? */
2004     sp_nodepath_update_handles(nodepath);
2006     sp_nodepath_update_repr(nodepath, _("Add node"));
2008     sp_nodepath_update_statusbar(nodepath);
2011 /*
2012  * Adjusts a segment so that t moves by a certain delta for dragging
2013  * converts lines to curves
2014  *
2015  * method and idea borrowed from Simon Budig  <simon@gimp.org> and the GIMP
2016  * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2017  */
2018 void
2019 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2021     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2023     //fixme: e and e->p can be NULL, so check for those before proceeding
2024     g_return_if_fail(e != NULL);
2025     g_return_if_fail(&e->p != NULL);
2027     /* feel good is an arbitrary parameter that distributes the delta between handles
2028      * if t of the drag point is less than 1/6 distance form the endpoint only
2029      * the corresponding hadle is adjusted. This matches the behavior in GIMP
2030      */
2031     double feel_good;
2032     if (t <= 1.0 / 6.0)
2033         feel_good = 0;
2034     else if (t <= 0.5)
2035         feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2036     else if (t <= 5.0 / 6.0)
2037         feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2038     else
2039         feel_good = 1;
2041     //if we're dragging a line convert it to a curve
2042     if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2043         sp_nodepath_set_line_type(e, NR_CURVETO);
2044     }
2046     NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2047     NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2048     e->p.other->n.pos += offsetcoord0;
2049     e->p.pos += offsetcoord1;
2051     // adjust handles of adjacent nodes where necessary
2052     sp_node_adjust_handle(e,1);
2053     sp_node_adjust_handle(e->p.other,-1);
2055     sp_nodepath_update_handles(e->subpath->nodepath);
2057     update_object(e->subpath->nodepath);
2059     sp_nodepath_update_statusbar(e->subpath->nodepath);
2063 /**
2064  * Call sp_nodepath_break() for all selected segments.
2065  */
2066 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2068     if (!nodepath) return;
2070     GList *tempin = g_list_copy(nodepath->selected);
2071     GList *temp = NULL;
2072     for (GList *l = tempin; l != NULL; l = l->next) {
2073        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2074        Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2075         if (nn == NULL) continue; // no break, no new node
2076         temp = g_list_prepend(temp, nn);
2077     }
2078     g_list_free(tempin);
2080     if (temp) {
2081         sp_nodepath_deselect(nodepath);
2082     }
2083     for (GList *l = temp; l != NULL; l = l->next) {
2084         sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2085     }
2087     sp_nodepath_update_handles(nodepath);
2089     sp_nodepath_update_repr(nodepath, _("Break path"));
2092 /**
2093  * Duplicate the selected node(s).
2094  */
2095 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2097     if (!nodepath) {
2098         return;
2099     }
2101     GList *temp = NULL;
2102     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2103        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2104        Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2105         if (nn == NULL) continue; // could not duplicate
2106         temp = g_list_prepend(temp, nn);
2107     }
2109     if (temp) {
2110         sp_nodepath_deselect(nodepath);
2111     }
2112     for (GList *l = temp; l != NULL; l = l->next) {
2113         sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2114     }
2116     sp_nodepath_update_handles(nodepath);
2118     sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2121 /**
2122  *  Internal function to join two nodes by merging them into one.
2123  */
2124 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2126     /* a and b are endpoints */
2128     // if one of the two nodes is mouseovered, fix its position
2129     NR::Point c;
2130     if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2131         c = a->pos;
2132     } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2133         c = b->pos;
2134     } else {
2135         // otherwise, move joined node to the midpoint
2136         c = (a->pos + b->pos) / 2;
2137     }
2139     if (a->subpath == b->subpath) {
2140        Inkscape::NodePath::SubPath *sp = a->subpath;
2141         sp_nodepath_subpath_close(sp);
2142         sp_node_moveto (sp->first, c);
2144         sp_nodepath_update_handles(sp->nodepath);
2145         sp_nodepath_update_repr(nodepath, _("Close subpath"));
2146         return;
2147     }
2149     /* a and b are separate subpaths */
2150     Inkscape::NodePath::SubPath *sa = a->subpath;
2151     Inkscape::NodePath::SubPath *sb = b->subpath;
2152     NR::Point p;
2153     Inkscape::NodePath::Node *n;
2154     NRPathcode code;
2155     if (a == sa->first) {
2156         // we will now reverse sa, so that a is its last node, not first, and drop that node
2157         p = sa->first->n.pos;
2158         code = (NRPathcode)sa->first->n.other->code;
2159         // create new subpath
2160        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2161        // create a first moveto node on it
2162         n = sa->last;
2163         sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2164         n = n->p.other;
2165         if (n == sa->first) n = NULL;
2166         while (n) {
2167             // copy the rest of the nodes from sa to t, going backwards
2168             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2169             n = n->p.other;
2170             if (n == sa->first) n = NULL;
2171         }
2172         // replace sa with t
2173         sp_nodepath_subpath_destroy(sa);
2174         sa = t;
2175     } else if (a == sa->last) {
2176         // a is already last, just drop it
2177         p = sa->last->p.pos;
2178         code = (NRPathcode)sa->last->code;
2179         sp_nodepath_node_destroy(sa->last);
2180     } else {
2181         code = NR_END;
2182         g_assert_not_reached();
2183     }
2185     if (b == sb->first) {
2186         // copy all nodes from b to a, forward 
2187         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2188         for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2189             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2190         }
2191     } else if (b == sb->last) {
2192         // copy all nodes from b to a, backward 
2193         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2194         for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2195             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2196         }
2197     } else {
2198         g_assert_not_reached();
2199     }
2200     /* and now destroy sb */
2202     sp_nodepath_subpath_destroy(sb);
2204     sp_nodepath_update_handles(sa->nodepath);
2206     sp_nodepath_update_repr(nodepath, _("Join nodes"));
2208     sp_nodepath_update_statusbar(nodepath);
2211 /**
2212  *  Internal function to join two nodes by adding a segment between them.
2213  */
2214 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2216     if (a->subpath == b->subpath) {
2217        Inkscape::NodePath::SubPath *sp = a->subpath;
2219         /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2220         sp->closed = TRUE;
2222         sp->first->p.other = sp->last;
2223         sp->last->n.other  = sp->first;
2225         sp_node_handle_mirror_p_to_n(sp->last);
2226         sp_node_handle_mirror_n_to_p(sp->first);
2228         sp->first->code = sp->last->code;
2229         sp->first       = sp->last;
2231         sp_nodepath_update_handles(sp->nodepath);
2233         sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2235         return;
2236     }
2238     /* a and b are separate subpaths */
2239     Inkscape::NodePath::SubPath *sa = a->subpath;
2240     Inkscape::NodePath::SubPath *sb = b->subpath;
2242     Inkscape::NodePath::Node *n;
2243     NR::Point p;
2244     NRPathcode code;
2245     if (a == sa->first) {
2246         code = (NRPathcode) sa->first->n.other->code;
2247        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2248         n = sa->last;
2249         sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2250         for (n = n->p.other; n != NULL; n = n->p.other) {
2251             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2252         }
2253         sp_nodepath_subpath_destroy(sa);
2254         sa = t;
2255     } else if (a == sa->last) {
2256         code = (NRPathcode)sa->last->code;
2257     } else {
2258         code = NR_END;
2259         g_assert_not_reached();
2260     }
2262     if (b == sb->first) {
2263         n = sb->first;
2264         sp_node_handle_mirror_p_to_n(sa->last);
2265         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2266         sp_node_handle_mirror_n_to_p(sa->last);
2267         for (n = n->n.other; n != NULL; n = n->n.other) {
2268             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2269         }
2270     } else if (b == sb->last) {
2271         n = sb->last;
2272         sp_node_handle_mirror_p_to_n(sa->last);
2273         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2274         sp_node_handle_mirror_n_to_p(sa->last);
2275         for (n = n->p.other; n != NULL; n = n->p.other) {
2276             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2277         }
2278     } else {
2279         g_assert_not_reached();
2280     }
2281     /* and now destroy sb */
2283     sp_nodepath_subpath_destroy(sb);
2285     sp_nodepath_update_handles(sa->nodepath);
2287     sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2290 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2292 /**
2293  * Internal function to handle joining two nodes.
2294  */
2295 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2297     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2299     if (g_list_length(nodepath->selected) != 2) {
2300         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2301         return;
2302     }
2304     Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2305     Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2307     g_assert(a != b);
2308     if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2309         // someone tried to join an orphan node (i.e. a single-node subpath).
2310         // this is not worth an error message, just fail silently.
2311         return;
2312     }
2314     if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2315         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2316         return;
2317     }
2319     switch(mode) {
2320         case NODE_JOIN_ENDPOINTS:
2321             do_node_selected_join(nodepath, a, b);
2322             break;
2323         case NODE_JOIN_SEGMENT:
2324             do_node_selected_join_segment(nodepath, a, b);
2325             break;
2326     }
2329 /**
2330  *  Join two nodes by merging them into one.
2331  */
2332 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2334     node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2337 /**
2338  *  Join two nodes by adding a segment between them.
2339  */
2340 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2342     node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2345 /**
2346  * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2347  */
2348 void sp_node_delete_preserve(GList *nodes_to_delete)
2350     GSList *nodepaths = NULL;
2352     while (nodes_to_delete) {
2353         Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2354         Inkscape::NodePath::SubPath *sp = node->subpath;
2355         Inkscape::NodePath::Path *nodepath = sp->nodepath;
2356         Inkscape::NodePath::Node *sample_cursor = NULL;
2357         Inkscape::NodePath::Node *sample_end = NULL;
2358         Inkscape::NodePath::Node *delete_cursor = node;
2359         bool just_delete = false;
2361         //find the start of this contiguous selection
2362         //move left to the first node that is not selected
2363         //or the start of the non-closed path
2364         for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2365             delete_cursor = curr;
2366         }
2368         //just delete at the beginning of an open path
2369         if (!delete_cursor->p.other) {
2370             sample_cursor = delete_cursor;
2371             just_delete = true;
2372         } else {
2373             sample_cursor = delete_cursor->p.other;
2374         }
2376         //calculate points for each segment
2377         int rate = 5;
2378         float period = 1.0 / rate;
2379         std::vector<NR::Point> data;
2380         if (!just_delete) {
2381             data.push_back(sample_cursor->pos);
2382             for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2383                 //just delete at the end of an open path
2384                 if (!sp->closed && curr == sp->last) {
2385                     just_delete = true;
2386                     break;
2387                 }
2389                 //sample points on the contiguous selected segment
2390                 NR::Point *bez;
2391                 bez = new NR::Point [4];
2392                 bez[0] = curr->pos;
2393                 bez[1] = curr->n.pos;
2394                 bez[2] = curr->n.other->p.pos;
2395                 bez[3] = curr->n.other->pos;
2396                 for (int i=1; i<rate; i++) {
2397                     gdouble t = i * period;
2398                     NR::Point p = bezier_pt(3, bez, t);
2399                     data.push_back(p);
2400                 }
2401                 data.push_back(curr->n.other->pos);
2403                 sample_end = curr->n.other;
2404                 //break if we've come full circle or hit the end of the selection
2405                 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2406                     break;
2407                 }
2408             }
2409         }
2411         if (!just_delete) {
2412             //calculate the best fitting single segment and adjust the endpoints
2413             NR::Point *adata;
2414             adata = new NR::Point [data.size()];
2415             copy(data.begin(), data.end(), adata);
2417             NR::Point *bez;
2418             bez = new NR::Point [4];
2419             //would decreasing error create a better fitting approximation?
2420             gdouble error = 1.0;
2421             gint ret;
2422             ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2424             //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2425             //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2426             //the resulting nodes behave as expected.
2427             if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2428                 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2429             if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2430                 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2432             //adjust endpoints
2433             sample_cursor->n.pos = bez[1];
2434             sample_end->p.pos = bez[2];
2435         }
2437         //destroy this contiguous selection
2438         while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2439             Inkscape::NodePath::Node *temp = delete_cursor;
2440             if (delete_cursor->n.other == delete_cursor) {
2441                 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2442                 delete_cursor = NULL;
2443             } else {
2444                 delete_cursor = delete_cursor->n.other;
2445             }
2446             nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2447             sp_nodepath_node_destroy(temp);
2448         }
2450         sp_nodepath_update_handles(nodepath);
2452         if (!g_slist_find(nodepaths, nodepath))
2453             nodepaths = g_slist_prepend (nodepaths, nodepath);
2454     }
2456     for (GSList *i = nodepaths; i; i = i->next) {
2457         // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2458         // different nodepaths will give us one undo event per nodepath
2459         Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2461         // if the entire nodepath is removed, delete the selected object.
2462         if (nodepath->subpaths == NULL ||
2463             //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2464             //at least 2
2465             sp_nodepath_get_node_count(nodepath) < 2) {
2466             SPDocument *document = sp_desktop_document (nodepath->desktop);
2467             //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2468             //delete this nodepath's object, not the entire selection! (though at this time, this
2469             //does not matter)
2470             sp_selection_delete();
2471             sp_document_done (document, SP_VERB_CONTEXT_NODE,
2472                               _("Delete nodes"));
2473         } else {
2474             sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2475             sp_nodepath_update_statusbar(nodepath);
2476         }
2477     }
2479     g_slist_free (nodepaths);
2482 /**
2483  * Delete one or more selected nodes.
2484  */
2485 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2487     if (!nodepath) return;
2488     if (!nodepath->selected) return;
2490     /** \todo fixme: do it the right way */
2491     while (nodepath->selected) {
2492        Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2493         sp_nodepath_node_destroy(node);
2494     }
2497     //clean up the nodepath (such as for trivial subpaths)
2498     sp_nodepath_cleanup(nodepath);
2500     sp_nodepath_update_handles(nodepath);
2502     // if the entire nodepath is removed, delete the selected object.
2503     if (nodepath->subpaths == NULL ||
2504         sp_nodepath_get_node_count(nodepath) < 2) {
2505         SPDocument *document = sp_desktop_document (nodepath->desktop);
2506         sp_selection_delete();
2507         sp_document_done (document, SP_VERB_CONTEXT_NODE,
2508                           _("Delete nodes"));
2509         return;
2510     }
2512     sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2514     sp_nodepath_update_statusbar(nodepath);
2517 /**
2518  * Delete one or more segments between two selected nodes.
2519  * This is the code for 'split'.
2520  */
2521 void
2522 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2524    Inkscape::NodePath::Node *start, *end;     //Start , end nodes.  not inclusive
2525    Inkscape::NodePath::Node *curr, *next;     //Iterators
2527     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2529     if (g_list_length(nodepath->selected) != 2) {
2530         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2531                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2532         return;
2533     }
2535     //Selected nodes, not inclusive
2536    Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2537    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2539     if ( ( a==b)                       ||  //same node
2540          (a->subpath  != b->subpath )  ||  //not the same path
2541          (!a->p.other || !a->n.other)  ||  //one of a's sides does not have a segment
2542          (!b->p.other || !b->n.other) )    //one of b's sides does not have a segment
2543     {
2544         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2545                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2546         return;
2547     }
2549     //###########################################
2550     //# BEGIN EDITS
2551     //###########################################
2552     //##################################
2553     //# CLOSED PATH
2554     //##################################
2555     if (a->subpath->closed) {
2558         gboolean reversed = FALSE;
2560         //Since we can go in a circle, we need to find the shorter distance.
2561         //  a->b or b->a
2562         start = end = NULL;
2563         int distance    = 0;
2564         int minDistance = 0;
2565         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2566             if (curr==b) {
2567                 //printf("a to b:%d\n", distance);
2568                 start = a;//go from a to b
2569                 end   = b;
2570                 minDistance = distance;
2571                 //printf("A to B :\n");
2572                 break;
2573             }
2574             distance++;
2575         }
2577         //try again, the other direction
2578         distance = 0;
2579         for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2580             if (curr==a) {
2581                 //printf("b to a:%d\n", distance);
2582                 if (distance < minDistance) {
2583                     start    = b;  //we go from b to a
2584                     end      = a;
2585                     reversed = TRUE;
2586                     //printf("B to A\n");
2587                 }
2588                 break;
2589             }
2590             distance++;
2591         }
2594         //Copy everything from 'end' to 'start' to a new subpath
2595        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2596         for (curr=end ; curr ; curr=curr->n.other) {
2597             NRPathcode code = (NRPathcode) curr->code;
2598             if (curr == end)
2599                 code = NR_MOVETO;
2600             sp_nodepath_node_new(t, NULL,
2601                                  (Inkscape::NodePath::NodeType)curr->type, code,
2602                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2603             if (curr == start)
2604                 break;
2605         }
2606         sp_nodepath_subpath_destroy(a->subpath);
2609     }
2613     //##################################
2614     //# OPEN PATH
2615     //##################################
2616     else {
2618         //We need to get the direction of the list between A and B
2619         //Can we walk from a to b?
2620         start = end = NULL;
2621         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2622             if (curr==b) {
2623                 start = a;  //did it!  we go from a to b
2624                 end   = b;
2625                 //printf("A to B\n");
2626                 break;
2627             }
2628         }
2629         if (!start) {//didn't work?  let's try the other direction
2630             for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2631                 if (curr==a) {
2632                     start = b;  //did it!  we go from b to a
2633                     end   = a;
2634                     //printf("B to A\n");
2635                     break;
2636                 }
2637             }
2638         }
2639         if (!start) {
2640             nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2641                                                      _("Cannot find path between nodes."));
2642             return;
2643         }
2647         //Copy everything after 'end' to a new subpath
2648        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2649         for (curr=end ; curr ; curr=curr->n.other) {
2650             NRPathcode code = (NRPathcode) curr->code;
2651             if (curr == end)
2652                 code = NR_MOVETO;
2653             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2654                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2655         }
2657         //Now let us do our deletion.  Since the tail has been saved, go all the way to the end of the list
2658         for (curr = start->n.other ; curr  ; curr=next) {
2659             next = curr->n.other;
2660             sp_nodepath_node_destroy(curr);
2661         }
2663     }
2664     //###########################################
2665     //# END EDITS
2666     //###########################################
2668     //clean up the nodepath (such as for trivial subpaths)
2669     sp_nodepath_cleanup(nodepath);
2671     sp_nodepath_update_handles(nodepath);
2673     sp_nodepath_update_repr(nodepath, _("Delete segment"));
2675     sp_nodepath_update_statusbar(nodepath);
2678 /**
2679  * Call sp_nodepath_set_line() for all selected segments.
2680  */
2681 void
2682 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2684     if (nodepath == NULL) return;
2686     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2687        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2688         g_assert(n->selected);
2689         if (n->p.other && n->p.other->selected) {
2690             sp_nodepath_set_line_type(n, code);
2691         }
2692     }
2694     sp_nodepath_update_repr(nodepath, _("Change segment type"));
2697 /**
2698  * Call sp_nodepath_convert_node_type() for all selected nodes.
2699  */
2700 void
2701 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2703     if (nodepath == NULL) return;
2705     if (nodepath->straight_path) return; // don't change type when it is a straight path!
2707     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2708         sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2709     }
2711     sp_nodepath_update_repr(nodepath, _("Change node type"));
2714 /**
2715  * Change select status of node, update its own and neighbour handles.
2716  */
2717 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2719     node->selected = selected;
2721     if (selected) {
2722         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2723         node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2724         node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2725         sp_knot_update_ctrl(node->knot);
2726     } else {
2727         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2728         node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2729         node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2730         sp_knot_update_ctrl(node->knot);
2731     }
2733     sp_node_update_handles(node);
2734     if (node->n.other) sp_node_update_handles(node->n.other);
2735     if (node->p.other) sp_node_update_handles(node->p.other);
2738 /**
2739 \brief Select a node
2740 \param node     The node to select
2741 \param incremental   If true, add to selection, otherwise deselect others
2742 \param override   If true, always select this node, otherwise toggle selected status
2743 */
2744 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2746     Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2748     if (incremental) {
2749         if (override) {
2750             if (!g_list_find(nodepath->selected, node)) {
2751                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2752             }
2753             sp_node_set_selected(node, TRUE);
2754         } else { // toggle
2755             if (node->selected) {
2756                 g_assert(g_list_find(nodepath->selected, node));
2757                 nodepath->selected = g_list_remove(nodepath->selected, node);
2758             } else {
2759                 g_assert(!g_list_find(nodepath->selected, node));
2760                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2761             }
2762             sp_node_set_selected(node, !node->selected);
2763         }
2764     } else {
2765         sp_nodepath_deselect(nodepath);
2766         nodepath->selected = g_list_prepend(nodepath->selected, node);
2767         sp_node_set_selected(node, TRUE);
2768     }
2770     sp_nodepath_update_statusbar(nodepath);
2774 /**
2775 \brief Deselect all nodes in the nodepath
2776 */
2777 void
2778 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2780     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2782     while (nodepath->selected) {
2783         sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2784         nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2785     }
2786     sp_nodepath_update_statusbar(nodepath);
2789 /**
2790 \brief Select or invert selection of all nodes in the nodepath
2791 */
2792 void
2793 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2795     if (!nodepath) return;
2797     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2798        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2799         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2800            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2801            sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2802         }
2803     }
2806 /**
2807  * If nothing selected, does the same as sp_nodepath_select_all();
2808  * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2809  * (i.e., similar to "select all in layer", with the "selected" subpaths
2810  * being treated as "layers" in the path).
2811  */
2812 void
2813 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2815     if (!nodepath) return;
2817     if (g_list_length (nodepath->selected) == 0) {
2818         sp_nodepath_select_all (nodepath, invert);
2819         return;
2820     }
2822     GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2823     GSList *subpaths = NULL;
2825     for (GList *l = copy; l != NULL; l = l->next) {
2826         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2827         Inkscape::NodePath::SubPath *subpath = n->subpath;
2828         if (!g_slist_find (subpaths, subpath))
2829             subpaths = g_slist_prepend (subpaths, subpath);
2830     }
2832     for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2833         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2834         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2835             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2836             sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2837         }
2838     }
2840     g_slist_free (subpaths);
2841     g_list_free (copy);
2844 /**
2845  * \brief Select the node after the last selected; if none is selected,
2846  * select the first within path.
2847  */
2848 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2850     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2852    Inkscape::NodePath::Node *last = NULL;
2853     if (nodepath->selected) {
2854         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2855            Inkscape::NodePath::SubPath *subpath, *subpath_next;
2856             subpath = (Inkscape::NodePath::SubPath *) spl->data;
2857             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2858                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2859                 if (node->selected) {
2860                     if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2861                         if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2862                             if (spl->next) { // there's a next subpath
2863                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2864                                 last = subpath_next->first;
2865                             } else if (spl->prev) { // there's a previous subpath
2866                                 last = NULL; // to be set later to the first node of first subpath
2867                             } else {
2868                                 last = node->n.other;
2869                             }
2870                         } else {
2871                             last = node->n.other;
2872                         }
2873                     } else {
2874                         if (node->n.other) {
2875                             last = node->n.other;
2876                         } else {
2877                             if (spl->next) { // there's a next subpath
2878                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2879                                 last = subpath_next->first;
2880                             } else if (spl->prev) { // there's a previous subpath
2881                                 last = NULL; // to be set later to the first node of first subpath
2882                             } else {
2883                                 last = (Inkscape::NodePath::Node *) subpath->first;
2884                             }
2885                         }
2886                     }
2887                 }
2888             }
2889         }
2890         sp_nodepath_deselect(nodepath);
2891     }
2893     if (last) { // there's at least one more node after selected
2894         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2895     } else { // no more nodes, select the first one in first subpath
2896        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2897         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2898     }
2901 /**
2902  * \brief Select the node before the first selected; if none is selected,
2903  * select the last within path
2904  */
2905 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2907     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2909    Inkscape::NodePath::Node *last = NULL;
2910     if (nodepath->selected) {
2911         for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2912            Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2913             for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2914                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2915                 if (node->selected) {
2916                     if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2917                         if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2918                             if (spl->prev) { // there's a prev subpath
2919                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2920                                 last = subpath_prev->last;
2921                             } else if (spl->next) { // there's a next subpath
2922                                 last = NULL; // to be set later to the last node of last subpath
2923                             } else {
2924                                 last = node->p.other;
2925                             }
2926                         } else {
2927                             last = node->p.other;
2928                         }
2929                     } else {
2930                         if (node->p.other) {
2931                             last = node->p.other;
2932                         } else {
2933                             if (spl->prev) { // there's a prev subpath
2934                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2935                                 last = subpath_prev->last;
2936                             } else if (spl->next) { // there's a next subpath
2937                                 last = NULL; // to be set later to the last node of last subpath
2938                             } else {
2939                                 last = (Inkscape::NodePath::Node *) subpath->last;
2940                             }
2941                         }
2942                     }
2943                 }
2944             }
2945         }
2946         sp_nodepath_deselect(nodepath);
2947     }
2949     if (last) { // there's at least one more node before selected
2950         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2951     } else { // no more nodes, select the last one in last subpath
2952         GList *spl = g_list_last(nodepath->subpaths);
2953        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2954         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2955     }
2958 /**
2959  * \brief Select all nodes that are within the rectangle.
2960  */
2961 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2963     if (!incremental) {
2964         sp_nodepath_deselect(nodepath);
2965     }
2967     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2968        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2969         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2970            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2972             if (b.contains(node->pos)) {
2973                 sp_nodepath_node_select(node, TRUE, TRUE);
2974             }
2975         }
2976     }
2980 void
2981 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2983     g_assert (n);
2984     g_assert (nodepath);
2985     g_assert (n->subpath->nodepath == nodepath);
2987     if (g_list_length (nodepath->selected) == 0) {
2988         if (grow > 0) {
2989             sp_nodepath_node_select(n, TRUE, TRUE);
2990         }
2991         return;
2992     }
2994     if (g_list_length (nodepath->selected) == 1) {
2995         if (grow < 0) {
2996             sp_nodepath_deselect (nodepath);
2997             return;
2998         }
2999     }
3001         double n_sel_range = 0, p_sel_range = 0;
3002             Inkscape::NodePath::Node *farthest_n_node = n;
3003             Inkscape::NodePath::Node *farthest_p_node = n;
3005         // Calculate ranges
3006         {
3007             double n_range = 0, p_range = 0;
3008             bool n_going = true, p_going = true;
3009             Inkscape::NodePath::Node *n_node = n;
3010             Inkscape::NodePath::Node *p_node = n;
3011             do {
3012                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3013                 if (n_node && n_going)
3014                     n_node = n_node->n.other;
3015                 if (n_node == NULL) {
3016                     n_going = false;
3017                 } else {
3018                     n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3019                     if (n_node->selected) {
3020                         n_sel_range = n_range;
3021                         farthest_n_node = n_node;
3022                     }
3023                     if (n_node == p_node) {
3024                         n_going = false;
3025                         p_going = false;
3026                     }
3027                 }
3028                 if (p_node && p_going)
3029                     p_node = p_node->p.other;
3030                 if (p_node == NULL) {
3031                     p_going = false;
3032                 } else {
3033                     p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3034                     if (p_node->selected) {
3035                         p_sel_range = p_range;
3036                         farthest_p_node = p_node;
3037                     }
3038                     if (p_node == n_node) {
3039                         n_going = false;
3040                         p_going = false;
3041                     }
3042                 }
3043             } while (n_going || p_going);
3044         }
3046     if (grow > 0) {
3047         if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3048                 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3049         } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3050                 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3051         }
3052     } else {
3053         if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3054                 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3055         } else if (farthest_p_node && farthest_p_node->selected) {
3056                 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3057         }
3058     }
3061 void
3062 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3064     g_assert (n);
3065     g_assert (nodepath);
3066     g_assert (n->subpath->nodepath == nodepath);
3068     if (g_list_length (nodepath->selected) == 0) {
3069         if (grow > 0) {
3070             sp_nodepath_node_select(n, TRUE, TRUE);
3071         }
3072         return;
3073     }
3075     if (g_list_length (nodepath->selected) == 1) {
3076         if (grow < 0) {
3077             sp_nodepath_deselect (nodepath);
3078             return;
3079         }
3080     }
3082     Inkscape::NodePath::Node *farthest_selected = NULL;
3083     double farthest_dist = 0;
3085     Inkscape::NodePath::Node *closest_unselected = NULL;
3086     double closest_dist = NR_HUGE;
3088     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3089        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3090         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3091            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3092            if (node == n)
3093                continue;
3094            if (node->selected) {
3095                if (NR::L2(node->pos - n->pos) > farthest_dist) {
3096                    farthest_dist = NR::L2(node->pos - n->pos);
3097                    farthest_selected = node;
3098                }
3099            } else {
3100                if (NR::L2(node->pos - n->pos) < closest_dist) {
3101                    closest_dist = NR::L2(node->pos - n->pos);
3102                    closest_unselected = node;
3103                }
3104            }
3105         }
3106     }
3108     if (grow > 0) {
3109         if (closest_unselected) {
3110             sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3111         }
3112     } else {
3113         if (farthest_selected) {
3114             sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3115         }
3116     }
3120 /**
3121 \brief  Saves all nodes' and handles' current positions in their origin members
3122 */
3123 void
3124 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3126     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3127        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3128         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3129            Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3130            n->origin = n->pos;
3131            n->p.origin = n->p.pos;
3132            n->n.origin = n->n.pos;
3133         }
3134     }
3137 /**
3138 \brief  Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3139 */
3140 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3142     if (!nodepath->selected) {
3143         return NULL;
3144     }
3146     GList *r = NULL;
3147     guint i = 0;
3148     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3149        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3150         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3151            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3152             i++;
3153             if (node->selected) {
3154                 r = g_list_append(r, GINT_TO_POINTER(i));
3155             }
3156         }
3157     }
3158     return r;
3161 /**
3162 \brief  Restores selection by selecting nodes whose positions are in the list
3163 */
3164 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3166     sp_nodepath_deselect(nodepath);
3168     guint i = 0;
3169     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3170        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3171         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3172            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3173             i++;
3174             if (g_list_find(r, GINT_TO_POINTER(i))) {
3175                 sp_nodepath_node_select(node, TRUE, TRUE);
3176             }
3177         }
3178     }
3182 /**
3183 \brief Adjusts handle according to node type and line code.
3184 */
3185 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3187     g_assert(node);
3189    Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3190    Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3192    // nothing to do if we are an end node
3193     if (me->other == NULL) return;
3194     if (other->other == NULL) return;
3196     // nothing to do if we are a cusp node
3197     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3199     // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3200     NRPathcode mecode;
3201     if (which_adjust == 1) {
3202         mecode = (NRPathcode)me->other->code;
3203     } else {
3204         mecode = (NRPathcode)node->code;
3205     }
3206     if (mecode == NR_LINETO) return;
3208     if (sp_node_side_is_line(node, other)) {
3209         // other is a line, and we are either smooth or symm
3210        Inkscape::NodePath::Node *othernode = other->other;
3211         double len = NR::L2(me->pos - node->pos);
3212         NR::Point delta = node->pos - othernode->pos;
3213         double linelen = NR::L2(delta);
3214         if (linelen < 1e-18)
3215             return;
3216         me->pos = node->pos + (len / linelen)*delta;
3217         return;
3218     }
3220     if (node->type == Inkscape::NodePath::NODE_SYMM) {
3221         // symmetrize 
3222         me->pos = 2 * node->pos - other->pos;
3223         return;
3224     } else {
3225         // smoothify
3226         double len = NR::L2(me->pos - node->pos);
3227         NR::Point delta = other->pos - node->pos;
3228         double otherlen = NR::L2(delta);
3229         if (otherlen < 1e-18) return;
3230         me->pos = node->pos - (len / otherlen) * delta;
3231     }
3234 /**
3235  \brief Adjusts both handles according to node type and line code
3236  */
3237 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3239     g_assert(node);
3241     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3243     /* we are either smooth or symm */
3245     if (node->p.other == NULL) return;
3246     if (node->n.other == NULL) return;
3248     if (sp_node_side_is_line(node, &node->p)) {
3249         sp_node_adjust_handle(node, 1);
3250         return;
3251     }
3253     if (sp_node_side_is_line(node, &node->n)) {
3254         sp_node_adjust_handle(node, -1);
3255         return;
3256     }
3258     /* both are curves */
3259     NR::Point const delta( node->n.pos - node->p.pos );
3261     if (node->type == Inkscape::NodePath::NODE_SYMM) {
3262         node->p.pos = node->pos - delta / 2;
3263         node->n.pos = node->pos + delta / 2;
3264         return;
3265     }
3267     /* We are smooth */
3268     double plen = NR::L2(node->p.pos - node->pos);
3269     if (plen < 1e-18) return;
3270     double nlen = NR::L2(node->n.pos - node->pos);
3271     if (nlen < 1e-18) return;
3272     node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3273     node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3276 /**
3277  * Node event callback.
3278  */
3279 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3281     gboolean ret = FALSE;
3282     switch (event->type) {
3283         case GDK_ENTER_NOTIFY:
3284             Inkscape::NodePath::Path::active_node = n;
3285             break;
3286         case GDK_LEAVE_NOTIFY:
3287             Inkscape::NodePath::Path::active_node = NULL;
3288             break;
3289         case GDK_SCROLL:
3290             if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3291                 switch (event->scroll.direction) {
3292                     case GDK_SCROLL_UP:
3293                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3294                         break;
3295                     case GDK_SCROLL_DOWN:
3296                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3297                         break;
3298                     default:
3299                         break;
3300                 }
3301                 ret = TRUE;
3302             } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3303                 switch (event->scroll.direction) {
3304                     case GDK_SCROLL_UP:
3305                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3306                         break;
3307                     case GDK_SCROLL_DOWN:
3308                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3309                         break;
3310                     default:
3311                         break;
3312                 }
3313                 ret = TRUE;
3314             }
3315             break;
3316         case GDK_KEY_PRESS:
3317             switch (get_group0_keyval (&event->key)) {
3318                 case GDK_space:
3319                     if (event->key.state & GDK_BUTTON1_MASK) {
3320                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3321                         stamp_repr(nodepath);
3322                         ret = TRUE;
3323                     }
3324                     break;
3325                 case GDK_Page_Up:
3326                     if (event->key.state & GDK_CONTROL_MASK) {
3327                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3328                     } else {
3329                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3330                     }
3331                     break;
3332                 case GDK_Page_Down:
3333                     if (event->key.state & GDK_CONTROL_MASK) {
3334                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3335                     } else {
3336                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3337                     }
3338                     break;
3339                 default:
3340                     break;
3341             }
3342             break;
3343         default:
3344             break;
3345     }
3347     return ret;
3350 /**
3351  * Handle keypress on node; directly called.
3352  */
3353 gboolean node_key(GdkEvent *event)
3355     Inkscape::NodePath::Path *np;
3357     // there is no way to verify nodes so set active_node to nil when deleting!!
3358     if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3360     if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3361         gint ret = FALSE;
3362         switch (get_group0_keyval (&event->key)) {
3363             /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3364             case GDK_BackSpace:
3365                 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3366                 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3367                 sp_nodepath_update_repr(np, _("Delete node"));
3368                 Inkscape::NodePath::Path::active_node = NULL;
3369                 ret = TRUE;
3370                 break;
3371             case GDK_c:
3372                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3373                 ret = TRUE;
3374                 break;
3375             case GDK_s:
3376                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3377                 ret = TRUE;
3378                 break;
3379             case GDK_y:
3380                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3381                 ret = TRUE;
3382                 break;
3383             case GDK_b:
3384                 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3385                 ret = TRUE;
3386                 break;
3387         }
3388         return ret;
3389     }
3390     return FALSE;
3393 /**
3394  * Mouseclick on node callback.
3395  */
3396 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3398    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3400     if (state & GDK_CONTROL_MASK) {
3401         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3403         if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3404             if (n->type == Inkscape::NodePath::NODE_CUSP) {
3405                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3406             } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3407                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3408             } else {
3409                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3410             }
3411             sp_nodepath_update_repr(nodepath, _("Change node type"));
3412             sp_nodepath_update_statusbar(nodepath);
3414         } else { //ctrl+alt+click: delete node
3415             GList *node_to_delete = NULL;
3416             node_to_delete = g_list_append(node_to_delete, n);
3417             sp_node_delete_preserve(node_to_delete);
3418         }
3420     } else {
3421         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3422     }
3425 /**
3426  * Mouse grabbed node callback.
3427  */
3428 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3430    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3432     if (!n->selected) {
3433         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3434     }
3436     n->is_dragging = true;
3437     sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3439     sp_nodepath_remember_origins (n->subpath->nodepath);
3442 /**
3443  * Mouse ungrabbed node callback.
3444  */
3445 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3447    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3449    n->dragging_out = NULL;
3450    n->is_dragging = false;
3451    sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3453    sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3456 /**
3457  * The point on a line, given by its angle, closest to the given point.
3458  * \param p  A point.
3459  * \param a  Angle of the line; it is assumed to go through coordinate origin.
3460  * \param closest  Pointer to the point struct where the result is stored.
3461  * \todo FIXME: use dot product perhaps?
3462  */
3463 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3465     if (a == HUGE_VAL) { // vertical
3466         *closest = NR::Point(0, (*p)[NR::Y]);
3467     } else {
3468         (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3469         (*closest)[NR::Y] = a * (*closest)[NR::X];
3470     }
3473 /**
3474  * Distance from the point to a line given by its angle.
3475  * \param p  A point.
3476  * \param a  Angle of the line; it is assumed to go through coordinate origin.
3477  */
3478 static double point_line_distance(NR::Point *p, double a)
3480     NR::Point c;
3481     point_line_closest(p, a, &c);
3482     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]));
3485 /**
3486  * Callback for node "request" signal.
3487  * \todo fixme: This goes to "moved" event? (lauris)
3488  */
3489 static gboolean
3490 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3492     double yn, xn, yp, xp;
3493     double an, ap, na, pa;
3494     double d_an, d_ap, d_na, d_pa;
3495     gboolean collinear = FALSE;
3496     NR::Point c;
3497     NR::Point pr;
3499     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3501     n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3503     // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3504     if ( (!n->subpath->nodepath->straight_path) &&
3505          ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3506            || n->dragging_out ) )
3507     {
3508        NR::Point mouse = (*p);
3510        if (!n->dragging_out) {
3511            // This is the first drag-out event; find out which handle to drag out
3512            double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3513            double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3515            if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3516                return FALSE;
3518            Inkscape::NodePath::NodeSide *opposite;
3519            if (appr_p > appr_n) { // closer to p
3520                n->dragging_out = &n->p;
3521                opposite = &n->n;
3522                n->code = NR_CURVETO;
3523            } else if (appr_p < appr_n) { // closer to n
3524                n->dragging_out = &n->n;
3525                opposite = &n->p;
3526                n->n.other->code = NR_CURVETO;
3527            } else { // p and n nodes are the same
3528                if (n->n.pos != n->pos) { // n handle already dragged, drag p
3529                    n->dragging_out = &n->p;
3530                    opposite = &n->n;
3531                    n->code = NR_CURVETO;
3532                } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3533                    n->dragging_out = &n->n;
3534                    opposite = &n->p;
3535                    n->n.other->code = NR_CURVETO;
3536                } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3537                    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);
3538                    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);
3539                    if (appr_other_p > appr_other_n) { // closer to other's p handle
3540                        n->dragging_out = &n->n;
3541                        opposite = &n->p;
3542                        n->n.other->code = NR_CURVETO;
3543                    } else { // closer to other's n handle
3544                        n->dragging_out = &n->p;
3545                        opposite = &n->n;
3546                        n->code = NR_CURVETO;
3547                    }
3548                }
3549            }
3551            // if there's another handle, make sure the one we drag out starts parallel to it
3552            if (opposite->pos != n->pos) {
3553                mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3554            }
3556            // knots might not be created yet!
3557            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3558            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3559        }
3561        // pass this on to the handle-moved callback
3562        node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3563        sp_node_update_handles(n);
3564        return TRUE;
3565    }
3567     if (state & GDK_CONTROL_MASK) { // constrained motion
3569         // calculate relative distances of handles
3570         // n handle:
3571         yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3572         xn = n->n.pos[NR::X] - n->pos[NR::X];
3573         // if there's no n handle (straight line), see if we can use the direction to the next point on path
3574         if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3575             if (n->n.other) { // if there is the next point
3576                 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3577                     yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3578                     xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3579             }
3580         }
3581         if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3582         if (yn < 0) { xn = -xn; yn = -yn; }
3584         // p handle:
3585         yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3586         xp = n->p.pos[NR::X] - n->pos[NR::X];
3587         // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3588         if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3589             if (n->p.other) {
3590                 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3591                     yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3592                     xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3593             }
3594         }
3595         if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3596         if (yp < 0) { xp = -xp; yp = -yp; }
3598         if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3599             // sliding on handles, only if at least one of the handles is non-vertical
3600             // (otherwise it's the same as ctrl+drag anyway)
3602             // calculate angles of the handles
3603             if (xn == 0) {
3604                 if (yn == 0) { // no handle, consider it the continuation of the other one
3605                     an = 0;
3606                     collinear = TRUE;
3607                 }
3608                 else an = 0; // vertical; set the angle to horizontal
3609             } else an = yn/xn;
3611             if (xp == 0) {
3612                 if (yp == 0) { // no handle, consider it the continuation of the other one
3613                     ap = an;
3614                 }
3615                 else ap = 0; // vertical; set the angle to horizontal
3616             } else  ap = yp/xp;
3618             if (collinear) an = ap;
3620             // angles of the perpendiculars; HUGE_VAL means vertical
3621             if (an == 0) na = HUGE_VAL; else na = -1/an;
3622             if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3624             // mouse point relative to the node's original pos
3625             pr = (*p) - n->origin;
3627             // distances to the four lines (two handles and two perpendiculars)
3628             d_an = point_line_distance(&pr, an);
3629             d_na = point_line_distance(&pr, na);
3630             d_ap = point_line_distance(&pr, ap);
3631             d_pa = point_line_distance(&pr, pa);
3633             // find out which line is the closest, save its closest point in c
3634             if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3635                 point_line_closest(&pr, an, &c);
3636             } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3637                 point_line_closest(&pr, ap, &c);
3638             } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3639                 point_line_closest(&pr, na, &c);
3640             } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3641                 point_line_closest(&pr, pa, &c);
3642             }
3644             // move the node to the closest point
3645             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3646                                             n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3647                                             n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y], 
3648                                             true);
3650         } else {  // constraining to hor/vert
3652             if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3653                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3654                                                 (*p)[NR::X] - n->pos[NR::X], 
3655                                                 n->origin[NR::Y] - n->pos[NR::Y],
3656                                                 true, 
3657                                                 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3658             } else { // snap to vert
3659                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3660                                                 n->origin[NR::X] - n->pos[NR::X],
3661                                                 (*p)[NR::Y] - n->pos[NR::Y],
3662                                                 true,
3663                                                 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3664             }
3665         }
3666     } else { // move freely
3667         if (n->is_dragging) {
3668             if (state & GDK_MOD1_MASK) { // sculpt
3669                 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3670             } else {
3671                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3672                                             (*p)[NR::X] - n->pos[NR::X],
3673                                             (*p)[NR::Y] - n->pos[NR::Y],
3674                                             (state & GDK_SHIFT_MASK) == 0);
3675             }
3676         }
3677     }
3679     n->subpath->nodepath->desktop->scroll_to_point(p);
3681     return TRUE;
3684 /**
3685  * Node handle clicked callback.
3686  */
3687 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3689    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3691     if (state & GDK_CONTROL_MASK) { // "delete" handle
3692         if (n->p.knot == knot) {
3693             n->p.pos = n->pos;
3694         } else if (n->n.knot == knot) {
3695             n->n.pos = n->pos;
3696         }
3697         sp_node_update_handles(n);
3698         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3699         sp_nodepath_update_repr(nodepath, _("Retract handle"));
3700         sp_nodepath_update_statusbar(nodepath);
3702     } else { // just select or add to selection, depending in Shift
3703         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3704     }
3707 /**
3708  * Node handle grabbed callback.
3709  */
3710 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3712    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3714     if (!n->selected) {
3715         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3716     }
3718     // remember the origin point of the handle
3719     if (n->p.knot == knot) {
3720         n->p.origin_radial = n->p.pos - n->pos;
3721     } else if (n->n.knot == knot) {
3722         n->n.origin_radial = n->n.pos - n->pos;
3723     } else {
3724         g_assert_not_reached();
3725     }
3727     sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3730 /**
3731  * Node handle ungrabbed callback.
3732  */
3733 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3735    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3737     // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3738     if (n->p.knot == knot) {
3739         n->p.origin_radial.a = 0;
3740         sp_knot_set_position(knot, &n->p.pos, state);
3741     } else if (n->n.knot == knot) {
3742         n->n.origin_radial.a = 0;
3743         sp_knot_set_position(knot, &n->n.pos, state);
3744     } else {
3745         g_assert_not_reached();
3746     }
3748     sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3751 /**
3752  * Node handle "request" signal callback.
3753  */
3754 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3756     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3758     Inkscape::NodePath::NodeSide *me, *opposite;
3759     gint which;
3760     if (n->p.knot == knot) {
3761         me = &n->p;
3762         opposite = &n->n;
3763         which = -1;
3764     } else if (n->n.knot == knot) {
3765         me = &n->n;
3766         opposite = &n->p;
3767         which = 1;
3768     } else {
3769         me = opposite = NULL;
3770         which = 0;
3771         g_assert_not_reached();
3772     }
3774     SPDesktop *desktop = n->subpath->nodepath->desktop;
3775     SnapManager &m = desktop->namedview->snap_manager;
3776     m.setup(desktop, n->subpath->nodepath->item);
3777     Inkscape::SnappedPoint s ;
3779     Inkscape::NodePath::Node *othernode = opposite->other;
3780     if (othernode) {
3781         if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3782             /* We are smooth node adjacent with line */
3783             NR::Point const delta = *p - n->pos;
3784             NR::Coord const len = NR::L2(delta);
3785             Inkscape::NodePath::Node *othernode = opposite->other;
3786             NR::Point const ndelta = n->pos - othernode->pos;
3787             NR::Coord const linelen = NR::L2(ndelta);
3788             if (len > NR_EPSILON && linelen > NR_EPSILON) {
3789                 NR::Coord const scal = dot(delta, ndelta) / linelen;
3790                 (*p) = n->pos + (scal / linelen) * ndelta;
3791             }
3792             s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3793         } else {
3794             s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3795         }
3796     } else {
3797         s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3798     }
3799     
3800     s.getPoint(*p);
3801     
3802     sp_node_adjust_handle(n, -which);
3804     return FALSE;
3807 /**
3808  * Node handle moved callback.
3809  */
3810 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3812    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3814    Inkscape::NodePath::NodeSide *me;
3815    Inkscape::NodePath::NodeSide *other;
3816     if (n->p.knot == knot) {
3817         me = &n->p;
3818         other = &n->n;
3819     } else if (n->n.knot == knot) {
3820         me = &n->n;
3821         other = &n->p;
3822     } else {
3823         me = NULL;
3824         other = NULL;
3825         g_assert_not_reached();
3826     }
3828     // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3829     Radial rme(me->pos - n->pos);
3830     Radial rother(other->pos - n->pos);
3831     Radial rnew(*p - n->pos);
3833     if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3834         int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3835         /* 0 interpreted as "no snapping". */
3837         // 1. Snap to the closest PI/snaps angle, starting from zero.
3838         double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3840         // 2. Snap to the original angle, its opposite and perpendiculars
3841         if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3842             /* The closest PI/2 angle, starting from original angle */
3843             double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3845             // Snap to the closest.
3846             a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3847                        ? a_snapped
3848                        : a_ortho );
3849         }
3851         // 3. Snap to the angle of the opposite line, if any
3852         Inkscape::NodePath::Node *othernode = other->other;
3853         if (othernode) {
3854             NR::Point other_to_snap(0,0);
3855             if (sp_node_side_is_line(n, other)) {
3856                 other_to_snap = othernode->pos - n->pos;
3857             } else {
3858                 other_to_snap = other->pos - n->pos;
3859             }
3860             if (NR::L2(other_to_snap) > 1e-3) {
3861                 Radial rother_to_snap(other_to_snap);
3862                 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3863                 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3865                 // Snap to the closest.
3866                 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3867                        ? a_snapped
3868                        : a_oppo );
3869             }
3870         }
3872         rnew.a = a_snapped;
3873     }
3875     if (state & GDK_MOD1_MASK) {
3876         // lock handle length
3877         rnew.r = me->origin_radial.r;
3878     }
3880     if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3881         && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3882         // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3883         rother.a += rnew.a - rme.a;
3884         other->pos = NR::Point(rother) + n->pos;
3885         if (other->knot) {
3886             sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3887             sp_knot_moveto(other->knot, &other->pos);
3888         }
3889     }
3891     me->pos = NR::Point(rnew) + n->pos;
3892     sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3894     // move knot, but without emitting the signal:
3895     // we cannot emit a "moved" signal because we're now processing it
3896     sp_knot_moveto(me->knot, &(me->pos));
3898     update_object(n->subpath->nodepath);
3900     /* status text */
3901     SPDesktop *desktop = n->subpath->nodepath->desktop;
3902     if (!desktop) return;
3903     SPEventContext *ec = desktop->event_context;
3904     if (!ec) return;
3905     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3906     if (!mc) return;
3908     double degrees = 180 / M_PI * rnew.a;
3909     if (degrees > 180) degrees -= 360;
3910     if (degrees < -180) degrees += 360;
3911     if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3912         degrees = angle_to_compass (degrees);
3914     GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3916     mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3917          _("<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);
3919     g_string_free(length, TRUE);
3922 /**
3923  * Node handle event callback.
3924  */
3925 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3927     gboolean ret = FALSE;
3928     switch (event->type) {
3929         case GDK_KEY_PRESS:
3930             switch (get_group0_keyval (&event->key)) {
3931                 case GDK_space:
3932                     if (event->key.state & GDK_BUTTON1_MASK) {
3933                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3934                         stamp_repr(nodepath);
3935                         ret = TRUE;
3936                     }
3937                     break;
3938                 default:
3939                     break;
3940             }
3941             break;
3942         case GDK_ENTER_NOTIFY:
3943             // we use an experimentally determined threshold that seems to work fine
3944             if (NR::L2(n->pos - knot->pos) < 0.75)
3945                 Inkscape::NodePath::Path::active_node = n;
3946             break;
3947         case GDK_LEAVE_NOTIFY:
3948             // we use an experimentally determined threshold that seems to work fine
3949             if (NR::L2(n->pos - knot->pos) < 0.75)
3950                 Inkscape::NodePath::Path::active_node = NULL;
3951             break;
3952         default:
3953             break;
3954     }
3956     return ret;
3959 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3960                                  Radial &rme, Radial &rother, gboolean const both)
3962     rme.a += angle;
3963     if ( both
3964          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3965          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3966     {
3967         rother.a += angle;
3968     }
3971 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3972                                         Radial &rme, Radial &rother, gboolean const both)
3974     gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3976     gdouble r;
3977     if ( both
3978          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3979          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3980     {
3981         r = MAX(rme.r, rother.r);
3982     } else {
3983         r = rme.r;
3984     }
3986     gdouble const weird_angle = atan2(norm_angle, r);
3987 /* Bulia says norm_angle is just the visible distance that the
3988  * object's end must travel on the screen.  Left as 'angle' for want of
3989  * a better name.*/
3991     rme.a += weird_angle;
3992     if ( both
3993          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3994          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3995     {
3996         rother.a += weird_angle;
3997     }
4000 /**
4001  * Rotate one node.
4002  */
4003 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4005     Inkscape::NodePath::NodeSide *me, *other;
4006     bool both = false;
4008     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4009     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4011     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4012         me = &(n->p);
4013         other = &(n->n);
4014     } else if (!n->p.other) {
4015         me = &(n->n);
4016         other = &(n->p);
4017     } else {
4018         if (which > 0) { // right handle
4019             if (xn > xp) {
4020                 me = &(n->n);
4021                 other = &(n->p);
4022             } else {
4023                 me = &(n->p);
4024                 other = &(n->n);
4025             }
4026         } else if (which < 0){ // left handle
4027             if (xn <= xp) {
4028                 me = &(n->n);
4029                 other = &(n->p);
4030             } else {
4031                 me = &(n->p);
4032                 other = &(n->n);
4033             }
4034         } else { // both handles
4035             me = &(n->n);
4036             other = &(n->p);
4037             both = true;
4038         }
4039     }
4041     Radial rme(me->pos - n->pos);
4042     Radial rother(other->pos - n->pos);
4044     if (screen) {
4045         node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4046     } else {
4047         node_rotate_one_internal (*n, angle, rme, rother, both);
4048     }
4050     me->pos = n->pos + NR::Point(rme);
4052     if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4053         other->pos =  n->pos + NR::Point(rother);
4054     }
4056     // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4057     // so here we just move all the knots without emitting move signals, for speed
4058     sp_node_update_handles(n, false);
4061 /**
4062  * Rotate selected nodes.
4063  */
4064 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4066     if (!nodepath || !nodepath->selected) return;
4068     if (g_list_length(nodepath->selected) == 1) {
4069        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4070         node_rotate_one (n, angle, which, screen);
4071     } else {
4072        // rotate as an object:
4074         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4075         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4076         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4077             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4078             box.expandTo (n->pos); // contain all selected nodes
4079         }
4081         gdouble rot;
4082         if (screen) {
4083             gdouble const zoom = nodepath->desktop->current_zoom();
4084             gdouble const zmove = angle / zoom;
4085             gdouble const r = NR::L2(box.max() - box.midpoint());
4086             rot = atan2(zmove, r);
4087         } else {
4088             rot = angle;
4089         }
4091         NR::Point rot_center;
4092         if (Inkscape::NodePath::Path::active_node == NULL)
4093             rot_center = box.midpoint();
4094         else
4095             rot_center = Inkscape::NodePath::Path::active_node->pos;
4097         NR::Matrix t =
4098             NR::Matrix (NR::translate(-rot_center)) *
4099             NR::Matrix (NR::rotate(rot)) *
4100             NR::Matrix (NR::translate(rot_center));
4102         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4103             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4104             n->pos *= t;
4105             n->n.pos *= t;
4106             n->p.pos *= t;
4107             sp_node_update_handles(n, false);
4108         }
4109     }
4111     sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4114 /**
4115  * Scale one node.
4116  */
4117 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4119     bool both = false;
4120     Inkscape::NodePath::NodeSide *me, *other;
4122     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4123     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4125     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4126         me = &(n->p);
4127         other = &(n->n);
4128         n->code = NR_CURVETO;
4129     } else if (!n->p.other) {
4130         me = &(n->n);
4131         other = &(n->p);
4132         if (n->n.other)
4133             n->n.other->code = NR_CURVETO;
4134     } else {
4135         if (which > 0) { // right handle
4136             if (xn > xp) {
4137                 me = &(n->n);
4138                 other = &(n->p);
4139                 if (n->n.other)
4140                     n->n.other->code = NR_CURVETO;
4141             } else {
4142                 me = &(n->p);
4143                 other = &(n->n);
4144                 n->code = NR_CURVETO;
4145             }
4146         } else if (which < 0){ // left handle
4147             if (xn <= xp) {
4148                 me = &(n->n);
4149                 other = &(n->p);
4150                 if (n->n.other)
4151                     n->n.other->code = NR_CURVETO;
4152             } else {
4153                 me = &(n->p);
4154                 other = &(n->n);
4155                 n->code = NR_CURVETO;
4156             }
4157         } else { // both handles
4158             me = &(n->n);
4159             other = &(n->p);
4160             both = true;
4161             n->code = NR_CURVETO;
4162             if (n->n.other)
4163                 n->n.other->code = NR_CURVETO;
4164         }
4165     }
4167     Radial rme(me->pos - n->pos);
4168     Radial rother(other->pos - n->pos);
4170     rme.r += grow;
4171     if (rme.r < 0) rme.r = 0;
4172     if (rme.a == HUGE_VAL) {
4173         if (me->other) { // if direction is unknown, initialize it towards the next node
4174             Radial rme_next(me->other->pos - n->pos);
4175             rme.a = rme_next.a;
4176         } else { // if there's no next, initialize to 0
4177             rme.a = 0;
4178         }
4179     }
4180     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4181         rother.r += grow;
4182         if (rother.r < 0) rother.r = 0;
4183         if (rother.a == HUGE_VAL) {
4184             rother.a = rme.a + M_PI;
4185         }
4186     }
4188     me->pos = n->pos + NR::Point(rme);
4190     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4191         other->pos = n->pos + NR::Point(rother);
4192     }
4194     // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4195     // so here we just move all the knots without emitting move signals, for speed
4196     sp_node_update_handles(n, false);
4199 /**
4200  * Scale selected nodes.
4201  */
4202 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4204     if (!nodepath || !nodepath->selected) return;
4206     if (g_list_length(nodepath->selected) == 1) {
4207         // scale handles of the single selected node
4208         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4209         node_scale_one (n, grow, which);
4210     } else {
4211         // scale nodes as an "object":
4213         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4214         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4215         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4216             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4217             box.expandTo (n->pos); // contain all selected nodes
4218         }
4220         double scale = (box.maxExtent() + grow)/box.maxExtent();
4222         NR::Point scale_center;
4223         if (Inkscape::NodePath::Path::active_node == NULL)
4224             scale_center = box.midpoint();
4225         else
4226             scale_center = Inkscape::NodePath::Path::active_node->pos;
4228         NR::Matrix t =
4229             NR::Matrix (NR::translate(-scale_center)) *
4230             NR::Matrix (NR::scale(scale, scale)) *
4231             NR::Matrix (NR::translate(scale_center));
4233         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4234             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4235             n->pos *= t;
4236             n->n.pos *= t;
4237             n->p.pos *= t;
4238             sp_node_update_handles(n, false);
4239         }
4240     }
4242     sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4245 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4247     if (!nodepath) return;
4248     sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4251 /**
4252  * Flip selected nodes horizontally/vertically.
4253  */
4254 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4256     if (!nodepath || !nodepath->selected) return;
4258     if (g_list_length(nodepath->selected) == 1 && !center) {
4259         // flip handles of the single selected node
4260         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4261         double temp = n->p.pos[axis];
4262         n->p.pos[axis] = n->n.pos[axis];
4263         n->n.pos[axis] = temp;
4264         sp_node_update_handles(n, false);
4265     } else {
4266         // scale nodes as an "object":
4268         NR::Rect box = sp_node_selected_bbox (nodepath);
4269         if (!center) {
4270             center = box.midpoint();
4271         }
4272         NR::Matrix t =
4273             NR::Matrix (NR::translate(- *center)) *
4274             NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4275             NR::Matrix (NR::translate(*center));
4277         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4278             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4279             n->pos *= t;
4280             n->n.pos *= t;
4281             n->p.pos *= t;
4282             sp_node_update_handles(n, false);
4283         }
4284     }
4286     sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4289 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4291     g_assert (nodepath->selected);
4293     Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4294     NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4295     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4296         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4297         box.expandTo (n->pos); // contain all selected nodes
4298     }
4299     return box;
4302 //-----------------------------------------------
4303 /**
4304  * Return new subpath under given nodepath.
4305  */
4306 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4308     g_assert(nodepath);
4309     g_assert(nodepath->desktop);
4311    Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4313     s->nodepath = nodepath;
4314     s->closed = FALSE;
4315     s->nodes = NULL;
4316     s->first = NULL;
4317     s->last = NULL;
4319     // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4320     // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4321     nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4323     return s;
4326 /**
4327  * Destroy nodes in subpath, then subpath itself.
4328  */
4329 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4331     g_assert(subpath);
4332     g_assert(subpath->nodepath);
4333     g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4335     while (subpath->nodes) {
4336         sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4337     }
4339     subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4341     g_free(subpath);
4344 /**
4345  * Link head to tail in subpath.
4346  */
4347 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4349     g_assert(!sp->closed);
4350     g_assert(sp->last != sp->first);
4351     g_assert(sp->first->code == NR_MOVETO);
4353     sp->closed = TRUE;
4355     //Link the head to the tail
4356     sp->first->p.other = sp->last;
4357     sp->last->n.other  = sp->first;
4358     sp->last->n.pos    = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4359     sp->first          = sp->last;
4361     //Remove the extra end node
4362     sp_nodepath_node_destroy(sp->last->n.other);
4365 /**
4366  * Open closed (loopy) subpath at node.
4367  */
4368 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4370     g_assert(sp->closed);
4371     g_assert(n->subpath == sp);
4372     g_assert(sp->first == sp->last);
4374     /* We create new startpoint, current node will become last one */
4376    Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4377                                                 &n->pos, &n->pos, &n->n.pos);
4380     sp->closed        = FALSE;
4382     //Unlink to make a head and tail
4383     sp->first         = new_path;
4384     sp->last          = n;
4385     n->n.other        = NULL;
4386     new_path->p.other = NULL;
4389 /**
4390  * Return new node in subpath with given properties.
4391  * \param pos Position of node.
4392  * \param ppos Handle position in previous direction
4393  * \param npos Handle position in previous direction
4394  */
4395 Inkscape::NodePath::Node *
4396 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)
4398     g_assert(sp);
4399     g_assert(sp->nodepath);
4400     g_assert(sp->nodepath->desktop);
4402     if (nodechunk == NULL)
4403         nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4405     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4407     n->subpath  = sp;
4409     if (type != Inkscape::NodePath::NODE_NONE) {
4410         // use the type from sodipodi:nodetypes
4411         n->type = type;
4412     } else {
4413         if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4414             // points are (almost) collinear
4415             if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4416                 // endnode, or a node with a retracted handle
4417                 n->type = Inkscape::NodePath::NODE_CUSP;
4418             } else {
4419                 n->type = Inkscape::NodePath::NODE_SMOOTH;
4420             }
4421         } else {
4422             n->type = Inkscape::NodePath::NODE_CUSP;
4423         }
4424     }
4426     n->code     = code;
4427     n->selected = FALSE;
4428     n->pos      = *pos;
4429     n->p.pos    = *ppos;
4430     n->n.pos    = *npos;
4432     n->dragging_out = NULL;
4434     Inkscape::NodePath::Node *prev;
4435     if (next) {
4436         //g_assert(g_list_find(sp->nodes, next));
4437         prev = next->p.other;
4438     } else {
4439         prev = sp->last;
4440     }
4442     if (prev)
4443         prev->n.other = n;
4444     else
4445         sp->first = n;
4447     if (next)
4448         next->p.other = n;
4449     else
4450         sp->last = n;
4452     n->p.other = prev;
4453     n->n.other = next;
4455     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"));
4456     sp_knot_set_position(n->knot, pos, 0);
4458     n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4459     n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4460     n->knot->setAnchor (GTK_ANCHOR_CENTER);
4461     n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4462     n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4463     sp_knot_update_ctrl(n->knot);
4465     g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4466     g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4467     g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4468     g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4469     g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4470     sp_knot_show(n->knot);
4472     // We only create handle knots and lines on demand
4473     n->p.knot = NULL;
4474     n->p.line = NULL;
4475     n->n.knot = NULL;
4476     n->n.line = NULL;
4478     sp->nodes = g_list_prepend(sp->nodes, n);
4480     return n;
4483 /**
4484  * Destroy node and its knots, link neighbors in subpath.
4485  */
4486 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4488     g_assert(node);
4489     g_assert(node->subpath);
4490     g_assert(SP_IS_KNOT(node->knot));
4492    Inkscape::NodePath::SubPath *sp = node->subpath;
4494     if (node->selected) { // first, deselect
4495         g_assert(g_list_find(node->subpath->nodepath->selected, node));
4496         node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4497     }
4499     node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4501     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4502     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4503     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4504     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4505     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4506     g_object_unref(G_OBJECT(node->knot));
4508     if (node->p.knot) {
4509         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4510         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4511         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4512         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4513         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4514         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4515         g_object_unref(G_OBJECT(node->p.knot));
4516         node->p.knot = NULL;
4517     }
4519     if (node->n.knot) {
4520         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4521         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4522         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4523         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4524         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4525         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4526         g_object_unref(G_OBJECT(node->n.knot));
4527         node->n.knot = NULL;
4528     }
4530     if (node->p.line)
4531         gtk_object_destroy(GTK_OBJECT(node->p.line));
4532     if (node->n.line)
4533         gtk_object_destroy(GTK_OBJECT(node->n.line));
4535     if (sp->nodes) { // there are others nodes on the subpath
4536         if (sp->closed) {
4537             if (sp->first == node) {
4538                 g_assert(sp->last == node);
4539                 sp->first = node->n.other;
4540                 sp->last = sp->first;
4541             }
4542             node->p.other->n.other = node->n.other;
4543             node->n.other->p.other = node->p.other;
4544         } else {
4545             if (sp->first == node) {
4546                 sp->first = node->n.other;
4547                 sp->first->code = NR_MOVETO;
4548             }
4549             if (sp->last == node) sp->last = node->p.other;
4550             if (node->p.other) node->p.other->n.other = node->n.other;
4551             if (node->n.other) node->n.other->p.other = node->p.other;
4552         }
4553     } else { // this was the last node on subpath
4554         sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4555     }
4557     g_mem_chunk_free(nodechunk, node);
4560 /**
4561  * Returns one of the node's two sides.
4562  * \param which Indicates which side.
4563  * \return Pointer to previous node side if which==-1, next if which==1.
4564  */
4565 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4567     g_assert(node);
4569     switch (which) {
4570         case -1:
4571             return &node->p;
4572         case 1:
4573             return &node->n;
4574         default:
4575             break;
4576     }
4578     g_assert_not_reached();
4580     return NULL;
4583 /**
4584  * Return the other side of the node, given one of its sides.
4585  */
4586 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4588     g_assert(node);
4590     if (me == &node->p) return &node->n;
4591     if (me == &node->n) return &node->p;
4593     g_assert_not_reached();
4595     return NULL;
4598 /**
4599  * Return NRPathcode on the given side of the node.
4600  */
4601 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4603     g_assert(node);
4605     if (me == &node->p) {
4606         if (node->p.other) return (NRPathcode)node->code;
4607         return NR_MOVETO;
4608     }
4610     if (me == &node->n) {
4611         if (node->n.other) return (NRPathcode)node->n.other->code;
4612         return NR_MOVETO;
4613     }
4615     g_assert_not_reached();
4617     return NR_END;
4620 /**
4621  * Return node with the given index
4622  */
4623 Inkscape::NodePath::Node *
4624 sp_nodepath_get_node_by_index(int index)
4626     Inkscape::NodePath::Node *e = NULL;
4628     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4629     if (!nodepath) {
4630         return e;
4631     }
4633     //find segment
4634     for (GList *l = nodepath->subpaths; l ; l=l->next) {
4636         Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4637         int n = g_list_length(sp->nodes);
4638         if (sp->closed) {
4639             n++;
4640         }
4642         //if the piece belongs to this subpath grab it
4643         //otherwise move onto the next subpath
4644         if (index < n) {
4645             e = sp->first;
4646             for (int i = 0; i < index; ++i) {
4647                 e = e->n.other;
4648             }
4649             break;
4650         } else {
4651             if (sp->closed) {
4652                 index -= (n+1);
4653             } else {
4654                 index -= n;
4655             }
4656         }
4657     }
4659     return e;
4662 /**
4663  * Returns plain text meaning of node type.
4664  */
4665 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4667     unsigned retracted = 0;
4668     bool endnode = false;
4670     for (int which = -1; which <= 1; which += 2) {
4671         Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4672         if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4673             retracted ++;
4674         if (!side->other)
4675             endnode = true;
4676     }
4678     if (retracted == 0) {
4679         if (endnode) {
4680                 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4681                 return _("end node");
4682         } else {
4683             switch (node->type) {
4684                 case Inkscape::NodePath::NODE_CUSP:
4685                     // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4686                     return _("cusp");
4687                 case Inkscape::NodePath::NODE_SMOOTH:
4688                     // TRANSLATORS: "smooth" is an adjective here
4689                     return _("smooth");
4690                 case Inkscape::NodePath::NODE_SYMM:
4691                     return _("symmetric");
4692             }
4693         }
4694     } else if (retracted == 1) {
4695         if (endnode) {
4696             // TRANSLATORS: "end" is an adjective here (NOT a verb)
4697             return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4698         } else {
4699             return _("one handle retracted (drag with <b>Shift</b> to extend)");
4700         }
4701     } else {
4702         return _("both handles retracted (drag with <b>Shift</b> to extend)");
4703     }
4705     return NULL;
4708 /**
4709  * Handles content of statusbar as long as node tool is active.
4710  */
4711 void
4712 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4714     gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>&lt; &gt;</b> to scale, <b>[ ]</b> to rotate");
4715     gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4717     gint total_nodes = sp_nodepath_get_node_count(nodepath);
4718     gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4719     gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4720     gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4722     SPDesktop *desktop = NULL;
4723     if (nodepath) {
4724         desktop = nodepath->desktop;
4725     } else {
4726         desktop = SP_ACTIVE_DESKTOP;
4727     }
4729     SPEventContext *ec = desktop->event_context;
4730     if (!ec) return;
4731     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4732     if (!mc) return;
4734     inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4736     if (selected_nodes == 0) {
4737         Inkscape::Selection *sel = desktop->selection;
4738         if (!sel || sel->isEmpty()) {
4739             mc->setF(Inkscape::NORMAL_MESSAGE,
4740                      _("Select a single object to edit its nodes or handles."));
4741         } else {
4742             if (nodepath) {
4743             mc->setF(Inkscape::NORMAL_MESSAGE,
4744                      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.",
4745                               "<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.",
4746                               total_nodes),
4747                      total_nodes);
4748             } else {
4749                 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4750                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4751                 } else {
4752                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4753                 }
4754             }
4755         }
4756     } else if (nodepath && selected_nodes == 1) {
4757         mc->setF(Inkscape::NORMAL_MESSAGE,
4758                  ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4759                           "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4760                           total_nodes),
4761                  selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4762     } else {
4763         if (selected_subpaths > 1) {
4764             mc->setF(Inkscape::NORMAL_MESSAGE,
4765                      ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4766                               "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4767                               total_nodes),
4768                      selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4769         } else {
4770             mc->setF(Inkscape::NORMAL_MESSAGE,
4771                      ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4772                               "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4773                               total_nodes),
4774                      selected_nodes, total_nodes, when_selected);
4775         }
4776     }
4779 /*
4780  * returns a *copy* of the curve of that object.
4781  */
4782 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4783     if (!object)
4784         return NULL;
4786     SPCurve *curve = NULL;
4787     if (SP_IS_PATH(object)) {
4788         SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4789         curve = curve_new->copy();
4790     } else if ( IS_LIVEPATHEFFECT(object) && key) {
4791         const gchar *svgd = object->repr->attribute(key);
4792         if (svgd) {
4793             Geom::PathVector pv = sp_svg_read_pathv(svgd);
4794             SPCurve *curve_new = new SPCurve(pv);
4795             if (curve_new) {
4796                 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4797             }
4798         }
4799     }
4801     return curve;
4804 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4805     if (!np || !np->object || !curve)
4806         return;
4808     if (SP_IS_PATH(np->object)) {
4809         if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4810             sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4811         } else {
4812             sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4813         }
4814     } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4815         // FIXME: this writing to string and then reading from string is bound to be slow.
4816         // create a method to convert from curve directly to 2geom...
4817         gchar *svgpath = sp_svg_write_path( np->curve->get_pathvector() );
4818         LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4819         g_free(svgpath);
4821         np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4822     }
4825 SPCanvasItem *
4826 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4827     SPCurve *flash_curve = curve->copy();
4828     Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4829     flash_curve->transform(i2d);
4830     SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4831     // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4832     // unless we also flash the nodes...
4833     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4834     sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4835     sp_canvas_item_show(canvasitem);
4836     flash_curve->unref();
4837     return canvasitem;
4840 SPCanvasItem *
4841 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4842     return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4843                                            prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4846 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4847     np->show_helperpath = show;
4849     if (show) {
4850         SPCurve *helper_curve = np->curve->copy();
4851         helper_curve->transform(np->i2d );
4852         if (!np->helper_path) {
4853             np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4854             sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4855             sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4856             sp_canvas_item_move_to_z(np->helper_path, 0);
4857             sp_canvas_item_show(np->helper_path);
4858         } else {
4859             sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4860         }
4861         helper_curve->unref();
4862     } else {
4863         if (np->helper_path) {
4864             GtkObject *temp = np->helper_path;
4865             np->helper_path = NULL;
4866             gtk_object_destroy(temp);
4867         }
4868     }
4871 /* sp_nodepath_make_straight_path:
4872  *   Prevents user from curving the path by dragging a segment or activating handles etc.
4873  *   The resulting path is a linear interpolation between nodal points, with only straight segments.
4874  * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4875  */
4876 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4877     np->straight_path = true;
4878     np->show_handles = false;
4879     g_message("add code to make the path straight.");
4880     // do sp_nodepath_convert_node_type on all nodes?
4881     // coding tip: search for this text : "Make selected segments lines"
4885 /*
4886   Local Variables:
4887   mode:c++
4888   c-file-style:"stroustrup"
4889   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4890   indent-tabs-mode:nil
4891   fill-column:99
4892   End:
4893 */
4894 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :