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