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 "helper/units.h"
27 #include "knot.h"
28 #include "inkscape.h"
29 #include "document.h"
30 #include "sp-namedview.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "snap.h"
34 #include "message-stack.h"
35 #include "message-context.h"
36 #include "node-context.h"
37 #include "shape-editor.h"
38 #include "selection-chemistry.h"
39 #include "selection.h"
40 #include "xml/repr.h"
41 #include "prefs-utils.h"
42 #include "sp-metrics.h"
43 #include "sp-path.h"
44 #include "libnr/nr-matrix-ops.h"
45 #include "splivarot.h"
46 #include "svg/svg.h"
47 #include "verbs.h"
48 #include "display/bezier-utils.h"
49 #include <vector>
50 #include <algorithm>
51 #include <cstring>
52 #include <string>
53 #include "live_effects/lpeobject.h"
54 #include "live_effects/effect.h"
55 #include "live_effects/parameter/parameter.h"
56 #include "util/mathfns.h"
57 #include "display/snap-indicator.h"
58 #include "snapped-point.h"
60 class NR::Matrix;
62 /// \todo
63 /// evil evil evil. FIXME: conflict of two different Path classes!
64 /// There is a conflict in the namespace between two classes named Path.
65 /// #include "sp-flowtext.h"
66 /// #include "sp-flowregion.h"
68 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
69 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
70 GType sp_flowregion_get_type (void);
71 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
72 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
73 GType sp_flowtext_get_type (void);
74 // end evil workaround
76 #include "helper/stlport.h"
79 /// \todo fixme: Implement these via preferences */
81 #define NODE_FILL 0xbfbfbf00
82 #define NODE_STROKE 0x000000ff
83 #define NODE_FILL_HI 0xff000000
84 #define NODE_STROKE_HI 0x000000ff
85 #define NODE_FILL_SEL 0x0000ffff
86 #define NODE_STROKE_SEL 0x000000ff
87 #define NODE_FILL_SEL_HI 0xff000000
88 #define NODE_STROKE_SEL_HI 0x000000ff
89 #define KNOT_FILL 0xffffffff
90 #define KNOT_STROKE 0x000000ff
91 #define KNOT_FILL_HI 0xff000000
92 #define KNOT_STROKE_HI 0x000000ff
94 static GMemChunk *nodechunk = NULL;
96 /* Creation from object */
98 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
99 static void add_curve_to_subpath( Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c,
100 Inkscape::NodePath::NodeType const *t, guint & i, NR::Point & ppos, NRPathcode & pcode );
101 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
103 /* Object updating */
105 static void stamp_repr(Inkscape::NodePath::Path *np);
106 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
107 static gchar *create_typestr(Inkscape::NodePath::Path *np);
109 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
111 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
113 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
115 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
117 /* Adjust handle placement, if the node or the other handle is moved */
118 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
119 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
121 /* Node event callbacks */
122 static void node_clicked(SPKnot *knot, guint state, gpointer data);
123 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
124 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
125 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
127 /* Handle event callbacks */
128 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
129 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
130 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
131 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
132 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
133 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
135 /* Constructors and destructors */
137 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
138 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
139 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
140 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
141 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
142 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
143 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
145 /* Helpers */
147 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
148 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
149 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
151 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
152 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
154 // active_node indicates mouseover node
155 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
157 static void sp_nodepath_draw_helper_curve(Inkscape::NodePath::Path *np, SPDesktop *desktop) {
158 // Draw helper curve
159 if (np->show_helperpath) {
160 SPCurve *helper_curve = np->curve->copy();
161 helper_curve->transform(to_2geom(np->i2d));
162 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
163 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);
164 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
165 sp_canvas_item_move_to_z(np->helper_path, 0);
166 sp_canvas_item_show(np->helper_path);
167 helper_curve->unref();
168 }
169 }
171 /**
172 * \brief Creates new nodepath from item
173 */
174 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
175 {
176 Inkscape::XML::Node *repr = object->repr;
178 /** \todo
179 * FIXME: remove this. We don't want to edit paths inside flowtext.
180 * Instead we will build our flowtext with cloned paths, so that the
181 * real paths are outside the flowtext and thus editable as usual.
182 */
183 if (SP_IS_FLOWTEXT(object)) {
184 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
185 if SP_IS_FLOWREGION(child) {
186 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
187 if (grandchild && SP_IS_PATH(grandchild)) {
188 object = SP_ITEM(grandchild);
189 break;
190 }
191 }
192 }
193 }
195 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
197 if (curve == NULL)
198 return NULL;
200 if (curve->get_segment_count() < 1) {
201 curve->unref();
202 return NULL; // prevent crash for one-node paths
203 }
205 //Create new nodepath
206 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
207 if (!np) {
208 curve->unref();
209 return NULL;
210 }
212 // Set defaults
213 np->desktop = desktop;
214 np->object = object;
215 np->subpaths = NULL;
216 np->selected = NULL;
217 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
218 np->livarot_path = NULL;
219 np->local_change = 0;
220 np->show_handles = show_handles;
221 np->helper_path = NULL;
222 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
223 np->helperpath_width = 1.0;
224 np->curve = curve->copy();
225 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
226 if (SP_IS_LPE_ITEM(object)) {
227 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
228 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
229 np->show_helperpath = true;
230 }
231 }
232 np->straight_path = false;
233 if (IS_LIVEPATHEFFECT(object) && item) {
234 np->item = item;
235 } else {
236 np->item = SP_ITEM(object);
237 }
239 // we need to update item's transform from the repr here,
240 // because they may be out of sync when we respond
241 // to a change in repr by regenerating nodepath --bb
242 sp_object_read_attr(SP_OBJECT(np->item), "transform");
244 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
245 np->d2i = np->i2d.inverse();
247 np->repr = repr;
248 if (repr_key_in) { // apparantly the object is an LPEObject
249 np->repr_key = g_strdup(repr_key_in);
250 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
251 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
252 if (lpeparam) {
253 lpeparam->param_setup_nodepath(np);
254 }
255 } else {
256 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
257 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
258 np->repr_key = g_strdup("inkscape:original-d");
260 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
261 if (lpe) {
262 lpe->setup_nodepath(np);
263 }
264 } else {
265 np->repr_key = g_strdup("d");
266 }
267 }
269 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
270 * So for example a closed rectangle has a nodetypestring of length 5.
271 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
272 Geom::PathVector const &pathv = curve->get_pathvector();
273 guint length = curve->get_segment_count();
274 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
275 length += pit->empty() ? 0 : 1;
276 }
278 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
279 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
281 // create the subpath(s) from the bpath
282 subpaths_from_pathvector(np, pathv, typestr);
284 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
285 np->subpaths = g_list_reverse(np->subpaths);
287 delete[] typestr;
288 curve->unref();
290 // create the livarot representation from the same item
291 sp_nodepath_ensure_livarot_path(np);
293 sp_nodepath_draw_helper_curve(np, desktop);
295 return np;
296 }
298 /**
299 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
300 */
301 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
303 if (!np) //soft fail, like delete
304 return;
306 while (np->subpaths) {
307 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
308 }
310 //Inform the ShapeEditor that made me, if any, that I am gone.
311 if (np->shape_editor)
312 np->shape_editor->nodepath_destroyed();
314 g_assert(!np->selected);
316 if (np->livarot_path) {
317 delete np->livarot_path;
318 np->livarot_path = NULL;
319 }
321 if (np->helper_path) {
322 GtkObject *temp = np->helper_path;
323 np->helper_path = NULL;
324 gtk_object_destroy(temp);
325 }
326 if (np->curve) {
327 np->curve->unref();
328 np->curve = NULL;
329 }
331 if (np->repr_key) {
332 g_free(np->repr_key);
333 np->repr_key = NULL;
334 }
335 if (np->repr_nodetypes_key) {
336 g_free(np->repr_nodetypes_key);
337 np->repr_nodetypes_key = NULL;
338 }
340 np->desktop = NULL;
342 g_free(np);
343 }
346 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
347 {
348 if (np && np->livarot_path == NULL) {
349 SPCurve *curve = create_curve(np);
350 np->livarot_path = new Path;
351 np->livarot_path->LoadPathVector(curve->get_pathvector());
353 if (np->livarot_path)
354 np->livarot_path->ConvertWithBackData(0.01);
356 curve->unref();
357 }
358 }
361 /**
362 * Return the node count of a given NodeSubPath.
363 */
364 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
365 {
366 if (!subpath)
367 return 0;
368 gint nodeCount = g_list_length(subpath->nodes);
369 return nodeCount;
370 }
372 /**
373 * Return the node count of a given NodePath.
374 */
375 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
376 {
377 if (!np)
378 return 0;
379 gint nodeCount = 0;
380 for (GList *item = np->subpaths ; item ; item=item->next) {
381 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
382 nodeCount += g_list_length(subpath->nodes);
383 }
384 return nodeCount;
385 }
387 /**
388 * Return the subpath count of a given NodePath.
389 */
390 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
391 {
392 if (!np)
393 return 0;
394 return g_list_length (np->subpaths);
395 }
397 /**
398 * Return the selected node count of a given NodePath.
399 */
400 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
401 {
402 if (!np)
403 return 0;
404 return g_list_length (np->selected);
405 }
407 /**
408 * Return the number of subpaths where nodes are selected in a given NodePath.
409 */
410 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
411 {
412 if (!np)
413 return 0;
414 if (!np->selected)
415 return 0;
416 if (!np->selected->next)
417 return 1;
418 gint count = 0;
419 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
420 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
421 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
422 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
423 if (node->selected) {
424 count ++;
425 break;
426 }
427 }
428 }
429 return count;
430 }
432 /**
433 * Clean up a nodepath after editing.
434 *
435 * Currently we are deleting trivial subpaths.
436 */
437 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
438 {
439 GList *badSubPaths = NULL;
441 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
442 for (GList *l = nodepath->subpaths; l ; l=l->next) {
443 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
444 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
445 badSubPaths = g_list_append(badSubPaths, sp);
446 }
448 //Delete them. This second step is because sp_nodepath_subpath_destroy()
449 //also removes the subpath from nodepath->subpaths
450 for (GList *l = badSubPaths; l ; l=l->next) {
451 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
452 sp_nodepath_subpath_destroy(sp);
453 }
455 g_list_free(badSubPaths);
456 }
458 /**
459 * Create new nodepaths from pathvector, make it subpaths of np.
460 * \param t The node type array.
461 */
462 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
463 {
464 guint i = 0; // index into node type array
465 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
466 if (pit->empty())
467 continue; // don't add single knot paths
469 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
471 NR::Point ppos = from_2geom(pit->initialPoint()) * np->i2d;
472 NRPathcode pcode = NR_MOVETO;
474 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
475 add_curve_to_subpath(np, sp, *cit, t, i, ppos, pcode);
476 }
478 if (pit->closed()) {
479 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
480 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
481 * If the length is zero, don't add it to the nodepath. */
482 Geom::Curve const &closing_seg = pit->back_closed();
483 if ( ! closing_seg.isDegenerate() ) {
484 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
485 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
486 }
488 sp_nodepath_subpath_close(sp);
489 }
490 }
491 }
492 // should add initial point of curve with type of previous curve:
493 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,
494 NR::Point & ppos, NRPathcode & pcode)
495 {
496 if( dynamic_cast<Geom::LineSegment const*>(&c) ||
497 dynamic_cast<Geom::HLineSegment const*>(&c) ||
498 dynamic_cast<Geom::VLineSegment const*>(&c) )
499 {
500 NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
501 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
502 ppos = from_2geom(c.finalPoint());
503 pcode = NR_LINETO;
504 }
505 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
506 std::vector<Geom::Point> points = cubic_bezier->points();
507 NR::Point pos = from_2geom(points[0]) * np->i2d;
508 NR::Point npos = from_2geom(points[1]) * np->i2d;
509 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
510 ppos = from_2geom(points[2]) * np->i2d;
511 pcode = NR_CURVETO;
512 }
513 else {
514 //this case handles sbasis as well as all other curve types
515 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
517 for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
518 add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
519 }
520 }
521 }
524 /**
525 * Convert from sodipodi:nodetypes to new style type array.
526 */
527 static
528 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
529 {
530 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
532 guint pos = 0;
534 if (types) {
535 for (guint i = 0; types[i] && ( i < length ); i++) {
536 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
537 if (types[i] != '\0') {
538 switch (types[i]) {
539 case 's':
540 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
541 break;
542 case 'z':
543 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
544 break;
545 case 'c':
546 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
547 break;
548 default:
549 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
550 break;
551 }
552 }
553 }
554 }
556 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
558 return typestr;
559 }
561 /**
562 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
563 * updated but repr is not (for speed). Used during curve and node drag.
564 */
565 static void update_object(Inkscape::NodePath::Path *np)
566 {
567 g_assert(np);
569 np->curve->unref();
570 np->curve = create_curve(np);
572 sp_nodepath_set_curve(np, np->curve);
574 if (np->show_helperpath) {
575 SPCurve * helper_curve = np->curve->copy();
576 helper_curve->transform(to_2geom(np->i2d));
577 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
578 helper_curve->unref();
579 }
581 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
582 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
583 np->shape_editor->update_knotholder();
584 }
586 /**
587 * Update XML path node with data from path object.
588 */
589 static void update_repr_internal(Inkscape::NodePath::Path *np)
590 {
591 g_assert(np);
593 Inkscape::XML::Node *repr = np->object->repr;
595 np->curve->unref();
596 np->curve = create_curve(np);
598 gchar *typestr = create_typestr(np);
599 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
601 // determine if path has an effect applied and write to correct "d" attribute.
602 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
603 np->local_change++;
604 repr->setAttribute(np->repr_key, svgpath);
605 }
607 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
608 np->local_change++;
609 repr->setAttribute(np->repr_nodetypes_key, typestr);
610 }
612 g_free(svgpath);
613 g_free(typestr);
615 if (np->show_helperpath) {
616 SPCurve * helper_curve = np->curve->copy();
617 helper_curve->transform(to_2geom(np->i2d));
618 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
619 helper_curve->unref();
620 }
621 }
623 /**
624 * Update XML path node with data from path object, commit changes forever.
625 */
626 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
627 {
628 //fixme: np can be NULL, so check before proceeding
629 g_return_if_fail(np != NULL);
631 if (np->livarot_path) {
632 delete np->livarot_path;
633 np->livarot_path = NULL;
634 }
636 update_repr_internal(np);
637 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
639 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
640 annotation);
641 }
643 /**
644 * Update XML path node with data from path object, commit changes with undo.
645 */
646 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
647 {
648 if (np->livarot_path) {
649 delete np->livarot_path;
650 np->livarot_path = NULL;
651 }
653 update_repr_internal(np);
654 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
655 annotation);
656 }
658 /**
659 * Make duplicate of path, replace corresponding XML node in tree, commit.
660 */
661 static void stamp_repr(Inkscape::NodePath::Path *np)
662 {
663 g_assert(np);
665 Inkscape::XML::Node *old_repr = np->object->repr;
666 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
668 // remember the position of the item
669 gint pos = old_repr->position();
670 // remember parent
671 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
673 SPCurve *curve = create_curve(np);
674 gchar *typestr = create_typestr(np);
676 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
678 new_repr->setAttribute(np->repr_key, svgpath);
679 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
681 // add the new repr to the parent
682 parent->appendChild(new_repr);
683 // move to the saved position
684 new_repr->setPosition(pos > 0 ? pos : 0);
686 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
687 _("Stamp"));
689 Inkscape::GC::release(new_repr);
690 g_free(svgpath);
691 g_free(typestr);
692 curve->unref();
693 }
695 /**
696 * Create curve from path.
697 */
698 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
699 {
700 SPCurve *curve = new SPCurve();
702 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
703 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
704 curve->moveto(sp->first->pos * np->d2i);
705 Inkscape::NodePath::Node *n = sp->first->n.other;
706 while (n) {
707 NR::Point const end_pt = n->pos * np->d2i;
708 switch (n->code) {
709 case NR_LINETO:
710 curve->lineto(end_pt);
711 break;
712 case NR_CURVETO:
713 curve->curveto(n->p.other->n.pos * np->d2i,
714 n->p.pos * np->d2i,
715 end_pt);
716 break;
717 default:
718 g_assert_not_reached();
719 break;
720 }
721 if (n != sp->last) {
722 n = n->n.other;
723 } else {
724 n = NULL;
725 }
726 }
727 if (sp->closed) {
728 curve->closepath();
729 }
730 }
732 return curve;
733 }
735 /**
736 * Convert path type string to sodipodi:nodetypes style.
737 */
738 static gchar *create_typestr(Inkscape::NodePath::Path *np)
739 {
740 gchar *typestr = g_new(gchar, 32);
741 gint len = 32;
742 gint pos = 0;
744 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
745 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
747 if (pos >= len) {
748 typestr = g_renew(gchar, typestr, len + 32);
749 len += 32;
750 }
752 typestr[pos++] = 'c';
754 Inkscape::NodePath::Node *n;
755 n = sp->first->n.other;
756 while (n) {
757 gchar code;
759 switch (n->type) {
760 case Inkscape::NodePath::NODE_CUSP:
761 code = 'c';
762 break;
763 case Inkscape::NodePath::NODE_SMOOTH:
764 code = 's';
765 break;
766 case Inkscape::NodePath::NODE_SYMM:
767 code = 'z';
768 break;
769 default:
770 g_assert_not_reached();
771 code = '\0';
772 break;
773 }
775 if (pos >= len) {
776 typestr = g_renew(gchar, typestr, len + 32);
777 len += 32;
778 }
780 typestr[pos++] = code;
782 if (n != sp->last) {
783 n = n->n.other;
784 } else {
785 n = NULL;
786 }
787 }
788 }
790 if (pos >= len) {
791 typestr = g_renew(gchar, typestr, len + 1);
792 len += 1;
793 }
795 typestr[pos++] = '\0';
797 return typestr;
798 }
800 /**
801 * Returns current path in context. // later eliminate this function at all!
802 */
803 static Inkscape::NodePath::Path *sp_nodepath_current()
804 {
805 if (!SP_ACTIVE_DESKTOP) {
806 return NULL;
807 }
809 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
811 if (!SP_IS_NODE_CONTEXT(event_context)) {
812 return NULL;
813 }
815 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
816 }
820 /**
821 \brief Fills node and handle positions for three nodes, splitting line
822 marked by end at distance t.
823 */
824 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
825 {
826 g_assert(new_path != NULL);
827 g_assert(end != NULL);
829 g_assert(end->p.other == new_path);
830 Inkscape::NodePath::Node *start = new_path->p.other;
831 g_assert(start);
833 if (end->code == NR_LINETO) {
834 new_path->type =Inkscape::NodePath::NODE_CUSP;
835 new_path->code = NR_LINETO;
836 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
837 } else {
838 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
839 new_path->code = NR_CURVETO;
840 gdouble s = 1 - t;
841 for (int dim = 0; dim < 2; dim++) {
842 NR::Coord const f000 = start->pos[dim];
843 NR::Coord const f001 = start->n.pos[dim];
844 NR::Coord const f011 = end->p.pos[dim];
845 NR::Coord const f111 = end->pos[dim];
846 NR::Coord const f00t = s * f000 + t * f001;
847 NR::Coord const f01t = s * f001 + t * f011;
848 NR::Coord const f11t = s * f011 + t * f111;
849 NR::Coord const f0tt = s * f00t + t * f01t;
850 NR::Coord const f1tt = s * f01t + t * f11t;
851 NR::Coord const fttt = s * f0tt + t * f1tt;
852 start->n.pos[dim] = f00t;
853 new_path->p.pos[dim] = f0tt;
854 new_path->pos[dim] = fttt;
855 new_path->n.pos[dim] = f1tt;
856 end->p.pos[dim] = f11t;
857 }
858 }
859 }
861 /**
862 * Adds new node on direct line between two nodes, activates handles of all
863 * three nodes.
864 */
865 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
866 {
867 g_assert(end);
868 g_assert(end->subpath);
869 g_assert(g_list_find(end->subpath->nodes, end));
871 Inkscape::NodePath::Node *start = end->p.other;
872 g_assert( start->n.other == end );
873 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
874 end,
875 (NRPathcode)end->code == NR_LINETO?
876 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
877 (NRPathcode)end->code,
878 &start->pos, &start->pos, &start->n.pos);
879 sp_nodepath_line_midpoint(newnode, end, t);
881 sp_node_adjust_handles(start);
882 sp_node_update_handles(start);
883 sp_node_update_handles(newnode);
884 sp_node_adjust_handles(end);
885 sp_node_update_handles(end);
887 return newnode;
888 }
890 /**
891 \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
892 */
893 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
894 {
895 g_assert(node);
896 g_assert(node->subpath);
897 g_assert(g_list_find(node->subpath->nodes, node));
899 Inkscape::NodePath::SubPath *sp = node->subpath;
900 Inkscape::NodePath::Path *np = sp->nodepath;
902 if (sp->closed) {
903 sp_nodepath_subpath_open(sp, node);
904 return sp->first;
905 } else {
906 // no break for end nodes
907 if (node == sp->first) return NULL;
908 if (node == sp->last ) return NULL;
910 // create a new subpath
911 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
913 // duplicate the break node as start of the new subpath
914 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
916 // attach rest of curve to new node
917 g_assert(node->n.other);
918 newnode->n.other = node->n.other; node->n.other = NULL;
919 newnode->n.other->p.other = newnode;
920 newsubpath->last = sp->last;
921 sp->last = node;
922 node = newnode;
923 while (node->n.other) {
924 node = node->n.other;
925 node->subpath = newsubpath;
926 sp->nodes = g_list_remove(sp->nodes, node);
927 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
928 }
931 return newnode;
932 }
933 }
935 /**
936 * Duplicate node and connect to neighbours.
937 */
938 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
939 {
940 g_assert(node);
941 g_assert(node->subpath);
942 g_assert(g_list_find(node->subpath->nodes, node));
944 Inkscape::NodePath::SubPath *sp = node->subpath;
946 NRPathcode code = (NRPathcode) node->code;
947 if (code == NR_MOVETO) { // if node is the endnode,
948 node->code = NR_LINETO; // new one is inserted before it, so change that to line
949 }
951 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
953 if (!node->n.other || !node->p.other) // if node is an endnode, select it
954 return node;
955 else
956 return newnode; // otherwise select the newly created node
957 }
959 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
960 {
961 node->p.pos = (node->pos + (node->pos - node->n.pos));
962 }
964 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
965 {
966 node->n.pos = (node->pos + (node->pos - node->p.pos));
967 }
969 /**
970 * Change line type at node, with side effects on neighbours.
971 */
972 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
973 {
974 g_assert(end);
975 g_assert(end->subpath);
976 g_assert(end->p.other);
978 if (end->code == static_cast< guint > ( code ) )
979 return;
981 Inkscape::NodePath::Node *start = end->p.other;
983 end->code = code;
985 if (code == NR_LINETO) {
986 if (start->code == NR_LINETO) {
987 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
988 }
989 if (end->n.other) {
990 if (end->n.other->code == NR_LINETO) {
991 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
992 }
993 }
994 } else {
995 NR::Point delta = end->pos - start->pos;
996 start->n.pos = start->pos + delta / 3;
997 end->p.pos = end->pos - delta / 3;
998 sp_node_adjust_handle(start, 1);
999 sp_node_adjust_handle(end, -1);
1000 }
1002 sp_node_update_handles(start);
1003 sp_node_update_handles(end);
1004 }
1006 /**
1007 * Change node type, and its handles accordingly.
1008 */
1009 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1010 {
1011 g_assert(node);
1012 g_assert(node->subpath);
1014 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1015 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1016 type =Inkscape::NodePath::NODE_CUSP;
1017 }
1018 }
1020 node->type = type;
1022 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1023 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1024 node->knot->setSize (node->selected? 11 : 9);
1025 sp_knot_update_ctrl(node->knot);
1026 } else {
1027 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1028 node->knot->setSize (node->selected? 9 : 7);
1029 sp_knot_update_ctrl(node->knot);
1030 }
1032 // if one of handles is mouseovered, preserve its position
1033 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1034 sp_node_adjust_handle(node, 1);
1035 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1036 sp_node_adjust_handle(node, -1);
1037 } else {
1038 sp_node_adjust_handles(node);
1039 }
1041 sp_node_update_handles(node);
1043 sp_nodepath_update_statusbar(node->subpath->nodepath);
1045 return node;
1046 }
1048 bool
1049 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1050 {
1051 Inkscape::NodePath::Node *othernode = side->other;
1052 if (!othernode)
1053 return false;
1054 NRPathcode const code = sp_node_path_code_from_side(node, side);
1055 if (code == NR_LINETO)
1056 return true;
1057 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1058 if (&node->p == side) {
1059 other_to_me = &othernode->n;
1060 } else if (&node->n == side) {
1061 other_to_me = &othernode->p;
1062 }
1063 if (!other_to_me)
1064 return false;
1065 bool is_line =
1066 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1067 NR::L2(node->pos - side->pos) < 1e-6);
1068 return is_line;
1069 }
1071 /**
1072 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1073 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1074 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1075 * If already cusp and set to cusp, retracts handles.
1076 */
1077 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1078 {
1079 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1081 /*
1082 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1084 if (two_handles) {
1085 // do nothing, adjust_handles called via set_node_type will line them up
1086 } else if (one_handle) {
1087 if (opposite_to_handle_is_line) {
1088 if (lined_up) {
1089 // already half-smooth; pull opposite handle too making it fully smooth
1090 } else {
1091 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1092 }
1093 } else {
1094 // pull opposite handle in line with the existing one
1095 }
1096 } else if (no_handles) {
1097 if (both_segments_are_lines OR both_segments_are_curves) {
1098 //pull both handles
1099 } else {
1100 // pull the handle opposite to line segment, making node half-smooth
1101 }
1102 }
1103 */
1104 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1105 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1106 bool p_is_line = sp_node_side_is_line(node, &node->p);
1107 bool n_is_line = sp_node_side_is_line(node, &node->n);
1109 if (p_has_handle && n_has_handle) {
1110 // do nothing, adjust_handles will line them up
1111 } else if (p_has_handle || n_has_handle) {
1112 if (p_has_handle && n_is_line) {
1113 Radial line (node->n.other->pos - node->pos);
1114 Radial handle (node->pos - node->p.pos);
1115 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1116 // already half-smooth; pull opposite handle too making it fully smooth
1117 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1118 } else {
1119 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1120 }
1121 } else if (n_has_handle && p_is_line) {
1122 Radial line (node->p.other->pos - node->pos);
1123 Radial handle (node->pos - node->n.pos);
1124 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1125 // already half-smooth; pull opposite handle too making it fully smooth
1126 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1127 } else {
1128 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1129 }
1130 } else if (p_has_handle && node->n.other) {
1131 // pull n handle
1132 node->n.other->code = NR_CURVETO;
1133 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1134 NR::L2(node->p.pos - node->pos) :
1135 NR::L2(node->n.other->pos - node->pos) / 3;
1136 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1137 } else if (n_has_handle && node->p.other) {
1138 // pull p handle
1139 node->code = NR_CURVETO;
1140 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1141 NR::L2(node->n.pos - node->pos) :
1142 NR::L2(node->p.other->pos - node->pos) / 3;
1143 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1144 }
1145 } else if (!p_has_handle && !n_has_handle) {
1146 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1147 // no handles, but both segments are either lnes or curves:
1148 //pull both handles
1150 // convert both to curves:
1151 node->code = NR_CURVETO;
1152 node->n.other->code = NR_CURVETO;
1154 NR::Point leg_prev = node->pos - node->p.other->pos;
1155 NR::Point leg_next = node->pos - node->n.other->pos;
1157 double norm_leg_prev = L2(leg_prev);
1158 double norm_leg_next = L2(leg_next);
1160 NR::Point delta;
1161 if (norm_leg_next > 0.0) {
1162 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1163 (&delta)->normalize();
1164 }
1166 if (type == Inkscape::NodePath::NODE_SYMM) {
1167 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1168 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1169 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1170 } else {
1171 // length of handle is proportional to distance to adjacent node
1172 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1173 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1174 }
1176 } else {
1177 // pull the handle opposite to line segment, making it half-smooth
1178 if (p_is_line && node->n.other) {
1179 if (type != Inkscape::NodePath::NODE_SYMM) {
1180 // pull n handle
1181 node->n.other->code = NR_CURVETO;
1182 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1183 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1184 }
1185 } else if (n_is_line && node->p.other) {
1186 if (type != Inkscape::NodePath::NODE_SYMM) {
1187 // pull p handle
1188 node->code = NR_CURVETO;
1189 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1190 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1191 }
1192 }
1193 }
1194 }
1195 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1196 // cusping a cusp: retract nodes
1197 node->p.pos = node->pos;
1198 node->n.pos = node->pos;
1199 }
1201 sp_nodepath_set_node_type (node, type);
1202 }
1204 /**
1205 * Move node to point, and adjust its and neighbouring handles.
1206 */
1207 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1208 {
1209 NR::Point delta = p - node->pos;
1210 node->pos = p;
1212 node->p.pos += delta;
1213 node->n.pos += delta;
1215 Inkscape::NodePath::Node *node_p = NULL;
1216 Inkscape::NodePath::Node *node_n = NULL;
1218 if (node->p.other) {
1219 if (node->code == NR_LINETO) {
1220 sp_node_adjust_handle(node, 1);
1221 sp_node_adjust_handle(node->p.other, -1);
1222 node_p = node->p.other;
1223 }
1224 }
1225 if (node->n.other) {
1226 if (node->n.other->code == NR_LINETO) {
1227 sp_node_adjust_handle(node, -1);
1228 sp_node_adjust_handle(node->n.other, 1);
1229 node_n = node->n.other;
1230 }
1231 }
1233 // this function is only called from batch movers that will update display at the end
1234 // themselves, so here we just move all the knots without emitting move signals, for speed
1235 sp_node_update_handles(node, false);
1236 if (node_n) {
1237 sp_node_update_handles(node_n, false);
1238 }
1239 if (node_p) {
1240 sp_node_update_handles(node_p, false);
1241 }
1242 }
1244 /**
1245 * Call sp_node_moveto() for node selection and handle possible snapping.
1246 */
1247 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1248 bool const snap, bool constrained = false,
1249 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1250 {
1251 NR::Coord best = NR_HUGE;
1252 NR::Point delta(dx, dy);
1253 NR::Point best_pt = delta;
1254 Inkscape::SnappedPoint best_abs;
1256 if (snap) {
1257 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1258 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1259 * must provide that information. */
1261 // Build a list of the unselected nodes to which the snapper should snap
1262 std::vector<NR::Point> unselected_nodes;
1263 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1264 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1265 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1266 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1267 if (!node->selected) {
1268 unselected_nodes.push_back(node->pos);
1269 }
1270 }
1271 }
1273 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1275 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1276 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1277 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1278 Inkscape::SnappedPoint s;
1279 if (constrained) {
1280 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1281 dedicated_constraint.setPoint(n->pos);
1282 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1283 } else {
1284 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1285 }
1286 if (s.getSnapped() && (s.getDistance() < best)) {
1287 best = s.getDistance();
1288 best_abs = s;
1289 best_pt = s.getPoint() - n->pos;
1290 }
1291 }
1293 if (best_abs.getSnapped()) {
1294 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1295 } else {
1296 nodepath->desktop->snapindicator->remove_snappoint();
1297 }
1298 }
1300 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1301 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1302 sp_node_moveto(n, n->pos + best_pt);
1303 }
1305 // do not update repr here so that node dragging is acceptably fast
1306 update_object(nodepath);
1307 }
1309 /**
1310 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1311 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1312 near x = 0.
1313 */
1314 double
1315 sculpt_profile (double x, double alpha, guint profile)
1316 {
1317 if (x >= 1)
1318 return 0;
1319 if (x <= 0)
1320 return 1;
1322 switch (profile) {
1323 case SCULPT_PROFILE_LINEAR:
1324 return 1 - x;
1325 case SCULPT_PROFILE_BELL:
1326 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1327 case SCULPT_PROFILE_ELLIPTIC:
1328 return sqrt(1 - x*x);
1329 }
1331 return 1;
1332 }
1334 double
1335 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1336 {
1337 // extremely primitive for now, don't have time to look for the real one
1338 double lower = NR::L2(b - a);
1339 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1340 return (lower + upper)/2;
1341 }
1343 void
1344 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1345 {
1346 n->pos = n->origin + delta;
1347 n->n.pos = n->n.origin + delta_n;
1348 n->p.pos = n->p.origin + delta_p;
1349 sp_node_adjust_handles(n);
1350 sp_node_update_handles(n, false);
1351 }
1353 /**
1354 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1355 * on how far they are from the dragged node n.
1356 */
1357 static void
1358 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1359 {
1360 g_assert (n);
1361 g_assert (nodepath);
1362 g_assert (n->subpath->nodepath == nodepath);
1364 double pressure = n->knot->pressure;
1365 if (pressure == 0)
1366 pressure = 0.5; // default
1367 pressure = CLAMP (pressure, 0.2, 0.8);
1369 // map pressure to alpha = 1/5 ... 5
1370 double alpha = 1 - 2 * fabs(pressure - 0.5);
1371 if (pressure > 0.5)
1372 alpha = 1/alpha;
1374 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1376 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1377 // Only one subpath has selected nodes:
1378 // use linear mode, where the distance from n to node being dragged is calculated along the path
1380 double n_sel_range = 0, p_sel_range = 0;
1381 guint n_nodes = 0, p_nodes = 0;
1382 guint n_sel_nodes = 0, p_sel_nodes = 0;
1384 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1385 {
1386 double n_range = 0, p_range = 0;
1387 bool n_going = true, p_going = true;
1388 Inkscape::NodePath::Node *n_node = n;
1389 Inkscape::NodePath::Node *p_node = n;
1390 do {
1391 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1392 if (n_node && n_going)
1393 n_node = n_node->n.other;
1394 if (n_node == NULL) {
1395 n_going = false;
1396 } else {
1397 n_nodes ++;
1398 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1399 if (n_node->selected) {
1400 n_sel_nodes ++;
1401 n_sel_range = n_range;
1402 }
1403 if (n_node == p_node) {
1404 n_going = false;
1405 p_going = false;
1406 }
1407 }
1408 if (p_node && p_going)
1409 p_node = p_node->p.other;
1410 if (p_node == NULL) {
1411 p_going = false;
1412 } else {
1413 p_nodes ++;
1414 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1415 if (p_node->selected) {
1416 p_sel_nodes ++;
1417 p_sel_range = p_range;
1418 }
1419 if (p_node == n_node) {
1420 n_going = false;
1421 p_going = false;
1422 }
1423 }
1424 } while (n_going || p_going);
1425 }
1427 // Second pass: actually move nodes in this subpath
1428 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1429 {
1430 double n_range = 0, p_range = 0;
1431 bool n_going = true, p_going = true;
1432 Inkscape::NodePath::Node *n_node = n;
1433 Inkscape::NodePath::Node *p_node = n;
1434 do {
1435 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1436 if (n_node && n_going)
1437 n_node = n_node->n.other;
1438 if (n_node == NULL) {
1439 n_going = false;
1440 } else {
1441 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1442 if (n_node->selected) {
1443 sp_nodepath_move_node_and_handles (n_node,
1444 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1445 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1446 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1447 }
1448 if (n_node == p_node) {
1449 n_going = false;
1450 p_going = false;
1451 }
1452 }
1453 if (p_node && p_going)
1454 p_node = p_node->p.other;
1455 if (p_node == NULL) {
1456 p_going = false;
1457 } else {
1458 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1459 if (p_node->selected) {
1460 sp_nodepath_move_node_and_handles (p_node,
1461 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1462 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1463 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1464 }
1465 if (p_node == n_node) {
1466 n_going = false;
1467 p_going = false;
1468 }
1469 }
1470 } while (n_going || p_going);
1471 }
1473 } else {
1474 // Multiple subpaths have selected nodes:
1475 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1476 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1477 // fix the pear-like shape when sculpting e.g. a ring
1479 // First pass: calculate range
1480 gdouble direct_range = 0;
1481 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1482 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1483 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1484 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1485 if (node->selected) {
1486 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1487 }
1488 }
1489 }
1491 // Second pass: actually move nodes
1492 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1493 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1494 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1495 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1496 if (node->selected) {
1497 if (direct_range > 1e-6) {
1498 sp_nodepath_move_node_and_handles (node,
1499 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1500 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1501 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1502 } else {
1503 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1504 }
1506 }
1507 }
1508 }
1509 }
1511 // do not update repr here so that node dragging is acceptably fast
1512 update_object(nodepath);
1513 }
1516 /**
1517 * Move node selection to point, adjust its and neighbouring handles,
1518 * handle possible snapping, and commit the change with possible undo.
1519 */
1520 void
1521 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1522 {
1523 if (!nodepath) return;
1525 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1527 if (dx == 0) {
1528 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1529 } else if (dy == 0) {
1530 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1531 } else {
1532 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1533 }
1534 }
1536 /**
1537 * Move node selection off screen and commit the change.
1538 */
1539 void
1540 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1541 {
1542 // borrowed from sp_selection_move_screen in selection-chemistry.c
1543 // we find out the current zoom factor and divide deltas by it
1544 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1546 gdouble zoom = desktop->current_zoom();
1547 gdouble zdx = dx / zoom;
1548 gdouble zdy = dy / zoom;
1550 if (!nodepath) return;
1552 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1554 if (dx == 0) {
1555 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1556 } else if (dy == 0) {
1557 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1558 } else {
1559 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1560 }
1561 }
1563 /**
1564 * Move selected nodes to the absolute position given
1565 */
1566 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1567 {
1568 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1569 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1570 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1571 sp_node_moveto(n, npos);
1572 }
1574 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1575 }
1577 /**
1578 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1579 */
1580 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1581 {
1582 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1583 g_return_val_if_fail(nodepath->selected, no_coord);
1585 // determine coordinate of first selected node
1586 GList *nsel = nodepath->selected;
1587 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1588 NR::Coord coord = n->pos[axis];
1589 bool coincide = true;
1591 // compare it to the coordinates of all the other selected nodes
1592 for (GList *l = nsel->next; l != NULL; l = l->next) {
1593 n = (Inkscape::NodePath::Node *) l->data;
1594 if (n->pos[axis] != coord) {
1595 coincide = false;
1596 }
1597 }
1598 if (coincide) {
1599 return coord;
1600 } else {
1601 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1602 // currently we return the coordinate of the bounding box midpoint because I don't know how
1603 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1604 return bbox.midpoint()[axis];
1605 }
1606 }
1608 /** If they don't yet exist, creates knot and line for the given side of the node */
1609 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1610 {
1611 if (!side->knot) {
1612 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"));
1614 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1615 side->knot->setSize (7);
1616 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1617 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1618 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1619 sp_knot_update_ctrl(side->knot);
1621 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1622 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1623 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1624 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1625 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1626 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1627 }
1629 if (!side->line) {
1630 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1631 SP_TYPE_CTRLLINE, NULL);
1632 }
1633 }
1635 /**
1636 * Ensure the given handle of the node is visible/invisible, update its screen position
1637 */
1638 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1639 {
1640 g_assert(node != NULL);
1642 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1643 NRPathcode code = sp_node_path_code_from_side(node, side);
1645 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1647 if (show_handle) {
1648 if (!side->knot) { // No handle knot at all
1649 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1650 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1651 side->knot->pos = side->pos;
1652 if (side->knot->item)
1653 SP_CTRL(side->knot->item)->moveto(side->pos);
1654 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1655 sp_knot_show(side->knot);
1656 } else {
1657 if (side->knot->pos != side->pos) { // only if it's really moved
1658 if (fire_move_signals) {
1659 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1660 } else {
1661 sp_knot_moveto(side->knot, &side->pos);
1662 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1663 }
1664 }
1665 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1666 sp_knot_show(side->knot);
1667 }
1668 }
1669 sp_canvas_item_show(side->line);
1670 } else {
1671 if (side->knot) {
1672 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1673 sp_knot_hide(side->knot);
1674 }
1675 }
1676 if (side->line) {
1677 sp_canvas_item_hide(side->line);
1678 }
1679 }
1680 }
1682 /**
1683 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1684 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1685 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1686 * updated; otherwise, just move the knots silently (used in batch moves).
1687 */
1688 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1689 {
1690 g_assert(node != NULL);
1692 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1693 sp_knot_show(node->knot);
1694 }
1696 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1697 if (fire_move_signals)
1698 sp_knot_set_position(node->knot, &node->pos, 0);
1699 else
1700 sp_knot_moveto(node->knot, &node->pos);
1701 }
1703 gboolean show_handles = node->selected;
1704 if (node->p.other != NULL) {
1705 if (node->p.other->selected) show_handles = TRUE;
1706 }
1707 if (node->n.other != NULL) {
1708 if (node->n.other->selected) show_handles = TRUE;
1709 }
1711 if (node->subpath->nodepath->show_handles == false)
1712 show_handles = FALSE;
1714 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1715 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1716 }
1718 /**
1719 * Call sp_node_update_handles() for all nodes on subpath.
1720 */
1721 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1722 {
1723 g_assert(subpath != NULL);
1725 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1726 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1727 }
1728 }
1730 /**
1731 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1732 */
1733 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1734 {
1735 g_assert(nodepath != NULL);
1737 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1738 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1739 }
1740 }
1742 void
1743 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1744 {
1745 if (nodepath == NULL) return;
1747 nodepath->show_handles = show;
1748 sp_nodepath_update_handles(nodepath);
1749 }
1751 /**
1752 * Adds all selected nodes in nodepath to list.
1753 */
1754 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1755 {
1756 StlConv<Node *>::list(l, selected);
1757 /// \todo this adds a copying, rework when the selection becomes a stl list
1758 }
1760 /**
1761 * Align selected nodes on the specified axis.
1762 */
1763 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1764 {
1765 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1766 return;
1767 }
1769 if ( !nodepath->selected->next ) { // only one node selected
1770 return;
1771 }
1772 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1773 NR::Point dest(pNode->pos);
1774 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1775 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1776 if (pNode) {
1777 dest[axis] = pNode->pos[axis];
1778 sp_node_moveto(pNode, dest);
1779 }
1780 }
1782 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1783 }
1785 /// Helper struct.
1786 struct NodeSort
1787 {
1788 Inkscape::NodePath::Node *_node;
1789 NR::Coord _coord;
1790 /// \todo use vectorof pointers instead of calling copy ctor
1791 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1792 _node(node), _coord(node->pos[axis])
1793 {}
1795 };
1797 static bool operator<(NodeSort const &a, NodeSort const &b)
1798 {
1799 return (a._coord < b._coord);
1800 }
1802 /**
1803 * Distribute selected nodes on the specified axis.
1804 */
1805 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1806 {
1807 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1808 return;
1809 }
1811 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1812 return;
1813 }
1815 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1816 std::vector<NodeSort> sorted;
1817 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1818 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1819 if (pNode) {
1820 NodeSort n(pNode, axis);
1821 sorted.push_back(n);
1822 //dest[axis] = pNode->pos[axis];
1823 //sp_node_moveto(pNode, dest);
1824 }
1825 }
1826 std::sort(sorted.begin(), sorted.end());
1827 unsigned int len = sorted.size();
1828 //overall bboxes span
1829 float dist = (sorted.back()._coord -
1830 sorted.front()._coord);
1831 //new distance between each bbox
1832 float step = (dist) / (len - 1);
1833 float pos = sorted.front()._coord;
1834 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1835 it < sorted.end();
1836 it ++ )
1837 {
1838 NR::Point dest((*it)._node->pos);
1839 dest[axis] = pos;
1840 sp_node_moveto((*it)._node, dest);
1841 pos += step;
1842 }
1844 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1845 }
1848 /**
1849 * Call sp_nodepath_line_add_node() for all selected segments.
1850 */
1851 void
1852 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1853 {
1854 if (!nodepath) {
1855 return;
1856 }
1858 GList *nl = NULL;
1860 int n_added = 0;
1862 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1863 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1864 g_assert(t->selected);
1865 if (t->p.other && t->p.other->selected) {
1866 nl = g_list_prepend(nl, t);
1867 }
1868 }
1870 while (nl) {
1871 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1872 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1873 sp_nodepath_node_select(n, TRUE, FALSE);
1874 n_added ++;
1875 nl = g_list_remove(nl, t);
1876 }
1878 /** \todo fixme: adjust ? */
1879 sp_nodepath_update_handles(nodepath);
1881 if (n_added > 1) {
1882 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1883 } else if (n_added > 0) {
1884 sp_nodepath_update_repr(nodepath, _("Add node"));
1885 }
1887 sp_nodepath_update_statusbar(nodepath);
1888 }
1890 /**
1891 * Select segment nearest to point
1892 */
1893 void
1894 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1895 {
1896 if (!nodepath) {
1897 return;
1898 }
1900 sp_nodepath_ensure_livarot_path(nodepath);
1901 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1902 if (!maybe_position) {
1903 return;
1904 }
1905 Path::cut_position position = *maybe_position;
1907 //find segment to segment
1908 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1910 //fixme: this can return NULL, so check before proceeding.
1911 g_return_if_fail(e != NULL);
1913 gboolean force = FALSE;
1914 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1915 force = TRUE;
1916 }
1917 sp_nodepath_node_select(e, (gboolean) toggle, force);
1918 if (e->p.other)
1919 sp_nodepath_node_select(e->p.other, TRUE, force);
1921 sp_nodepath_update_handles(nodepath);
1923 sp_nodepath_update_statusbar(nodepath);
1924 }
1926 /**
1927 * Add a node nearest to point
1928 */
1929 void
1930 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1931 {
1932 if (!nodepath) {
1933 return;
1934 }
1936 sp_nodepath_ensure_livarot_path(nodepath);
1937 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1938 if (!maybe_position) {
1939 return;
1940 }
1941 Path::cut_position position = *maybe_position;
1943 //find segment to split
1944 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1946 //don't know why but t seems to flip for lines
1947 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1948 position.t = 1.0 - position.t;
1949 }
1950 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1951 sp_nodepath_node_select(n, FALSE, TRUE);
1953 /* fixme: adjust ? */
1954 sp_nodepath_update_handles(nodepath);
1956 sp_nodepath_update_repr(nodepath, _("Add node"));
1958 sp_nodepath_update_statusbar(nodepath);
1959 }
1961 /*
1962 * Adjusts a segment so that t moves by a certain delta for dragging
1963 * converts lines to curves
1964 *
1965 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1966 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1967 */
1968 void
1969 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1970 {
1971 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1973 //fixme: e and e->p can be NULL, so check for those before proceeding
1974 g_return_if_fail(e != NULL);
1975 g_return_if_fail(&e->p != NULL);
1977 /* feel good is an arbitrary parameter that distributes the delta between handles
1978 * if t of the drag point is less than 1/6 distance form the endpoint only
1979 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1980 */
1981 double feel_good;
1982 if (t <= 1.0 / 6.0)
1983 feel_good = 0;
1984 else if (t <= 0.5)
1985 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1986 else if (t <= 5.0 / 6.0)
1987 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1988 else
1989 feel_good = 1;
1991 //if we're dragging a line convert it to a curve
1992 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1993 sp_nodepath_set_line_type(e, NR_CURVETO);
1994 }
1996 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1997 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1998 e->p.other->n.pos += offsetcoord0;
1999 e->p.pos += offsetcoord1;
2001 // adjust handles of adjacent nodes where necessary
2002 sp_node_adjust_handle(e,1);
2003 sp_node_adjust_handle(e->p.other,-1);
2005 sp_nodepath_update_handles(e->subpath->nodepath);
2007 update_object(e->subpath->nodepath);
2009 sp_nodepath_update_statusbar(e->subpath->nodepath);
2010 }
2013 /**
2014 * Call sp_nodepath_break() for all selected segments.
2015 */
2016 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2017 {
2018 if (!nodepath) return;
2020 GList *tempin = g_list_copy(nodepath->selected);
2021 GList *temp = NULL;
2022 for (GList *l = tempin; l != NULL; l = l->next) {
2023 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2024 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2025 if (nn == NULL) continue; // no break, no new node
2026 temp = g_list_prepend(temp, nn);
2027 }
2028 g_list_free(tempin);
2030 if (temp) {
2031 sp_nodepath_deselect(nodepath);
2032 }
2033 for (GList *l = temp; l != NULL; l = l->next) {
2034 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2035 }
2037 sp_nodepath_update_handles(nodepath);
2039 sp_nodepath_update_repr(nodepath, _("Break path"));
2040 }
2042 /**
2043 * Duplicate the selected node(s).
2044 */
2045 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2046 {
2047 if (!nodepath) {
2048 return;
2049 }
2051 GList *temp = NULL;
2052 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2053 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2054 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2055 if (nn == NULL) continue; // could not duplicate
2056 temp = g_list_prepend(temp, nn);
2057 }
2059 if (temp) {
2060 sp_nodepath_deselect(nodepath);
2061 }
2062 for (GList *l = temp; l != NULL; l = l->next) {
2063 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2064 }
2066 sp_nodepath_update_handles(nodepath);
2068 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2069 }
2071 /**
2072 * Internal function to join two nodes by merging them into one.
2073 */
2074 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2075 {
2076 /* a and b are endpoints */
2078 // if one of the two nodes is mouseovered, fix its position
2079 NR::Point c;
2080 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2081 c = a->pos;
2082 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2083 c = b->pos;
2084 } else {
2085 // otherwise, move joined node to the midpoint
2086 c = (a->pos + b->pos) / 2;
2087 }
2089 if (a->subpath == b->subpath) {
2090 Inkscape::NodePath::SubPath *sp = a->subpath;
2091 sp_nodepath_subpath_close(sp);
2092 sp_node_moveto (sp->first, c);
2094 sp_nodepath_update_handles(sp->nodepath);
2095 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2096 return;
2097 }
2099 /* a and b are separate subpaths */
2100 Inkscape::NodePath::SubPath *sa = a->subpath;
2101 Inkscape::NodePath::SubPath *sb = b->subpath;
2102 NR::Point p;
2103 Inkscape::NodePath::Node *n;
2104 NRPathcode code;
2105 if (a == sa->first) {
2106 // we will now reverse sa, so that a is its last node, not first, and drop that node
2107 p = sa->first->n.pos;
2108 code = (NRPathcode)sa->first->n.other->code;
2109 // create new subpath
2110 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2111 // create a first moveto node on it
2112 n = sa->last;
2113 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2114 n = n->p.other;
2115 if (n == sa->first) n = NULL;
2116 while (n) {
2117 // copy the rest of the nodes from sa to t, going backwards
2118 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2119 n = n->p.other;
2120 if (n == sa->first) n = NULL;
2121 }
2122 // replace sa with t
2123 sp_nodepath_subpath_destroy(sa);
2124 sa = t;
2125 } else if (a == sa->last) {
2126 // a is already last, just drop it
2127 p = sa->last->p.pos;
2128 code = (NRPathcode)sa->last->code;
2129 sp_nodepath_node_destroy(sa->last);
2130 } else {
2131 code = NR_END;
2132 g_assert_not_reached();
2133 }
2135 if (b == sb->first) {
2136 // copy all nodes from b to a, forward
2137 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2138 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2139 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2140 }
2141 } else if (b == sb->last) {
2142 // copy all nodes from b to a, backward
2143 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2144 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2145 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2146 }
2147 } else {
2148 g_assert_not_reached();
2149 }
2150 /* and now destroy sb */
2152 sp_nodepath_subpath_destroy(sb);
2154 sp_nodepath_update_handles(sa->nodepath);
2156 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2158 sp_nodepath_update_statusbar(nodepath);
2159 }
2161 /**
2162 * Internal function to join two nodes by adding a segment between them.
2163 */
2164 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2165 {
2166 if (a->subpath == b->subpath) {
2167 Inkscape::NodePath::SubPath *sp = a->subpath;
2169 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2170 sp->closed = TRUE;
2172 sp->first->p.other = sp->last;
2173 sp->last->n.other = sp->first;
2175 sp_node_handle_mirror_p_to_n(sp->last);
2176 sp_node_handle_mirror_n_to_p(sp->first);
2178 sp->first->code = sp->last->code;
2179 sp->first = sp->last;
2181 sp_nodepath_update_handles(sp->nodepath);
2183 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2185 return;
2186 }
2188 /* a and b are separate subpaths */
2189 Inkscape::NodePath::SubPath *sa = a->subpath;
2190 Inkscape::NodePath::SubPath *sb = b->subpath;
2192 Inkscape::NodePath::Node *n;
2193 NR::Point p;
2194 NRPathcode code;
2195 if (a == sa->first) {
2196 code = (NRPathcode) sa->first->n.other->code;
2197 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2198 n = sa->last;
2199 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2200 for (n = n->p.other; n != NULL; n = n->p.other) {
2201 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2202 }
2203 sp_nodepath_subpath_destroy(sa);
2204 sa = t;
2205 } else if (a == sa->last) {
2206 code = (NRPathcode)sa->last->code;
2207 } else {
2208 code = NR_END;
2209 g_assert_not_reached();
2210 }
2212 if (b == sb->first) {
2213 n = sb->first;
2214 sp_node_handle_mirror_p_to_n(sa->last);
2215 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2216 sp_node_handle_mirror_n_to_p(sa->last);
2217 for (n = n->n.other; n != NULL; n = n->n.other) {
2218 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2219 }
2220 } else if (b == sb->last) {
2221 n = sb->last;
2222 sp_node_handle_mirror_p_to_n(sa->last);
2223 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2224 sp_node_handle_mirror_n_to_p(sa->last);
2225 for (n = n->p.other; n != NULL; n = n->p.other) {
2226 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2227 }
2228 } else {
2229 g_assert_not_reached();
2230 }
2231 /* and now destroy sb */
2233 sp_nodepath_subpath_destroy(sb);
2235 sp_nodepath_update_handles(sa->nodepath);
2237 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2238 }
2240 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2242 /**
2243 * Internal function to handle joining two nodes.
2244 */
2245 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2246 {
2247 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2249 if (g_list_length(nodepath->selected) != 2) {
2250 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2251 return;
2252 }
2254 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2255 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2257 g_assert(a != b);
2258 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2259 // someone tried to join an orphan node (i.e. a single-node subpath).
2260 // this is not worth an error message, just fail silently.
2261 return;
2262 }
2264 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2265 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2266 return;
2267 }
2269 switch(mode) {
2270 case NODE_JOIN_ENDPOINTS:
2271 do_node_selected_join(nodepath, a, b);
2272 break;
2273 case NODE_JOIN_SEGMENT:
2274 do_node_selected_join_segment(nodepath, a, b);
2275 break;
2276 }
2277 }
2279 /**
2280 * Join two nodes by merging them into one.
2281 */
2282 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2283 {
2284 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2285 }
2287 /**
2288 * Join two nodes by adding a segment between them.
2289 */
2290 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2291 {
2292 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2293 }
2295 /**
2296 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2297 */
2298 void sp_node_delete_preserve(GList *nodes_to_delete)
2299 {
2300 GSList *nodepaths = NULL;
2302 while (nodes_to_delete) {
2303 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2304 Inkscape::NodePath::SubPath *sp = node->subpath;
2305 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2306 Inkscape::NodePath::Node *sample_cursor = NULL;
2307 Inkscape::NodePath::Node *sample_end = NULL;
2308 Inkscape::NodePath::Node *delete_cursor = node;
2309 bool just_delete = false;
2311 //find the start of this contiguous selection
2312 //move left to the first node that is not selected
2313 //or the start of the non-closed path
2314 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2315 delete_cursor = curr;
2316 }
2318 //just delete at the beginning of an open path
2319 if (!delete_cursor->p.other) {
2320 sample_cursor = delete_cursor;
2321 just_delete = true;
2322 } else {
2323 sample_cursor = delete_cursor->p.other;
2324 }
2326 //calculate points for each segment
2327 int rate = 5;
2328 float period = 1.0 / rate;
2329 std::vector<NR::Point> data;
2330 if (!just_delete) {
2331 data.push_back(sample_cursor->pos);
2332 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2333 //just delete at the end of an open path
2334 if (!sp->closed && curr == sp->last) {
2335 just_delete = true;
2336 break;
2337 }
2339 //sample points on the contiguous selected segment
2340 NR::Point *bez;
2341 bez = new NR::Point [4];
2342 bez[0] = curr->pos;
2343 bez[1] = curr->n.pos;
2344 bez[2] = curr->n.other->p.pos;
2345 bez[3] = curr->n.other->pos;
2346 for (int i=1; i<rate; i++) {
2347 gdouble t = i * period;
2348 NR::Point p = bezier_pt(3, bez, t);
2349 data.push_back(p);
2350 }
2351 data.push_back(curr->n.other->pos);
2353 sample_end = curr->n.other;
2354 //break if we've come full circle or hit the end of the selection
2355 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2356 break;
2357 }
2358 }
2359 }
2361 if (!just_delete) {
2362 //calculate the best fitting single segment and adjust the endpoints
2363 NR::Point *adata;
2364 adata = new NR::Point [data.size()];
2365 copy(data.begin(), data.end(), adata);
2367 NR::Point *bez;
2368 bez = new NR::Point [4];
2369 //would decreasing error create a better fitting approximation?
2370 gdouble error = 1.0;
2371 gint ret;
2372 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2374 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2375 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2376 //the resulting nodes behave as expected.
2377 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2378 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2379 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2380 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2382 //adjust endpoints
2383 sample_cursor->n.pos = bez[1];
2384 sample_end->p.pos = bez[2];
2385 }
2387 //destroy this contiguous selection
2388 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2389 Inkscape::NodePath::Node *temp = delete_cursor;
2390 if (delete_cursor->n.other == delete_cursor) {
2391 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2392 delete_cursor = NULL;
2393 } else {
2394 delete_cursor = delete_cursor->n.other;
2395 }
2396 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2397 sp_nodepath_node_destroy(temp);
2398 }
2400 sp_nodepath_update_handles(nodepath);
2402 if (!g_slist_find(nodepaths, nodepath))
2403 nodepaths = g_slist_prepend (nodepaths, nodepath);
2404 }
2406 for (GSList *i = nodepaths; i; i = i->next) {
2407 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2408 // different nodepaths will give us one undo event per nodepath
2409 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2411 // if the entire nodepath is removed, delete the selected object.
2412 if (nodepath->subpaths == NULL ||
2413 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2414 //at least 2
2415 sp_nodepath_get_node_count(nodepath) < 2) {
2416 SPDocument *document = sp_desktop_document (nodepath->desktop);
2417 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2418 //delete this nodepath's object, not the entire selection! (though at this time, this
2419 //does not matter)
2420 sp_selection_delete();
2421 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2422 _("Delete nodes"));
2423 } else {
2424 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2425 sp_nodepath_update_statusbar(nodepath);
2426 }
2427 }
2429 g_slist_free (nodepaths);
2430 }
2432 /**
2433 * Delete one or more selected nodes.
2434 */
2435 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2436 {
2437 if (!nodepath) return;
2438 if (!nodepath->selected) return;
2440 /** \todo fixme: do it the right way */
2441 while (nodepath->selected) {
2442 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2443 sp_nodepath_node_destroy(node);
2444 }
2447 //clean up the nodepath (such as for trivial subpaths)
2448 sp_nodepath_cleanup(nodepath);
2450 sp_nodepath_update_handles(nodepath);
2452 // if the entire nodepath is removed, delete the selected object.
2453 if (nodepath->subpaths == NULL ||
2454 sp_nodepath_get_node_count(nodepath) < 2) {
2455 SPDocument *document = sp_desktop_document (nodepath->desktop);
2456 sp_selection_delete();
2457 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2458 _("Delete nodes"));
2459 return;
2460 }
2462 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2464 sp_nodepath_update_statusbar(nodepath);
2465 }
2467 /**
2468 * Delete one or more segments between two selected nodes.
2469 * This is the code for 'split'.
2470 */
2471 void
2472 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2473 {
2474 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2475 Inkscape::NodePath::Node *curr, *next; //Iterators
2477 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2479 if (g_list_length(nodepath->selected) != 2) {
2480 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2481 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2482 return;
2483 }
2485 //Selected nodes, not inclusive
2486 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2487 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2489 if ( ( a==b) || //same node
2490 (a->subpath != b->subpath ) || //not the same path
2491 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2492 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2493 {
2494 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2495 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2496 return;
2497 }
2499 //###########################################
2500 //# BEGIN EDITS
2501 //###########################################
2502 //##################################
2503 //# CLOSED PATH
2504 //##################################
2505 if (a->subpath->closed) {
2508 gboolean reversed = FALSE;
2510 //Since we can go in a circle, we need to find the shorter distance.
2511 // a->b or b->a
2512 start = end = NULL;
2513 int distance = 0;
2514 int minDistance = 0;
2515 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2516 if (curr==b) {
2517 //printf("a to b:%d\n", distance);
2518 start = a;//go from a to b
2519 end = b;
2520 minDistance = distance;
2521 //printf("A to B :\n");
2522 break;
2523 }
2524 distance++;
2525 }
2527 //try again, the other direction
2528 distance = 0;
2529 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2530 if (curr==a) {
2531 //printf("b to a:%d\n", distance);
2532 if (distance < minDistance) {
2533 start = b; //we go from b to a
2534 end = a;
2535 reversed = TRUE;
2536 //printf("B to A\n");
2537 }
2538 break;
2539 }
2540 distance++;
2541 }
2544 //Copy everything from 'end' to 'start' to a new subpath
2545 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2546 for (curr=end ; curr ; curr=curr->n.other) {
2547 NRPathcode code = (NRPathcode) curr->code;
2548 if (curr == end)
2549 code = NR_MOVETO;
2550 sp_nodepath_node_new(t, NULL,
2551 (Inkscape::NodePath::NodeType)curr->type, code,
2552 &curr->p.pos, &curr->pos, &curr->n.pos);
2553 if (curr == start)
2554 break;
2555 }
2556 sp_nodepath_subpath_destroy(a->subpath);
2559 }
2563 //##################################
2564 //# OPEN PATH
2565 //##################################
2566 else {
2568 //We need to get the direction of the list between A and B
2569 //Can we walk from a to b?
2570 start = end = NULL;
2571 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2572 if (curr==b) {
2573 start = a; //did it! we go from a to b
2574 end = b;
2575 //printf("A to B\n");
2576 break;
2577 }
2578 }
2579 if (!start) {//didn't work? let's try the other direction
2580 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2581 if (curr==a) {
2582 start = b; //did it! we go from b to a
2583 end = a;
2584 //printf("B to A\n");
2585 break;
2586 }
2587 }
2588 }
2589 if (!start) {
2590 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2591 _("Cannot find path between nodes."));
2592 return;
2593 }
2597 //Copy everything after 'end' to a new subpath
2598 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2599 for (curr=end ; curr ; curr=curr->n.other) {
2600 NRPathcode code = (NRPathcode) curr->code;
2601 if (curr == end)
2602 code = NR_MOVETO;
2603 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2604 &curr->p.pos, &curr->pos, &curr->n.pos);
2605 }
2607 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2608 for (curr = start->n.other ; curr ; curr=next) {
2609 next = curr->n.other;
2610 sp_nodepath_node_destroy(curr);
2611 }
2613 }
2614 //###########################################
2615 //# END EDITS
2616 //###########################################
2618 //clean up the nodepath (such as for trivial subpaths)
2619 sp_nodepath_cleanup(nodepath);
2621 sp_nodepath_update_handles(nodepath);
2623 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2625 sp_nodepath_update_statusbar(nodepath);
2626 }
2628 /**
2629 * Call sp_nodepath_set_line() for all selected segments.
2630 */
2631 void
2632 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2633 {
2634 if (nodepath == NULL) return;
2636 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2637 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2638 g_assert(n->selected);
2639 if (n->p.other && n->p.other->selected) {
2640 sp_nodepath_set_line_type(n, code);
2641 }
2642 }
2644 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2645 }
2647 /**
2648 * Call sp_nodepath_convert_node_type() for all selected nodes.
2649 */
2650 void
2651 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2652 {
2653 if (nodepath == NULL) return;
2655 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2657 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2658 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2659 }
2661 sp_nodepath_update_repr(nodepath, _("Change node type"));
2662 }
2664 /**
2665 * Change select status of node, update its own and neighbour handles.
2666 */
2667 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2668 {
2669 node->selected = selected;
2671 if (selected) {
2672 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2673 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2674 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2675 sp_knot_update_ctrl(node->knot);
2676 } else {
2677 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2678 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2679 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2680 sp_knot_update_ctrl(node->knot);
2681 }
2683 sp_node_update_handles(node);
2684 if (node->n.other) sp_node_update_handles(node->n.other);
2685 if (node->p.other) sp_node_update_handles(node->p.other);
2686 }
2688 /**
2689 \brief Select a node
2690 \param node The node to select
2691 \param incremental If true, add to selection, otherwise deselect others
2692 \param override If true, always select this node, otherwise toggle selected status
2693 */
2694 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2695 {
2696 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2698 if (incremental) {
2699 if (override) {
2700 if (!g_list_find(nodepath->selected, node)) {
2701 nodepath->selected = g_list_prepend(nodepath->selected, node);
2702 }
2703 sp_node_set_selected(node, TRUE);
2704 } else { // toggle
2705 if (node->selected) {
2706 g_assert(g_list_find(nodepath->selected, node));
2707 nodepath->selected = g_list_remove(nodepath->selected, node);
2708 } else {
2709 g_assert(!g_list_find(nodepath->selected, node));
2710 nodepath->selected = g_list_prepend(nodepath->selected, node);
2711 }
2712 sp_node_set_selected(node, !node->selected);
2713 }
2714 } else {
2715 sp_nodepath_deselect(nodepath);
2716 nodepath->selected = g_list_prepend(nodepath->selected, node);
2717 sp_node_set_selected(node, TRUE);
2718 }
2720 sp_nodepath_update_statusbar(nodepath);
2721 }
2724 /**
2725 \brief Deselect all nodes in the nodepath
2726 */
2727 void
2728 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2729 {
2730 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2732 while (nodepath->selected) {
2733 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2734 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2735 }
2736 sp_nodepath_update_statusbar(nodepath);
2737 }
2739 /**
2740 \brief Select or invert selection of all nodes in the nodepath
2741 */
2742 void
2743 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2744 {
2745 if (!nodepath) return;
2747 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2748 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2749 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2750 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2751 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2752 }
2753 }
2754 }
2756 /**
2757 * If nothing selected, does the same as sp_nodepath_select_all();
2758 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2759 * (i.e., similar to "select all in layer", with the "selected" subpaths
2760 * being treated as "layers" in the path).
2761 */
2762 void
2763 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2764 {
2765 if (!nodepath) return;
2767 if (g_list_length (nodepath->selected) == 0) {
2768 sp_nodepath_select_all (nodepath, invert);
2769 return;
2770 }
2772 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2773 GSList *subpaths = NULL;
2775 for (GList *l = copy; l != NULL; l = l->next) {
2776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2777 Inkscape::NodePath::SubPath *subpath = n->subpath;
2778 if (!g_slist_find (subpaths, subpath))
2779 subpaths = g_slist_prepend (subpaths, subpath);
2780 }
2782 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2783 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2784 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2785 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2786 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2787 }
2788 }
2790 g_slist_free (subpaths);
2791 g_list_free (copy);
2792 }
2794 /**
2795 * \brief Select the node after the last selected; if none is selected,
2796 * select the first within path.
2797 */
2798 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2799 {
2800 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2802 Inkscape::NodePath::Node *last = NULL;
2803 if (nodepath->selected) {
2804 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2805 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2806 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2807 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2808 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2809 if (node->selected) {
2810 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2811 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2812 if (spl->next) { // there's a next subpath
2813 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2814 last = subpath_next->first;
2815 } else if (spl->prev) { // there's a previous subpath
2816 last = NULL; // to be set later to the first node of first subpath
2817 } else {
2818 last = node->n.other;
2819 }
2820 } else {
2821 last = node->n.other;
2822 }
2823 } else {
2824 if (node->n.other) {
2825 last = node->n.other;
2826 } else {
2827 if (spl->next) { // there's a next subpath
2828 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2829 last = subpath_next->first;
2830 } else if (spl->prev) { // there's a previous subpath
2831 last = NULL; // to be set later to the first node of first subpath
2832 } else {
2833 last = (Inkscape::NodePath::Node *) subpath->first;
2834 }
2835 }
2836 }
2837 }
2838 }
2839 }
2840 sp_nodepath_deselect(nodepath);
2841 }
2843 if (last) { // there's at least one more node after selected
2844 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2845 } else { // no more nodes, select the first one in first subpath
2846 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2847 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2848 }
2849 }
2851 /**
2852 * \brief Select the node before the first selected; if none is selected,
2853 * select the last within path
2854 */
2855 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2856 {
2857 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2859 Inkscape::NodePath::Node *last = NULL;
2860 if (nodepath->selected) {
2861 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2862 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2863 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2864 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2865 if (node->selected) {
2866 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2867 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2868 if (spl->prev) { // there's a prev subpath
2869 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2870 last = subpath_prev->last;
2871 } else if (spl->next) { // there's a next subpath
2872 last = NULL; // to be set later to the last node of last subpath
2873 } else {
2874 last = node->p.other;
2875 }
2876 } else {
2877 last = node->p.other;
2878 }
2879 } else {
2880 if (node->p.other) {
2881 last = node->p.other;
2882 } else {
2883 if (spl->prev) { // there's a prev subpath
2884 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2885 last = subpath_prev->last;
2886 } else if (spl->next) { // there's a next subpath
2887 last = NULL; // to be set later to the last node of last subpath
2888 } else {
2889 last = (Inkscape::NodePath::Node *) subpath->last;
2890 }
2891 }
2892 }
2893 }
2894 }
2895 }
2896 sp_nodepath_deselect(nodepath);
2897 }
2899 if (last) { // there's at least one more node before selected
2900 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2901 } else { // no more nodes, select the last one in last subpath
2902 GList *spl = g_list_last(nodepath->subpaths);
2903 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2904 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2905 }
2906 }
2908 /**
2909 * \brief Select all nodes that are within the rectangle.
2910 */
2911 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2912 {
2913 if (!incremental) {
2914 sp_nodepath_deselect(nodepath);
2915 }
2917 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2918 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2919 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2920 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2922 if (b.contains(node->pos)) {
2923 sp_nodepath_node_select(node, TRUE, TRUE);
2924 }
2925 }
2926 }
2927 }
2930 void
2931 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2932 {
2933 g_assert (n);
2934 g_assert (nodepath);
2935 g_assert (n->subpath->nodepath == nodepath);
2937 if (g_list_length (nodepath->selected) == 0) {
2938 if (grow > 0) {
2939 sp_nodepath_node_select(n, TRUE, TRUE);
2940 }
2941 return;
2942 }
2944 if (g_list_length (nodepath->selected) == 1) {
2945 if (grow < 0) {
2946 sp_nodepath_deselect (nodepath);
2947 return;
2948 }
2949 }
2951 double n_sel_range = 0, p_sel_range = 0;
2952 Inkscape::NodePath::Node *farthest_n_node = n;
2953 Inkscape::NodePath::Node *farthest_p_node = n;
2955 // Calculate ranges
2956 {
2957 double n_range = 0, p_range = 0;
2958 bool n_going = true, p_going = true;
2959 Inkscape::NodePath::Node *n_node = n;
2960 Inkscape::NodePath::Node *p_node = n;
2961 do {
2962 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2963 if (n_node && n_going)
2964 n_node = n_node->n.other;
2965 if (n_node == NULL) {
2966 n_going = false;
2967 } else {
2968 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2969 if (n_node->selected) {
2970 n_sel_range = n_range;
2971 farthest_n_node = n_node;
2972 }
2973 if (n_node == p_node) {
2974 n_going = false;
2975 p_going = false;
2976 }
2977 }
2978 if (p_node && p_going)
2979 p_node = p_node->p.other;
2980 if (p_node == NULL) {
2981 p_going = false;
2982 } else {
2983 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2984 if (p_node->selected) {
2985 p_sel_range = p_range;
2986 farthest_p_node = p_node;
2987 }
2988 if (p_node == n_node) {
2989 n_going = false;
2990 p_going = false;
2991 }
2992 }
2993 } while (n_going || p_going);
2994 }
2996 if (grow > 0) {
2997 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2998 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2999 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3000 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3001 }
3002 } else {
3003 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3004 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3005 } else if (farthest_p_node && farthest_p_node->selected) {
3006 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3007 }
3008 }
3009 }
3011 void
3012 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3013 {
3014 g_assert (n);
3015 g_assert (nodepath);
3016 g_assert (n->subpath->nodepath == nodepath);
3018 if (g_list_length (nodepath->selected) == 0) {
3019 if (grow > 0) {
3020 sp_nodepath_node_select(n, TRUE, TRUE);
3021 }
3022 return;
3023 }
3025 if (g_list_length (nodepath->selected) == 1) {
3026 if (grow < 0) {
3027 sp_nodepath_deselect (nodepath);
3028 return;
3029 }
3030 }
3032 Inkscape::NodePath::Node *farthest_selected = NULL;
3033 double farthest_dist = 0;
3035 Inkscape::NodePath::Node *closest_unselected = NULL;
3036 double closest_dist = NR_HUGE;
3038 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3039 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3040 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3041 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3042 if (node == n)
3043 continue;
3044 if (node->selected) {
3045 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3046 farthest_dist = NR::L2(node->pos - n->pos);
3047 farthest_selected = node;
3048 }
3049 } else {
3050 if (NR::L2(node->pos - n->pos) < closest_dist) {
3051 closest_dist = NR::L2(node->pos - n->pos);
3052 closest_unselected = node;
3053 }
3054 }
3055 }
3056 }
3058 if (grow > 0) {
3059 if (closest_unselected) {
3060 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3061 }
3062 } else {
3063 if (farthest_selected) {
3064 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3065 }
3066 }
3067 }
3070 /**
3071 \brief Saves all nodes' and handles' current positions in their origin members
3072 */
3073 void
3074 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3075 {
3076 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3077 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3078 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3079 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3080 n->origin = n->pos;
3081 n->p.origin = n->p.pos;
3082 n->n.origin = n->n.pos;
3083 }
3084 }
3085 }
3087 /**
3088 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3089 */
3090 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3091 {
3092 if (!nodepath->selected) {
3093 return NULL;
3094 }
3096 GList *r = NULL;
3097 guint i = 0;
3098 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3099 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3100 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3101 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3102 i++;
3103 if (node->selected) {
3104 r = g_list_append(r, GINT_TO_POINTER(i));
3105 }
3106 }
3107 }
3108 return r;
3109 }
3111 /**
3112 \brief Restores selection by selecting nodes whose positions are in the list
3113 */
3114 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3115 {
3116 sp_nodepath_deselect(nodepath);
3118 guint i = 0;
3119 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3120 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3121 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3122 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3123 i++;
3124 if (g_list_find(r, GINT_TO_POINTER(i))) {
3125 sp_nodepath_node_select(node, TRUE, TRUE);
3126 }
3127 }
3128 }
3129 }
3132 /**
3133 \brief Adjusts handle according to node type and line code.
3134 */
3135 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3136 {
3137 g_assert(node);
3139 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3140 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3142 // nothing to do if we are an end node
3143 if (me->other == NULL) return;
3144 if (other->other == NULL) return;
3146 // nothing to do if we are a cusp node
3147 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3149 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3150 NRPathcode mecode;
3151 if (which_adjust == 1) {
3152 mecode = (NRPathcode)me->other->code;
3153 } else {
3154 mecode = (NRPathcode)node->code;
3155 }
3156 if (mecode == NR_LINETO) return;
3158 if (sp_node_side_is_line(node, other)) {
3159 // other is a line, and we are either smooth or symm
3160 Inkscape::NodePath::Node *othernode = other->other;
3161 double len = NR::L2(me->pos - node->pos);
3162 NR::Point delta = node->pos - othernode->pos;
3163 double linelen = NR::L2(delta);
3164 if (linelen < 1e-18)
3165 return;
3166 me->pos = node->pos + (len / linelen)*delta;
3167 return;
3168 }
3170 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3171 // symmetrize
3172 me->pos = 2 * node->pos - other->pos;
3173 return;
3174 } else {
3175 // smoothify
3176 double len = NR::L2(me->pos - node->pos);
3177 NR::Point delta = other->pos - node->pos;
3178 double otherlen = NR::L2(delta);
3179 if (otherlen < 1e-18) return;
3180 me->pos = node->pos - (len / otherlen) * delta;
3181 }
3182 }
3184 /**
3185 \brief Adjusts both handles according to node type and line code
3186 */
3187 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3188 {
3189 g_assert(node);
3191 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3193 /* we are either smooth or symm */
3195 if (node->p.other == NULL) return;
3196 if (node->n.other == NULL) return;
3198 if (sp_node_side_is_line(node, &node->p)) {
3199 sp_node_adjust_handle(node, 1);
3200 return;
3201 }
3203 if (sp_node_side_is_line(node, &node->n)) {
3204 sp_node_adjust_handle(node, -1);
3205 return;
3206 }
3208 /* both are curves */
3209 NR::Point const delta( node->n.pos - node->p.pos );
3211 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3212 node->p.pos = node->pos - delta / 2;
3213 node->n.pos = node->pos + delta / 2;
3214 return;
3215 }
3217 /* We are smooth */
3218 double plen = NR::L2(node->p.pos - node->pos);
3219 if (plen < 1e-18) return;
3220 double nlen = NR::L2(node->n.pos - node->pos);
3221 if (nlen < 1e-18) return;
3222 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3223 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3224 }
3226 /**
3227 * Node event callback.
3228 */
3229 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3230 {
3231 gboolean ret = FALSE;
3232 switch (event->type) {
3233 case GDK_ENTER_NOTIFY:
3234 Inkscape::NodePath::Path::active_node = n;
3235 break;
3236 case GDK_LEAVE_NOTIFY:
3237 Inkscape::NodePath::Path::active_node = NULL;
3238 break;
3239 case GDK_SCROLL:
3240 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3241 switch (event->scroll.direction) {
3242 case GDK_SCROLL_UP:
3243 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3244 break;
3245 case GDK_SCROLL_DOWN:
3246 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3247 break;
3248 default:
3249 break;
3250 }
3251 ret = TRUE;
3252 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3253 switch (event->scroll.direction) {
3254 case GDK_SCROLL_UP:
3255 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3256 break;
3257 case GDK_SCROLL_DOWN:
3258 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3259 break;
3260 default:
3261 break;
3262 }
3263 ret = TRUE;
3264 }
3265 break;
3266 case GDK_KEY_PRESS:
3267 switch (get_group0_keyval (&event->key)) {
3268 case GDK_space:
3269 if (event->key.state & GDK_BUTTON1_MASK) {
3270 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3271 stamp_repr(nodepath);
3272 ret = TRUE;
3273 }
3274 break;
3275 case GDK_Page_Up:
3276 if (event->key.state & GDK_CONTROL_MASK) {
3277 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3278 } else {
3279 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3280 }
3281 break;
3282 case GDK_Page_Down:
3283 if (event->key.state & GDK_CONTROL_MASK) {
3284 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3285 } else {
3286 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3287 }
3288 break;
3289 default:
3290 break;
3291 }
3292 break;
3293 default:
3294 break;
3295 }
3297 return ret;
3298 }
3300 /**
3301 * Handle keypress on node; directly called.
3302 */
3303 gboolean node_key(GdkEvent *event)
3304 {
3305 Inkscape::NodePath::Path *np;
3307 // there is no way to verify nodes so set active_node to nil when deleting!!
3308 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3310 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3311 gint ret = FALSE;
3312 switch (get_group0_keyval (&event->key)) {
3313 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3314 case GDK_BackSpace:
3315 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3316 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3317 sp_nodepath_update_repr(np, _("Delete node"));
3318 Inkscape::NodePath::Path::active_node = NULL;
3319 ret = TRUE;
3320 break;
3321 case GDK_c:
3322 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3323 ret = TRUE;
3324 break;
3325 case GDK_s:
3326 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3327 ret = TRUE;
3328 break;
3329 case GDK_y:
3330 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3331 ret = TRUE;
3332 break;
3333 case GDK_b:
3334 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3335 ret = TRUE;
3336 break;
3337 }
3338 return ret;
3339 }
3340 return FALSE;
3341 }
3343 /**
3344 * Mouseclick on node callback.
3345 */
3346 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3347 {
3348 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3350 if (state & GDK_CONTROL_MASK) {
3351 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3353 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3354 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3355 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3356 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3357 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3358 } else {
3359 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3360 }
3361 sp_nodepath_update_repr(nodepath, _("Change node type"));
3362 sp_nodepath_update_statusbar(nodepath);
3364 } else { //ctrl+alt+click: delete node
3365 GList *node_to_delete = NULL;
3366 node_to_delete = g_list_append(node_to_delete, n);
3367 sp_node_delete_preserve(node_to_delete);
3368 }
3370 } else {
3371 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3372 }
3373 }
3375 /**
3376 * Mouse grabbed node callback.
3377 */
3378 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3379 {
3380 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3382 if (!n->selected) {
3383 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3384 }
3386 n->is_dragging = true;
3387 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3389 sp_nodepath_remember_origins (n->subpath->nodepath);
3390 }
3392 /**
3393 * Mouse ungrabbed node callback.
3394 */
3395 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3396 {
3397 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3399 n->dragging_out = NULL;
3400 n->is_dragging = false;
3401 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3403 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3404 }
3406 /**
3407 * The point on a line, given by its angle, closest to the given point.
3408 * \param p A point.
3409 * \param a Angle of the line; it is assumed to go through coordinate origin.
3410 * \param closest Pointer to the point struct where the result is stored.
3411 * \todo FIXME: use dot product perhaps?
3412 */
3413 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3414 {
3415 if (a == HUGE_VAL) { // vertical
3416 *closest = NR::Point(0, (*p)[NR::Y]);
3417 } else {
3418 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3419 (*closest)[NR::Y] = a * (*closest)[NR::X];
3420 }
3421 }
3423 /**
3424 * Distance from the point to a line given by its angle.
3425 * \param p A point.
3426 * \param a Angle of the line; it is assumed to go through coordinate origin.
3427 */
3428 static double point_line_distance(NR::Point *p, double a)
3429 {
3430 NR::Point c;
3431 point_line_closest(p, a, &c);
3432 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]));
3433 }
3435 /**
3436 * Callback for node "request" signal.
3437 * \todo fixme: This goes to "moved" event? (lauris)
3438 */
3439 static gboolean
3440 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3441 {
3442 double yn, xn, yp, xp;
3443 double an, ap, na, pa;
3444 double d_an, d_ap, d_na, d_pa;
3445 gboolean collinear = FALSE;
3446 NR::Point c;
3447 NR::Point pr;
3449 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3451 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3453 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3454 if ( (!n->subpath->nodepath->straight_path) &&
3455 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3456 || n->dragging_out ) )
3457 {
3458 NR::Point mouse = (*p);
3460 if (!n->dragging_out) {
3461 // This is the first drag-out event; find out which handle to drag out
3462 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3463 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3465 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3466 return FALSE;
3468 Inkscape::NodePath::NodeSide *opposite;
3469 if (appr_p > appr_n) { // closer to p
3470 n->dragging_out = &n->p;
3471 opposite = &n->n;
3472 n->code = NR_CURVETO;
3473 } else if (appr_p < appr_n) { // closer to n
3474 n->dragging_out = &n->n;
3475 opposite = &n->p;
3476 n->n.other->code = NR_CURVETO;
3477 } else { // p and n nodes are the same
3478 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3479 n->dragging_out = &n->p;
3480 opposite = &n->n;
3481 n->code = NR_CURVETO;
3482 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3483 n->dragging_out = &n->n;
3484 opposite = &n->p;
3485 n->n.other->code = NR_CURVETO;
3486 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3487 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);
3488 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);
3489 if (appr_other_p > appr_other_n) { // closer to other's p handle
3490 n->dragging_out = &n->n;
3491 opposite = &n->p;
3492 n->n.other->code = NR_CURVETO;
3493 } else { // closer to other's n handle
3494 n->dragging_out = &n->p;
3495 opposite = &n->n;
3496 n->code = NR_CURVETO;
3497 }
3498 }
3499 }
3501 // if there's another handle, make sure the one we drag out starts parallel to it
3502 if (opposite->pos != n->pos) {
3503 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3504 }
3506 // knots might not be created yet!
3507 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3508 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3509 }
3511 // pass this on to the handle-moved callback
3512 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3513 sp_node_update_handles(n);
3514 return TRUE;
3515 }
3517 if (state & GDK_CONTROL_MASK) { // constrained motion
3519 // calculate relative distances of handles
3520 // n handle:
3521 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3522 xn = n->n.pos[NR::X] - n->pos[NR::X];
3523 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3524 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3525 if (n->n.other) { // if there is the next point
3526 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3527 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3528 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3529 }
3530 }
3531 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3532 if (yn < 0) { xn = -xn; yn = -yn; }
3534 // p handle:
3535 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3536 xp = n->p.pos[NR::X] - n->pos[NR::X];
3537 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3538 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3539 if (n->p.other) {
3540 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3541 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3542 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3543 }
3544 }
3545 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3546 if (yp < 0) { xp = -xp; yp = -yp; }
3548 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3549 // sliding on handles, only if at least one of the handles is non-vertical
3550 // (otherwise it's the same as ctrl+drag anyway)
3552 // calculate angles of the handles
3553 if (xn == 0) {
3554 if (yn == 0) { // no handle, consider it the continuation of the other one
3555 an = 0;
3556 collinear = TRUE;
3557 }
3558 else an = 0; // vertical; set the angle to horizontal
3559 } else an = yn/xn;
3561 if (xp == 0) {
3562 if (yp == 0) { // no handle, consider it the continuation of the other one
3563 ap = an;
3564 }
3565 else ap = 0; // vertical; set the angle to horizontal
3566 } else ap = yp/xp;
3568 if (collinear) an = ap;
3570 // angles of the perpendiculars; HUGE_VAL means vertical
3571 if (an == 0) na = HUGE_VAL; else na = -1/an;
3572 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3574 // mouse point relative to the node's original pos
3575 pr = (*p) - n->origin;
3577 // distances to the four lines (two handles and two perpendiculars)
3578 d_an = point_line_distance(&pr, an);
3579 d_na = point_line_distance(&pr, na);
3580 d_ap = point_line_distance(&pr, ap);
3581 d_pa = point_line_distance(&pr, pa);
3583 // find out which line is the closest, save its closest point in c
3584 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3585 point_line_closest(&pr, an, &c);
3586 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3587 point_line_closest(&pr, ap, &c);
3588 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3589 point_line_closest(&pr, na, &c);
3590 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3591 point_line_closest(&pr, pa, &c);
3592 }
3594 // move the node to the closest point
3595 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3596 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3597 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3598 true);
3600 } else { // constraining to hor/vert
3602 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3603 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3604 (*p)[NR::X] - n->pos[NR::X],
3605 n->origin[NR::Y] - n->pos[NR::Y],
3606 true,
3607 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3608 } else { // snap to vert
3609 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3610 n->origin[NR::X] - n->pos[NR::X],
3611 (*p)[NR::Y] - n->pos[NR::Y],
3612 true,
3613 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3614 }
3615 }
3616 } else { // move freely
3617 if (n->is_dragging) {
3618 if (state & GDK_MOD1_MASK) { // sculpt
3619 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3620 } else {
3621 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3622 (*p)[NR::X] - n->pos[NR::X],
3623 (*p)[NR::Y] - n->pos[NR::Y],
3624 (state & GDK_SHIFT_MASK) == 0);
3625 }
3626 }
3627 }
3629 n->subpath->nodepath->desktop->scroll_to_point(p);
3631 return TRUE;
3632 }
3634 /**
3635 * Node handle clicked callback.
3636 */
3637 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3638 {
3639 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3641 if (state & GDK_CONTROL_MASK) { // "delete" handle
3642 if (n->p.knot == knot) {
3643 n->p.pos = n->pos;
3644 } else if (n->n.knot == knot) {
3645 n->n.pos = n->pos;
3646 }
3647 sp_node_update_handles(n);
3648 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3649 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3650 sp_nodepath_update_statusbar(nodepath);
3652 } else { // just select or add to selection, depending in Shift
3653 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3654 }
3655 }
3657 /**
3658 * Node handle grabbed callback.
3659 */
3660 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3661 {
3662 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3664 if (!n->selected) {
3665 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3666 }
3668 // remember the origin point of the handle
3669 if (n->p.knot == knot) {
3670 n->p.origin_radial = n->p.pos - n->pos;
3671 } else if (n->n.knot == knot) {
3672 n->n.origin_radial = n->n.pos - n->pos;
3673 } else {
3674 g_assert_not_reached();
3675 }
3677 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3678 }
3680 /**
3681 * Node handle ungrabbed callback.
3682 */
3683 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3684 {
3685 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3687 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3688 if (n->p.knot == knot) {
3689 n->p.origin_radial.a = 0;
3690 sp_knot_set_position(knot, &n->p.pos, state);
3691 } else if (n->n.knot == knot) {
3692 n->n.origin_radial.a = 0;
3693 sp_knot_set_position(knot, &n->n.pos, state);
3694 } else {
3695 g_assert_not_reached();
3696 }
3698 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3699 }
3701 /**
3702 * Node handle "request" signal callback.
3703 */
3704 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3705 {
3706 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3708 Inkscape::NodePath::NodeSide *me, *opposite;
3709 gint which;
3710 if (n->p.knot == knot) {
3711 me = &n->p;
3712 opposite = &n->n;
3713 which = -1;
3714 } else if (n->n.knot == knot) {
3715 me = &n->n;
3716 opposite = &n->p;
3717 which = 1;
3718 } else {
3719 me = opposite = NULL;
3720 which = 0;
3721 g_assert_not_reached();
3722 }
3724 SPDesktop *desktop = n->subpath->nodepath->desktop;
3725 SnapManager &m = desktop->namedview->snap_manager;
3726 m.setup(desktop, n->subpath->nodepath->item);
3727 Inkscape::SnappedPoint s ;
3729 Inkscape::NodePath::Node *othernode = opposite->other;
3730 if (othernode) {
3731 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3732 /* We are smooth node adjacent with line */
3733 NR::Point const delta = *p - n->pos;
3734 NR::Coord const len = NR::L2(delta);
3735 Inkscape::NodePath::Node *othernode = opposite->other;
3736 NR::Point const ndelta = n->pos - othernode->pos;
3737 NR::Coord const linelen = NR::L2(ndelta);
3738 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3739 NR::Coord const scal = dot(delta, ndelta) / linelen;
3740 (*p) = n->pos + (scal / linelen) * ndelta;
3741 }
3742 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3743 } else {
3744 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3745 }
3746 } else {
3747 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3748 }
3750 s.getPoint(*p);
3752 sp_node_adjust_handle(n, -which);
3754 return FALSE;
3755 }
3757 /**
3758 * Node handle moved callback.
3759 */
3760 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3761 {
3762 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3764 Inkscape::NodePath::NodeSide *me;
3765 Inkscape::NodePath::NodeSide *other;
3766 if (n->p.knot == knot) {
3767 me = &n->p;
3768 other = &n->n;
3769 } else if (n->n.knot == knot) {
3770 me = &n->n;
3771 other = &n->p;
3772 } else {
3773 me = NULL;
3774 other = NULL;
3775 g_assert_not_reached();
3776 }
3778 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3779 Radial rme(me->pos - n->pos);
3780 Radial rother(other->pos - n->pos);
3781 Radial rnew(*p - n->pos);
3783 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3784 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3785 /* 0 interpreted as "no snapping". */
3787 // 1. Snap to the closest PI/snaps angle, starting from zero.
3788 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3790 // 2. Snap to the original angle, its opposite and perpendiculars
3791 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3792 /* The closest PI/2 angle, starting from original angle */
3793 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3795 // Snap to the closest.
3796 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3797 ? a_snapped
3798 : a_ortho );
3799 }
3801 // 3. Snap to the angle of the opposite line, if any
3802 Inkscape::NodePath::Node *othernode = other->other;
3803 if (othernode) {
3804 NR::Point other_to_snap(0,0);
3805 if (sp_node_side_is_line(n, other)) {
3806 other_to_snap = othernode->pos - n->pos;
3807 } else {
3808 other_to_snap = other->pos - n->pos;
3809 }
3810 if (NR::L2(other_to_snap) > 1e-3) {
3811 Radial rother_to_snap(other_to_snap);
3812 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3813 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3815 // Snap to the closest.
3816 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3817 ? a_snapped
3818 : a_oppo );
3819 }
3820 }
3822 rnew.a = a_snapped;
3823 }
3825 if (state & GDK_MOD1_MASK) {
3826 // lock handle length
3827 rnew.r = me->origin_radial.r;
3828 }
3830 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3831 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3832 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3833 rother.a += rnew.a - rme.a;
3834 other->pos = NR::Point(rother) + n->pos;
3835 if (other->knot) {
3836 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3837 sp_knot_moveto(other->knot, &other->pos);
3838 }
3839 }
3841 me->pos = NR::Point(rnew) + n->pos;
3842 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3844 // move knot, but without emitting the signal:
3845 // we cannot emit a "moved" signal because we're now processing it
3846 sp_knot_moveto(me->knot, &(me->pos));
3848 update_object(n->subpath->nodepath);
3850 /* status text */
3851 SPDesktop *desktop = n->subpath->nodepath->desktop;
3852 if (!desktop) return;
3853 SPEventContext *ec = desktop->event_context;
3854 if (!ec) return;
3855 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3856 if (!mc) return;
3858 double degrees = 180 / M_PI * rnew.a;
3859 if (degrees > 180) degrees -= 360;
3860 if (degrees < -180) degrees += 360;
3861 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3862 degrees = angle_to_compass (degrees);
3864 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3866 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3867 _("<b>Node handle</b>: angle %0.2f°, length %s; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"), degrees, length->str);
3869 g_string_free(length, TRUE);
3870 }
3872 /**
3873 * Node handle event callback.
3874 */
3875 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3876 {
3877 gboolean ret = FALSE;
3878 switch (event->type) {
3879 case GDK_KEY_PRESS:
3880 switch (get_group0_keyval (&event->key)) {
3881 case GDK_space:
3882 if (event->key.state & GDK_BUTTON1_MASK) {
3883 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3884 stamp_repr(nodepath);
3885 ret = TRUE;
3886 }
3887 break;
3888 default:
3889 break;
3890 }
3891 break;
3892 case GDK_ENTER_NOTIFY:
3893 // we use an experimentally determined threshold that seems to work fine
3894 if (NR::L2(n->pos - knot->pos) < 0.75)
3895 Inkscape::NodePath::Path::active_node = n;
3896 break;
3897 case GDK_LEAVE_NOTIFY:
3898 // we use an experimentally determined threshold that seems to work fine
3899 if (NR::L2(n->pos - knot->pos) < 0.75)
3900 Inkscape::NodePath::Path::active_node = NULL;
3901 break;
3902 default:
3903 break;
3904 }
3906 return ret;
3907 }
3909 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3910 Radial &rme, Radial &rother, gboolean const both)
3911 {
3912 rme.a += angle;
3913 if ( both
3914 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3915 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3916 {
3917 rother.a += angle;
3918 }
3919 }
3921 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3922 Radial &rme, Radial &rother, gboolean const both)
3923 {
3924 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3926 gdouble r;
3927 if ( both
3928 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3929 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3930 {
3931 r = MAX(rme.r, rother.r);
3932 } else {
3933 r = rme.r;
3934 }
3936 gdouble const weird_angle = atan2(norm_angle, r);
3937 /* Bulia says norm_angle is just the visible distance that the
3938 * object's end must travel on the screen. Left as 'angle' for want of
3939 * a better name.*/
3941 rme.a += weird_angle;
3942 if ( both
3943 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3944 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3945 {
3946 rother.a += weird_angle;
3947 }
3948 }
3950 /**
3951 * Rotate one node.
3952 */
3953 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3954 {
3955 Inkscape::NodePath::NodeSide *me, *other;
3956 bool both = false;
3958 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3959 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3961 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3962 me = &(n->p);
3963 other = &(n->n);
3964 } else if (!n->p.other) {
3965 me = &(n->n);
3966 other = &(n->p);
3967 } else {
3968 if (which > 0) { // right handle
3969 if (xn > xp) {
3970 me = &(n->n);
3971 other = &(n->p);
3972 } else {
3973 me = &(n->p);
3974 other = &(n->n);
3975 }
3976 } else if (which < 0){ // left handle
3977 if (xn <= xp) {
3978 me = &(n->n);
3979 other = &(n->p);
3980 } else {
3981 me = &(n->p);
3982 other = &(n->n);
3983 }
3984 } else { // both handles
3985 me = &(n->n);
3986 other = &(n->p);
3987 both = true;
3988 }
3989 }
3991 Radial rme(me->pos - n->pos);
3992 Radial rother(other->pos - n->pos);
3994 if (screen) {
3995 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3996 } else {
3997 node_rotate_one_internal (*n, angle, rme, rother, both);
3998 }
4000 me->pos = n->pos + NR::Point(rme);
4002 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4003 other->pos = n->pos + NR::Point(rother);
4004 }
4006 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4007 // so here we just move all the knots without emitting move signals, for speed
4008 sp_node_update_handles(n, false);
4009 }
4011 /**
4012 * Rotate selected nodes.
4013 */
4014 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4015 {
4016 if (!nodepath || !nodepath->selected) return;
4018 if (g_list_length(nodepath->selected) == 1) {
4019 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4020 node_rotate_one (n, angle, which, screen);
4021 } else {
4022 // rotate as an object:
4024 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4025 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4026 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4028 box.expandTo (n->pos); // contain all selected nodes
4029 }
4031 gdouble rot;
4032 if (screen) {
4033 gdouble const zoom = nodepath->desktop->current_zoom();
4034 gdouble const zmove = angle / zoom;
4035 gdouble const r = NR::L2(box.max() - box.midpoint());
4036 rot = atan2(zmove, r);
4037 } else {
4038 rot = angle;
4039 }
4041 NR::Point rot_center;
4042 if (Inkscape::NodePath::Path::active_node == NULL)
4043 rot_center = box.midpoint();
4044 else
4045 rot_center = Inkscape::NodePath::Path::active_node->pos;
4047 NR::Matrix t =
4048 NR::Matrix (NR::translate(-rot_center)) *
4049 NR::Matrix (NR::rotate(rot)) *
4050 NR::Matrix (NR::translate(rot_center));
4052 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4053 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4054 n->pos *= t;
4055 n->n.pos *= t;
4056 n->p.pos *= t;
4057 sp_node_update_handles(n, false);
4058 }
4059 }
4061 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4062 }
4064 /**
4065 * Scale one node.
4066 */
4067 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4068 {
4069 bool both = false;
4070 Inkscape::NodePath::NodeSide *me, *other;
4072 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4073 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4075 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4076 me = &(n->p);
4077 other = &(n->n);
4078 n->code = NR_CURVETO;
4079 } else if (!n->p.other) {
4080 me = &(n->n);
4081 other = &(n->p);
4082 if (n->n.other)
4083 n->n.other->code = NR_CURVETO;
4084 } else {
4085 if (which > 0) { // right handle
4086 if (xn > xp) {
4087 me = &(n->n);
4088 other = &(n->p);
4089 if (n->n.other)
4090 n->n.other->code = NR_CURVETO;
4091 } else {
4092 me = &(n->p);
4093 other = &(n->n);
4094 n->code = NR_CURVETO;
4095 }
4096 } else if (which < 0){ // left handle
4097 if (xn <= xp) {
4098 me = &(n->n);
4099 other = &(n->p);
4100 if (n->n.other)
4101 n->n.other->code = NR_CURVETO;
4102 } else {
4103 me = &(n->p);
4104 other = &(n->n);
4105 n->code = NR_CURVETO;
4106 }
4107 } else { // both handles
4108 me = &(n->n);
4109 other = &(n->p);
4110 both = true;
4111 n->code = NR_CURVETO;
4112 if (n->n.other)
4113 n->n.other->code = NR_CURVETO;
4114 }
4115 }
4117 Radial rme(me->pos - n->pos);
4118 Radial rother(other->pos - n->pos);
4120 rme.r += grow;
4121 if (rme.r < 0) rme.r = 0;
4122 if (rme.a == HUGE_VAL) {
4123 if (me->other) { // if direction is unknown, initialize it towards the next node
4124 Radial rme_next(me->other->pos - n->pos);
4125 rme.a = rme_next.a;
4126 } else { // if there's no next, initialize to 0
4127 rme.a = 0;
4128 }
4129 }
4130 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4131 rother.r += grow;
4132 if (rother.r < 0) rother.r = 0;
4133 if (rother.a == HUGE_VAL) {
4134 rother.a = rme.a + M_PI;
4135 }
4136 }
4138 me->pos = n->pos + NR::Point(rme);
4140 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4141 other->pos = n->pos + NR::Point(rother);
4142 }
4144 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4145 // so here we just move all the knots without emitting move signals, for speed
4146 sp_node_update_handles(n, false);
4147 }
4149 /**
4150 * Scale selected nodes.
4151 */
4152 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4153 {
4154 if (!nodepath || !nodepath->selected) return;
4156 if (g_list_length(nodepath->selected) == 1) {
4157 // scale handles of the single selected node
4158 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4159 node_scale_one (n, grow, which);
4160 } else {
4161 // scale nodes as an "object":
4163 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4164 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4165 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4166 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4167 box.expandTo (n->pos); // contain all selected nodes
4168 }
4170 double scale = (box.maxExtent() + grow)/box.maxExtent();
4172 NR::Point scale_center;
4173 if (Inkscape::NodePath::Path::active_node == NULL)
4174 scale_center = box.midpoint();
4175 else
4176 scale_center = Inkscape::NodePath::Path::active_node->pos;
4178 NR::Matrix t =
4179 NR::Matrix (NR::translate(-scale_center)) *
4180 NR::Matrix (NR::scale(scale, scale)) *
4181 NR::Matrix (NR::translate(scale_center));
4183 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4184 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4185 n->pos *= t;
4186 n->n.pos *= t;
4187 n->p.pos *= t;
4188 sp_node_update_handles(n, false);
4189 }
4190 }
4192 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4193 }
4195 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4196 {
4197 if (!nodepath) return;
4198 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4199 }
4201 /**
4202 * Flip selected nodes horizontally/vertically.
4203 */
4204 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4205 {
4206 if (!nodepath || !nodepath->selected) return;
4208 if (g_list_length(nodepath->selected) == 1 && !center) {
4209 // flip handles of the single selected node
4210 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4211 double temp = n->p.pos[axis];
4212 n->p.pos[axis] = n->n.pos[axis];
4213 n->n.pos[axis] = temp;
4214 sp_node_update_handles(n, false);
4215 } else {
4216 // scale nodes as an "object":
4218 NR::Rect box = sp_node_selected_bbox (nodepath);
4219 if (!center) {
4220 center = box.midpoint();
4221 }
4222 NR::Matrix t =
4223 NR::Matrix (NR::translate(- *center)) *
4224 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4225 NR::Matrix (NR::translate(*center));
4227 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4228 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4229 n->pos *= t;
4230 n->n.pos *= t;
4231 n->p.pos *= t;
4232 sp_node_update_handles(n, false);
4233 }
4234 }
4236 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4237 }
4239 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4240 {
4241 g_assert (nodepath->selected);
4243 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4244 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4245 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4246 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4247 box.expandTo (n->pos); // contain all selected nodes
4248 }
4249 return box;
4250 }
4252 //-----------------------------------------------
4253 /**
4254 * Return new subpath under given nodepath.
4255 */
4256 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4257 {
4258 g_assert(nodepath);
4259 g_assert(nodepath->desktop);
4261 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4263 s->nodepath = nodepath;
4264 s->closed = FALSE;
4265 s->nodes = NULL;
4266 s->first = NULL;
4267 s->last = NULL;
4269 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4270 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4271 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4273 return s;
4274 }
4276 /**
4277 * Destroy nodes in subpath, then subpath itself.
4278 */
4279 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4280 {
4281 g_assert(subpath);
4282 g_assert(subpath->nodepath);
4283 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4285 while (subpath->nodes) {
4286 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4287 }
4289 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4291 g_free(subpath);
4292 }
4294 /**
4295 * Link head to tail in subpath.
4296 */
4297 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4298 {
4299 g_assert(!sp->closed);
4300 g_assert(sp->last != sp->first);
4301 g_assert(sp->first->code == NR_MOVETO);
4303 sp->closed = TRUE;
4305 //Link the head to the tail
4306 sp->first->p.other = sp->last;
4307 sp->last->n.other = sp->first;
4308 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4309 sp->first = sp->last;
4311 //Remove the extra end node
4312 sp_nodepath_node_destroy(sp->last->n.other);
4313 }
4315 /**
4316 * Open closed (loopy) subpath at node.
4317 */
4318 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4319 {
4320 g_assert(sp->closed);
4321 g_assert(n->subpath == sp);
4322 g_assert(sp->first == sp->last);
4324 /* We create new startpoint, current node will become last one */
4326 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4327 &n->pos, &n->pos, &n->n.pos);
4330 sp->closed = FALSE;
4332 //Unlink to make a head and tail
4333 sp->first = new_path;
4334 sp->last = n;
4335 n->n.other = NULL;
4336 new_path->p.other = NULL;
4337 }
4339 /**
4340 * Return new node in subpath with given properties.
4341 * \param pos Position of node.
4342 * \param ppos Handle position in previous direction
4343 * \param npos Handle position in previous direction
4344 */
4345 Inkscape::NodePath::Node *
4346 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 {
4348 g_assert(sp);
4349 g_assert(sp->nodepath);
4350 g_assert(sp->nodepath->desktop);
4352 if (nodechunk == NULL)
4353 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4355 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4357 n->subpath = sp;
4359 if (type != Inkscape::NodePath::NODE_NONE) {
4360 // use the type from sodipodi:nodetypes
4361 n->type = type;
4362 } else {
4363 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4364 // points are (almost) collinear
4365 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4366 // endnode, or a node with a retracted handle
4367 n->type = Inkscape::NodePath::NODE_CUSP;
4368 } else {
4369 n->type = Inkscape::NodePath::NODE_SMOOTH;
4370 }
4371 } else {
4372 n->type = Inkscape::NodePath::NODE_CUSP;
4373 }
4374 }
4376 n->code = code;
4377 n->selected = FALSE;
4378 n->pos = *pos;
4379 n->p.pos = *ppos;
4380 n->n.pos = *npos;
4382 n->dragging_out = NULL;
4384 Inkscape::NodePath::Node *prev;
4385 if (next) {
4386 //g_assert(g_list_find(sp->nodes, next));
4387 prev = next->p.other;
4388 } else {
4389 prev = sp->last;
4390 }
4392 if (prev)
4393 prev->n.other = n;
4394 else
4395 sp->first = n;
4397 if (next)
4398 next->p.other = n;
4399 else
4400 sp->last = n;
4402 n->p.other = prev;
4403 n->n.other = next;
4405 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"));
4406 sp_knot_set_position(n->knot, pos, 0);
4408 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4409 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4410 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4411 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4412 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4413 sp_knot_update_ctrl(n->knot);
4415 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4416 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4417 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4418 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4419 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4420 sp_knot_show(n->knot);
4422 // We only create handle knots and lines on demand
4423 n->p.knot = NULL;
4424 n->p.line = NULL;
4425 n->n.knot = NULL;
4426 n->n.line = NULL;
4428 sp->nodes = g_list_prepend(sp->nodes, n);
4430 return n;
4431 }
4433 /**
4434 * Destroy node and its knots, link neighbors in subpath.
4435 */
4436 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4437 {
4438 g_assert(node);
4439 g_assert(node->subpath);
4440 g_assert(SP_IS_KNOT(node->knot));
4442 Inkscape::NodePath::SubPath *sp = node->subpath;
4444 if (node->selected) { // first, deselect
4445 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4446 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4447 }
4449 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4451 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4452 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4453 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4454 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4455 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4456 g_object_unref(G_OBJECT(node->knot));
4458 if (node->p.knot) {
4459 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4460 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4461 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4462 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4463 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4464 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4465 g_object_unref(G_OBJECT(node->p.knot));
4466 node->p.knot = NULL;
4467 }
4469 if (node->n.knot) {
4470 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4471 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4472 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4473 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4474 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4475 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4476 g_object_unref(G_OBJECT(node->n.knot));
4477 node->n.knot = NULL;
4478 }
4480 if (node->p.line)
4481 gtk_object_destroy(GTK_OBJECT(node->p.line));
4482 if (node->n.line)
4483 gtk_object_destroy(GTK_OBJECT(node->n.line));
4485 if (sp->nodes) { // there are others nodes on the subpath
4486 if (sp->closed) {
4487 if (sp->first == node) {
4488 g_assert(sp->last == node);
4489 sp->first = node->n.other;
4490 sp->last = sp->first;
4491 }
4492 node->p.other->n.other = node->n.other;
4493 node->n.other->p.other = node->p.other;
4494 } else {
4495 if (sp->first == node) {
4496 sp->first = node->n.other;
4497 sp->first->code = NR_MOVETO;
4498 }
4499 if (sp->last == node) sp->last = node->p.other;
4500 if (node->p.other) node->p.other->n.other = node->n.other;
4501 if (node->n.other) node->n.other->p.other = node->p.other;
4502 }
4503 } else { // this was the last node on subpath
4504 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4505 }
4507 g_mem_chunk_free(nodechunk, node);
4508 }
4510 /**
4511 * Returns one of the node's two sides.
4512 * \param which Indicates which side.
4513 * \return Pointer to previous node side if which==-1, next if which==1.
4514 */
4515 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4516 {
4517 g_assert(node);
4519 switch (which) {
4520 case -1:
4521 return &node->p;
4522 case 1:
4523 return &node->n;
4524 default:
4525 break;
4526 }
4528 g_assert_not_reached();
4530 return NULL;
4531 }
4533 /**
4534 * Return the other side of the node, given one of its sides.
4535 */
4536 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4537 {
4538 g_assert(node);
4540 if (me == &node->p) return &node->n;
4541 if (me == &node->n) return &node->p;
4543 g_assert_not_reached();
4545 return NULL;
4546 }
4548 /**
4549 * Return NRPathcode on the given side of the node.
4550 */
4551 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4552 {
4553 g_assert(node);
4555 if (me == &node->p) {
4556 if (node->p.other) return (NRPathcode)node->code;
4557 return NR_MOVETO;
4558 }
4560 if (me == &node->n) {
4561 if (node->n.other) return (NRPathcode)node->n.other->code;
4562 return NR_MOVETO;
4563 }
4565 g_assert_not_reached();
4567 return NR_END;
4568 }
4570 /**
4571 * Return node with the given index
4572 */
4573 Inkscape::NodePath::Node *
4574 sp_nodepath_get_node_by_index(int index)
4575 {
4576 Inkscape::NodePath::Node *e = NULL;
4578 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4579 if (!nodepath) {
4580 return e;
4581 }
4583 //find segment
4584 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4586 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4587 int n = g_list_length(sp->nodes);
4588 if (sp->closed) {
4589 n++;
4590 }
4592 //if the piece belongs to this subpath grab it
4593 //otherwise move onto the next subpath
4594 if (index < n) {
4595 e = sp->first;
4596 for (int i = 0; i < index; ++i) {
4597 e = e->n.other;
4598 }
4599 break;
4600 } else {
4601 if (sp->closed) {
4602 index -= (n+1);
4603 } else {
4604 index -= n;
4605 }
4606 }
4607 }
4609 return e;
4610 }
4612 /**
4613 * Returns plain text meaning of node type.
4614 */
4615 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4616 {
4617 unsigned retracted = 0;
4618 bool endnode = false;
4620 for (int which = -1; which <= 1; which += 2) {
4621 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4622 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4623 retracted ++;
4624 if (!side->other)
4625 endnode = true;
4626 }
4628 if (retracted == 0) {
4629 if (endnode) {
4630 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4631 return _("end node");
4632 } else {
4633 switch (node->type) {
4634 case Inkscape::NodePath::NODE_CUSP:
4635 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4636 return _("cusp");
4637 case Inkscape::NodePath::NODE_SMOOTH:
4638 // TRANSLATORS: "smooth" is an adjective here
4639 return _("smooth");
4640 case Inkscape::NodePath::NODE_SYMM:
4641 return _("symmetric");
4642 }
4643 }
4644 } else if (retracted == 1) {
4645 if (endnode) {
4646 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4647 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4648 } else {
4649 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4650 }
4651 } else {
4652 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4653 }
4655 return NULL;
4656 }
4658 /**
4659 * Handles content of statusbar as long as node tool is active.
4660 */
4661 void
4662 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4663 {
4664 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>< ></b> to scale, <b>[ ]</b> to rotate");
4665 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4667 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4668 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4669 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4670 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4672 SPDesktop *desktop = NULL;
4673 if (nodepath) {
4674 desktop = nodepath->desktop;
4675 } else {
4676 desktop = SP_ACTIVE_DESKTOP;
4677 }
4679 SPEventContext *ec = desktop->event_context;
4680 if (!ec) return;
4681 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4682 if (!mc) return;
4684 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4686 if (selected_nodes == 0) {
4687 Inkscape::Selection *sel = desktop->selection;
4688 if (!sel || sel->isEmpty()) {
4689 mc->setF(Inkscape::NORMAL_MESSAGE,
4690 _("Select a single object to edit its nodes or handles."));
4691 } else {
4692 if (nodepath) {
4693 mc->setF(Inkscape::NORMAL_MESSAGE,
4694 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.",
4695 "<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.",
4696 total_nodes),
4697 total_nodes);
4698 } else {
4699 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4700 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4701 } else {
4702 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4703 }
4704 }
4705 }
4706 } else if (nodepath && selected_nodes == 1) {
4707 mc->setF(Inkscape::NORMAL_MESSAGE,
4708 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4709 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4710 total_nodes),
4711 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4712 } else {
4713 if (selected_subpaths > 1) {
4714 mc->setF(Inkscape::NORMAL_MESSAGE,
4715 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4716 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4717 total_nodes),
4718 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4719 } else {
4720 mc->setF(Inkscape::NORMAL_MESSAGE,
4721 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4722 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4723 total_nodes),
4724 selected_nodes, total_nodes, when_selected);
4725 }
4726 }
4727 }
4729 /*
4730 * returns a *copy* of the curve of that object.
4731 */
4732 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4733 if (!object)
4734 return NULL;
4736 SPCurve *curve = NULL;
4737 if (SP_IS_PATH(object)) {
4738 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4739 curve = curve_new->copy();
4740 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4741 const gchar *svgd = object->repr->attribute(key);
4742 if (svgd) {
4743 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4744 SPCurve *curve_new = new SPCurve(pv);
4745 if (curve_new) {
4746 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4747 }
4748 }
4749 }
4751 return curve;
4752 }
4754 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4755 if (!np || !np->object || !curve)
4756 return;
4758 if (SP_IS_PATH(np->object)) {
4759 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4760 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4761 } else {
4762 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4763 }
4764 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4765 // FIXME: this writing to string and then reading from string is bound to be slow.
4766 // create a method to convert from curve directly to 2geom...
4767 gchar *svgpath = sp_svg_write_path( np->curve->get_pathvector() );
4768 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4769 g_free(svgpath);
4771 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4772 }
4773 }
4775 SPCanvasItem *
4776 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4777 SPCurve *flash_curve = curve->copy();
4778 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4779 flash_curve->transform(i2d);
4780 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4781 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4782 // unless we also flash the nodes...
4783 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4784 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4785 sp_canvas_item_show(canvasitem);
4786 flash_curve->unref();
4787 return canvasitem;
4788 }
4790 SPCanvasItem *
4791 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4792 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4793 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4794 }
4796 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4797 np->show_helperpath = show;
4799 if (show) {
4800 SPCurve *helper_curve = np->curve->copy();
4801 helper_curve->transform(to_2geom(np->i2d));
4802 if (!np->helper_path) {
4803 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4804 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);
4805 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4806 sp_canvas_item_move_to_z(np->helper_path, 0);
4807 sp_canvas_item_show(np->helper_path);
4808 } else {
4809 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4810 }
4811 helper_curve->unref();
4812 } else {
4813 if (np->helper_path) {
4814 GtkObject *temp = np->helper_path;
4815 np->helper_path = NULL;
4816 gtk_object_destroy(temp);
4817 }
4818 }
4819 }
4821 /* sp_nodepath_make_straight_path:
4822 * Prevents user from curving the path by dragging a segment or activating handles etc.
4823 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4824 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4825 */
4826 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4827 np->straight_path = true;
4828 np->show_handles = false;
4829 g_message("add code to make the path straight.");
4830 // do sp_nodepath_convert_node_type on all nodes?
4831 // coding tip: search for this text : "Make selected segments lines"
4832 }
4835 /*
4836 Local Variables:
4837 mode:c++
4838 c-file-style:"stroustrup"
4839 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4840 indent-tabs-mode:nil
4841 fill-column:99
4842 End:
4843 */
4844 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :