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