Code

purge livarot stuff from 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 <2geom/pathvector.h>
25 #include <2geom/sbasis-to-bezier.h>
26 #include <2geom/bezier-curve.h>
27 #include <2geom/hvlinesegment.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 "svg/svg.h"
48 #include "verbs.h"
49 #include "display/bezier-utils.h"
50 #include <vector>
51 #include <algorithm>
52 #include <cstring>
53 #include <cmath>
54 #include <string>
55 #include "live_effects/lpeobject.h"
56 #include "live_effects/effect.h"
57 #include "live_effects/parameter/parameter.h"
58 #include "live_effects/parameter/path.h"
59 #include "util/mathfns.h"
60 #include "display/snap-indicator.h"
61 #include "snapped-point.h"
63 class NR::Matrix;
65 /// \todo
66 /// evil evil evil. FIXME: conflict of two different Path classes!
67 /// There is a conflict in the namespace between two classes named Path.
68 /// #include "sp-flowtext.h"
69 /// #include "sp-flowregion.h"
71 #define SP_TYPE_FLOWREGION            (sp_flowregion_get_type ())
72 #define SP_IS_FLOWREGION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
73 GType sp_flowregion_get_type (void);
74 #define SP_TYPE_FLOWTEXT            (sp_flowtext_get_type ())
75 #define SP_IS_FLOWTEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
76 GType sp_flowtext_get_type (void);
77 // end evil workaround
79 #include "helper/stlport.h"
82 /// \todo fixme: Implement these via preferences */
84 #define NODE_FILL          0xbfbfbf00
85 #define NODE_STROKE        0x000000ff
86 #define NODE_FILL_HI       0xff000000
87 #define NODE_STROKE_HI     0x000000ff
88 #define NODE_FILL_SEL      0x0000ffff
89 #define NODE_STROKE_SEL    0x000000ff
90 #define NODE_FILL_SEL_HI   0xff000000
91 #define NODE_STROKE_SEL_HI 0x000000ff
92 #define KNOT_FILL          0xffffffff
93 #define KNOT_STROKE        0x000000ff
94 #define KNOT_FILL_HI       0xff000000
95 #define KNOT_STROKE_HI     0x000000ff
97 static GMemChunk *nodechunk = NULL;
99 /* Creation from object */
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, guint 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(to_2geom(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     if (curve->get_segment_count() < 1) {
204         curve->unref();
205         return NULL; // prevent crash for one-node paths
206     }
208     //Create new nodepath
209     Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
210     if (!np) {
211         curve->unref();
212         return NULL;
213     }
215     // Set defaults
216     np->desktop     = desktop;
217     np->object      = object;
218     np->subpaths    = NULL;
219     np->selected    = NULL;
220     np->shape_editor = NULL; //Let the shapeeditor that makes this set it
221     np->local_change = 0;
222     np->show_handles = show_handles;
223     np->helper_path = NULL;
224     np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
225     np->helperpath_width = 1.0;
226     np->curve = curve->copy();
227     np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath",  0) == 1);
228     if (SP_IS_LPE_ITEM(object)) {
229         Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
230         if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
231             np->show_helperpath = true;
232         }            
233     }
234     np->straight_path = false;
235     if (IS_LIVEPATHEFFECT(object) && item) {
236         np->item = item;
237     } else {
238         np->item = SP_ITEM(object);
239     }
241     // we need to update item's transform from the repr here,
242     // because they may be out of sync when we respond
243     // to a change in repr by regenerating nodepath     --bb
244     sp_object_read_attr(SP_OBJECT(np->item), "transform");
246     np->i2d  = from_2geom(sp_item_i2d_affine(np->item));
247     np->d2i  = np->i2d.inverse();
249     np->repr = repr;
250     if (repr_key_in) { // apparantly the object is an LPEObject
251         np->repr_key = g_strdup(repr_key_in);
252         np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
253         Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
254         if (lpeparam) {
255             lpeparam->param_setup_nodepath(np);
256         }
257     } else {
258         np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
259         if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
260             np->repr_key = g_strdup("inkscape:original-d");
262             Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
263             if (lpe) {
264                 lpe->setup_nodepath(np);
265             }
266         } else {
267             np->repr_key = g_strdup("d");
268         }
269     }
271     /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
272      * So for example a closed rectangle has a nodetypestring of length 5.
273      * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
274     Geom::PathVector const &pathv = curve->get_pathvector();
275     guint length = curve->get_segment_count();
276     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
277         length += pit->empty() ? 0 : 1;
278     }
280     gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
281     Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
283     // create the subpath(s) from the bpath
284     subpaths_from_pathvector(np, pathv, typestr);
286     // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
287     np->subpaths = g_list_reverse(np->subpaths);
289     delete[] typestr;
290     curve->unref();
292     sp_nodepath_draw_helper_curve(np, desktop);
294     return np;
297 /**
298  * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
299  */
300 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
302     if (!np)  //soft fail, like delete
303         return;
305     while (np->subpaths) {
306         sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
307     }
309     //Inform the ShapeEditor that made me, if any, that I am gone.
310     if (np->shape_editor)
311         np->shape_editor->nodepath_destroyed();
313     g_assert(!np->selected);
315     if (np->helper_path) {
316         GtkObject *temp = np->helper_path;
317         np->helper_path = NULL;
318         gtk_object_destroy(temp);
319     }
320     if (np->curve) {
321         np->curve->unref();
322         np->curve = NULL;
323     }
325     if (np->repr_key) {
326         g_free(np->repr_key);
327         np->repr_key = NULL;
328     }
329     if (np->repr_nodetypes_key) {
330         g_free(np->repr_nodetypes_key);
331         np->repr_nodetypes_key = NULL;
332     }
334     np->desktop = NULL;
336     g_free(np);
339 /**
340  *  Return the node count of a given NodeSubPath.
341  */
342 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
344     if (!subpath)
345         return 0;
346     gint nodeCount = g_list_length(subpath->nodes);
347     return nodeCount;
350 /**
351  *  Return the node count of a given NodePath.
352  */
353 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
355     if (!np)
356         return 0;
357     gint nodeCount = 0;
358     for (GList *item = np->subpaths ; item ; item=item->next) {
359        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
360         nodeCount += g_list_length(subpath->nodes);
361     }
362     return nodeCount;
365 /**
366  *  Return the subpath count of a given NodePath.
367  */
368 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
370     if (!np)
371         return 0;
372     return g_list_length (np->subpaths);
375 /**
376  *  Return the selected node count of a given NodePath.
377  */
378 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
380     if (!np)
381         return 0;
382     return g_list_length (np->selected);
385 /**
386  *  Return the number of subpaths where nodes are selected in a given NodePath.
387  */
388 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
390     if (!np)
391         return 0;
392     if (!np->selected)
393         return 0;
394     if (!np->selected->next)
395         return 1;
396     gint count = 0;
397     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
398         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
399         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
400             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
401             if (node->selected) {
402                 count ++;
403                 break;
404             }
405         }
406     }
407     return count;
410 /**
411  * Clean up a nodepath after editing.
412  *
413  * Currently we are deleting trivial subpaths.
414  */
415 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
417     GList *badSubPaths = NULL;
419     //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
420     for (GList *l = nodepath->subpaths; l ; l=l->next) {
421        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
422        if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
423             badSubPaths = g_list_append(badSubPaths, sp);
424     }
426     //Delete them.  This second step is because sp_nodepath_subpath_destroy()
427     //also removes the subpath from nodepath->subpaths
428     for (GList *l = badSubPaths; l ; l=l->next) {
429        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
430         sp_nodepath_subpath_destroy(sp);
431     }
433     g_list_free(badSubPaths);
436 /**
437  * Create new nodepaths from pathvector, make it subpaths of np.
438  * \param t The node type array.
439  */
440 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
442     guint i = 0;  // index into node type array
443     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
444         if (pit->empty())
445             continue;  // don't add single knot paths
447         Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
449         NR::Point ppos = from_2geom(pit->initialPoint()) * np->i2d;
450         NRPathcode pcode = NR_MOVETO;
452         for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
453             add_curve_to_subpath(np, sp, *cit, t, i, ppos, pcode);
454         }
456         if (pit->closed()) {
457             // Add last knot (because sp_nodepath_subpath_close kills the last knot)
458             /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
459              * If the length is zero, don't add it to the nodepath. */
460             Geom::Curve const &closing_seg = pit->back_closed();
461             if ( ! closing_seg.isDegenerate() ) {
462                 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
463                 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
464             }
466             sp_nodepath_subpath_close(sp);
467         }
468     }
470 // should add initial point of curve with type of previous curve:
471 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,
472                                  NR::Point & ppos, NRPathcode & pcode)
474     if( dynamic_cast<Geom::LineSegment const*>(&c) ||
475         dynamic_cast<Geom::HLineSegment const*>(&c) ||
476         dynamic_cast<Geom::VLineSegment const*>(&c) )
477     {
478         NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
479         sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
480         ppos = from_2geom(c.finalPoint());
481         pcode = NR_LINETO;
482     }
483     else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
484         std::vector<Geom::Point> points = cubic_bezier->points();
485         NR::Point pos = from_2geom(points[0]) * np->i2d;
486         NR::Point npos = from_2geom(points[1]) * np->i2d;
487         sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
488         ppos = from_2geom(points[2]) * np->i2d;
489         pcode = NR_CURVETO;
490     }
491     else {
492         //this case handles sbasis as well as all other curve types
493         Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
495         for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
496             add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
497         }
498     }
502 /**
503  * Convert from sodipodi:nodetypes to new style type array.
504  */
505 static
506 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
508     Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
510     guint pos = 0;
512     if (types) {
513         for (guint i = 0; types[i] && ( i < length ); i++) {
514             while ((types[i] > '\0') && (types[i] <= ' ')) i++;
515             if (types[i] != '\0') {
516                 switch (types[i]) {
517                     case 's':
518                         typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
519                         break;
520                     case 'z':
521                         typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
522                         break;
523                     case 'c':
524                         typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
525                         break;
526                     default:
527                         typestr[pos++] =Inkscape::NodePath::NODE_NONE;
528                         break;
529                 }
530             }
531         }
532     }
534     while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
536     return typestr;
539 /**
540  * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
541  * updated but repr is not (for speed). Used during curve and node drag.
542  */
543 static void update_object(Inkscape::NodePath::Path *np)
545     g_assert(np);
547     np->curve->unref();
548     np->curve = create_curve(np);
550     sp_nodepath_set_curve(np, np->curve);
552     if (np->show_helperpath) {
553         SPCurve * helper_curve = np->curve->copy();
554         helper_curve->transform(to_2geom(np->i2d));
555         sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
556         helper_curve->unref();
557     }
559     // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
560     // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
561     np->shape_editor->update_knotholder();
564 /**
565  * Update XML path node with data from path object.
566  */
567 static void update_repr_internal(Inkscape::NodePath::Path *np)
569     g_assert(np);
571     Inkscape::XML::Node *repr = np->object->repr;
573     np->curve->unref();
574     np->curve = create_curve(np);
576     gchar *typestr = create_typestr(np);
577     gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
579     // determine if path has an effect applied and write to correct "d" attribute.
580     if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
581         np->local_change++;
582         repr->setAttribute(np->repr_key, svgpath);
583     }
585     if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
586         np->local_change++;
587         repr->setAttribute(np->repr_nodetypes_key, typestr);
588     }
590     g_free(svgpath);
591     g_free(typestr);
593     if (np->show_helperpath) {
594         SPCurve * helper_curve = np->curve->copy();
595         helper_curve->transform(to_2geom(np->i2d));
596         sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
597         helper_curve->unref();
598     }
599  }
601 /**
602  * Update XML path node with data from path object, commit changes forever.
603  */
604 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
606     //fixme: np can be NULL, so check before proceeding
607     g_return_if_fail(np != NULL);
609     update_repr_internal(np);
610     sp_canvas_end_forced_full_redraws(np->desktop->canvas);
612     sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
613                      annotation);
616 /**
617  * Update XML path node with data from path object, commit changes with undo.
618  */
619 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
621     update_repr_internal(np);
622     sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
623                            annotation);
626 /**
627  * Make duplicate of path, replace corresponding XML node in tree, commit.
628  */
629 static void stamp_repr(Inkscape::NodePath::Path *np)
631     g_assert(np);
633     Inkscape::XML::Node *old_repr = np->object->repr;
634     Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
636     // remember the position of the item
637     gint pos = old_repr->position();
638     // remember parent
639     Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
641     SPCurve *curve = create_curve(np);
642     gchar *typestr = create_typestr(np);
644     gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
646     new_repr->setAttribute(np->repr_key, svgpath);
647     new_repr->setAttribute(np->repr_nodetypes_key, typestr);
649     // add the new repr to the parent
650     parent->appendChild(new_repr);
651     // move to the saved position
652     new_repr->setPosition(pos > 0 ? pos : 0);
654     sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
655                      _("Stamp"));
657     Inkscape::GC::release(new_repr);
658     g_free(svgpath);
659     g_free(typestr);
660     curve->unref();
663 /**
664  * Create curve from path.
665  */
666 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
668     SPCurve *curve = new SPCurve();
670     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
671        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
672         curve->moveto(sp->first->pos * np->d2i);
673        Inkscape::NodePath::Node *n = sp->first->n.other;
674         while (n) {
675             NR::Point const end_pt = n->pos * np->d2i;
676             switch (n->code) {
677                 case NR_LINETO:
678                     curve->lineto(end_pt);
679                     break;
680                 case NR_CURVETO:
681                     curve->curveto(n->p.other->n.pos * np->d2i,
682                                      n->p.pos * np->d2i,
683                                      end_pt);
684                     break;
685                 default:
686                     g_assert_not_reached();
687                     break;
688             }
689             if (n != sp->last) {
690                 n = n->n.other;
691             } else {
692                 n = NULL;
693             }
694         }
695         if (sp->closed) {
696             curve->closepath();
697         }
698     }
700     return curve;
703 /**
704  * Convert path type string to sodipodi:nodetypes style.
705  */
706 static gchar *create_typestr(Inkscape::NodePath::Path *np)
708     gchar *typestr = g_new(gchar, 32);
709     gint len = 32;
710     gint pos = 0;
712     for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
713        Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
715         if (pos >= len) {
716             typestr = g_renew(gchar, typestr, len + 32);
717             len += 32;
718         }
720         typestr[pos++] = 'c';
722        Inkscape::NodePath::Node *n;
723         n = sp->first->n.other;
724         while (n) {
725             gchar code;
727             switch (n->type) {
728                 case Inkscape::NodePath::NODE_CUSP:
729                     code = 'c';
730                     break;
731                 case Inkscape::NodePath::NODE_SMOOTH:
732                     code = 's';
733                     break;
734                 case Inkscape::NodePath::NODE_SYMM:
735                     code = 'z';
736                     break;
737                 default:
738                     g_assert_not_reached();
739                     code = '\0';
740                     break;
741             }
743             if (pos >= len) {
744                 typestr = g_renew(gchar, typestr, len + 32);
745                 len += 32;
746             }
748             typestr[pos++] = code;
750             if (n != sp->last) {
751                 n = n->n.other;
752             } else {
753                 n = NULL;
754             }
755         }
756     }
758     if (pos >= len) {
759         typestr = g_renew(gchar, typestr, len + 1);
760         len += 1;
761     }
763     typestr[pos++] = '\0';
765     return typestr;
768 /**
769  * Returns current path in context. // later eliminate this function at all!
770  */
771 static Inkscape::NodePath::Path *sp_nodepath_current()
773     if (!SP_ACTIVE_DESKTOP) {
774         return NULL;
775     }
777     SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
779     if (!SP_IS_NODE_CONTEXT(event_context)) {
780         return NULL;
781     }
783     return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
788 /**
789  \brief Fills node and handle positions for three nodes, splitting line
790   marked by end at distance t.
791  */
792 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
794     g_assert(new_path != NULL);
795     g_assert(end      != NULL);
797     g_assert(end->p.other == new_path);
798    Inkscape::NodePath::Node *start = new_path->p.other;
799     g_assert(start);
801     if (end->code == NR_LINETO) {
802         new_path->type =Inkscape::NodePath::NODE_CUSP;
803         new_path->code = NR_LINETO;
804         new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
805     } else {
806         new_path->type =Inkscape::NodePath::NODE_SMOOTH;
807         new_path->code = NR_CURVETO;
808         gdouble s      = 1 - t;
809         for (int dim = 0; dim < 2; dim++) {
810             NR::Coord const f000 = start->pos[dim];
811             NR::Coord const f001 = start->n.pos[dim];
812             NR::Coord const f011 = end->p.pos[dim];
813             NR::Coord const f111 = end->pos[dim];
814             NR::Coord const f00t = s * f000 + t * f001;
815             NR::Coord const f01t = s * f001 + t * f011;
816             NR::Coord const f11t = s * f011 + t * f111;
817             NR::Coord const f0tt = s * f00t + t * f01t;
818             NR::Coord const f1tt = s * f01t + t * f11t;
819             NR::Coord const fttt = s * f0tt + t * f1tt;
820             start->n.pos[dim]    = f00t;
821             new_path->p.pos[dim] = f0tt;
822             new_path->pos[dim]   = fttt;
823             new_path->n.pos[dim] = f1tt;
824             end->p.pos[dim]      = f11t;
825         }
826     }
829 /**
830  * Adds new node on direct line between two nodes, activates handles of all
831  * three nodes.
832  */
833 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
835     g_assert(end);
836     g_assert(end->subpath);
837     g_assert(g_list_find(end->subpath->nodes, end));
839    Inkscape::NodePath::Node *start = end->p.other;
840     g_assert( start->n.other == end );
841    Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
842                                                end,
843                                                (NRPathcode)end->code == NR_LINETO?
844                                                   Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
845                                                (NRPathcode)end->code,
846                                                &start->pos, &start->pos, &start->n.pos);
847     sp_nodepath_line_midpoint(newnode, end, t);
849     sp_node_adjust_handles(start);
850     sp_node_update_handles(start);
851     sp_node_update_handles(newnode);
852     sp_node_adjust_handles(end);
853     sp_node_update_handles(end);
855     return newnode;
858 /**
859 \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
860 */
861 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
863     g_assert(node);
864     g_assert(node->subpath);
865     g_assert(g_list_find(node->subpath->nodes, node));
867    Inkscape::NodePath::SubPath *sp = node->subpath;
868     Inkscape::NodePath::Path *np    = sp->nodepath;
870     if (sp->closed) {
871         sp_nodepath_subpath_open(sp, node);
872         return sp->first;
873     } else {
874         // no break for end nodes
875         if (node == sp->first) return NULL;
876         if (node == sp->last ) return NULL;
878         // create a new subpath
879        Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
881         // duplicate the break node as start of the new subpath
882         Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
884         // attach rest of curve to new node
885         g_assert(node->n.other);
886         newnode->n.other = node->n.other; node->n.other = NULL;
887         newnode->n.other->p.other = newnode;
888         newsubpath->last = sp->last;
889         sp->last = node;
890         node = newnode;
891         while (node->n.other) {
892             node = node->n.other;
893             node->subpath = newsubpath;
894             sp->nodes = g_list_remove(sp->nodes, node);
895             newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
896         }
899         return newnode;
900     }
903 /**
904  * Duplicate node and connect to neighbours.
905  */
906 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
908     g_assert(node);
909     g_assert(node->subpath);
910     g_assert(g_list_find(node->subpath->nodes, node));
912    Inkscape::NodePath::SubPath *sp = node->subpath;
914     NRPathcode code = (NRPathcode) node->code;
915     if (code == NR_MOVETO) { // if node is the endnode,
916         node->code = NR_LINETO; // new one is inserted before it, so change that to line
917     }
919     Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
921     if (!node->n.other || !node->p.other) // if node is an endnode, select it
922         return node;
923     else
924         return newnode; // otherwise select the newly created node
927 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
929     node->p.pos = (node->pos + (node->pos - node->n.pos));
932 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
934     node->n.pos = (node->pos + (node->pos - node->p.pos));
937 /**
938  * Change line type at node, with side effects on neighbours.
939  */
940 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
942     g_assert(end);
943     g_assert(end->subpath);
944     g_assert(end->p.other);
946     if (end->code == static_cast< guint > ( code ) )
947         return;
949    Inkscape::NodePath::Node *start = end->p.other;
951     end->code = code;
953     if (code == NR_LINETO) {
954         if (start->code == NR_LINETO) {
955             sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
956         }
957         if (end->n.other) {
958             if (end->n.other->code == NR_LINETO) {
959                 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
960             }
961         }
962     } else {
963         NR::Point delta = end->pos - start->pos;
964         start->n.pos = start->pos + delta / 3;
965         end->p.pos = end->pos - delta / 3;
966         sp_node_adjust_handle(start, 1);
967         sp_node_adjust_handle(end, -1);
968     }
970     sp_node_update_handles(start);
971     sp_node_update_handles(end);
974 /**
975  * Change node type, and its handles accordingly.
976  */
977 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
979     g_assert(node);
980     g_assert(node->subpath);
982     if ((node->p.other != NULL) && (node->n.other != NULL)) {
983         if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
984             type =Inkscape::NodePath::NODE_CUSP;
985         }
986     }
988     node->type = type;
990     if (node->type == Inkscape::NodePath::NODE_CUSP) {
991         node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
992         node->knot->setSize (node->selected? 11 : 9);
993         sp_knot_update_ctrl(node->knot);
994     } else {
995         node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
996         node->knot->setSize (node->selected? 9 : 7);
997         sp_knot_update_ctrl(node->knot);
998     }
1000     // if one of handles is mouseovered, preserve its position
1001     if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1002         sp_node_adjust_handle(node, 1);
1003     } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1004         sp_node_adjust_handle(node, -1);
1005     } else {
1006         sp_node_adjust_handles(node);
1007     }
1009     sp_node_update_handles(node);
1011     sp_nodepath_update_statusbar(node->subpath->nodepath);
1013     return node;
1016 bool
1017 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1019         Inkscape::NodePath::Node *othernode = side->other;
1020         if (!othernode)
1021             return false;
1022         NRPathcode const code = sp_node_path_code_from_side(node, side);
1023         if (code == NR_LINETO)
1024             return true;
1025         Inkscape::NodePath::NodeSide *other_to_me = NULL;
1026         if (&node->p == side) {
1027             other_to_me = &othernode->n;
1028         } else if (&node->n == side) {
1029             other_to_me = &othernode->p;
1030         } 
1031         if (!other_to_me)
1032             return false;
1033         bool is_line = 
1034              (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1035               NR::L2(node->pos - side->pos) < 1e-6);
1036         return is_line;
1039 /**
1040  * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1041  * lines to curves.  If adjacent to one line segment, pulls out or rotates opposite handle to align
1042  * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too. 
1043  * If already cusp and set to cusp, retracts handles.
1044 */
1045 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1047     if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1049 /* 
1050   Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1051  
1052         if (two_handles) {
1053             // do nothing, adjust_handles called via set_node_type will line them up
1054         } else if (one_handle) {
1055             if (opposite_to_handle_is_line) {
1056                 if (lined_up) {
1057                     // already half-smooth; pull opposite handle too making it fully smooth
1058                 } else {
1059                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1060                 }
1061             } else {
1062                 // pull opposite handle in line with the existing one
1063             }
1064         } else if (no_handles) {
1065             if (both_segments_are_lines OR both_segments_are_curves) {
1066                 //pull both handles
1067             } else {
1068                 // pull the handle opposite to line segment, making node half-smooth
1069             }
1070         }
1071 */
1072         bool p_has_handle = (NR::L2(node->pos  - node->p.pos) > 1e-6);
1073         bool n_has_handle = (NR::L2(node->pos  - node->n.pos) > 1e-6);
1074         bool p_is_line = sp_node_side_is_line(node, &node->p);
1075         bool n_is_line = sp_node_side_is_line(node, &node->n);
1077         if (p_has_handle && n_has_handle) {
1078             // do nothing, adjust_handles will line them up
1079         } else if (p_has_handle || n_has_handle) {
1080             if (p_has_handle && n_is_line) {
1081                 Radial line (node->n.other->pos - node->pos);
1082                 Radial handle (node->pos - node->p.pos);
1083                 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1084                     // already half-smooth; pull opposite handle too making it fully smooth
1085                     node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1086                 } else {
1087                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1088                 }
1089             } else if (n_has_handle && p_is_line) {
1090                 Radial line (node->p.other->pos - node->pos);
1091                 Radial handle (node->pos - node->n.pos);
1092                 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1093                     // already half-smooth; pull opposite handle too making it fully smooth
1094                     node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1095                 } else {
1096                     // do nothing, adjust_handles will line the handle  up, producing a half-smooth node
1097                 }
1098             } else if (p_has_handle && node->n.other) {
1099                 // pull n handle
1100                 node->n.other->code = NR_CURVETO;
1101                 double len =  (type == Inkscape::NodePath::NODE_SYMM)?
1102                     NR::L2(node->p.pos - node->pos) :
1103                     NR::L2(node->n.other->pos - node->pos) / 3;
1104                 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1105             } else if (n_has_handle && node->p.other) {
1106                 // pull p handle
1107                 node->code = NR_CURVETO;
1108                 double len =  (type == Inkscape::NodePath::NODE_SYMM)?
1109                     NR::L2(node->n.pos - node->pos) :
1110                     NR::L2(node->p.other->pos - node->pos) / 3;
1111                 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1112             }
1113         } else if (!p_has_handle && !n_has_handle) {
1114             if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1115                 // no handles, but both segments are either lnes or curves:
1116                 //pull both handles
1118                 // convert both to curves:
1119                 node->code = NR_CURVETO;
1120                 node->n.other->code = NR_CURVETO;
1122                 NR::Point leg_prev = node->pos - node->p.other->pos;
1123                 NR::Point leg_next = node->pos - node->n.other->pos;
1125                 double norm_leg_prev = L2(leg_prev);
1126                 double norm_leg_next = L2(leg_next);
1128                 NR::Point delta;
1129                 if (norm_leg_next > 0.0) {
1130                     delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1131                     (&delta)->normalize();
1132                 }
1134                 if (type == Inkscape::NodePath::NODE_SYMM) {
1135                     double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1136                     node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1137                     node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1138                 } else {
1139                     // length of handle is proportional to distance to adjacent node
1140                     node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1141                     node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1142                 }
1144             } else {
1145                 // pull the handle opposite to line segment, making it half-smooth
1146                 if (p_is_line && node->n.other) {
1147                     if (type != Inkscape::NodePath::NODE_SYMM) {
1148                         // pull n handle
1149                         node->n.other->code = NR_CURVETO;
1150                         double len =  NR::L2(node->n.other->pos - node->pos) / 3;
1151                         node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1152                     }
1153                 } else if (n_is_line && node->p.other) {
1154                     if (type != Inkscape::NodePath::NODE_SYMM) {
1155                         // pull p handle
1156                         node->code = NR_CURVETO;
1157                         double len =  NR::L2(node->p.other->pos - node->pos) / 3;
1158                         node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1159                     }
1160                 }
1161             }
1162         }
1163     } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1164         // cusping a cusp: retract nodes
1165         node->p.pos = node->pos;
1166         node->n.pos = node->pos;
1167     }
1169     sp_nodepath_set_node_type (node, type);
1172 /**
1173  * Move node to point, and adjust its and neighbouring handles.
1174  */
1175 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1177     NR::Point delta = p - node->pos;
1178     node->pos = p;
1180     node->p.pos += delta;
1181     node->n.pos += delta;
1183     Inkscape::NodePath::Node *node_p = NULL;
1184     Inkscape::NodePath::Node *node_n = NULL;
1186     if (node->p.other) {
1187         if (node->code == NR_LINETO) {
1188             sp_node_adjust_handle(node, 1);
1189             sp_node_adjust_handle(node->p.other, -1);
1190             node_p = node->p.other;
1191         }
1192     }
1193     if (node->n.other) {
1194         if (node->n.other->code == NR_LINETO) {
1195             sp_node_adjust_handle(node, -1);
1196             sp_node_adjust_handle(node->n.other, 1);
1197             node_n = node->n.other;
1198         }
1199     }
1201     // this function is only called from batch movers that will update display at the end
1202     // themselves, so here we just move all the knots without emitting move signals, for speed
1203     sp_node_update_handles(node, false);
1204     if (node_n) {
1205         sp_node_update_handles(node_n, false);
1206     }
1207     if (node_p) {
1208         sp_node_update_handles(node_p, false);
1209     }
1212 /**
1213  * Call sp_node_moveto() for node selection and handle possible snapping.
1214  */
1215 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1216                                             bool const snap, bool constrained = false, 
1217                                             Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1219     NR::Coord best = NR_HUGE;
1220     NR::Point delta(dx, dy);
1221     NR::Point best_pt = delta;
1222     Inkscape::SnappedPoint best_abs;
1223     
1224     if (snap) {    
1225         /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1226          * not to itself. The snapper however can not tell which nodes are selected and which are not, so we 
1227          * must provide that information. */
1228           
1229         // Build a list of the unselected nodes to which the snapper should snap 
1230         std::vector<NR::Point> unselected_nodes;
1231         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1232             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1233             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1234                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1235                 if (!node->selected) {
1236                     unselected_nodes.push_back(node->pos);
1237                 }    
1238             }
1239         }        
1240         
1241         SnapManager &m = nodepath->desktop->namedview->snap_manager;
1242         
1243         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1244             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1245             m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1246             Inkscape::SnappedPoint s;
1247             if (constrained) {
1248                 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1249                 dedicated_constraint.setPoint(n->pos);
1250                 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1251             } else {
1252                 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1253             }            
1254             if (s.getSnapped() && (s.getDistance() < best)) {
1255                 best = s.getDistance();
1256                 best_abs = s;
1257                 best_pt = s.getPoint() - n->pos;
1258             }
1259         }
1260                         
1261         if (best_abs.getSnapped()) {
1262             nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1263         } else {
1264             nodepath->desktop->snapindicator->remove_snappoint();    
1265         }
1266     }
1268     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1269         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1270         sp_node_moveto(n, n->pos + best_pt);
1271     }
1273     // do not update repr here so that node dragging is acceptably fast
1274     update_object(nodepath);
1277 /**
1278 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1279 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1280 near x = 0.
1281  */
1282 double
1283 sculpt_profile (double x, double alpha, guint profile)
1285     if (x >= 1)
1286         return 0;
1287     if (x <= 0)
1288         return 1;
1290     switch (profile) {
1291         case SCULPT_PROFILE_LINEAR:
1292         return 1 - x;
1293         case SCULPT_PROFILE_BELL:
1294         return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1295         case SCULPT_PROFILE_ELLIPTIC:
1296         return sqrt(1 - x*x);
1297     }
1299     return 1;
1302 double
1303 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1305     // extremely primitive for now, don't have time to look for the real one
1306     double lower = NR::L2(b - a);
1307     double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1308     return (lower + upper)/2;
1311 void
1312 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1314     n->pos = n->origin + delta;
1315     n->n.pos = n->n.origin + delta_n;
1316     n->p.pos = n->p.origin + delta_p;
1317     sp_node_adjust_handles(n);
1318     sp_node_update_handles(n, false);
1321 /**
1322  * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1323  * on how far they are from the dragged node n.
1324  */
1325 static void
1326 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1328     g_assert (n);
1329     g_assert (nodepath);
1330     g_assert (n->subpath->nodepath == nodepath);
1332     double pressure = n->knot->pressure;
1333     if (pressure == 0)
1334         pressure = 0.5; // default
1335     pressure = CLAMP (pressure, 0.2, 0.8);
1337     // map pressure to alpha = 1/5 ... 5
1338     double alpha = 1 - 2 * fabs(pressure - 0.5);
1339     if (pressure > 0.5)
1340         alpha = 1/alpha;
1342     guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1344     if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1345         // Only one subpath has selected nodes:
1346         // use linear mode, where the distance from n to node being dragged is calculated along the path
1348         double n_sel_range = 0, p_sel_range = 0;
1349         guint n_nodes = 0, p_nodes = 0;
1350         guint n_sel_nodes = 0, p_sel_nodes = 0;
1352         // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1353         {
1354             double n_range = 0, p_range = 0;
1355             bool n_going = true, p_going = true;
1356             Inkscape::NodePath::Node *n_node = n;
1357             Inkscape::NodePath::Node *p_node = n;
1358             do {
1359                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1360                 if (n_node && n_going)
1361                     n_node = n_node->n.other;
1362                 if (n_node == NULL) {
1363                     n_going = false;
1364                 } else {
1365                     n_nodes ++;
1366                     n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1367                     if (n_node->selected) {
1368                         n_sel_nodes ++;
1369                         n_sel_range = n_range;
1370                     }
1371                     if (n_node == p_node) {
1372                         n_going = false;
1373                         p_going = false;
1374                     }
1375                 }
1376                 if (p_node && p_going)
1377                     p_node = p_node->p.other;
1378                 if (p_node == NULL) {
1379                     p_going = false;
1380                 } else {
1381                     p_nodes ++;
1382                     p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1383                     if (p_node->selected) {
1384                         p_sel_nodes ++;
1385                         p_sel_range = p_range;
1386                     }
1387                     if (p_node == n_node) {
1388                         n_going = false;
1389                         p_going = false;
1390                     }
1391                 }
1392             } while (n_going || p_going);
1393         }
1395         // Second pass: actually move nodes in this subpath
1396         sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1397         {
1398             double n_range = 0, p_range = 0;
1399             bool n_going = true, p_going = true;
1400             Inkscape::NodePath::Node *n_node = n;
1401             Inkscape::NodePath::Node *p_node = n;
1402             do {
1403                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1404                 if (n_node && n_going)
1405                     n_node = n_node->n.other;
1406                 if (n_node == NULL) {
1407                     n_going = false;
1408                 } else {
1409                     n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1410                     if (n_node->selected) {
1411                         sp_nodepath_move_node_and_handles (n_node,
1412                                                            sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1413                                                            sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1414                                                            sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1415                     }
1416                     if (n_node == p_node) {
1417                         n_going = false;
1418                         p_going = false;
1419                     }
1420                 }
1421                 if (p_node && p_going)
1422                     p_node = p_node->p.other;
1423                 if (p_node == NULL) {
1424                     p_going = false;
1425                 } else {
1426                     p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1427                     if (p_node->selected) {
1428                         sp_nodepath_move_node_and_handles (p_node,
1429                                                            sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1430                                                            sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1431                                                            sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1432                     }
1433                     if (p_node == n_node) {
1434                         n_going = false;
1435                         p_going = false;
1436                     }
1437                 }
1438             } while (n_going || p_going);
1439         }
1441     } else {
1442         // Multiple subpaths have selected nodes:
1443         // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1444         // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1445         // fix the pear-like shape when sculpting e.g. a ring
1447         // First pass: calculate range
1448         gdouble direct_range = 0;
1449         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1450             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1451             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1452                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1453                 if (node->selected) {
1454                     direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1455                 }
1456             }
1457         }
1459         // Second pass: actually move nodes
1460         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1461             Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1462             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1463                 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1464                 if (node->selected) {
1465                     if (direct_range > 1e-6) {
1466                         sp_nodepath_move_node_and_handles (node,
1467                                                        sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1468                                                        sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1469                                                        sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1470                     } else {
1471                         sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1472                     }
1474                 }
1475             }
1476         }
1477     }
1479     // do not update repr here so that node dragging is acceptably fast
1480     update_object(nodepath);
1484 /**
1485  * Move node selection to point, adjust its and neighbouring handles,
1486  * handle possible snapping, and commit the change with possible undo.
1487  */
1488 void
1489 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1491     if (!nodepath) return;
1493     sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1495     if (dx == 0) {
1496         sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1497     } else if (dy == 0) {
1498         sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1499     } else {
1500         sp_nodepath_update_repr(nodepath, _("Move nodes"));
1501     }
1504 /**
1505  * Move node selection off screen and commit the change.
1506  */
1507 void
1508 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1510     // borrowed from sp_selection_move_screen in selection-chemistry.c
1511     // we find out the current zoom factor and divide deltas by it
1512     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1514     gdouble zoom = desktop->current_zoom();
1515     gdouble zdx = dx / zoom;
1516     gdouble zdy = dy / zoom;
1518     if (!nodepath) return;
1520     sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1522     if (dx == 0) {
1523         sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1524     } else if (dy == 0) {
1525         sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1526     } else {
1527         sp_nodepath_update_repr(nodepath, _("Move nodes"));
1528     }
1531 /**
1532  * Move selected nodes to the absolute position given
1533  */
1534 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1536     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1537         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1538         NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1539         sp_node_moveto(n, npos);
1540     }
1542     sp_nodepath_update_repr(nodepath, _("Move nodes"));
1545 /**
1546  * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1547  */
1548 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1550     NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1551     g_return_val_if_fail(nodepath->selected, no_coord);
1553     // determine coordinate of first selected node
1554     GList *nsel = nodepath->selected;
1555     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1556     NR::Coord coord = n->pos[axis];
1557     bool coincide = true;
1559     // compare it to the coordinates of all the other selected nodes
1560     for (GList *l = nsel->next; l != NULL; l = l->next) {
1561         n = (Inkscape::NodePath::Node *) l->data;
1562         if (n->pos[axis] != coord) {
1563             coincide = false;
1564         }
1565     }
1566     if (coincide) {
1567         return coord;
1568     } else {
1569         NR::Rect bbox = sp_node_selected_bbox(nodepath);
1570         // currently we return the coordinate of the bounding box midpoint because I don't know how
1571         // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1572         return bbox.midpoint()[axis];
1573     }
1576 /** If they don't yet exist, creates knot and line for the given side of the node */
1577 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1579     if (!side->knot) {
1580         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"));
1582         side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1583         side->knot->setSize (7);
1584         side->knot->setAnchor (GTK_ANCHOR_CENTER);
1585         side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1586         side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1587         sp_knot_update_ctrl(side->knot);
1589         g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1590         g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1591         g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1592         g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1593         g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1594         g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1595     }
1597     if (!side->line) {
1598         side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1599                                         SP_TYPE_CTRLLINE, NULL);
1600     }
1603 /**
1604  * Ensure the given handle of the node is visible/invisible, update its screen position
1605  */
1606 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1608     g_assert(node != NULL);
1610    Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1611     NRPathcode code = sp_node_path_code_from_side(node, side);
1613     show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1615     if (show_handle) {
1616         if (!side->knot) { // No handle knot at all
1617             sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1618             // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1619             side->knot->pos = side->pos;
1620             if (side->knot->item)
1621                 SP_CTRL(side->knot->item)->moveto(side->pos);
1622             sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1623             sp_knot_show(side->knot);
1624         } else {
1625             if (side->knot->pos != side->pos) { // only if it's really moved
1626                 if (fire_move_signals) {
1627                     sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1628                 } else {
1629                     sp_knot_moveto(side->knot, &side->pos);
1630                     sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1631                 }
1632             }
1633             if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1634                 sp_knot_show(side->knot);
1635             }
1636         }
1637         sp_canvas_item_show(side->line);
1638     } else {
1639         if (side->knot) {
1640             if (SP_KNOT_IS_VISIBLE(side->knot)) {
1641                 sp_knot_hide(side->knot);
1642             }
1643         }
1644         if (side->line) {
1645             sp_canvas_item_hide(side->line);
1646         }
1647     }
1650 /**
1651  * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1652  * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1653  * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1654  * updated; otherwise, just move the knots silently (used in batch moves).
1655  */
1656 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1658     g_assert(node != NULL);
1660     if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1661         sp_knot_show(node->knot);
1662     }
1664     if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1665         if (fire_move_signals)
1666             sp_knot_set_position(node->knot, &node->pos, 0);
1667         else
1668             sp_knot_moveto(node->knot, &node->pos);
1669     }
1671     gboolean show_handles = node->selected;
1672     if (node->p.other != NULL) {
1673         if (node->p.other->selected) show_handles = TRUE;
1674     }
1675     if (node->n.other != NULL) {
1676         if (node->n.other->selected) show_handles = TRUE;
1677     }
1679     if (node->subpath->nodepath->show_handles == false)
1680         show_handles = FALSE;
1682     sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1683     sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1686 /**
1687  * Call sp_node_update_handles() for all nodes on subpath.
1688  */
1689 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1691     g_assert(subpath != NULL);
1693     for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1694         sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1695     }
1698 /**
1699  * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1700  */
1701 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1703     g_assert(nodepath != NULL);
1705     for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1706         sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1707     }
1710 void
1711 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1713     if (nodepath == NULL) return;
1715     nodepath->show_handles = show;
1716     sp_nodepath_update_handles(nodepath);
1719 /**
1720  * Adds all selected nodes in nodepath to list.
1721  */
1722 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1724     StlConv<Node *>::list(l, selected);
1725 /// \todo this adds a copying, rework when the selection becomes a stl list
1728 /**
1729  * Align selected nodes on the specified axis.
1730  */
1731 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1733     if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1734         return;
1735     }
1737     if ( !nodepath->selected->next ) { // only one node selected
1738         return;
1739     }
1740    Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1741     NR::Point dest(pNode->pos);
1742     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1743         pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1744         if (pNode) {
1745             dest[axis] = pNode->pos[axis];
1746             sp_node_moveto(pNode, dest);
1747         }
1748     }
1750     sp_nodepath_update_repr(nodepath, _("Align nodes"));
1753 /// Helper struct.
1754 struct NodeSort
1756    Inkscape::NodePath::Node *_node;
1757     NR::Coord _coord;
1758     /// \todo use vectorof pointers instead of calling copy ctor
1759     NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1760         _node(node), _coord(node->pos[axis])
1761     {}
1763 };
1765 static bool operator<(NodeSort const &a, NodeSort const &b)
1767     return (a._coord < b._coord);
1770 /**
1771  * Distribute selected nodes on the specified axis.
1772  */
1773 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1775     if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1776         return;
1777     }
1779     if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1780         return;
1781     }
1783    Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1784     std::vector<NodeSort> sorted;
1785     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1786         pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1787         if (pNode) {
1788             NodeSort n(pNode, axis);
1789             sorted.push_back(n);
1790             //dest[axis] = pNode->pos[axis];
1791             //sp_node_moveto(pNode, dest);
1792         }
1793     }
1794     std::sort(sorted.begin(), sorted.end());
1795     unsigned int len = sorted.size();
1796     //overall bboxes span
1797     float dist = (sorted.back()._coord -
1798                   sorted.front()._coord);
1799     //new distance between each bbox
1800     float step = (dist) / (len - 1);
1801     float pos = sorted.front()._coord;
1802     for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1803           it < sorted.end();
1804           it ++ )
1805     {
1806         NR::Point dest((*it)._node->pos);
1807         dest[axis] = pos;
1808         sp_node_moveto((*it)._node, dest);
1809         pos += step;
1810     }
1812     sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1816 /**
1817  * Call sp_nodepath_line_add_node() for all selected segments.
1818  */
1819 void
1820 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1822     if (!nodepath) {
1823         return;
1824     }
1826     GList *nl = NULL;
1828     int n_added = 0;
1830     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1831        Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1832         g_assert(t->selected);
1833         if (t->p.other && t->p.other->selected) {
1834             nl = g_list_prepend(nl, t);
1835         }
1836     }
1838     while (nl) {
1839        Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1840        Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1841        sp_nodepath_node_select(n, TRUE, FALSE);
1842        n_added ++;
1843        nl = g_list_remove(nl, t);
1844     }
1846     /** \todo fixme: adjust ? */
1847     sp_nodepath_update_handles(nodepath);
1849     if (n_added > 1) {
1850         sp_nodepath_update_repr(nodepath, _("Add nodes"));
1851     } else if (n_added > 0) {
1852         sp_nodepath_update_repr(nodepath, _("Add node"));
1853     }
1855     sp_nodepath_update_statusbar(nodepath);
1858 /**
1859  * Select segment nearest to point
1860  */
1861 void
1862 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1864     if (!nodepath) {
1865         return;
1866     }
1868     SPCurve *curve = create_curve(nodepath);   // perhaps we can use nodepath->curve here instead?
1869     Geom::PathVector const &pathv = curve->get_pathvector();
1870     Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1872     // calculate index for nodepath's representation.
1873     unsigned int segment_index = floor(pvpos.t) + 1;
1874     for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1875         segment_index += pathv[i].size() + 1;
1876         if (pathv[i].closed()) {
1877             segment_index += 1;
1878         }
1879     }
1881     curve->unref();
1883     //find segment to segment
1884     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1886     //fixme: this can return NULL, so check before proceeding.
1887     g_return_if_fail(e != NULL);
1889     gboolean force = FALSE;
1890     if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1891         force = TRUE;
1892     }
1893     sp_nodepath_node_select(e, (gboolean) toggle, force);
1894     if (e->p.other)
1895         sp_nodepath_node_select(e->p.other, TRUE, force);
1897     sp_nodepath_update_handles(nodepath);
1899     sp_nodepath_update_statusbar(nodepath);
1902 /**
1903  * Add a node nearest to point
1904  */
1905 void
1906 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1908     if (!nodepath) {
1909         return;
1910     }
1912     SPCurve *curve = create_curve(nodepath);   // perhaps we can use nodepath->curve here instead?
1913     Geom::PathVector const &pathv = curve->get_pathvector();
1914     Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1916     // calculate index for nodepath's representation.
1917     double int_part;
1918     double t = std::modf(pvpos.t, &int_part);
1919     unsigned int segment_index = (unsigned int)int_part + 1;
1920     for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1921         segment_index += pathv[i].size() + 1;
1922         if (pathv[i].closed()) {
1923             segment_index += 1;
1924         }
1925     }
1927     curve->unref();
1929     //find segment to split
1930     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1932     //don't know why but t seems to flip for lines
1933     if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1934         t = 1.0 - t;
1935     }
1937     Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
1938     sp_nodepath_node_select(n, FALSE, TRUE);
1940     /* fixme: adjust ? */
1941     sp_nodepath_update_handles(nodepath);
1943     sp_nodepath_update_repr(nodepath, _("Add node"));
1945     sp_nodepath_update_statusbar(nodepath);
1948 /*
1949  * Adjusts a segment so that t moves by a certain delta for dragging
1950  * converts lines to curves
1951  *
1952  * method and idea borrowed from Simon Budig  <simon@gimp.org> and the GIMP
1953  * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1954  */
1955 void
1956 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1958     Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1960     //fixme: e and e->p can be NULL, so check for those before proceeding
1961     g_return_if_fail(e != NULL);
1962     g_return_if_fail(&e->p != NULL);
1964     /* feel good is an arbitrary parameter that distributes the delta between handles
1965      * if t of the drag point is less than 1/6 distance form the endpoint only
1966      * the corresponding hadle is adjusted. This matches the behavior in GIMP
1967      */
1968     double feel_good;
1969     if (t <= 1.0 / 6.0)
1970         feel_good = 0;
1971     else if (t <= 0.5)
1972         feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1973     else if (t <= 5.0 / 6.0)
1974         feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1975     else
1976         feel_good = 1;
1978     //if we're dragging a line convert it to a curve
1979     if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1980         sp_nodepath_set_line_type(e, NR_CURVETO);
1981     }
1983     NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1984     NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1985     e->p.other->n.pos += offsetcoord0;
1986     e->p.pos += offsetcoord1;
1988     // adjust handles of adjacent nodes where necessary
1989     sp_node_adjust_handle(e,1);
1990     sp_node_adjust_handle(e->p.other,-1);
1992     sp_nodepath_update_handles(e->subpath->nodepath);
1994     update_object(e->subpath->nodepath);
1996     sp_nodepath_update_statusbar(e->subpath->nodepath);
2000 /**
2001  * Call sp_nodepath_break() for all selected segments.
2002  */
2003 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2005     if (!nodepath) return;
2007     GList *tempin = g_list_copy(nodepath->selected);
2008     GList *temp = NULL;
2009     for (GList *l = tempin; l != NULL; l = l->next) {
2010        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2011        Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2012         if (nn == NULL) continue; // no break, no new node
2013         temp = g_list_prepend(temp, nn);
2014     }
2015     g_list_free(tempin);
2017     if (temp) {
2018         sp_nodepath_deselect(nodepath);
2019     }
2020     for (GList *l = temp; l != NULL; l = l->next) {
2021         sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2022     }
2024     sp_nodepath_update_handles(nodepath);
2026     sp_nodepath_update_repr(nodepath, _("Break path"));
2029 /**
2030  * Duplicate the selected node(s).
2031  */
2032 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2034     if (!nodepath) {
2035         return;
2036     }
2038     GList *temp = NULL;
2039     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2040        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2041        Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2042         if (nn == NULL) continue; // could not duplicate
2043         temp = g_list_prepend(temp, nn);
2044     }
2046     if (temp) {
2047         sp_nodepath_deselect(nodepath);
2048     }
2049     for (GList *l = temp; l != NULL; l = l->next) {
2050         sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2051     }
2053     sp_nodepath_update_handles(nodepath);
2055     sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2058 /**
2059  *  Internal function to join two nodes by merging them into one.
2060  */
2061 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2063     /* a and b are endpoints */
2065     // if one of the two nodes is mouseovered, fix its position
2066     NR::Point c;
2067     if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2068         c = a->pos;
2069     } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2070         c = b->pos;
2071     } else {
2072         // otherwise, move joined node to the midpoint
2073         c = (a->pos + b->pos) / 2;
2074     }
2076     if (a->subpath == b->subpath) {
2077        Inkscape::NodePath::SubPath *sp = a->subpath;
2078         sp_nodepath_subpath_close(sp);
2079         sp_node_moveto (sp->first, c);
2081         sp_nodepath_update_handles(sp->nodepath);
2082         sp_nodepath_update_repr(nodepath, _("Close subpath"));
2083         return;
2084     }
2086     /* a and b are separate subpaths */
2087     Inkscape::NodePath::SubPath *sa = a->subpath;
2088     Inkscape::NodePath::SubPath *sb = b->subpath;
2089     NR::Point p;
2090     Inkscape::NodePath::Node *n;
2091     NRPathcode code;
2092     if (a == sa->first) {
2093         // we will now reverse sa, so that a is its last node, not first, and drop that node
2094         p = sa->first->n.pos;
2095         code = (NRPathcode)sa->first->n.other->code;
2096         // create new subpath
2097        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2098        // create a first moveto node on it
2099         n = sa->last;
2100         sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2101         n = n->p.other;
2102         if (n == sa->first) n = NULL;
2103         while (n) {
2104             // copy the rest of the nodes from sa to t, going backwards
2105             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2106             n = n->p.other;
2107             if (n == sa->first) n = NULL;
2108         }
2109         // replace sa with t
2110         sp_nodepath_subpath_destroy(sa);
2111         sa = t;
2112     } else if (a == sa->last) {
2113         // a is already last, just drop it
2114         p = sa->last->p.pos;
2115         code = (NRPathcode)sa->last->code;
2116         sp_nodepath_node_destroy(sa->last);
2117     } else {
2118         code = NR_END;
2119         g_assert_not_reached();
2120     }
2122     if (b == sb->first) {
2123         // copy all nodes from b to a, forward 
2124         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2125         for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2126             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2127         }
2128     } else if (b == sb->last) {
2129         // copy all nodes from b to a, backward 
2130         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2131         for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2132             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2133         }
2134     } else {
2135         g_assert_not_reached();
2136     }
2137     /* and now destroy sb */
2139     sp_nodepath_subpath_destroy(sb);
2141     sp_nodepath_update_handles(sa->nodepath);
2143     sp_nodepath_update_repr(nodepath, _("Join nodes"));
2145     sp_nodepath_update_statusbar(nodepath);
2148 /**
2149  *  Internal function to join two nodes by adding a segment between them.
2150  */
2151 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2153     if (a->subpath == b->subpath) {
2154        Inkscape::NodePath::SubPath *sp = a->subpath;
2156         /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2157         sp->closed = TRUE;
2159         sp->first->p.other = sp->last;
2160         sp->last->n.other  = sp->first;
2162         sp_node_handle_mirror_p_to_n(sp->last);
2163         sp_node_handle_mirror_n_to_p(sp->first);
2165         sp->first->code = sp->last->code;
2166         sp->first       = sp->last;
2168         sp_nodepath_update_handles(sp->nodepath);
2170         sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2172         return;
2173     }
2175     /* a and b are separate subpaths */
2176     Inkscape::NodePath::SubPath *sa = a->subpath;
2177     Inkscape::NodePath::SubPath *sb = b->subpath;
2179     Inkscape::NodePath::Node *n;
2180     NR::Point p;
2181     NRPathcode code;
2182     if (a == sa->first) {
2183         code = (NRPathcode) sa->first->n.other->code;
2184        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2185         n = sa->last;
2186         sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2187         for (n = n->p.other; n != NULL; n = n->p.other) {
2188             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2189         }
2190         sp_nodepath_subpath_destroy(sa);
2191         sa = t;
2192     } else if (a == sa->last) {
2193         code = (NRPathcode)sa->last->code;
2194     } else {
2195         code = NR_END;
2196         g_assert_not_reached();
2197     }
2199     if (b == sb->first) {
2200         n = sb->first;
2201         sp_node_handle_mirror_p_to_n(sa->last);
2202         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2203         sp_node_handle_mirror_n_to_p(sa->last);
2204         for (n = n->n.other; n != NULL; n = n->n.other) {
2205             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2206         }
2207     } else if (b == sb->last) {
2208         n = sb->last;
2209         sp_node_handle_mirror_p_to_n(sa->last);
2210         sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2211         sp_node_handle_mirror_n_to_p(sa->last);
2212         for (n = n->p.other; n != NULL; n = n->p.other) {
2213             sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2214         }
2215     } else {
2216         g_assert_not_reached();
2217     }
2218     /* and now destroy sb */
2220     sp_nodepath_subpath_destroy(sb);
2222     sp_nodepath_update_handles(sa->nodepath);
2224     sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2227 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2229 /**
2230  * Internal function to handle joining two nodes.
2231  */
2232 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2234     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2236     if (g_list_length(nodepath->selected) != 2) {
2237         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2238         return;
2239     }
2241     Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2242     Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2244     g_assert(a != b);
2245     if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2246         // someone tried to join an orphan node (i.e. a single-node subpath).
2247         // this is not worth an error message, just fail silently.
2248         return;
2249     }
2251     if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2252         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2253         return;
2254     }
2256     switch(mode) {
2257         case NODE_JOIN_ENDPOINTS:
2258             do_node_selected_join(nodepath, a, b);
2259             break;
2260         case NODE_JOIN_SEGMENT:
2261             do_node_selected_join_segment(nodepath, a, b);
2262             break;
2263     }
2266 /**
2267  *  Join two nodes by merging them into one.
2268  */
2269 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2271     node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2274 /**
2275  *  Join two nodes by adding a segment between them.
2276  */
2277 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2279     node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2282 /**
2283  * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2284  */
2285 void sp_node_delete_preserve(GList *nodes_to_delete)
2287     GSList *nodepaths = NULL;
2289     while (nodes_to_delete) {
2290         Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2291         Inkscape::NodePath::SubPath *sp = node->subpath;
2292         Inkscape::NodePath::Path *nodepath = sp->nodepath;
2293         Inkscape::NodePath::Node *sample_cursor = NULL;
2294         Inkscape::NodePath::Node *sample_end = NULL;
2295         Inkscape::NodePath::Node *delete_cursor = node;
2296         bool just_delete = false;
2298         //find the start of this contiguous selection
2299         //move left to the first node that is not selected
2300         //or the start of the non-closed path
2301         for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2302             delete_cursor = curr;
2303         }
2305         //just delete at the beginning of an open path
2306         if (!delete_cursor->p.other) {
2307             sample_cursor = delete_cursor;
2308             just_delete = true;
2309         } else {
2310             sample_cursor = delete_cursor->p.other;
2311         }
2313         //calculate points for each segment
2314         int rate = 5;
2315         float period = 1.0 / rate;
2316         std::vector<NR::Point> data;
2317         if (!just_delete) {
2318             data.push_back(sample_cursor->pos);
2319             for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2320                 //just delete at the end of an open path
2321                 if (!sp->closed && curr == sp->last) {
2322                     just_delete = true;
2323                     break;
2324                 }
2326                 //sample points on the contiguous selected segment
2327                 NR::Point *bez;
2328                 bez = new NR::Point [4];
2329                 bez[0] = curr->pos;
2330                 bez[1] = curr->n.pos;
2331                 bez[2] = curr->n.other->p.pos;
2332                 bez[3] = curr->n.other->pos;
2333                 for (int i=1; i<rate; i++) {
2334                     gdouble t = i * period;
2335                     NR::Point p = bezier_pt(3, bez, t);
2336                     data.push_back(p);
2337                 }
2338                 data.push_back(curr->n.other->pos);
2340                 sample_end = curr->n.other;
2341                 //break if we've come full circle or hit the end of the selection
2342                 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2343                     break;
2344                 }
2345             }
2346         }
2348         if (!just_delete) {
2349             //calculate the best fitting single segment and adjust the endpoints
2350             NR::Point *adata;
2351             adata = new NR::Point [data.size()];
2352             copy(data.begin(), data.end(), adata);
2354             NR::Point *bez;
2355             bez = new NR::Point [4];
2356             //would decreasing error create a better fitting approximation?
2357             gdouble error = 1.0;
2358             gint ret;
2359             ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2361             //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2362             //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2363             //the resulting nodes behave as expected.
2364             if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2365                 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2366             if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2367                 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2369             //adjust endpoints
2370             sample_cursor->n.pos = bez[1];
2371             sample_end->p.pos = bez[2];
2372         }
2374         //destroy this contiguous selection
2375         while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2376             Inkscape::NodePath::Node *temp = delete_cursor;
2377             if (delete_cursor->n.other == delete_cursor) {
2378                 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2379                 delete_cursor = NULL;
2380             } else {
2381                 delete_cursor = delete_cursor->n.other;
2382             }
2383             nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2384             sp_nodepath_node_destroy(temp);
2385         }
2387         sp_nodepath_update_handles(nodepath);
2389         if (!g_slist_find(nodepaths, nodepath))
2390             nodepaths = g_slist_prepend (nodepaths, nodepath);
2391     }
2393     for (GSList *i = nodepaths; i; i = i->next) {
2394         // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2395         // different nodepaths will give us one undo event per nodepath
2396         Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2398         // if the entire nodepath is removed, delete the selected object.
2399         if (nodepath->subpaths == NULL ||
2400             //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2401             //at least 2
2402             sp_nodepath_get_node_count(nodepath) < 2) {
2403             SPDocument *document = sp_desktop_document (nodepath->desktop);
2404             //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2405             //delete this nodepath's object, not the entire selection! (though at this time, this
2406             //does not matter)
2407             sp_selection_delete();
2408             sp_document_done (document, SP_VERB_CONTEXT_NODE,
2409                               _("Delete nodes"));
2410         } else {
2411             sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2412             sp_nodepath_update_statusbar(nodepath);
2413         }
2414     }
2416     g_slist_free (nodepaths);
2419 /**
2420  * Delete one or more selected nodes.
2421  */
2422 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2424     if (!nodepath) return;
2425     if (!nodepath->selected) return;
2427     /** \todo fixme: do it the right way */
2428     while (nodepath->selected) {
2429        Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2430         sp_nodepath_node_destroy(node);
2431     }
2434     //clean up the nodepath (such as for trivial subpaths)
2435     sp_nodepath_cleanup(nodepath);
2437     sp_nodepath_update_handles(nodepath);
2439     // if the entire nodepath is removed, delete the selected object.
2440     if (nodepath->subpaths == NULL ||
2441         sp_nodepath_get_node_count(nodepath) < 2) {
2442         SPDocument *document = sp_desktop_document (nodepath->desktop);
2443         sp_selection_delete();
2444         sp_document_done (document, SP_VERB_CONTEXT_NODE,
2445                           _("Delete nodes"));
2446         return;
2447     }
2449     sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2451     sp_nodepath_update_statusbar(nodepath);
2454 /**
2455  * Delete one or more segments between two selected nodes.
2456  * This is the code for 'split'.
2457  */
2458 void
2459 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2461    Inkscape::NodePath::Node *start, *end;     //Start , end nodes.  not inclusive
2462    Inkscape::NodePath::Node *curr, *next;     //Iterators
2464     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2466     if (g_list_length(nodepath->selected) != 2) {
2467         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2468                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2469         return;
2470     }
2472     //Selected nodes, not inclusive
2473    Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2474    Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2476     if ( ( a==b)                       ||  //same node
2477          (a->subpath  != b->subpath )  ||  //not the same path
2478          (!a->p.other || !a->n.other)  ||  //one of a's sides does not have a segment
2479          (!b->p.other || !b->n.other) )    //one of b's sides does not have a segment
2480     {
2481         nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2482                                                  _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2483         return;
2484     }
2486     //###########################################
2487     //# BEGIN EDITS
2488     //###########################################
2489     //##################################
2490     //# CLOSED PATH
2491     //##################################
2492     if (a->subpath->closed) {
2495         gboolean reversed = FALSE;
2497         //Since we can go in a circle, we need to find the shorter distance.
2498         //  a->b or b->a
2499         start = end = NULL;
2500         int distance    = 0;
2501         int minDistance = 0;
2502         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2503             if (curr==b) {
2504                 //printf("a to b:%d\n", distance);
2505                 start = a;//go from a to b
2506                 end   = b;
2507                 minDistance = distance;
2508                 //printf("A to B :\n");
2509                 break;
2510             }
2511             distance++;
2512         }
2514         //try again, the other direction
2515         distance = 0;
2516         for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2517             if (curr==a) {
2518                 //printf("b to a:%d\n", distance);
2519                 if (distance < minDistance) {
2520                     start    = b;  //we go from b to a
2521                     end      = a;
2522                     reversed = TRUE;
2523                     //printf("B to A\n");
2524                 }
2525                 break;
2526             }
2527             distance++;
2528         }
2531         //Copy everything from 'end' to 'start' to a new subpath
2532        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2533         for (curr=end ; curr ; curr=curr->n.other) {
2534             NRPathcode code = (NRPathcode) curr->code;
2535             if (curr == end)
2536                 code = NR_MOVETO;
2537             sp_nodepath_node_new(t, NULL,
2538                                  (Inkscape::NodePath::NodeType)curr->type, code,
2539                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2540             if (curr == start)
2541                 break;
2542         }
2543         sp_nodepath_subpath_destroy(a->subpath);
2546     }
2550     //##################################
2551     //# OPEN PATH
2552     //##################################
2553     else {
2555         //We need to get the direction of the list between A and B
2556         //Can we walk from a to b?
2557         start = end = NULL;
2558         for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2559             if (curr==b) {
2560                 start = a;  //did it!  we go from a to b
2561                 end   = b;
2562                 //printf("A to B\n");
2563                 break;
2564             }
2565         }
2566         if (!start) {//didn't work?  let's try the other direction
2567             for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2568                 if (curr==a) {
2569                     start = b;  //did it!  we go from b to a
2570                     end   = a;
2571                     //printf("B to A\n");
2572                     break;
2573                 }
2574             }
2575         }
2576         if (!start) {
2577             nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2578                                                      _("Cannot find path between nodes."));
2579             return;
2580         }
2584         //Copy everything after 'end' to a new subpath
2585        Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2586         for (curr=end ; curr ; curr=curr->n.other) {
2587             NRPathcode code = (NRPathcode) curr->code;
2588             if (curr == end)
2589                 code = NR_MOVETO;
2590             sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2591                                  &curr->p.pos, &curr->pos, &curr->n.pos);
2592         }
2594         //Now let us do our deletion.  Since the tail has been saved, go all the way to the end of the list
2595         for (curr = start->n.other ; curr  ; curr=next) {
2596             next = curr->n.other;
2597             sp_nodepath_node_destroy(curr);
2598         }
2600     }
2601     //###########################################
2602     //# END EDITS
2603     //###########################################
2605     //clean up the nodepath (such as for trivial subpaths)
2606     sp_nodepath_cleanup(nodepath);
2608     sp_nodepath_update_handles(nodepath);
2610     sp_nodepath_update_repr(nodepath, _("Delete segment"));
2612     sp_nodepath_update_statusbar(nodepath);
2615 /**
2616  * Call sp_nodepath_set_line() for all selected segments.
2617  */
2618 void
2619 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2621     if (nodepath == NULL) return;
2623     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2624        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2625         g_assert(n->selected);
2626         if (n->p.other && n->p.other->selected) {
2627             sp_nodepath_set_line_type(n, code);
2628         }
2629     }
2631     sp_nodepath_update_repr(nodepath, _("Change segment type"));
2634 /**
2635  * Call sp_nodepath_convert_node_type() for all selected nodes.
2636  */
2637 void
2638 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2640     if (nodepath == NULL) return;
2642     if (nodepath->straight_path) return; // don't change type when it is a straight path!
2644     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2645         sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2646     }
2648     sp_nodepath_update_repr(nodepath, _("Change node type"));
2651 /**
2652  * Change select status of node, update its own and neighbour handles.
2653  */
2654 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2656     node->selected = selected;
2658     if (selected) {
2659         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2660         node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2661         node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2662         sp_knot_update_ctrl(node->knot);
2663     } else {
2664         node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2665         node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2666         node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2667         sp_knot_update_ctrl(node->knot);
2668     }
2670     sp_node_update_handles(node);
2671     if (node->n.other) sp_node_update_handles(node->n.other);
2672     if (node->p.other) sp_node_update_handles(node->p.other);
2675 /**
2676 \brief Select a node
2677 \param node     The node to select
2678 \param incremental   If true, add to selection, otherwise deselect others
2679 \param override   If true, always select this node, otherwise toggle selected status
2680 */
2681 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2683     Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2685     if (incremental) {
2686         if (override) {
2687             if (!g_list_find(nodepath->selected, node)) {
2688                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2689             }
2690             sp_node_set_selected(node, TRUE);
2691         } else { // toggle
2692             if (node->selected) {
2693                 g_assert(g_list_find(nodepath->selected, node));
2694                 nodepath->selected = g_list_remove(nodepath->selected, node);
2695             } else {
2696                 g_assert(!g_list_find(nodepath->selected, node));
2697                 nodepath->selected = g_list_prepend(nodepath->selected, node);
2698             }
2699             sp_node_set_selected(node, !node->selected);
2700         }
2701     } else {
2702         sp_nodepath_deselect(nodepath);
2703         nodepath->selected = g_list_prepend(nodepath->selected, node);
2704         sp_node_set_selected(node, TRUE);
2705     }
2707     sp_nodepath_update_statusbar(nodepath);
2711 /**
2712 \brief Deselect all nodes in the nodepath
2713 */
2714 void
2715 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2717     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2719     while (nodepath->selected) {
2720         sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2721         nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2722     }
2723     sp_nodepath_update_statusbar(nodepath);
2726 /**
2727 \brief Select or invert selection of all nodes in the nodepath
2728 */
2729 void
2730 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2732     if (!nodepath) return;
2734     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2735        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2736         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2737            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2738            sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2739         }
2740     }
2743 /**
2744  * If nothing selected, does the same as sp_nodepath_select_all();
2745  * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2746  * (i.e., similar to "select all in layer", with the "selected" subpaths
2747  * being treated as "layers" in the path).
2748  */
2749 void
2750 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2752     if (!nodepath) return;
2754     if (g_list_length (nodepath->selected) == 0) {
2755         sp_nodepath_select_all (nodepath, invert);
2756         return;
2757     }
2759     GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2760     GSList *subpaths = NULL;
2762     for (GList *l = copy; l != NULL; l = l->next) {
2763         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2764         Inkscape::NodePath::SubPath *subpath = n->subpath;
2765         if (!g_slist_find (subpaths, subpath))
2766             subpaths = g_slist_prepend (subpaths, subpath);
2767     }
2769     for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2770         Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2771         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2772             Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2773             sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2774         }
2775     }
2777     g_slist_free (subpaths);
2778     g_list_free (copy);
2781 /**
2782  * \brief Select the node after the last selected; if none is selected,
2783  * select the first within path.
2784  */
2785 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2787     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2789    Inkscape::NodePath::Node *last = NULL;
2790     if (nodepath->selected) {
2791         for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2792            Inkscape::NodePath::SubPath *subpath, *subpath_next;
2793             subpath = (Inkscape::NodePath::SubPath *) spl->data;
2794             for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2795                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2796                 if (node->selected) {
2797                     if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2798                         if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2799                             if (spl->next) { // there's a next subpath
2800                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2801                                 last = subpath_next->first;
2802                             } else if (spl->prev) { // there's a previous subpath
2803                                 last = NULL; // to be set later to the first node of first subpath
2804                             } else {
2805                                 last = node->n.other;
2806                             }
2807                         } else {
2808                             last = node->n.other;
2809                         }
2810                     } else {
2811                         if (node->n.other) {
2812                             last = node->n.other;
2813                         } else {
2814                             if (spl->next) { // there's a next subpath
2815                                 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2816                                 last = subpath_next->first;
2817                             } else if (spl->prev) { // there's a previous subpath
2818                                 last = NULL; // to be set later to the first node of first subpath
2819                             } else {
2820                                 last = (Inkscape::NodePath::Node *) subpath->first;
2821                             }
2822                         }
2823                     }
2824                 }
2825             }
2826         }
2827         sp_nodepath_deselect(nodepath);
2828     }
2830     if (last) { // there's at least one more node after selected
2831         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2832     } else { // no more nodes, select the first one in first subpath
2833        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2834         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2835     }
2838 /**
2839  * \brief Select the node before the first selected; if none is selected,
2840  * select the last within path
2841  */
2842 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2844     if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2846    Inkscape::NodePath::Node *last = NULL;
2847     if (nodepath->selected) {
2848         for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2849            Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2850             for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2851                Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2852                 if (node->selected) {
2853                     if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2854                         if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2855                             if (spl->prev) { // there's a prev subpath
2856                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2857                                 last = subpath_prev->last;
2858                             } else if (spl->next) { // there's a next subpath
2859                                 last = NULL; // to be set later to the last node of last subpath
2860                             } else {
2861                                 last = node->p.other;
2862                             }
2863                         } else {
2864                             last = node->p.other;
2865                         }
2866                     } else {
2867                         if (node->p.other) {
2868                             last = node->p.other;
2869                         } else {
2870                             if (spl->prev) { // there's a prev subpath
2871                                Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2872                                 last = subpath_prev->last;
2873                             } else if (spl->next) { // there's a next subpath
2874                                 last = NULL; // to be set later to the last node of last subpath
2875                             } else {
2876                                 last = (Inkscape::NodePath::Node *) subpath->last;
2877                             }
2878                         }
2879                     }
2880                 }
2881             }
2882         }
2883         sp_nodepath_deselect(nodepath);
2884     }
2886     if (last) { // there's at least one more node before selected
2887         sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2888     } else { // no more nodes, select the last one in last subpath
2889         GList *spl = g_list_last(nodepath->subpaths);
2890        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2891         sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2892     }
2895 /**
2896  * \brief Select all nodes that are within the rectangle.
2897  */
2898 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2900     if (!incremental) {
2901         sp_nodepath_deselect(nodepath);
2902     }
2904     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2905        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2906         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2907            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2909             if (b.contains(node->pos)) {
2910                 sp_nodepath_node_select(node, TRUE, TRUE);
2911             }
2912         }
2913     }
2917 void
2918 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2920     g_assert (n);
2921     g_assert (nodepath);
2922     g_assert (n->subpath->nodepath == nodepath);
2924     if (g_list_length (nodepath->selected) == 0) {
2925         if (grow > 0) {
2926             sp_nodepath_node_select(n, TRUE, TRUE);
2927         }
2928         return;
2929     }
2931     if (g_list_length (nodepath->selected) == 1) {
2932         if (grow < 0) {
2933             sp_nodepath_deselect (nodepath);
2934             return;
2935         }
2936     }
2938         double n_sel_range = 0, p_sel_range = 0;
2939             Inkscape::NodePath::Node *farthest_n_node = n;
2940             Inkscape::NodePath::Node *farthest_p_node = n;
2942         // Calculate ranges
2943         {
2944             double n_range = 0, p_range = 0;
2945             bool n_going = true, p_going = true;
2946             Inkscape::NodePath::Node *n_node = n;
2947             Inkscape::NodePath::Node *p_node = n;
2948             do {
2949                 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2950                 if (n_node && n_going)
2951                     n_node = n_node->n.other;
2952                 if (n_node == NULL) {
2953                     n_going = false;
2954                 } else {
2955                     n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2956                     if (n_node->selected) {
2957                         n_sel_range = n_range;
2958                         farthest_n_node = n_node;
2959                     }
2960                     if (n_node == p_node) {
2961                         n_going = false;
2962                         p_going = false;
2963                     }
2964                 }
2965                 if (p_node && p_going)
2966                     p_node = p_node->p.other;
2967                 if (p_node == NULL) {
2968                     p_going = false;
2969                 } else {
2970                     p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2971                     if (p_node->selected) {
2972                         p_sel_range = p_range;
2973                         farthest_p_node = p_node;
2974                     }
2975                     if (p_node == n_node) {
2976                         n_going = false;
2977                         p_going = false;
2978                     }
2979                 }
2980             } while (n_going || p_going);
2981         }
2983     if (grow > 0) {
2984         if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2985                 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2986         } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2987                 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2988         }
2989     } else {
2990         if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2991                 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2992         } else if (farthest_p_node && farthest_p_node->selected) {
2993                 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2994         }
2995     }
2998 void
2999 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3001     g_assert (n);
3002     g_assert (nodepath);
3003     g_assert (n->subpath->nodepath == nodepath);
3005     if (g_list_length (nodepath->selected) == 0) {
3006         if (grow > 0) {
3007             sp_nodepath_node_select(n, TRUE, TRUE);
3008         }
3009         return;
3010     }
3012     if (g_list_length (nodepath->selected) == 1) {
3013         if (grow < 0) {
3014             sp_nodepath_deselect (nodepath);
3015             return;
3016         }
3017     }
3019     Inkscape::NodePath::Node *farthest_selected = NULL;
3020     double farthest_dist = 0;
3022     Inkscape::NodePath::Node *closest_unselected = NULL;
3023     double closest_dist = NR_HUGE;
3025     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3026        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3027         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3028            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3029            if (node == n)
3030                continue;
3031            if (node->selected) {
3032                if (NR::L2(node->pos - n->pos) > farthest_dist) {
3033                    farthest_dist = NR::L2(node->pos - n->pos);
3034                    farthest_selected = node;
3035                }
3036            } else {
3037                if (NR::L2(node->pos - n->pos) < closest_dist) {
3038                    closest_dist = NR::L2(node->pos - n->pos);
3039                    closest_unselected = node;
3040                }
3041            }
3042         }
3043     }
3045     if (grow > 0) {
3046         if (closest_unselected) {
3047             sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3048         }
3049     } else {
3050         if (farthest_selected) {
3051             sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3052         }
3053     }
3057 /**
3058 \brief  Saves all nodes' and handles' current positions in their origin members
3059 */
3060 void
3061 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3063     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3064        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3065         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3066            Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3067            n->origin = n->pos;
3068            n->p.origin = n->p.pos;
3069            n->n.origin = n->n.pos;
3070         }
3071     }
3074 /**
3075 \brief  Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3076 */
3077 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3079     if (!nodepath->selected) {
3080         return NULL;
3081     }
3083     GList *r = NULL;
3084     guint i = 0;
3085     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3086        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3087         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3088            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3089             i++;
3090             if (node->selected) {
3091                 r = g_list_append(r, GINT_TO_POINTER(i));
3092             }
3093         }
3094     }
3095     return r;
3098 /**
3099 \brief  Restores selection by selecting nodes whose positions are in the list
3100 */
3101 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3103     sp_nodepath_deselect(nodepath);
3105     guint i = 0;
3106     for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3107        Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3108         for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3109            Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3110             i++;
3111             if (g_list_find(r, GINT_TO_POINTER(i))) {
3112                 sp_nodepath_node_select(node, TRUE, TRUE);
3113             }
3114         }
3115     }
3119 /**
3120 \brief Adjusts handle according to node type and line code.
3121 */
3122 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3124     g_assert(node);
3126    Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3127    Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3129    // nothing to do if we are an end node
3130     if (me->other == NULL) return;
3131     if (other->other == NULL) return;
3133     // nothing to do if we are a cusp node
3134     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3136     // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3137     NRPathcode mecode;
3138     if (which_adjust == 1) {
3139         mecode = (NRPathcode)me->other->code;
3140     } else {
3141         mecode = (NRPathcode)node->code;
3142     }
3143     if (mecode == NR_LINETO) return;
3145     if (sp_node_side_is_line(node, other)) {
3146         // other is a line, and we are either smooth or symm
3147        Inkscape::NodePath::Node *othernode = other->other;
3148         double len = NR::L2(me->pos - node->pos);
3149         NR::Point delta = node->pos - othernode->pos;
3150         double linelen = NR::L2(delta);
3151         if (linelen < 1e-18)
3152             return;
3153         me->pos = node->pos + (len / linelen)*delta;
3154         return;
3155     }
3157     if (node->type == Inkscape::NodePath::NODE_SYMM) {
3158         // symmetrize 
3159         me->pos = 2 * node->pos - other->pos;
3160         return;
3161     } else {
3162         // smoothify
3163         double len = NR::L2(me->pos - node->pos);
3164         NR::Point delta = other->pos - node->pos;
3165         double otherlen = NR::L2(delta);
3166         if (otherlen < 1e-18) return;
3167         me->pos = node->pos - (len / otherlen) * delta;
3168     }
3171 /**
3172  \brief Adjusts both handles according to node type and line code
3173  */
3174 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3176     g_assert(node);
3178     if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3180     /* we are either smooth or symm */
3182     if (node->p.other == NULL) return;
3183     if (node->n.other == NULL) return;
3185     if (sp_node_side_is_line(node, &node->p)) {
3186         sp_node_adjust_handle(node, 1);
3187         return;
3188     }
3190     if (sp_node_side_is_line(node, &node->n)) {
3191         sp_node_adjust_handle(node, -1);
3192         return;
3193     }
3195     /* both are curves */
3196     NR::Point const delta( node->n.pos - node->p.pos );
3198     if (node->type == Inkscape::NodePath::NODE_SYMM) {
3199         node->p.pos = node->pos - delta / 2;
3200         node->n.pos = node->pos + delta / 2;
3201         return;
3202     }
3204     /* We are smooth */
3205     double plen = NR::L2(node->p.pos - node->pos);
3206     if (plen < 1e-18) return;
3207     double nlen = NR::L2(node->n.pos - node->pos);
3208     if (nlen < 1e-18) return;
3209     node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3210     node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3213 /**
3214  * Node event callback.
3215  */
3216 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3218     gboolean ret = FALSE;
3219     switch (event->type) {
3220         case GDK_ENTER_NOTIFY:
3221             Inkscape::NodePath::Path::active_node = n;
3222             break;
3223         case GDK_LEAVE_NOTIFY:
3224             Inkscape::NodePath::Path::active_node = NULL;
3225             break;
3226         case GDK_SCROLL:
3227             if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3228                 switch (event->scroll.direction) {
3229                     case GDK_SCROLL_UP:
3230                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3231                         break;
3232                     case GDK_SCROLL_DOWN:
3233                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3234                         break;
3235                     default:
3236                         break;
3237                 }
3238                 ret = TRUE;
3239             } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3240                 switch (event->scroll.direction) {
3241                     case GDK_SCROLL_UP:
3242                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3243                         break;
3244                     case GDK_SCROLL_DOWN:
3245                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3246                         break;
3247                     default:
3248                         break;
3249                 }
3250                 ret = TRUE;
3251             }
3252             break;
3253         case GDK_KEY_PRESS:
3254             switch (get_group0_keyval (&event->key)) {
3255                 case GDK_space:
3256                     if (event->key.state & GDK_BUTTON1_MASK) {
3257                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3258                         stamp_repr(nodepath);
3259                         ret = TRUE;
3260                     }
3261                     break;
3262                 case GDK_Page_Up:
3263                     if (event->key.state & GDK_CONTROL_MASK) {
3264                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3265                     } else {
3266                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3267                     }
3268                     break;
3269                 case GDK_Page_Down:
3270                     if (event->key.state & GDK_CONTROL_MASK) {
3271                         nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3272                     } else {
3273                         nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3274                     }
3275                     break;
3276                 default:
3277                     break;
3278             }
3279             break;
3280         default:
3281             break;
3282     }
3284     return ret;
3287 /**
3288  * Handle keypress on node; directly called.
3289  */
3290 gboolean node_key(GdkEvent *event)
3292     Inkscape::NodePath::Path *np;
3294     // there is no way to verify nodes so set active_node to nil when deleting!!
3295     if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3297     if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3298         gint ret = FALSE;
3299         switch (get_group0_keyval (&event->key)) {
3300             /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3301             case GDK_BackSpace:
3302                 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3303                 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3304                 sp_nodepath_update_repr(np, _("Delete node"));
3305                 Inkscape::NodePath::Path::active_node = NULL;
3306                 ret = TRUE;
3307                 break;
3308             case GDK_c:
3309                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3310                 ret = TRUE;
3311                 break;
3312             case GDK_s:
3313                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3314                 ret = TRUE;
3315                 break;
3316             case GDK_y:
3317                 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3318                 ret = TRUE;
3319                 break;
3320             case GDK_b:
3321                 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3322                 ret = TRUE;
3323                 break;
3324         }
3325         return ret;
3326     }
3327     return FALSE;
3330 /**
3331  * Mouseclick on node callback.
3332  */
3333 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3335    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3337     if (state & GDK_CONTROL_MASK) {
3338         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3340         if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3341             if (n->type == Inkscape::NodePath::NODE_CUSP) {
3342                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3343             } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3344                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3345             } else {
3346                 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3347             }
3348             sp_nodepath_update_repr(nodepath, _("Change node type"));
3349             sp_nodepath_update_statusbar(nodepath);
3351         } else { //ctrl+alt+click: delete node
3352             GList *node_to_delete = NULL;
3353             node_to_delete = g_list_append(node_to_delete, n);
3354             sp_node_delete_preserve(node_to_delete);
3355         }
3357     } else {
3358         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3359     }
3362 /**
3363  * Mouse grabbed node callback.
3364  */
3365 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3367    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3369     if (!n->selected) {
3370         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3371     }
3373     n->is_dragging = true;
3374     sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3376     sp_nodepath_remember_origins (n->subpath->nodepath);
3379 /**
3380  * Mouse ungrabbed node callback.
3381  */
3382 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3384    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3386    n->dragging_out = NULL;
3387    n->is_dragging = false;
3388    sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3390    sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3393 /**
3394  * The point on a line, given by its angle, closest to the given point.
3395  * \param p  A point.
3396  * \param a  Angle of the line; it is assumed to go through coordinate origin.
3397  * \param closest  Pointer to the point struct where the result is stored.
3398  * \todo FIXME: use dot product perhaps?
3399  */
3400 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3402     if (a == HUGE_VAL) { // vertical
3403         *closest = NR::Point(0, (*p)[NR::Y]);
3404     } else {
3405         (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3406         (*closest)[NR::Y] = a * (*closest)[NR::X];
3407     }
3410 /**
3411  * Distance from the point to a line given by its angle.
3412  * \param p  A point.
3413  * \param a  Angle of the line; it is assumed to go through coordinate origin.
3414  */
3415 static double point_line_distance(NR::Point *p, double a)
3417     NR::Point c;
3418     point_line_closest(p, a, &c);
3419     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]));
3422 /**
3423  * Callback for node "request" signal.
3424  * \todo fixme: This goes to "moved" event? (lauris)
3425  */
3426 static gboolean
3427 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3429     double yn, xn, yp, xp;
3430     double an, ap, na, pa;
3431     double d_an, d_ap, d_na, d_pa;
3432     gboolean collinear = FALSE;
3433     NR::Point c;
3434     NR::Point pr;
3436     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3438     n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3440     // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3441     if ( (!n->subpath->nodepath->straight_path) &&
3442          ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3443            || n->dragging_out ) )
3444     {
3445        NR::Point mouse = (*p);
3447        if (!n->dragging_out) {
3448            // This is the first drag-out event; find out which handle to drag out
3449            double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3450            double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3452            if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3453                return FALSE;
3455            Inkscape::NodePath::NodeSide *opposite;
3456            if (appr_p > appr_n) { // closer to p
3457                n->dragging_out = &n->p;
3458                opposite = &n->n;
3459                n->code = NR_CURVETO;
3460            } else if (appr_p < appr_n) { // closer to n
3461                n->dragging_out = &n->n;
3462                opposite = &n->p;
3463                n->n.other->code = NR_CURVETO;
3464            } else { // p and n nodes are the same
3465                if (n->n.pos != n->pos) { // n handle already dragged, drag p
3466                    n->dragging_out = &n->p;
3467                    opposite = &n->n;
3468                    n->code = NR_CURVETO;
3469                } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3470                    n->dragging_out = &n->n;
3471                    opposite = &n->p;
3472                    n->n.other->code = NR_CURVETO;
3473                } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3474                    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);
3475                    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);
3476                    if (appr_other_p > appr_other_n) { // closer to other's p handle
3477                        n->dragging_out = &n->n;
3478                        opposite = &n->p;
3479                        n->n.other->code = NR_CURVETO;
3480                    } else { // closer to other's n handle
3481                        n->dragging_out = &n->p;
3482                        opposite = &n->n;
3483                        n->code = NR_CURVETO;
3484                    }
3485                }
3486            }
3488            // if there's another handle, make sure the one we drag out starts parallel to it
3489            if (opposite->pos != n->pos) {
3490                mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3491            }
3493            // knots might not be created yet!
3494            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3495            sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3496        }
3498        // pass this on to the handle-moved callback
3499        node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3500        sp_node_update_handles(n);
3501        return TRUE;
3502    }
3504     if (state & GDK_CONTROL_MASK) { // constrained motion
3506         // calculate relative distances of handles
3507         // n handle:
3508         yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3509         xn = n->n.pos[NR::X] - n->pos[NR::X];
3510         // if there's no n handle (straight line), see if we can use the direction to the next point on path
3511         if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3512             if (n->n.other) { // if there is the next point
3513                 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3514                     yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3515                     xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3516             }
3517         }
3518         if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3519         if (yn < 0) { xn = -xn; yn = -yn; }
3521         // p handle:
3522         yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3523         xp = n->p.pos[NR::X] - n->pos[NR::X];
3524         // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3525         if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3526             if (n->p.other) {
3527                 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3528                     yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3529                     xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3530             }
3531         }
3532         if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3533         if (yp < 0) { xp = -xp; yp = -yp; }
3535         if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3536             // sliding on handles, only if at least one of the handles is non-vertical
3537             // (otherwise it's the same as ctrl+drag anyway)
3539             // calculate angles of the handles
3540             if (xn == 0) {
3541                 if (yn == 0) { // no handle, consider it the continuation of the other one
3542                     an = 0;
3543                     collinear = TRUE;
3544                 }
3545                 else an = 0; // vertical; set the angle to horizontal
3546             } else an = yn/xn;
3548             if (xp == 0) {
3549                 if (yp == 0) { // no handle, consider it the continuation of the other one
3550                     ap = an;
3551                 }
3552                 else ap = 0; // vertical; set the angle to horizontal
3553             } else  ap = yp/xp;
3555             if (collinear) an = ap;
3557             // angles of the perpendiculars; HUGE_VAL means vertical
3558             if (an == 0) na = HUGE_VAL; else na = -1/an;
3559             if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3561             // mouse point relative to the node's original pos
3562             pr = (*p) - n->origin;
3564             // distances to the four lines (two handles and two perpendiculars)
3565             d_an = point_line_distance(&pr, an);
3566             d_na = point_line_distance(&pr, na);
3567             d_ap = point_line_distance(&pr, ap);
3568             d_pa = point_line_distance(&pr, pa);
3570             // find out which line is the closest, save its closest point in c
3571             if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3572                 point_line_closest(&pr, an, &c);
3573             } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3574                 point_line_closest(&pr, ap, &c);
3575             } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3576                 point_line_closest(&pr, na, &c);
3577             } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3578                 point_line_closest(&pr, pa, &c);
3579             }
3581             // move the node to the closest point
3582             sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3583                                             n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3584                                             n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y], 
3585                                             true);
3587         } else {  // constraining to hor/vert
3589             if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3590                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3591                                                 (*p)[NR::X] - n->pos[NR::X], 
3592                                                 n->origin[NR::Y] - n->pos[NR::Y],
3593                                                 true, 
3594                                                 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3595             } else { // snap to vert
3596                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3597                                                 n->origin[NR::X] - n->pos[NR::X],
3598                                                 (*p)[NR::Y] - n->pos[NR::Y],
3599                                                 true,
3600                                                 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3601             }
3602         }
3603     } else { // move freely
3604         if (n->is_dragging) {
3605             if (state & GDK_MOD1_MASK) { // sculpt
3606                 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3607             } else {
3608                 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3609                                             (*p)[NR::X] - n->pos[NR::X],
3610                                             (*p)[NR::Y] - n->pos[NR::Y],
3611                                             (state & GDK_SHIFT_MASK) == 0);
3612             }
3613         }
3614     }
3616     n->subpath->nodepath->desktop->scroll_to_point(p);
3618     return TRUE;
3621 /**
3622  * Node handle clicked callback.
3623  */
3624 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3626    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3628     if (state & GDK_CONTROL_MASK) { // "delete" handle
3629         if (n->p.knot == knot) {
3630             n->p.pos = n->pos;
3631         } else if (n->n.knot == knot) {
3632             n->n.pos = n->pos;
3633         }
3634         sp_node_update_handles(n);
3635         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3636         sp_nodepath_update_repr(nodepath, _("Retract handle"));
3637         sp_nodepath_update_statusbar(nodepath);
3639     } else { // just select or add to selection, depending in Shift
3640         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3641     }
3644 /**
3645  * Node handle grabbed callback.
3646  */
3647 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3649    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3651     if (!n->selected) {
3652         sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3653     }
3655     // remember the origin point of the handle
3656     if (n->p.knot == knot) {
3657         n->p.origin_radial = n->p.pos - n->pos;
3658     } else if (n->n.knot == knot) {
3659         n->n.origin_radial = n->n.pos - n->pos;
3660     } else {
3661         g_assert_not_reached();
3662     }
3664     sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3667 /**
3668  * Node handle ungrabbed callback.
3669  */
3670 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3672    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3674     // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3675     if (n->p.knot == knot) {
3676         n->p.origin_radial.a = 0;
3677         sp_knot_set_position(knot, &n->p.pos, state);
3678     } else if (n->n.knot == knot) {
3679         n->n.origin_radial.a = 0;
3680         sp_knot_set_position(knot, &n->n.pos, state);
3681     } else {
3682         g_assert_not_reached();
3683     }
3685     sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3688 /**
3689  * Node handle "request" signal callback.
3690  */
3691 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3693     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3695     Inkscape::NodePath::NodeSide *me, *opposite;
3696     gint which;
3697     if (n->p.knot == knot) {
3698         me = &n->p;
3699         opposite = &n->n;
3700         which = -1;
3701     } else if (n->n.knot == knot) {
3702         me = &n->n;
3703         opposite = &n->p;
3704         which = 1;
3705     } else {
3706         me = opposite = NULL;
3707         which = 0;
3708         g_assert_not_reached();
3709     }
3711     SPDesktop *desktop = n->subpath->nodepath->desktop;
3712     SnapManager &m = desktop->namedview->snap_manager;
3713     m.setup(desktop, n->subpath->nodepath->item);
3714     Inkscape::SnappedPoint s;
3715     
3716     if ((state & GDK_SHIFT_MASK) != 0) {
3717         // We will not try to snap when the shift-key is pressed
3718         // so remove the old snap indicator and don't wait for it to time-out  
3719         desktop->snapindicator->remove_snappoint();     
3720     }
3722     Inkscape::NodePath::Node *othernode = opposite->other;
3723     if (othernode) {
3724         if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3725             /* We are smooth node adjacent with line */
3726             NR::Point const delta = *p - n->pos;
3727             NR::Coord const len = NR::L2(delta);
3728             Inkscape::NodePath::Node *othernode = opposite->other;
3729             NR::Point const ndelta = n->pos - othernode->pos;
3730             NR::Coord const linelen = NR::L2(ndelta);
3731             if (len > NR_EPSILON && linelen > NR_EPSILON) {
3732                 NR::Coord const scal = dot(delta, ndelta) / linelen;
3733                 (*p) = n->pos + (scal / linelen) * ndelta;
3734             }
3735             if ((state & GDK_SHIFT_MASK) == 0) {
3736                 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3737             }
3738         } else {
3739                 if ((state & GDK_SHIFT_MASK) == 0) {
3740                         s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3741                 }
3742         }
3743     } else {
3744         if ((state & GDK_SHIFT_MASK) == 0) {
3745                 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3746         }
3747     }
3748     
3749     s.getPoint(*p);
3750     
3751     sp_node_adjust_handle(n, -which);
3753     return FALSE;
3756 /**
3757  * Node handle moved callback.
3758  */
3759 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3761    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3763    Inkscape::NodePath::NodeSide *me;
3764    Inkscape::NodePath::NodeSide *other;
3765     if (n->p.knot == knot) {
3766         me = &n->p;
3767         other = &n->n;
3768     } else if (n->n.knot == knot) {
3769         me = &n->n;
3770         other = &n->p;
3771     } else {
3772         me = NULL;
3773         other = NULL;
3774         g_assert_not_reached();
3775     }
3777     // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3778     Radial rme(me->pos - n->pos);
3779     Radial rother(other->pos - n->pos);
3780     Radial rnew(*p - n->pos);
3782     if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3783         int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3784         /* 0 interpreted as "no snapping". */
3786         // 1. Snap to the closest PI/snaps angle, starting from zero.
3787         double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3789         // 2. Snap to the original angle, its opposite and perpendiculars
3790         if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3791             /* The closest PI/2 angle, starting from original angle */
3792             double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3794             // Snap to the closest.
3795             a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3796                        ? a_snapped
3797                        : a_ortho );
3798         }
3800         // 3. Snap to the angle of the opposite line, if any
3801         Inkscape::NodePath::Node *othernode = other->other;
3802         if (othernode) {
3803             NR::Point other_to_snap(0,0);
3804             if (sp_node_side_is_line(n, other)) {
3805                 other_to_snap = othernode->pos - n->pos;
3806             } else {
3807                 other_to_snap = other->pos - n->pos;
3808             }
3809             if (NR::L2(other_to_snap) > 1e-3) {
3810                 Radial rother_to_snap(other_to_snap);
3811                 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3812                 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3814                 // Snap to the closest.
3815                 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3816                        ? a_snapped
3817                        : a_oppo );
3818             }
3819         }
3821         rnew.a = a_snapped;
3822     }
3824     if (state & GDK_MOD1_MASK) {
3825         // lock handle length
3826         rnew.r = me->origin_radial.r;
3827     }
3829     if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3830         && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3831         // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3832         rother.a += rnew.a - rme.a;
3833         other->pos = NR::Point(rother) + n->pos;
3834         if (other->knot) {
3835             sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3836             sp_knot_moveto(other->knot, &other->pos);
3837         }
3838     }
3840     me->pos = NR::Point(rnew) + n->pos;
3841     sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3843     // move knot, but without emitting the signal:
3844     // we cannot emit a "moved" signal because we're now processing it
3845     sp_knot_moveto(me->knot, &(me->pos));
3847     update_object(n->subpath->nodepath);
3849     /* status text */
3850     SPDesktop *desktop = n->subpath->nodepath->desktop;
3851     if (!desktop) return;
3852     SPEventContext *ec = desktop->event_context;
3853     if (!ec) return;
3854     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3855     if (!mc) return;
3857     double degrees = 180 / M_PI * rnew.a;
3858     if (degrees > 180) degrees -= 360;
3859     if (degrees < -180) degrees += 360;
3860     if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3861         degrees = angle_to_compass (degrees);
3863     GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3865     mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3866          _("<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);
3868     g_string_free(length, TRUE);
3871 /**
3872  * Node handle event callback.
3873  */
3874 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3876     gboolean ret = FALSE;
3877     switch (event->type) {
3878         case GDK_KEY_PRESS:
3879             switch (get_group0_keyval (&event->key)) {
3880                 case GDK_space:
3881                     if (event->key.state & GDK_BUTTON1_MASK) {
3882                         Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3883                         stamp_repr(nodepath);
3884                         ret = TRUE;
3885                     }
3886                     break;
3887                 default:
3888                     break;
3889             }
3890             break;
3891         case GDK_ENTER_NOTIFY:
3892             // we use an experimentally determined threshold that seems to work fine
3893             if (NR::L2(n->pos - knot->pos) < 0.75)
3894                 Inkscape::NodePath::Path::active_node = n;
3895             break;
3896         case GDK_LEAVE_NOTIFY:
3897             // we use an experimentally determined threshold that seems to work fine
3898             if (NR::L2(n->pos - knot->pos) < 0.75)
3899                 Inkscape::NodePath::Path::active_node = NULL;
3900             break;
3901         default:
3902             break;
3903     }
3905     return ret;
3908 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3909                                  Radial &rme, Radial &rother, gboolean const both)
3911     rme.a += angle;
3912     if ( both
3913          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3914          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3915     {
3916         rother.a += angle;
3917     }
3920 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3921                                         Radial &rme, Radial &rother, gboolean const both)
3923     gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3925     gdouble r;
3926     if ( both
3927          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3928          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3929     {
3930         r = MAX(rme.r, rother.r);
3931     } else {
3932         r = rme.r;
3933     }
3935     gdouble const weird_angle = atan2(norm_angle, r);
3936 /* Bulia says norm_angle is just the visible distance that the
3937  * object's end must travel on the screen.  Left as 'angle' for want of
3938  * a better name.*/
3940     rme.a += weird_angle;
3941     if ( both
3942          || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3943          || ( n.type == Inkscape::NodePath::NODE_SYMM )  )
3944     {
3945         rother.a += weird_angle;
3946     }
3949 /**
3950  * Rotate one node.
3951  */
3952 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3954     Inkscape::NodePath::NodeSide *me, *other;
3955     bool both = false;
3957     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3958     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3960     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3961         me = &(n->p);
3962         other = &(n->n);
3963     } else if (!n->p.other) {
3964         me = &(n->n);
3965         other = &(n->p);
3966     } else {
3967         if (which > 0) { // right handle
3968             if (xn > xp) {
3969                 me = &(n->n);
3970                 other = &(n->p);
3971             } else {
3972                 me = &(n->p);
3973                 other = &(n->n);
3974             }
3975         } else if (which < 0){ // left handle
3976             if (xn <= xp) {
3977                 me = &(n->n);
3978                 other = &(n->p);
3979             } else {
3980                 me = &(n->p);
3981                 other = &(n->n);
3982             }
3983         } else { // both handles
3984             me = &(n->n);
3985             other = &(n->p);
3986             both = true;
3987         }
3988     }
3990     Radial rme(me->pos - n->pos);
3991     Radial rother(other->pos - n->pos);
3993     if (screen) {
3994         node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3995     } else {
3996         node_rotate_one_internal (*n, angle, rme, rother, both);
3997     }
3999     me->pos = n->pos + NR::Point(rme);
4001     if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4002         other->pos =  n->pos + NR::Point(rother);
4003     }
4005     // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4006     // so here we just move all the knots without emitting move signals, for speed
4007     sp_node_update_handles(n, false);
4010 /**
4011  * Rotate selected nodes.
4012  */
4013 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4015     if (!nodepath || !nodepath->selected) return;
4017     if (g_list_length(nodepath->selected) == 1) {
4018        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4019         node_rotate_one (n, angle, which, screen);
4020     } else {
4021        // rotate as an object:
4023         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4024         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4025         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4026             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4027             box.expandTo (n->pos); // contain all selected nodes
4028         }
4030         gdouble rot;
4031         if (screen) {
4032             gdouble const zoom = nodepath->desktop->current_zoom();
4033             gdouble const zmove = angle / zoom;
4034             gdouble const r = NR::L2(box.max() - box.midpoint());
4035             rot = atan2(zmove, r);
4036         } else {
4037             rot = angle;
4038         }
4040         NR::Point rot_center;
4041         if (Inkscape::NodePath::Path::active_node == NULL)
4042             rot_center = box.midpoint();
4043         else
4044             rot_center = Inkscape::NodePath::Path::active_node->pos;
4046         NR::Matrix t =
4047             NR::Matrix (NR::translate(-rot_center)) *
4048             NR::Matrix (NR::rotate(rot)) *
4049             NR::Matrix (NR::translate(rot_center));
4051         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4052             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4053             n->pos *= t;
4054             n->n.pos *= t;
4055             n->p.pos *= t;
4056             sp_node_update_handles(n, false);
4057         }
4058     }
4060     sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4063 /**
4064  * Scale one node.
4065  */
4066 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4068     bool both = false;
4069     Inkscape::NodePath::NodeSide *me, *other;
4071     double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4072     double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4074     if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4075         me = &(n->p);
4076         other = &(n->n);
4077         n->code = NR_CURVETO;
4078     } else if (!n->p.other) {
4079         me = &(n->n);
4080         other = &(n->p);
4081         if (n->n.other)
4082             n->n.other->code = NR_CURVETO;
4083     } else {
4084         if (which > 0) { // right handle
4085             if (xn > xp) {
4086                 me = &(n->n);
4087                 other = &(n->p);
4088                 if (n->n.other)
4089                     n->n.other->code = NR_CURVETO;
4090             } else {
4091                 me = &(n->p);
4092                 other = &(n->n);
4093                 n->code = NR_CURVETO;
4094             }
4095         } else if (which < 0){ // left handle
4096             if (xn <= xp) {
4097                 me = &(n->n);
4098                 other = &(n->p);
4099                 if (n->n.other)
4100                     n->n.other->code = NR_CURVETO;
4101             } else {
4102                 me = &(n->p);
4103                 other = &(n->n);
4104                 n->code = NR_CURVETO;
4105             }
4106         } else { // both handles
4107             me = &(n->n);
4108             other = &(n->p);
4109             both = true;
4110             n->code = NR_CURVETO;
4111             if (n->n.other)
4112                 n->n.other->code = NR_CURVETO;
4113         }
4114     }
4116     Radial rme(me->pos - n->pos);
4117     Radial rother(other->pos - n->pos);
4119     rme.r += grow;
4120     if (rme.r < 0) rme.r = 0;
4121     if (rme.a == HUGE_VAL) {
4122         if (me->other) { // if direction is unknown, initialize it towards the next node
4123             Radial rme_next(me->other->pos - n->pos);
4124             rme.a = rme_next.a;
4125         } else { // if there's no next, initialize to 0
4126             rme.a = 0;
4127         }
4128     }
4129     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4130         rother.r += grow;
4131         if (rother.r < 0) rother.r = 0;
4132         if (rother.a == HUGE_VAL) {
4133             rother.a = rme.a + M_PI;
4134         }
4135     }
4137     me->pos = n->pos + NR::Point(rme);
4139     if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4140         other->pos = n->pos + NR::Point(rother);
4141     }
4143     // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4144     // so here we just move all the knots without emitting move signals, for speed
4145     sp_node_update_handles(n, false);
4148 /**
4149  * Scale selected nodes.
4150  */
4151 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4153     if (!nodepath || !nodepath->selected) return;
4155     if (g_list_length(nodepath->selected) == 1) {
4156         // scale handles of the single selected node
4157         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4158         node_scale_one (n, grow, which);
4159     } else {
4160         // scale nodes as an "object":
4162         Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4163         NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4164         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4165             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4166             box.expandTo (n->pos); // contain all selected nodes
4167         }
4169         double scale = (box.maxExtent() + grow)/box.maxExtent();
4171         NR::Point scale_center;
4172         if (Inkscape::NodePath::Path::active_node == NULL)
4173             scale_center = box.midpoint();
4174         else
4175             scale_center = Inkscape::NodePath::Path::active_node->pos;
4177         NR::Matrix t =
4178             NR::Matrix (NR::translate(-scale_center)) *
4179             NR::Matrix (NR::scale(scale, scale)) *
4180             NR::Matrix (NR::translate(scale_center));
4182         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4183             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4184             n->pos *= t;
4185             n->n.pos *= t;
4186             n->p.pos *= t;
4187             sp_node_update_handles(n, false);
4188         }
4189     }
4191     sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4194 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4196     if (!nodepath) return;
4197     sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4200 /**
4201  * Flip selected nodes horizontally/vertically.
4202  */
4203 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4205     if (!nodepath || !nodepath->selected) return;
4207     if (g_list_length(nodepath->selected) == 1 && !center) {
4208         // flip handles of the single selected node
4209         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4210         double temp = n->p.pos[axis];
4211         n->p.pos[axis] = n->n.pos[axis];
4212         n->n.pos[axis] = temp;
4213         sp_node_update_handles(n, false);
4214     } else {
4215         // scale nodes as an "object":
4217         NR::Rect box = sp_node_selected_bbox (nodepath);
4218         if (!center) {
4219             center = box.midpoint();
4220         }
4221         NR::Matrix t =
4222             NR::Matrix (NR::translate(- *center)) *
4223             NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4224             NR::Matrix (NR::translate(*center));
4226         for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4227             Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4228             n->pos *= t;
4229             n->n.pos *= t;
4230             n->p.pos *= t;
4231             sp_node_update_handles(n, false);
4232         }
4233     }
4235     sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4238 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4240     g_assert (nodepath->selected);
4242     Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4243     NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4244     for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4245         Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4246         box.expandTo (n->pos); // contain all selected nodes
4247     }
4248     return box;
4251 //-----------------------------------------------
4252 /**
4253  * Return new subpath under given nodepath.
4254  */
4255 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4257     g_assert(nodepath);
4258     g_assert(nodepath->desktop);
4260    Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4262     s->nodepath = nodepath;
4263     s->closed = FALSE;
4264     s->nodes = NULL;
4265     s->first = NULL;
4266     s->last = NULL;
4268     // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4269     // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4270     nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4272     return s;
4275 /**
4276  * Destroy nodes in subpath, then subpath itself.
4277  */
4278 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4280     g_assert(subpath);
4281     g_assert(subpath->nodepath);
4282     g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4284     while (subpath->nodes) {
4285         sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4286     }
4288     subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4290     g_free(subpath);
4293 /**
4294  * Link head to tail in subpath.
4295  */
4296 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4298     g_assert(!sp->closed);
4299     g_assert(sp->last != sp->first);
4300     g_assert(sp->first->code == NR_MOVETO);
4302     sp->closed = TRUE;
4304     //Link the head to the tail
4305     sp->first->p.other = sp->last;
4306     sp->last->n.other  = sp->first;
4307     sp->last->n.pos    = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4308     sp->first          = sp->last;
4310     //Remove the extra end node
4311     sp_nodepath_node_destroy(sp->last->n.other);
4314 /**
4315  * Open closed (loopy) subpath at node.
4316  */
4317 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4319     g_assert(sp->closed);
4320     g_assert(n->subpath == sp);
4321     g_assert(sp->first == sp->last);
4323     /* We create new startpoint, current node will become last one */
4325    Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4326                                                 &n->pos, &n->pos, &n->n.pos);
4329     sp->closed        = FALSE;
4331     //Unlink to make a head and tail
4332     sp->first         = new_path;
4333     sp->last          = n;
4334     n->n.other        = NULL;
4335     new_path->p.other = NULL;
4338 /**
4339  * Return new node in subpath with given properties.
4340  * \param pos Position of node.
4341  * \param ppos Handle position in previous direction
4342  * \param npos Handle position in previous direction
4343  */
4344 Inkscape::NodePath::Node *
4345 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)
4347     g_assert(sp);
4348     g_assert(sp->nodepath);
4349     g_assert(sp->nodepath->desktop);
4351     if (nodechunk == NULL)
4352         nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4354     Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4356     n->subpath  = sp;
4358     if (type != Inkscape::NodePath::NODE_NONE) {
4359         // use the type from sodipodi:nodetypes
4360         n->type = type;
4361     } else {
4362         if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4363             // points are (almost) collinear
4364             if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4365                 // endnode, or a node with a retracted handle
4366                 n->type = Inkscape::NodePath::NODE_CUSP;
4367             } else {
4368                 n->type = Inkscape::NodePath::NODE_SMOOTH;
4369             }
4370         } else {
4371             n->type = Inkscape::NodePath::NODE_CUSP;
4372         }
4373     }
4375     n->code     = code;
4376     n->selected = FALSE;
4377     n->pos      = *pos;
4378     n->p.pos    = *ppos;
4379     n->n.pos    = *npos;
4381     n->dragging_out = NULL;
4383     Inkscape::NodePath::Node *prev;
4384     if (next) {
4385         //g_assert(g_list_find(sp->nodes, next));
4386         prev = next->p.other;
4387     } else {
4388         prev = sp->last;
4389     }
4391     if (prev)
4392         prev->n.other = n;
4393     else
4394         sp->first = n;
4396     if (next)
4397         next->p.other = n;
4398     else
4399         sp->last = n;
4401     n->p.other = prev;
4402     n->n.other = next;
4404     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"));
4405     sp_knot_set_position(n->knot, pos, 0);
4407     n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4408     n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4409     n->knot->setAnchor (GTK_ANCHOR_CENTER);
4410     n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4411     n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4412     sp_knot_update_ctrl(n->knot);
4414     g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4415     g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4416     g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4417     g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4418     g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4419     sp_knot_show(n->knot);
4421     // We only create handle knots and lines on demand
4422     n->p.knot = NULL;
4423     n->p.line = NULL;
4424     n->n.knot = NULL;
4425     n->n.line = NULL;
4427     sp->nodes = g_list_prepend(sp->nodes, n);
4429     return n;
4432 /**
4433  * Destroy node and its knots, link neighbors in subpath.
4434  */
4435 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4437     g_assert(node);
4438     g_assert(node->subpath);
4439     g_assert(SP_IS_KNOT(node->knot));
4441    Inkscape::NodePath::SubPath *sp = node->subpath;
4443     if (node->selected) { // first, deselect
4444         g_assert(g_list_find(node->subpath->nodepath->selected, node));
4445         node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4446     }
4448     node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4450     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4451     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4452     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4453     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4454     g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4455     g_object_unref(G_OBJECT(node->knot));
4457     if (node->p.knot) {
4458         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4459         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4460         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4461         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4462         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4463         g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4464         g_object_unref(G_OBJECT(node->p.knot));
4465         node->p.knot = NULL;
4466     }
4468     if (node->n.knot) {
4469         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4470         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4471         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4472         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4473         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4474         g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4475         g_object_unref(G_OBJECT(node->n.knot));
4476         node->n.knot = NULL;
4477     }
4479     if (node->p.line)
4480         gtk_object_destroy(GTK_OBJECT(node->p.line));
4481     if (node->n.line)
4482         gtk_object_destroy(GTK_OBJECT(node->n.line));
4484     if (sp->nodes) { // there are others nodes on the subpath
4485         if (sp->closed) {
4486             if (sp->first == node) {
4487                 g_assert(sp->last == node);
4488                 sp->first = node->n.other;
4489                 sp->last = sp->first;
4490             }
4491             node->p.other->n.other = node->n.other;
4492             node->n.other->p.other = node->p.other;
4493         } else {
4494             if (sp->first == node) {
4495                 sp->first = node->n.other;
4496                 sp->first->code = NR_MOVETO;
4497             }
4498             if (sp->last == node) sp->last = node->p.other;
4499             if (node->p.other) node->p.other->n.other = node->n.other;
4500             if (node->n.other) node->n.other->p.other = node->p.other;
4501         }
4502     } else { // this was the last node on subpath
4503         sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4504     }
4506     g_mem_chunk_free(nodechunk, node);
4509 /**
4510  * Returns one of the node's two sides.
4511  * \param which Indicates which side.
4512  * \return Pointer to previous node side if which==-1, next if which==1.
4513  */
4514 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4516     g_assert(node);
4518     switch (which) {
4519         case -1:
4520             return &node->p;
4521         case 1:
4522             return &node->n;
4523         default:
4524             break;
4525     }
4527     g_assert_not_reached();
4529     return NULL;
4532 /**
4533  * Return the other side of the node, given one of its sides.
4534  */
4535 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4537     g_assert(node);
4539     if (me == &node->p) return &node->n;
4540     if (me == &node->n) return &node->p;
4542     g_assert_not_reached();
4544     return NULL;
4547 /**
4548  * Return NRPathcode on the given side of the node.
4549  */
4550 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4552     g_assert(node);
4554     if (me == &node->p) {
4555         if (node->p.other) return (NRPathcode)node->code;
4556         return NR_MOVETO;
4557     }
4559     if (me == &node->n) {
4560         if (node->n.other) return (NRPathcode)node->n.other->code;
4561         return NR_MOVETO;
4562     }
4564     g_assert_not_reached();
4566     return NR_END;
4569 /**
4570  * Return node with the given index
4571  */
4572 Inkscape::NodePath::Node *
4573 sp_nodepath_get_node_by_index(int index)
4575     Inkscape::NodePath::Node *e = NULL;
4577     Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4578     if (!nodepath) {
4579         return e;
4580     }
4582     //find segment
4583     for (GList *l = nodepath->subpaths; l ; l=l->next) {
4585         Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4586         int n = g_list_length(sp->nodes);
4587         if (sp->closed) {
4588             n++;
4589         }
4591         //if the piece belongs to this subpath grab it
4592         //otherwise move onto the next subpath
4593         if (index < n) {
4594             e = sp->first;
4595             for (int i = 0; i < index; ++i) {
4596                 e = e->n.other;
4597             }
4598             break;
4599         } else {
4600             if (sp->closed) {
4601                 index -= (n+1);
4602             } else {
4603                 index -= n;
4604             }
4605         }
4606     }
4608     return e;
4611 /**
4612  * Returns plain text meaning of node type.
4613  */
4614 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4616     unsigned retracted = 0;
4617     bool endnode = false;
4619     for (int which = -1; which <= 1; which += 2) {
4620         Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4621         if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4622             retracted ++;
4623         if (!side->other)
4624             endnode = true;
4625     }
4627     if (retracted == 0) {
4628         if (endnode) {
4629                 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4630                 return _("end node");
4631         } else {
4632             switch (node->type) {
4633                 case Inkscape::NodePath::NODE_CUSP:
4634                     // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4635                     return _("cusp");
4636                 case Inkscape::NodePath::NODE_SMOOTH:
4637                     // TRANSLATORS: "smooth" is an adjective here
4638                     return _("smooth");
4639                 case Inkscape::NodePath::NODE_SYMM:
4640                     return _("symmetric");
4641             }
4642         }
4643     } else if (retracted == 1) {
4644         if (endnode) {
4645             // TRANSLATORS: "end" is an adjective here (NOT a verb)
4646             return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4647         } else {
4648             return _("one handle retracted (drag with <b>Shift</b> to extend)");
4649         }
4650     } else {
4651         return _("both handles retracted (drag with <b>Shift</b> to extend)");
4652     }
4654     return NULL;
4657 /**
4658  * Handles content of statusbar as long as node tool is active.
4659  */
4660 void
4661 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4663     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");
4664     gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4666     gint total_nodes = sp_nodepath_get_node_count(nodepath);
4667     gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4668     gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4669     gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4671     SPDesktop *desktop = NULL;
4672     if (nodepath) {
4673         desktop = nodepath->desktop;
4674     } else {
4675         desktop = SP_ACTIVE_DESKTOP;
4676     }
4678     SPEventContext *ec = desktop->event_context;
4679     if (!ec) return;
4680     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4681     if (!mc) return;
4683     inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4685     if (selected_nodes == 0) {
4686         Inkscape::Selection *sel = desktop->selection;
4687         if (!sel || sel->isEmpty()) {
4688             mc->setF(Inkscape::NORMAL_MESSAGE,
4689                      _("Select a single object to edit its nodes or handles."));
4690         } else {
4691             if (nodepath) {
4692             mc->setF(Inkscape::NORMAL_MESSAGE,
4693                      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.",
4694                               "<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.",
4695                               total_nodes),
4696                      total_nodes);
4697             } else {
4698                 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4699                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4700                 } else {
4701                     mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4702                 }
4703             }
4704         }
4705     } else if (nodepath && selected_nodes == 1) {
4706         mc->setF(Inkscape::NORMAL_MESSAGE,
4707                  ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4708                           "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4709                           total_nodes),
4710                  selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4711     } else {
4712         if (selected_subpaths > 1) {
4713             mc->setF(Inkscape::NORMAL_MESSAGE,
4714                      ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4715                               "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4716                               total_nodes),
4717                      selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4718         } else {
4719             mc->setF(Inkscape::NORMAL_MESSAGE,
4720                      ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4721                               "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4722                               total_nodes),
4723                      selected_nodes, total_nodes, when_selected);
4724         }
4725     }
4728 /*
4729  * returns a *copy* of the curve of that object.
4730  */
4731 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4732     if (!object)
4733         return NULL;
4735     SPCurve *curve = NULL;
4736     if (SP_IS_PATH(object)) {
4737         SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4738         curve = curve_new->copy();
4739     } else if ( IS_LIVEPATHEFFECT(object) && key) {
4740         const gchar *svgd = object->repr->attribute(key);
4741         if (svgd) {
4742             Geom::PathVector pv = sp_svg_read_pathv(svgd);
4743             SPCurve *curve_new = new SPCurve(pv);
4744             if (curve_new) {
4745                 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4746             }
4747         }
4748     }
4750     return curve;
4753 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4754     if (!np || !np->object || !curve)
4755         return;
4757     if (SP_IS_PATH(np->object)) {
4758         if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4759             sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4760         } else {
4761             sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4762         }
4763     } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4764         Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4765         if (pathparam) {
4766             pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4767             np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4768         }
4769     }
4772 SPCanvasItem *
4773 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4774     SPCurve *flash_curve = curve->copy();
4775     Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4776     flash_curve->transform(i2d);
4777     SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4778     // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4779     // unless we also flash the nodes...
4780     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4781     sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4782     sp_canvas_item_show(canvasitem);
4783     flash_curve->unref();
4784     return canvasitem;
4787 SPCanvasItem *
4788 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4789     return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4790                                            prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4793 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4794     np->show_helperpath = show;
4796     if (show) {
4797         SPCurve *helper_curve = np->curve->copy();
4798         helper_curve->transform(to_2geom(np->i2d));
4799         if (!np->helper_path) {
4800             np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4801             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);
4802             sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4803             sp_canvas_item_move_to_z(np->helper_path, 0);
4804             sp_canvas_item_show(np->helper_path);
4805         } else {
4806             sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4807         }
4808         helper_curve->unref();
4809     } else {
4810         if (np->helper_path) {
4811             GtkObject *temp = np->helper_path;
4812             np->helper_path = NULL;
4813             gtk_object_destroy(temp);
4814         }
4815     }
4818 /* sp_nodepath_make_straight_path:
4819  *   Prevents user from curving the path by dragging a segment or activating handles etc.
4820  *   The resulting path is a linear interpolation between nodal points, with only straight segments.
4821  * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4822  */
4823 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4824     np->straight_path = true;
4825     np->show_handles = false;
4826     g_message("add code to make the path straight.");
4827     // do sp_nodepath_convert_node_type on all nodes?
4828     // coding tip: search for this text : "Make selected segments lines"
4832 /*
4833   Local Variables:
4834   mode:c++
4835   c-file-style:"stroustrup"
4836   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4837   indent-tabs-mode:nil
4838   fill-column:99
4839   End:
4840 */
4841 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :