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 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1905 Geom::PathVector const &pathv = curve->get_pathvector();
1906 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1908 // calculate index for nodepath's representation.
1909 unsigned int segment_index = floor(pvpos.t) + 1;
1910 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1911 segment_index += pathv[i].size() + 1;
1912 if (pathv[i].closed()) {
1913 segment_index += 1;
1914 }
1915 }
1917 curve->unref();
1919 //find segment to segment
1920 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1922 //fixme: this can return NULL, so check before proceeding.
1923 g_return_if_fail(e != NULL);
1925 gboolean force = FALSE;
1926 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1927 force = TRUE;
1928 }
1929 sp_nodepath_node_select(e, (gboolean) toggle, force);
1930 if (e->p.other)
1931 sp_nodepath_node_select(e->p.other, TRUE, force);
1933 sp_nodepath_update_handles(nodepath);
1935 sp_nodepath_update_statusbar(nodepath);
1936 }
1938 /**
1939 * Add a node nearest to point
1940 */
1941 void
1942 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1943 {
1944 if (!nodepath) {
1945 return;
1946 }
1948 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1949 Geom::PathVector const &pathv = curve->get_pathvector();
1950 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1952 // calculate index for nodepath's representation.
1953 double int_part;
1954 double t = std::modf(pvpos.t, &int_part);
1955 unsigned int segment_index = (unsigned int)int_part + 1;
1956 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1957 segment_index += pathv[i].size() + 1;
1958 if (pathv[i].closed()) {
1959 segment_index += 1;
1960 }
1961 }
1963 curve->unref();
1965 //find segment to split
1966 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1968 //don't know why but t seems to flip for lines
1969 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1970 t = 1.0 - t;
1971 }
1973 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
1974 sp_nodepath_node_select(n, FALSE, TRUE);
1976 /* fixme: adjust ? */
1977 sp_nodepath_update_handles(nodepath);
1979 sp_nodepath_update_repr(nodepath, _("Add node"));
1981 sp_nodepath_update_statusbar(nodepath);
1982 }
1984 /*
1985 * Adjusts a segment so that t moves by a certain delta for dragging
1986 * converts lines to curves
1987 *
1988 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1989 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1990 */
1991 void
1992 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1993 {
1994 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1996 //fixme: e and e->p can be NULL, so check for those before proceeding
1997 g_return_if_fail(e != NULL);
1998 g_return_if_fail(&e->p != NULL);
2000 /* feel good is an arbitrary parameter that distributes the delta between handles
2001 * if t of the drag point is less than 1/6 distance form the endpoint only
2002 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2003 */
2004 double feel_good;
2005 if (t <= 1.0 / 6.0)
2006 feel_good = 0;
2007 else if (t <= 0.5)
2008 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2009 else if (t <= 5.0 / 6.0)
2010 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2011 else
2012 feel_good = 1;
2014 //if we're dragging a line convert it to a curve
2015 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2016 sp_nodepath_set_line_type(e, NR_CURVETO);
2017 }
2019 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2020 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2021 e->p.other->n.pos += offsetcoord0;
2022 e->p.pos += offsetcoord1;
2024 // adjust handles of adjacent nodes where necessary
2025 sp_node_adjust_handle(e,1);
2026 sp_node_adjust_handle(e->p.other,-1);
2028 sp_nodepath_update_handles(e->subpath->nodepath);
2030 update_object(e->subpath->nodepath);
2032 sp_nodepath_update_statusbar(e->subpath->nodepath);
2033 }
2036 /**
2037 * Call sp_nodepath_break() for all selected segments.
2038 */
2039 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2040 {
2041 if (!nodepath) return;
2043 GList *tempin = g_list_copy(nodepath->selected);
2044 GList *temp = NULL;
2045 for (GList *l = tempin; l != NULL; l = l->next) {
2046 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2047 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2048 if (nn == NULL) continue; // no break, no new node
2049 temp = g_list_prepend(temp, nn);
2050 }
2051 g_list_free(tempin);
2053 if (temp) {
2054 sp_nodepath_deselect(nodepath);
2055 }
2056 for (GList *l = temp; l != NULL; l = l->next) {
2057 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2058 }
2060 sp_nodepath_update_handles(nodepath);
2062 sp_nodepath_update_repr(nodepath, _("Break path"));
2063 }
2065 /**
2066 * Duplicate the selected node(s).
2067 */
2068 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2069 {
2070 if (!nodepath) {
2071 return;
2072 }
2074 GList *temp = NULL;
2075 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2076 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2077 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2078 if (nn == NULL) continue; // could not duplicate
2079 temp = g_list_prepend(temp, nn);
2080 }
2082 if (temp) {
2083 sp_nodepath_deselect(nodepath);
2084 }
2085 for (GList *l = temp; l != NULL; l = l->next) {
2086 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2087 }
2089 sp_nodepath_update_handles(nodepath);
2091 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2092 }
2094 /**
2095 * Internal function to join two nodes by merging them into one.
2096 */
2097 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2098 {
2099 /* a and b are endpoints */
2101 // if one of the two nodes is mouseovered, fix its position
2102 NR::Point c;
2103 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2104 c = a->pos;
2105 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2106 c = b->pos;
2107 } else {
2108 // otherwise, move joined node to the midpoint
2109 c = (a->pos + b->pos) / 2;
2110 }
2112 if (a->subpath == b->subpath) {
2113 Inkscape::NodePath::SubPath *sp = a->subpath;
2114 sp_nodepath_subpath_close(sp);
2115 sp_node_moveto (sp->first, c);
2117 sp_nodepath_update_handles(sp->nodepath);
2118 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2119 return;
2120 }
2122 /* a and b are separate subpaths */
2123 Inkscape::NodePath::SubPath *sa = a->subpath;
2124 Inkscape::NodePath::SubPath *sb = b->subpath;
2125 NR::Point p;
2126 Inkscape::NodePath::Node *n;
2127 NRPathcode code;
2128 if (a == sa->first) {
2129 // we will now reverse sa, so that a is its last node, not first, and drop that node
2130 p = sa->first->n.pos;
2131 code = (NRPathcode)sa->first->n.other->code;
2132 // create new subpath
2133 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2134 // create a first moveto node on it
2135 n = sa->last;
2136 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2137 n = n->p.other;
2138 if (n == sa->first) n = NULL;
2139 while (n) {
2140 // copy the rest of the nodes from sa to t, going backwards
2141 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2142 n = n->p.other;
2143 if (n == sa->first) n = NULL;
2144 }
2145 // replace sa with t
2146 sp_nodepath_subpath_destroy(sa);
2147 sa = t;
2148 } else if (a == sa->last) {
2149 // a is already last, just drop it
2150 p = sa->last->p.pos;
2151 code = (NRPathcode)sa->last->code;
2152 sp_nodepath_node_destroy(sa->last);
2153 } else {
2154 code = NR_END;
2155 g_assert_not_reached();
2156 }
2158 if (b == sb->first) {
2159 // copy all nodes from b to a, forward
2160 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2161 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2162 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2163 }
2164 } else if (b == sb->last) {
2165 // copy all nodes from b to a, backward
2166 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2167 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2168 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2169 }
2170 } else {
2171 g_assert_not_reached();
2172 }
2173 /* and now destroy sb */
2175 sp_nodepath_subpath_destroy(sb);
2177 sp_nodepath_update_handles(sa->nodepath);
2179 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2181 sp_nodepath_update_statusbar(nodepath);
2182 }
2184 /**
2185 * Internal function to join two nodes by adding a segment between them.
2186 */
2187 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2188 {
2189 if (a->subpath == b->subpath) {
2190 Inkscape::NodePath::SubPath *sp = a->subpath;
2192 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2193 sp->closed = TRUE;
2195 sp->first->p.other = sp->last;
2196 sp->last->n.other = sp->first;
2198 sp_node_handle_mirror_p_to_n(sp->last);
2199 sp_node_handle_mirror_n_to_p(sp->first);
2201 sp->first->code = sp->last->code;
2202 sp->first = sp->last;
2204 sp_nodepath_update_handles(sp->nodepath);
2206 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2208 return;
2209 }
2211 /* a and b are separate subpaths */
2212 Inkscape::NodePath::SubPath *sa = a->subpath;
2213 Inkscape::NodePath::SubPath *sb = b->subpath;
2215 Inkscape::NodePath::Node *n;
2216 NR::Point p;
2217 NRPathcode code;
2218 if (a == sa->first) {
2219 code = (NRPathcode) sa->first->n.other->code;
2220 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2221 n = sa->last;
2222 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2223 for (n = n->p.other; n != NULL; n = n->p.other) {
2224 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2225 }
2226 sp_nodepath_subpath_destroy(sa);
2227 sa = t;
2228 } else if (a == sa->last) {
2229 code = (NRPathcode)sa->last->code;
2230 } else {
2231 code = NR_END;
2232 g_assert_not_reached();
2233 }
2235 if (b == sb->first) {
2236 n = sb->first;
2237 sp_node_handle_mirror_p_to_n(sa->last);
2238 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2239 sp_node_handle_mirror_n_to_p(sa->last);
2240 for (n = n->n.other; n != NULL; n = n->n.other) {
2241 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2242 }
2243 } else if (b == sb->last) {
2244 n = sb->last;
2245 sp_node_handle_mirror_p_to_n(sa->last);
2246 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2247 sp_node_handle_mirror_n_to_p(sa->last);
2248 for (n = n->p.other; n != NULL; n = n->p.other) {
2249 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2250 }
2251 } else {
2252 g_assert_not_reached();
2253 }
2254 /* and now destroy sb */
2256 sp_nodepath_subpath_destroy(sb);
2258 sp_nodepath_update_handles(sa->nodepath);
2260 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2261 }
2263 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2265 /**
2266 * Internal function to handle joining two nodes.
2267 */
2268 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2269 {
2270 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2272 if (g_list_length(nodepath->selected) != 2) {
2273 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2274 return;
2275 }
2277 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2278 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2280 g_assert(a != b);
2281 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2282 // someone tried to join an orphan node (i.e. a single-node subpath).
2283 // this is not worth an error message, just fail silently.
2284 return;
2285 }
2287 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2288 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2289 return;
2290 }
2292 switch(mode) {
2293 case NODE_JOIN_ENDPOINTS:
2294 do_node_selected_join(nodepath, a, b);
2295 break;
2296 case NODE_JOIN_SEGMENT:
2297 do_node_selected_join_segment(nodepath, a, b);
2298 break;
2299 }
2300 }
2302 /**
2303 * Join two nodes by merging them into one.
2304 */
2305 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2306 {
2307 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2308 }
2310 /**
2311 * Join two nodes by adding a segment between them.
2312 */
2313 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2314 {
2315 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2316 }
2318 /**
2319 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2320 */
2321 void sp_node_delete_preserve(GList *nodes_to_delete)
2322 {
2323 GSList *nodepaths = NULL;
2325 while (nodes_to_delete) {
2326 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2327 Inkscape::NodePath::SubPath *sp = node->subpath;
2328 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2329 Inkscape::NodePath::Node *sample_cursor = NULL;
2330 Inkscape::NodePath::Node *sample_end = NULL;
2331 Inkscape::NodePath::Node *delete_cursor = node;
2332 bool just_delete = false;
2334 //find the start of this contiguous selection
2335 //move left to the first node that is not selected
2336 //or the start of the non-closed path
2337 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2338 delete_cursor = curr;
2339 }
2341 //just delete at the beginning of an open path
2342 if (!delete_cursor->p.other) {
2343 sample_cursor = delete_cursor;
2344 just_delete = true;
2345 } else {
2346 sample_cursor = delete_cursor->p.other;
2347 }
2349 //calculate points for each segment
2350 int rate = 5;
2351 float period = 1.0 / rate;
2352 std::vector<NR::Point> data;
2353 if (!just_delete) {
2354 data.push_back(sample_cursor->pos);
2355 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2356 //just delete at the end of an open path
2357 if (!sp->closed && curr == sp->last) {
2358 just_delete = true;
2359 break;
2360 }
2362 //sample points on the contiguous selected segment
2363 NR::Point *bez;
2364 bez = new NR::Point [4];
2365 bez[0] = curr->pos;
2366 bez[1] = curr->n.pos;
2367 bez[2] = curr->n.other->p.pos;
2368 bez[3] = curr->n.other->pos;
2369 for (int i=1; i<rate; i++) {
2370 gdouble t = i * period;
2371 NR::Point p = bezier_pt(3, bez, t);
2372 data.push_back(p);
2373 }
2374 data.push_back(curr->n.other->pos);
2376 sample_end = curr->n.other;
2377 //break if we've come full circle or hit the end of the selection
2378 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2379 break;
2380 }
2381 }
2382 }
2384 if (!just_delete) {
2385 //calculate the best fitting single segment and adjust the endpoints
2386 NR::Point *adata;
2387 adata = new NR::Point [data.size()];
2388 copy(data.begin(), data.end(), adata);
2390 NR::Point *bez;
2391 bez = new NR::Point [4];
2392 //would decreasing error create a better fitting approximation?
2393 gdouble error = 1.0;
2394 gint ret;
2395 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2397 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2398 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2399 //the resulting nodes behave as expected.
2400 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2401 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2402 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2403 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2405 //adjust endpoints
2406 sample_cursor->n.pos = bez[1];
2407 sample_end->p.pos = bez[2];
2408 }
2410 //destroy this contiguous selection
2411 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2412 Inkscape::NodePath::Node *temp = delete_cursor;
2413 if (delete_cursor->n.other == delete_cursor) {
2414 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2415 delete_cursor = NULL;
2416 } else {
2417 delete_cursor = delete_cursor->n.other;
2418 }
2419 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2420 sp_nodepath_node_destroy(temp);
2421 }
2423 sp_nodepath_update_handles(nodepath);
2425 if (!g_slist_find(nodepaths, nodepath))
2426 nodepaths = g_slist_prepend (nodepaths, nodepath);
2427 }
2429 for (GSList *i = nodepaths; i; i = i->next) {
2430 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2431 // different nodepaths will give us one undo event per nodepath
2432 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2434 // if the entire nodepath is removed, delete the selected object.
2435 if (nodepath->subpaths == NULL ||
2436 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2437 //at least 2
2438 sp_nodepath_get_node_count(nodepath) < 2) {
2439 SPDocument *document = sp_desktop_document (nodepath->desktop);
2440 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2441 //delete this nodepath's object, not the entire selection! (though at this time, this
2442 //does not matter)
2443 sp_selection_delete();
2444 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2445 _("Delete nodes"));
2446 } else {
2447 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2448 sp_nodepath_update_statusbar(nodepath);
2449 }
2450 }
2452 g_slist_free (nodepaths);
2453 }
2455 /**
2456 * Delete one or more selected nodes.
2457 */
2458 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2459 {
2460 if (!nodepath) return;
2461 if (!nodepath->selected) return;
2463 /** \todo fixme: do it the right way */
2464 while (nodepath->selected) {
2465 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2466 sp_nodepath_node_destroy(node);
2467 }
2470 //clean up the nodepath (such as for trivial subpaths)
2471 sp_nodepath_cleanup(nodepath);
2473 sp_nodepath_update_handles(nodepath);
2475 // if the entire nodepath is removed, delete the selected object.
2476 if (nodepath->subpaths == NULL ||
2477 sp_nodepath_get_node_count(nodepath) < 2) {
2478 SPDocument *document = sp_desktop_document (nodepath->desktop);
2479 sp_selection_delete();
2480 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2481 _("Delete nodes"));
2482 return;
2483 }
2485 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2487 sp_nodepath_update_statusbar(nodepath);
2488 }
2490 /**
2491 * Delete one or more segments between two selected nodes.
2492 * This is the code for 'split'.
2493 */
2494 void
2495 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2496 {
2497 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2498 Inkscape::NodePath::Node *curr, *next; //Iterators
2500 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2502 if (g_list_length(nodepath->selected) != 2) {
2503 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2504 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2505 return;
2506 }
2508 //Selected nodes, not inclusive
2509 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2510 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2512 if ( ( a==b) || //same node
2513 (a->subpath != b->subpath ) || //not the same path
2514 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2515 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2516 {
2517 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2518 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2519 return;
2520 }
2522 //###########################################
2523 //# BEGIN EDITS
2524 //###########################################
2525 //##################################
2526 //# CLOSED PATH
2527 //##################################
2528 if (a->subpath->closed) {
2531 gboolean reversed = FALSE;
2533 //Since we can go in a circle, we need to find the shorter distance.
2534 // a->b or b->a
2535 start = end = NULL;
2536 int distance = 0;
2537 int minDistance = 0;
2538 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2539 if (curr==b) {
2540 //printf("a to b:%d\n", distance);
2541 start = a;//go from a to b
2542 end = b;
2543 minDistance = distance;
2544 //printf("A to B :\n");
2545 break;
2546 }
2547 distance++;
2548 }
2550 //try again, the other direction
2551 distance = 0;
2552 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2553 if (curr==a) {
2554 //printf("b to a:%d\n", distance);
2555 if (distance < minDistance) {
2556 start = b; //we go from b to a
2557 end = a;
2558 reversed = TRUE;
2559 //printf("B to A\n");
2560 }
2561 break;
2562 }
2563 distance++;
2564 }
2567 //Copy everything from 'end' to 'start' to a new subpath
2568 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2569 for (curr=end ; curr ; curr=curr->n.other) {
2570 NRPathcode code = (NRPathcode) curr->code;
2571 if (curr == end)
2572 code = NR_MOVETO;
2573 sp_nodepath_node_new(t, NULL,
2574 (Inkscape::NodePath::NodeType)curr->type, code,
2575 &curr->p.pos, &curr->pos, &curr->n.pos);
2576 if (curr == start)
2577 break;
2578 }
2579 sp_nodepath_subpath_destroy(a->subpath);
2582 }
2586 //##################################
2587 //# OPEN PATH
2588 //##################################
2589 else {
2591 //We need to get the direction of the list between A and B
2592 //Can we walk from a to b?
2593 start = end = NULL;
2594 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2595 if (curr==b) {
2596 start = a; //did it! we go from a to b
2597 end = b;
2598 //printf("A to B\n");
2599 break;
2600 }
2601 }
2602 if (!start) {//didn't work? let's try the other direction
2603 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2604 if (curr==a) {
2605 start = b; //did it! we go from b to a
2606 end = a;
2607 //printf("B to A\n");
2608 break;
2609 }
2610 }
2611 }
2612 if (!start) {
2613 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2614 _("Cannot find path between nodes."));
2615 return;
2616 }
2620 //Copy everything after 'end' to a new subpath
2621 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2622 for (curr=end ; curr ; curr=curr->n.other) {
2623 NRPathcode code = (NRPathcode) curr->code;
2624 if (curr == end)
2625 code = NR_MOVETO;
2626 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2627 &curr->p.pos, &curr->pos, &curr->n.pos);
2628 }
2630 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2631 for (curr = start->n.other ; curr ; curr=next) {
2632 next = curr->n.other;
2633 sp_nodepath_node_destroy(curr);
2634 }
2636 }
2637 //###########################################
2638 //# END EDITS
2639 //###########################################
2641 //clean up the nodepath (such as for trivial subpaths)
2642 sp_nodepath_cleanup(nodepath);
2644 sp_nodepath_update_handles(nodepath);
2646 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2648 sp_nodepath_update_statusbar(nodepath);
2649 }
2651 /**
2652 * Call sp_nodepath_set_line() for all selected segments.
2653 */
2654 void
2655 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2656 {
2657 if (nodepath == NULL) return;
2659 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2660 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2661 g_assert(n->selected);
2662 if (n->p.other && n->p.other->selected) {
2663 sp_nodepath_set_line_type(n, code);
2664 }
2665 }
2667 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2668 }
2670 /**
2671 * Call sp_nodepath_convert_node_type() for all selected nodes.
2672 */
2673 void
2674 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2675 {
2676 if (nodepath == NULL) return;
2678 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2680 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2681 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2682 }
2684 sp_nodepath_update_repr(nodepath, _("Change node type"));
2685 }
2687 /**
2688 * Change select status of node, update its own and neighbour handles.
2689 */
2690 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2691 {
2692 node->selected = selected;
2694 if (selected) {
2695 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2696 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2697 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2698 sp_knot_update_ctrl(node->knot);
2699 } else {
2700 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2701 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2702 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2703 sp_knot_update_ctrl(node->knot);
2704 }
2706 sp_node_update_handles(node);
2707 if (node->n.other) sp_node_update_handles(node->n.other);
2708 if (node->p.other) sp_node_update_handles(node->p.other);
2709 }
2711 /**
2712 \brief Select a node
2713 \param node The node to select
2714 \param incremental If true, add to selection, otherwise deselect others
2715 \param override If true, always select this node, otherwise toggle selected status
2716 */
2717 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2718 {
2719 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2721 if (incremental) {
2722 if (override) {
2723 if (!g_list_find(nodepath->selected, node)) {
2724 nodepath->selected = g_list_prepend(nodepath->selected, node);
2725 }
2726 sp_node_set_selected(node, TRUE);
2727 } else { // toggle
2728 if (node->selected) {
2729 g_assert(g_list_find(nodepath->selected, node));
2730 nodepath->selected = g_list_remove(nodepath->selected, node);
2731 } else {
2732 g_assert(!g_list_find(nodepath->selected, node));
2733 nodepath->selected = g_list_prepend(nodepath->selected, node);
2734 }
2735 sp_node_set_selected(node, !node->selected);
2736 }
2737 } else {
2738 sp_nodepath_deselect(nodepath);
2739 nodepath->selected = g_list_prepend(nodepath->selected, node);
2740 sp_node_set_selected(node, TRUE);
2741 }
2743 sp_nodepath_update_statusbar(nodepath);
2744 }
2747 /**
2748 \brief Deselect all nodes in the nodepath
2749 */
2750 void
2751 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2752 {
2753 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2755 while (nodepath->selected) {
2756 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2757 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2758 }
2759 sp_nodepath_update_statusbar(nodepath);
2760 }
2762 /**
2763 \brief Select or invert selection of all nodes in the nodepath
2764 */
2765 void
2766 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2767 {
2768 if (!nodepath) return;
2770 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2771 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2772 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2773 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2774 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2775 }
2776 }
2777 }
2779 /**
2780 * If nothing selected, does the same as sp_nodepath_select_all();
2781 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2782 * (i.e., similar to "select all in layer", with the "selected" subpaths
2783 * being treated as "layers" in the path).
2784 */
2785 void
2786 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2787 {
2788 if (!nodepath) return;
2790 if (g_list_length (nodepath->selected) == 0) {
2791 sp_nodepath_select_all (nodepath, invert);
2792 return;
2793 }
2795 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2796 GSList *subpaths = NULL;
2798 for (GList *l = copy; l != NULL; l = l->next) {
2799 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2800 Inkscape::NodePath::SubPath *subpath = n->subpath;
2801 if (!g_slist_find (subpaths, subpath))
2802 subpaths = g_slist_prepend (subpaths, subpath);
2803 }
2805 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2806 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2807 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2808 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2809 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2810 }
2811 }
2813 g_slist_free (subpaths);
2814 g_list_free (copy);
2815 }
2817 /**
2818 * \brief Select the node after the last selected; if none is selected,
2819 * select the first within path.
2820 */
2821 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2822 {
2823 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2825 Inkscape::NodePath::Node *last = NULL;
2826 if (nodepath->selected) {
2827 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2828 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2829 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2830 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2831 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2832 if (node->selected) {
2833 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2834 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2835 if (spl->next) { // there's a next subpath
2836 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2837 last = subpath_next->first;
2838 } else if (spl->prev) { // there's a previous subpath
2839 last = NULL; // to be set later to the first node of first subpath
2840 } else {
2841 last = node->n.other;
2842 }
2843 } else {
2844 last = node->n.other;
2845 }
2846 } else {
2847 if (node->n.other) {
2848 last = node->n.other;
2849 } else {
2850 if (spl->next) { // there's a next subpath
2851 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2852 last = subpath_next->first;
2853 } else if (spl->prev) { // there's a previous subpath
2854 last = NULL; // to be set later to the first node of first subpath
2855 } else {
2856 last = (Inkscape::NodePath::Node *) subpath->first;
2857 }
2858 }
2859 }
2860 }
2861 }
2862 }
2863 sp_nodepath_deselect(nodepath);
2864 }
2866 if (last) { // there's at least one more node after selected
2867 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2868 } else { // no more nodes, select the first one in first subpath
2869 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2870 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2871 }
2872 }
2874 /**
2875 * \brief Select the node before the first selected; if none is selected,
2876 * select the last within path
2877 */
2878 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2879 {
2880 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2882 Inkscape::NodePath::Node *last = NULL;
2883 if (nodepath->selected) {
2884 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2885 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2886 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2887 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2888 if (node->selected) {
2889 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2890 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2891 if (spl->prev) { // there's a prev subpath
2892 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2893 last = subpath_prev->last;
2894 } else if (spl->next) { // there's a next subpath
2895 last = NULL; // to be set later to the last node of last subpath
2896 } else {
2897 last = node->p.other;
2898 }
2899 } else {
2900 last = node->p.other;
2901 }
2902 } else {
2903 if (node->p.other) {
2904 last = node->p.other;
2905 } else {
2906 if (spl->prev) { // there's a prev subpath
2907 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2908 last = subpath_prev->last;
2909 } else if (spl->next) { // there's a next subpath
2910 last = NULL; // to be set later to the last node of last subpath
2911 } else {
2912 last = (Inkscape::NodePath::Node *) subpath->last;
2913 }
2914 }
2915 }
2916 }
2917 }
2918 }
2919 sp_nodepath_deselect(nodepath);
2920 }
2922 if (last) { // there's at least one more node before selected
2923 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2924 } else { // no more nodes, select the last one in last subpath
2925 GList *spl = g_list_last(nodepath->subpaths);
2926 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2927 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2928 }
2929 }
2931 /**
2932 * \brief Select all nodes that are within the rectangle.
2933 */
2934 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2935 {
2936 if (!incremental) {
2937 sp_nodepath_deselect(nodepath);
2938 }
2940 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2941 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2942 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2943 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2945 if (b.contains(node->pos)) {
2946 sp_nodepath_node_select(node, TRUE, TRUE);
2947 }
2948 }
2949 }
2950 }
2953 void
2954 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2955 {
2956 g_assert (n);
2957 g_assert (nodepath);
2958 g_assert (n->subpath->nodepath == nodepath);
2960 if (g_list_length (nodepath->selected) == 0) {
2961 if (grow > 0) {
2962 sp_nodepath_node_select(n, TRUE, TRUE);
2963 }
2964 return;
2965 }
2967 if (g_list_length (nodepath->selected) == 1) {
2968 if (grow < 0) {
2969 sp_nodepath_deselect (nodepath);
2970 return;
2971 }
2972 }
2974 double n_sel_range = 0, p_sel_range = 0;
2975 Inkscape::NodePath::Node *farthest_n_node = n;
2976 Inkscape::NodePath::Node *farthest_p_node = n;
2978 // Calculate ranges
2979 {
2980 double n_range = 0, p_range = 0;
2981 bool n_going = true, p_going = true;
2982 Inkscape::NodePath::Node *n_node = n;
2983 Inkscape::NodePath::Node *p_node = n;
2984 do {
2985 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2986 if (n_node && n_going)
2987 n_node = n_node->n.other;
2988 if (n_node == NULL) {
2989 n_going = false;
2990 } else {
2991 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2992 if (n_node->selected) {
2993 n_sel_range = n_range;
2994 farthest_n_node = n_node;
2995 }
2996 if (n_node == p_node) {
2997 n_going = false;
2998 p_going = false;
2999 }
3000 }
3001 if (p_node && p_going)
3002 p_node = p_node->p.other;
3003 if (p_node == NULL) {
3004 p_going = false;
3005 } else {
3006 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3007 if (p_node->selected) {
3008 p_sel_range = p_range;
3009 farthest_p_node = p_node;
3010 }
3011 if (p_node == n_node) {
3012 n_going = false;
3013 p_going = false;
3014 }
3015 }
3016 } while (n_going || p_going);
3017 }
3019 if (grow > 0) {
3020 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3021 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3022 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3023 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3024 }
3025 } else {
3026 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3027 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3028 } else if (farthest_p_node && farthest_p_node->selected) {
3029 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3030 }
3031 }
3032 }
3034 void
3035 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3036 {
3037 g_assert (n);
3038 g_assert (nodepath);
3039 g_assert (n->subpath->nodepath == nodepath);
3041 if (g_list_length (nodepath->selected) == 0) {
3042 if (grow > 0) {
3043 sp_nodepath_node_select(n, TRUE, TRUE);
3044 }
3045 return;
3046 }
3048 if (g_list_length (nodepath->selected) == 1) {
3049 if (grow < 0) {
3050 sp_nodepath_deselect (nodepath);
3051 return;
3052 }
3053 }
3055 Inkscape::NodePath::Node *farthest_selected = NULL;
3056 double farthest_dist = 0;
3058 Inkscape::NodePath::Node *closest_unselected = NULL;
3059 double closest_dist = NR_HUGE;
3061 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3062 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3063 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3064 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3065 if (node == n)
3066 continue;
3067 if (node->selected) {
3068 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3069 farthest_dist = NR::L2(node->pos - n->pos);
3070 farthest_selected = node;
3071 }
3072 } else {
3073 if (NR::L2(node->pos - n->pos) < closest_dist) {
3074 closest_dist = NR::L2(node->pos - n->pos);
3075 closest_unselected = node;
3076 }
3077 }
3078 }
3079 }
3081 if (grow > 0) {
3082 if (closest_unselected) {
3083 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3084 }
3085 } else {
3086 if (farthest_selected) {
3087 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3088 }
3089 }
3090 }
3093 /**
3094 \brief Saves all nodes' and handles' current positions in their origin members
3095 */
3096 void
3097 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3098 {
3099 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3100 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3101 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3102 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3103 n->origin = n->pos;
3104 n->p.origin = n->p.pos;
3105 n->n.origin = n->n.pos;
3106 }
3107 }
3108 }
3110 /**
3111 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3112 */
3113 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3114 {
3115 if (!nodepath->selected) {
3116 return NULL;
3117 }
3119 GList *r = NULL;
3120 guint i = 0;
3121 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3122 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3123 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3124 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3125 i++;
3126 if (node->selected) {
3127 r = g_list_append(r, GINT_TO_POINTER(i));
3128 }
3129 }
3130 }
3131 return r;
3132 }
3134 /**
3135 \brief Restores selection by selecting nodes whose positions are in the list
3136 */
3137 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3138 {
3139 sp_nodepath_deselect(nodepath);
3141 guint i = 0;
3142 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3143 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3144 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3145 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3146 i++;
3147 if (g_list_find(r, GINT_TO_POINTER(i))) {
3148 sp_nodepath_node_select(node, TRUE, TRUE);
3149 }
3150 }
3151 }
3152 }
3155 /**
3156 \brief Adjusts handle according to node type and line code.
3157 */
3158 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3159 {
3160 g_assert(node);
3162 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3163 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3165 // nothing to do if we are an end node
3166 if (me->other == NULL) return;
3167 if (other->other == NULL) return;
3169 // nothing to do if we are a cusp node
3170 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3172 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3173 NRPathcode mecode;
3174 if (which_adjust == 1) {
3175 mecode = (NRPathcode)me->other->code;
3176 } else {
3177 mecode = (NRPathcode)node->code;
3178 }
3179 if (mecode == NR_LINETO) return;
3181 if (sp_node_side_is_line(node, other)) {
3182 // other is a line, and we are either smooth or symm
3183 Inkscape::NodePath::Node *othernode = other->other;
3184 double len = NR::L2(me->pos - node->pos);
3185 NR::Point delta = node->pos - othernode->pos;
3186 double linelen = NR::L2(delta);
3187 if (linelen < 1e-18)
3188 return;
3189 me->pos = node->pos + (len / linelen)*delta;
3190 return;
3191 }
3193 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3194 // symmetrize
3195 me->pos = 2 * node->pos - other->pos;
3196 return;
3197 } else {
3198 // smoothify
3199 double len = NR::L2(me->pos - node->pos);
3200 NR::Point delta = other->pos - node->pos;
3201 double otherlen = NR::L2(delta);
3202 if (otherlen < 1e-18) return;
3203 me->pos = node->pos - (len / otherlen) * delta;
3204 }
3205 }
3207 /**
3208 \brief Adjusts both handles according to node type and line code
3209 */
3210 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3211 {
3212 g_assert(node);
3214 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3216 /* we are either smooth or symm */
3218 if (node->p.other == NULL) return;
3219 if (node->n.other == NULL) return;
3221 if (sp_node_side_is_line(node, &node->p)) {
3222 sp_node_adjust_handle(node, 1);
3223 return;
3224 }
3226 if (sp_node_side_is_line(node, &node->n)) {
3227 sp_node_adjust_handle(node, -1);
3228 return;
3229 }
3231 /* both are curves */
3232 NR::Point const delta( node->n.pos - node->p.pos );
3234 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3235 node->p.pos = node->pos - delta / 2;
3236 node->n.pos = node->pos + delta / 2;
3237 return;
3238 }
3240 /* We are smooth */
3241 double plen = NR::L2(node->p.pos - node->pos);
3242 if (plen < 1e-18) return;
3243 double nlen = NR::L2(node->n.pos - node->pos);
3244 if (nlen < 1e-18) return;
3245 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3246 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3247 }
3249 /**
3250 * Node event callback.
3251 */
3252 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3253 {
3254 gboolean ret = FALSE;
3255 switch (event->type) {
3256 case GDK_ENTER_NOTIFY:
3257 Inkscape::NodePath::Path::active_node = n;
3258 break;
3259 case GDK_LEAVE_NOTIFY:
3260 Inkscape::NodePath::Path::active_node = NULL;
3261 break;
3262 case GDK_SCROLL:
3263 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3264 switch (event->scroll.direction) {
3265 case GDK_SCROLL_UP:
3266 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3267 break;
3268 case GDK_SCROLL_DOWN:
3269 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3270 break;
3271 default:
3272 break;
3273 }
3274 ret = TRUE;
3275 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3276 switch (event->scroll.direction) {
3277 case GDK_SCROLL_UP:
3278 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3279 break;
3280 case GDK_SCROLL_DOWN:
3281 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3282 break;
3283 default:
3284 break;
3285 }
3286 ret = TRUE;
3287 }
3288 break;
3289 case GDK_KEY_PRESS:
3290 switch (get_group0_keyval (&event->key)) {
3291 case GDK_space:
3292 if (event->key.state & GDK_BUTTON1_MASK) {
3293 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3294 stamp_repr(nodepath);
3295 ret = TRUE;
3296 }
3297 break;
3298 case GDK_Page_Up:
3299 if (event->key.state & GDK_CONTROL_MASK) {
3300 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3301 } else {
3302 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3303 }
3304 break;
3305 case GDK_Page_Down:
3306 if (event->key.state & GDK_CONTROL_MASK) {
3307 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3308 } else {
3309 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3310 }
3311 break;
3312 default:
3313 break;
3314 }
3315 break;
3316 default:
3317 break;
3318 }
3320 return ret;
3321 }
3323 /**
3324 * Handle keypress on node; directly called.
3325 */
3326 gboolean node_key(GdkEvent *event)
3327 {
3328 Inkscape::NodePath::Path *np;
3330 // there is no way to verify nodes so set active_node to nil when deleting!!
3331 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3333 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3334 gint ret = FALSE;
3335 switch (get_group0_keyval (&event->key)) {
3336 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3337 case GDK_BackSpace:
3338 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3339 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3340 sp_nodepath_update_repr(np, _("Delete node"));
3341 Inkscape::NodePath::Path::active_node = NULL;
3342 ret = TRUE;
3343 break;
3344 case GDK_c:
3345 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3346 ret = TRUE;
3347 break;
3348 case GDK_s:
3349 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3350 ret = TRUE;
3351 break;
3352 case GDK_y:
3353 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3354 ret = TRUE;
3355 break;
3356 case GDK_b:
3357 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3358 ret = TRUE;
3359 break;
3360 }
3361 return ret;
3362 }
3363 return FALSE;
3364 }
3366 /**
3367 * Mouseclick on node callback.
3368 */
3369 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3370 {
3371 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3373 if (state & GDK_CONTROL_MASK) {
3374 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3376 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3377 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3378 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3379 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3380 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3381 } else {
3382 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3383 }
3384 sp_nodepath_update_repr(nodepath, _("Change node type"));
3385 sp_nodepath_update_statusbar(nodepath);
3387 } else { //ctrl+alt+click: delete node
3388 GList *node_to_delete = NULL;
3389 node_to_delete = g_list_append(node_to_delete, n);
3390 sp_node_delete_preserve(node_to_delete);
3391 }
3393 } else {
3394 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3395 }
3396 }
3398 /**
3399 * Mouse grabbed node callback.
3400 */
3401 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3402 {
3403 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3405 if (!n->selected) {
3406 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3407 }
3409 n->is_dragging = true;
3410 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3412 sp_nodepath_remember_origins (n->subpath->nodepath);
3413 }
3415 /**
3416 * Mouse ungrabbed node callback.
3417 */
3418 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3419 {
3420 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3422 n->dragging_out = NULL;
3423 n->is_dragging = false;
3424 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3426 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3427 }
3429 /**
3430 * The point on a line, given by its angle, closest to the given point.
3431 * \param p A point.
3432 * \param a Angle of the line; it is assumed to go through coordinate origin.
3433 * \param closest Pointer to the point struct where the result is stored.
3434 * \todo FIXME: use dot product perhaps?
3435 */
3436 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3437 {
3438 if (a == HUGE_VAL) { // vertical
3439 *closest = NR::Point(0, (*p)[NR::Y]);
3440 } else {
3441 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3442 (*closest)[NR::Y] = a * (*closest)[NR::X];
3443 }
3444 }
3446 /**
3447 * Distance from the point to a line given by its angle.
3448 * \param p A point.
3449 * \param a Angle of the line; it is assumed to go through coordinate origin.
3450 */
3451 static double point_line_distance(NR::Point *p, double a)
3452 {
3453 NR::Point c;
3454 point_line_closest(p, a, &c);
3455 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]));
3456 }
3458 /**
3459 * Callback for node "request" signal.
3460 * \todo fixme: This goes to "moved" event? (lauris)
3461 */
3462 static gboolean
3463 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3464 {
3465 double yn, xn, yp, xp;
3466 double an, ap, na, pa;
3467 double d_an, d_ap, d_na, d_pa;
3468 gboolean collinear = FALSE;
3469 NR::Point c;
3470 NR::Point pr;
3472 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3474 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3476 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3477 if ( (!n->subpath->nodepath->straight_path) &&
3478 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3479 || n->dragging_out ) )
3480 {
3481 NR::Point mouse = (*p);
3483 if (!n->dragging_out) {
3484 // This is the first drag-out event; find out which handle to drag out
3485 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3486 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3488 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3489 return FALSE;
3491 Inkscape::NodePath::NodeSide *opposite;
3492 if (appr_p > appr_n) { // closer to p
3493 n->dragging_out = &n->p;
3494 opposite = &n->n;
3495 n->code = NR_CURVETO;
3496 } else if (appr_p < appr_n) { // closer to n
3497 n->dragging_out = &n->n;
3498 opposite = &n->p;
3499 n->n.other->code = NR_CURVETO;
3500 } else { // p and n nodes are the same
3501 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3502 n->dragging_out = &n->p;
3503 opposite = &n->n;
3504 n->code = NR_CURVETO;
3505 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3506 n->dragging_out = &n->n;
3507 opposite = &n->p;
3508 n->n.other->code = NR_CURVETO;
3509 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3510 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);
3511 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);
3512 if (appr_other_p > appr_other_n) { // closer to other's p handle
3513 n->dragging_out = &n->n;
3514 opposite = &n->p;
3515 n->n.other->code = NR_CURVETO;
3516 } else { // closer to other's n handle
3517 n->dragging_out = &n->p;
3518 opposite = &n->n;
3519 n->code = NR_CURVETO;
3520 }
3521 }
3522 }
3524 // if there's another handle, make sure the one we drag out starts parallel to it
3525 if (opposite->pos != n->pos) {
3526 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3527 }
3529 // knots might not be created yet!
3530 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3531 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3532 }
3534 // pass this on to the handle-moved callback
3535 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3536 sp_node_update_handles(n);
3537 return TRUE;
3538 }
3540 if (state & GDK_CONTROL_MASK) { // constrained motion
3542 // calculate relative distances of handles
3543 // n handle:
3544 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3545 xn = n->n.pos[NR::X] - n->pos[NR::X];
3546 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3547 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3548 if (n->n.other) { // if there is the next point
3549 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3550 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3551 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3552 }
3553 }
3554 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3555 if (yn < 0) { xn = -xn; yn = -yn; }
3557 // p handle:
3558 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3559 xp = n->p.pos[NR::X] - n->pos[NR::X];
3560 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3561 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3562 if (n->p.other) {
3563 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3564 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3565 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3566 }
3567 }
3568 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3569 if (yp < 0) { xp = -xp; yp = -yp; }
3571 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3572 // sliding on handles, only if at least one of the handles is non-vertical
3573 // (otherwise it's the same as ctrl+drag anyway)
3575 // calculate angles of the handles
3576 if (xn == 0) {
3577 if (yn == 0) { // no handle, consider it the continuation of the other one
3578 an = 0;
3579 collinear = TRUE;
3580 }
3581 else an = 0; // vertical; set the angle to horizontal
3582 } else an = yn/xn;
3584 if (xp == 0) {
3585 if (yp == 0) { // no handle, consider it the continuation of the other one
3586 ap = an;
3587 }
3588 else ap = 0; // vertical; set the angle to horizontal
3589 } else ap = yp/xp;
3591 if (collinear) an = ap;
3593 // angles of the perpendiculars; HUGE_VAL means vertical
3594 if (an == 0) na = HUGE_VAL; else na = -1/an;
3595 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3597 // mouse point relative to the node's original pos
3598 pr = (*p) - n->origin;
3600 // distances to the four lines (two handles and two perpendiculars)
3601 d_an = point_line_distance(&pr, an);
3602 d_na = point_line_distance(&pr, na);
3603 d_ap = point_line_distance(&pr, ap);
3604 d_pa = point_line_distance(&pr, pa);
3606 // find out which line is the closest, save its closest point in c
3607 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3608 point_line_closest(&pr, an, &c);
3609 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3610 point_line_closest(&pr, ap, &c);
3611 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3612 point_line_closest(&pr, na, &c);
3613 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3614 point_line_closest(&pr, pa, &c);
3615 }
3617 // move the node to the closest point
3618 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3619 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3620 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3621 true);
3623 } else { // constraining to hor/vert
3625 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3626 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3627 (*p)[NR::X] - n->pos[NR::X],
3628 n->origin[NR::Y] - n->pos[NR::Y],
3629 true,
3630 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3631 } else { // snap to vert
3632 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3633 n->origin[NR::X] - n->pos[NR::X],
3634 (*p)[NR::Y] - n->pos[NR::Y],
3635 true,
3636 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3637 }
3638 }
3639 } else { // move freely
3640 if (n->is_dragging) {
3641 if (state & GDK_MOD1_MASK) { // sculpt
3642 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3643 } else {
3644 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3645 (*p)[NR::X] - n->pos[NR::X],
3646 (*p)[NR::Y] - n->pos[NR::Y],
3647 (state & GDK_SHIFT_MASK) == 0);
3648 }
3649 }
3650 }
3652 n->subpath->nodepath->desktop->scroll_to_point(p);
3654 return TRUE;
3655 }
3657 /**
3658 * Node handle clicked callback.
3659 */
3660 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3661 {
3662 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3664 if (state & GDK_CONTROL_MASK) { // "delete" handle
3665 if (n->p.knot == knot) {
3666 n->p.pos = n->pos;
3667 } else if (n->n.knot == knot) {
3668 n->n.pos = n->pos;
3669 }
3670 sp_node_update_handles(n);
3671 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3672 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3673 sp_nodepath_update_statusbar(nodepath);
3675 } else { // just select or add to selection, depending in Shift
3676 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3677 }
3678 }
3680 /**
3681 * Node handle grabbed callback.
3682 */
3683 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3684 {
3685 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3687 if (!n->selected) {
3688 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3689 }
3691 // remember the origin point of the handle
3692 if (n->p.knot == knot) {
3693 n->p.origin_radial = n->p.pos - n->pos;
3694 } else if (n->n.knot == knot) {
3695 n->n.origin_radial = n->n.pos - n->pos;
3696 } else {
3697 g_assert_not_reached();
3698 }
3700 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3701 }
3703 /**
3704 * Node handle ungrabbed callback.
3705 */
3706 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3707 {
3708 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3710 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3711 if (n->p.knot == knot) {
3712 n->p.origin_radial.a = 0;
3713 sp_knot_set_position(knot, &n->p.pos, state);
3714 } else if (n->n.knot == knot) {
3715 n->n.origin_radial.a = 0;
3716 sp_knot_set_position(knot, &n->n.pos, state);
3717 } else {
3718 g_assert_not_reached();
3719 }
3721 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3722 }
3724 /**
3725 * Node handle "request" signal callback.
3726 */
3727 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3728 {
3729 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3731 Inkscape::NodePath::NodeSide *me, *opposite;
3732 gint which;
3733 if (n->p.knot == knot) {
3734 me = &n->p;
3735 opposite = &n->n;
3736 which = -1;
3737 } else if (n->n.knot == knot) {
3738 me = &n->n;
3739 opposite = &n->p;
3740 which = 1;
3741 } else {
3742 me = opposite = NULL;
3743 which = 0;
3744 g_assert_not_reached();
3745 }
3747 SPDesktop *desktop = n->subpath->nodepath->desktop;
3748 SnapManager &m = desktop->namedview->snap_manager;
3749 m.setup(desktop, n->subpath->nodepath->item);
3750 Inkscape::SnappedPoint s;
3752 if ((state & GDK_SHIFT_MASK) != 0) {
3753 // We will not try to snap when the shift-key is pressed
3754 // so remove the old snap indicator and don't wait for it to time-out
3755 desktop->snapindicator->remove_snappoint();
3756 }
3758 Inkscape::NodePath::Node *othernode = opposite->other;
3759 if (othernode) {
3760 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3761 /* We are smooth node adjacent with line */
3762 NR::Point const delta = *p - n->pos;
3763 NR::Coord const len = NR::L2(delta);
3764 Inkscape::NodePath::Node *othernode = opposite->other;
3765 NR::Point const ndelta = n->pos - othernode->pos;
3766 NR::Coord const linelen = NR::L2(ndelta);
3767 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3768 NR::Coord const scal = dot(delta, ndelta) / linelen;
3769 (*p) = n->pos + (scal / linelen) * ndelta;
3770 }
3771 if ((state & GDK_SHIFT_MASK) == 0) {
3772 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3773 }
3774 } else {
3775 if ((state & GDK_SHIFT_MASK) == 0) {
3776 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3777 }
3778 }
3779 } else {
3780 if ((state & GDK_SHIFT_MASK) == 0) {
3781 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3782 }
3783 }
3785 s.getPoint(*p);
3787 sp_node_adjust_handle(n, -which);
3789 return FALSE;
3790 }
3792 /**
3793 * Node handle moved callback.
3794 */
3795 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3796 {
3797 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3799 Inkscape::NodePath::NodeSide *me;
3800 Inkscape::NodePath::NodeSide *other;
3801 if (n->p.knot == knot) {
3802 me = &n->p;
3803 other = &n->n;
3804 } else if (n->n.knot == knot) {
3805 me = &n->n;
3806 other = &n->p;
3807 } else {
3808 me = NULL;
3809 other = NULL;
3810 g_assert_not_reached();
3811 }
3813 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3814 Radial rme(me->pos - n->pos);
3815 Radial rother(other->pos - n->pos);
3816 Radial rnew(*p - n->pos);
3818 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3819 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3820 /* 0 interpreted as "no snapping". */
3822 // 1. Snap to the closest PI/snaps angle, starting from zero.
3823 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3825 // 2. Snap to the original angle, its opposite and perpendiculars
3826 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3827 /* The closest PI/2 angle, starting from original angle */
3828 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3830 // Snap to the closest.
3831 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3832 ? a_snapped
3833 : a_ortho );
3834 }
3836 // 3. Snap to the angle of the opposite line, if any
3837 Inkscape::NodePath::Node *othernode = other->other;
3838 if (othernode) {
3839 NR::Point other_to_snap(0,0);
3840 if (sp_node_side_is_line(n, other)) {
3841 other_to_snap = othernode->pos - n->pos;
3842 } else {
3843 other_to_snap = other->pos - n->pos;
3844 }
3845 if (NR::L2(other_to_snap) > 1e-3) {
3846 Radial rother_to_snap(other_to_snap);
3847 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3848 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3850 // Snap to the closest.
3851 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3852 ? a_snapped
3853 : a_oppo );
3854 }
3855 }
3857 rnew.a = a_snapped;
3858 }
3860 if (state & GDK_MOD1_MASK) {
3861 // lock handle length
3862 rnew.r = me->origin_radial.r;
3863 }
3865 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3866 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3867 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3868 rother.a += rnew.a - rme.a;
3869 other->pos = NR::Point(rother) + n->pos;
3870 if (other->knot) {
3871 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3872 sp_knot_moveto(other->knot, &other->pos);
3873 }
3874 }
3876 me->pos = NR::Point(rnew) + n->pos;
3877 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3879 // move knot, but without emitting the signal:
3880 // we cannot emit a "moved" signal because we're now processing it
3881 sp_knot_moveto(me->knot, &(me->pos));
3883 update_object(n->subpath->nodepath);
3885 /* status text */
3886 SPDesktop *desktop = n->subpath->nodepath->desktop;
3887 if (!desktop) return;
3888 SPEventContext *ec = desktop->event_context;
3889 if (!ec) return;
3890 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3891 if (!mc) return;
3893 double degrees = 180 / M_PI * rnew.a;
3894 if (degrees > 180) degrees -= 360;
3895 if (degrees < -180) degrees += 360;
3896 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3897 degrees = angle_to_compass (degrees);
3899 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3901 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3902 _("<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);
3904 g_string_free(length, TRUE);
3905 }
3907 /**
3908 * Node handle event callback.
3909 */
3910 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3911 {
3912 gboolean ret = FALSE;
3913 switch (event->type) {
3914 case GDK_KEY_PRESS:
3915 switch (get_group0_keyval (&event->key)) {
3916 case GDK_space:
3917 if (event->key.state & GDK_BUTTON1_MASK) {
3918 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3919 stamp_repr(nodepath);
3920 ret = TRUE;
3921 }
3922 break;
3923 default:
3924 break;
3925 }
3926 break;
3927 case GDK_ENTER_NOTIFY:
3928 // we use an experimentally determined threshold that seems to work fine
3929 if (NR::L2(n->pos - knot->pos) < 0.75)
3930 Inkscape::NodePath::Path::active_node = n;
3931 break;
3932 case GDK_LEAVE_NOTIFY:
3933 // we use an experimentally determined threshold that seems to work fine
3934 if (NR::L2(n->pos - knot->pos) < 0.75)
3935 Inkscape::NodePath::Path::active_node = NULL;
3936 break;
3937 default:
3938 break;
3939 }
3941 return ret;
3942 }
3944 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3945 Radial &rme, Radial &rother, gboolean const both)
3946 {
3947 rme.a += angle;
3948 if ( both
3949 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3950 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3951 {
3952 rother.a += angle;
3953 }
3954 }
3956 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3957 Radial &rme, Radial &rother, gboolean const both)
3958 {
3959 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3961 gdouble r;
3962 if ( both
3963 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3964 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3965 {
3966 r = MAX(rme.r, rother.r);
3967 } else {
3968 r = rme.r;
3969 }
3971 gdouble const weird_angle = atan2(norm_angle, r);
3972 /* Bulia says norm_angle is just the visible distance that the
3973 * object's end must travel on the screen. Left as 'angle' for want of
3974 * a better name.*/
3976 rme.a += weird_angle;
3977 if ( both
3978 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3979 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3980 {
3981 rother.a += weird_angle;
3982 }
3983 }
3985 /**
3986 * Rotate one node.
3987 */
3988 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3989 {
3990 Inkscape::NodePath::NodeSide *me, *other;
3991 bool both = false;
3993 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3994 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3996 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3997 me = &(n->p);
3998 other = &(n->n);
3999 } else if (!n->p.other) {
4000 me = &(n->n);
4001 other = &(n->p);
4002 } else {
4003 if (which > 0) { // right 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 if (which < 0){ // left handle
4012 if (xn <= xp) {
4013 me = &(n->n);
4014 other = &(n->p);
4015 } else {
4016 me = &(n->p);
4017 other = &(n->n);
4018 }
4019 } else { // both handles
4020 me = &(n->n);
4021 other = &(n->p);
4022 both = true;
4023 }
4024 }
4026 Radial rme(me->pos - n->pos);
4027 Radial rother(other->pos - n->pos);
4029 if (screen) {
4030 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4031 } else {
4032 node_rotate_one_internal (*n, angle, rme, rother, both);
4033 }
4035 me->pos = n->pos + NR::Point(rme);
4037 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4038 other->pos = n->pos + NR::Point(rother);
4039 }
4041 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4042 // so here we just move all the knots without emitting move signals, for speed
4043 sp_node_update_handles(n, false);
4044 }
4046 /**
4047 * Rotate selected nodes.
4048 */
4049 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4050 {
4051 if (!nodepath || !nodepath->selected) return;
4053 if (g_list_length(nodepath->selected) == 1) {
4054 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4055 node_rotate_one (n, angle, which, screen);
4056 } else {
4057 // rotate as an object:
4059 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4060 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4061 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4062 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4063 box.expandTo (n->pos); // contain all selected nodes
4064 }
4066 gdouble rot;
4067 if (screen) {
4068 gdouble const zoom = nodepath->desktop->current_zoom();
4069 gdouble const zmove = angle / zoom;
4070 gdouble const r = NR::L2(box.max() - box.midpoint());
4071 rot = atan2(zmove, r);
4072 } else {
4073 rot = angle;
4074 }
4076 NR::Point rot_center;
4077 if (Inkscape::NodePath::Path::active_node == NULL)
4078 rot_center = box.midpoint();
4079 else
4080 rot_center = Inkscape::NodePath::Path::active_node->pos;
4082 NR::Matrix t =
4083 NR::Matrix (NR::translate(-rot_center)) *
4084 NR::Matrix (NR::rotate(rot)) *
4085 NR::Matrix (NR::translate(rot_center));
4087 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4088 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4089 n->pos *= t;
4090 n->n.pos *= t;
4091 n->p.pos *= t;
4092 sp_node_update_handles(n, false);
4093 }
4094 }
4096 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4097 }
4099 /**
4100 * Scale one node.
4101 */
4102 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4103 {
4104 bool both = false;
4105 Inkscape::NodePath::NodeSide *me, *other;
4107 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4108 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4110 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4111 me = &(n->p);
4112 other = &(n->n);
4113 n->code = NR_CURVETO;
4114 } else if (!n->p.other) {
4115 me = &(n->n);
4116 other = &(n->p);
4117 if (n->n.other)
4118 n->n.other->code = NR_CURVETO;
4119 } else {
4120 if (which > 0) { // right handle
4121 if (xn > xp) {
4122 me = &(n->n);
4123 other = &(n->p);
4124 if (n->n.other)
4125 n->n.other->code = NR_CURVETO;
4126 } else {
4127 me = &(n->p);
4128 other = &(n->n);
4129 n->code = NR_CURVETO;
4130 }
4131 } else if (which < 0){ // left handle
4132 if (xn <= xp) {
4133 me = &(n->n);
4134 other = &(n->p);
4135 if (n->n.other)
4136 n->n.other->code = NR_CURVETO;
4137 } else {
4138 me = &(n->p);
4139 other = &(n->n);
4140 n->code = NR_CURVETO;
4141 }
4142 } else { // both handles
4143 me = &(n->n);
4144 other = &(n->p);
4145 both = true;
4146 n->code = NR_CURVETO;
4147 if (n->n.other)
4148 n->n.other->code = NR_CURVETO;
4149 }
4150 }
4152 Radial rme(me->pos - n->pos);
4153 Radial rother(other->pos - n->pos);
4155 rme.r += grow;
4156 if (rme.r < 0) rme.r = 0;
4157 if (rme.a == HUGE_VAL) {
4158 if (me->other) { // if direction is unknown, initialize it towards the next node
4159 Radial rme_next(me->other->pos - n->pos);
4160 rme.a = rme_next.a;
4161 } else { // if there's no next, initialize to 0
4162 rme.a = 0;
4163 }
4164 }
4165 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4166 rother.r += grow;
4167 if (rother.r < 0) rother.r = 0;
4168 if (rother.a == HUGE_VAL) {
4169 rother.a = rme.a + M_PI;
4170 }
4171 }
4173 me->pos = n->pos + NR::Point(rme);
4175 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4176 other->pos = n->pos + NR::Point(rother);
4177 }
4179 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4180 // so here we just move all the knots without emitting move signals, for speed
4181 sp_node_update_handles(n, false);
4182 }
4184 /**
4185 * Scale selected nodes.
4186 */
4187 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4188 {
4189 if (!nodepath || !nodepath->selected) return;
4191 if (g_list_length(nodepath->selected) == 1) {
4192 // scale handles of the single selected node
4193 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4194 node_scale_one (n, grow, which);
4195 } else {
4196 // scale nodes as an "object":
4198 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4199 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4200 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4201 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4202 box.expandTo (n->pos); // contain all selected nodes
4203 }
4205 double scale = (box.maxExtent() + grow)/box.maxExtent();
4207 NR::Point scale_center;
4208 if (Inkscape::NodePath::Path::active_node == NULL)
4209 scale_center = box.midpoint();
4210 else
4211 scale_center = Inkscape::NodePath::Path::active_node->pos;
4213 NR::Matrix t =
4214 NR::Matrix (NR::translate(-scale_center)) *
4215 NR::Matrix (NR::scale(scale, scale)) *
4216 NR::Matrix (NR::translate(scale_center));
4218 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4219 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4220 n->pos *= t;
4221 n->n.pos *= t;
4222 n->p.pos *= t;
4223 sp_node_update_handles(n, false);
4224 }
4225 }
4227 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4228 }
4230 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4231 {
4232 if (!nodepath) return;
4233 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4234 }
4236 /**
4237 * Flip selected nodes horizontally/vertically.
4238 */
4239 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4240 {
4241 if (!nodepath || !nodepath->selected) return;
4243 if (g_list_length(nodepath->selected) == 1 && !center) {
4244 // flip handles of the single selected node
4245 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4246 double temp = n->p.pos[axis];
4247 n->p.pos[axis] = n->n.pos[axis];
4248 n->n.pos[axis] = temp;
4249 sp_node_update_handles(n, false);
4250 } else {
4251 // scale nodes as an "object":
4253 NR::Rect box = sp_node_selected_bbox (nodepath);
4254 if (!center) {
4255 center = box.midpoint();
4256 }
4257 NR::Matrix t =
4258 NR::Matrix (NR::translate(- *center)) *
4259 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4260 NR::Matrix (NR::translate(*center));
4262 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4263 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4264 n->pos *= t;
4265 n->n.pos *= t;
4266 n->p.pos *= t;
4267 sp_node_update_handles(n, false);
4268 }
4269 }
4271 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4272 }
4274 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4275 {
4276 g_assert (nodepath->selected);
4278 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4279 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4280 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4281 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4282 box.expandTo (n->pos); // contain all selected nodes
4283 }
4284 return box;
4285 }
4287 //-----------------------------------------------
4288 /**
4289 * Return new subpath under given nodepath.
4290 */
4291 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4292 {
4293 g_assert(nodepath);
4294 g_assert(nodepath->desktop);
4296 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4298 s->nodepath = nodepath;
4299 s->closed = FALSE;
4300 s->nodes = NULL;
4301 s->first = NULL;
4302 s->last = NULL;
4304 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4305 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4306 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4308 return s;
4309 }
4311 /**
4312 * Destroy nodes in subpath, then subpath itself.
4313 */
4314 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4315 {
4316 g_assert(subpath);
4317 g_assert(subpath->nodepath);
4318 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4320 while (subpath->nodes) {
4321 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4322 }
4324 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4326 g_free(subpath);
4327 }
4329 /**
4330 * Link head to tail in subpath.
4331 */
4332 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4333 {
4334 g_assert(!sp->closed);
4335 g_assert(sp->last != sp->first);
4336 g_assert(sp->first->code == NR_MOVETO);
4338 sp->closed = TRUE;
4340 //Link the head to the tail
4341 sp->first->p.other = sp->last;
4342 sp->last->n.other = sp->first;
4343 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4344 sp->first = sp->last;
4346 //Remove the extra end node
4347 sp_nodepath_node_destroy(sp->last->n.other);
4348 }
4350 /**
4351 * Open closed (loopy) subpath at node.
4352 */
4353 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4354 {
4355 g_assert(sp->closed);
4356 g_assert(n->subpath == sp);
4357 g_assert(sp->first == sp->last);
4359 /* We create new startpoint, current node will become last one */
4361 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4362 &n->pos, &n->pos, &n->n.pos);
4365 sp->closed = FALSE;
4367 //Unlink to make a head and tail
4368 sp->first = new_path;
4369 sp->last = n;
4370 n->n.other = NULL;
4371 new_path->p.other = NULL;
4372 }
4374 /**
4375 * Return new node in subpath with given properties.
4376 * \param pos Position of node.
4377 * \param ppos Handle position in previous direction
4378 * \param npos Handle position in previous direction
4379 */
4380 Inkscape::NodePath::Node *
4381 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)
4382 {
4383 g_assert(sp);
4384 g_assert(sp->nodepath);
4385 g_assert(sp->nodepath->desktop);
4387 if (nodechunk == NULL)
4388 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4390 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4392 n->subpath = sp;
4394 if (type != Inkscape::NodePath::NODE_NONE) {
4395 // use the type from sodipodi:nodetypes
4396 n->type = type;
4397 } else {
4398 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4399 // points are (almost) collinear
4400 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4401 // endnode, or a node with a retracted handle
4402 n->type = Inkscape::NodePath::NODE_CUSP;
4403 } else {
4404 n->type = Inkscape::NodePath::NODE_SMOOTH;
4405 }
4406 } else {
4407 n->type = Inkscape::NodePath::NODE_CUSP;
4408 }
4409 }
4411 n->code = code;
4412 n->selected = FALSE;
4413 n->pos = *pos;
4414 n->p.pos = *ppos;
4415 n->n.pos = *npos;
4417 n->dragging_out = NULL;
4419 Inkscape::NodePath::Node *prev;
4420 if (next) {
4421 //g_assert(g_list_find(sp->nodes, next));
4422 prev = next->p.other;
4423 } else {
4424 prev = sp->last;
4425 }
4427 if (prev)
4428 prev->n.other = n;
4429 else
4430 sp->first = n;
4432 if (next)
4433 next->p.other = n;
4434 else
4435 sp->last = n;
4437 n->p.other = prev;
4438 n->n.other = next;
4440 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"));
4441 sp_knot_set_position(n->knot, pos, 0);
4443 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4444 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4445 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4446 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4447 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4448 sp_knot_update_ctrl(n->knot);
4450 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4451 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4452 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4453 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4454 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4455 sp_knot_show(n->knot);
4457 // We only create handle knots and lines on demand
4458 n->p.knot = NULL;
4459 n->p.line = NULL;
4460 n->n.knot = NULL;
4461 n->n.line = NULL;
4463 sp->nodes = g_list_prepend(sp->nodes, n);
4465 return n;
4466 }
4468 /**
4469 * Destroy node and its knots, link neighbors in subpath.
4470 */
4471 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4472 {
4473 g_assert(node);
4474 g_assert(node->subpath);
4475 g_assert(SP_IS_KNOT(node->knot));
4477 Inkscape::NodePath::SubPath *sp = node->subpath;
4479 if (node->selected) { // first, deselect
4480 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4481 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4482 }
4484 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4486 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4487 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4488 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4489 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4490 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4491 g_object_unref(G_OBJECT(node->knot));
4493 if (node->p.knot) {
4494 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4495 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4496 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4497 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4498 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4499 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4500 g_object_unref(G_OBJECT(node->p.knot));
4501 node->p.knot = NULL;
4502 }
4504 if (node->n.knot) {
4505 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4506 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4507 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4508 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4509 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4510 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4511 g_object_unref(G_OBJECT(node->n.knot));
4512 node->n.knot = NULL;
4513 }
4515 if (node->p.line)
4516 gtk_object_destroy(GTK_OBJECT(node->p.line));
4517 if (node->n.line)
4518 gtk_object_destroy(GTK_OBJECT(node->n.line));
4520 if (sp->nodes) { // there are others nodes on the subpath
4521 if (sp->closed) {
4522 if (sp->first == node) {
4523 g_assert(sp->last == node);
4524 sp->first = node->n.other;
4525 sp->last = sp->first;
4526 }
4527 node->p.other->n.other = node->n.other;
4528 node->n.other->p.other = node->p.other;
4529 } else {
4530 if (sp->first == node) {
4531 sp->first = node->n.other;
4532 sp->first->code = NR_MOVETO;
4533 }
4534 if (sp->last == node) sp->last = node->p.other;
4535 if (node->p.other) node->p.other->n.other = node->n.other;
4536 if (node->n.other) node->n.other->p.other = node->p.other;
4537 }
4538 } else { // this was the last node on subpath
4539 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4540 }
4542 g_mem_chunk_free(nodechunk, node);
4543 }
4545 /**
4546 * Returns one of the node's two sides.
4547 * \param which Indicates which side.
4548 * \return Pointer to previous node side if which==-1, next if which==1.
4549 */
4550 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4551 {
4552 g_assert(node);
4554 switch (which) {
4555 case -1:
4556 return &node->p;
4557 case 1:
4558 return &node->n;
4559 default:
4560 break;
4561 }
4563 g_assert_not_reached();
4565 return NULL;
4566 }
4568 /**
4569 * Return the other side of the node, given one of its sides.
4570 */
4571 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4572 {
4573 g_assert(node);
4575 if (me == &node->p) return &node->n;
4576 if (me == &node->n) return &node->p;
4578 g_assert_not_reached();
4580 return NULL;
4581 }
4583 /**
4584 * Return NRPathcode on the given side of the node.
4585 */
4586 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4587 {
4588 g_assert(node);
4590 if (me == &node->p) {
4591 if (node->p.other) return (NRPathcode)node->code;
4592 return NR_MOVETO;
4593 }
4595 if (me == &node->n) {
4596 if (node->n.other) return (NRPathcode)node->n.other->code;
4597 return NR_MOVETO;
4598 }
4600 g_assert_not_reached();
4602 return NR_END;
4603 }
4605 /**
4606 * Return node with the given index
4607 */
4608 Inkscape::NodePath::Node *
4609 sp_nodepath_get_node_by_index(int index)
4610 {
4611 Inkscape::NodePath::Node *e = NULL;
4613 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4614 if (!nodepath) {
4615 return e;
4616 }
4618 //find segment
4619 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4621 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4622 int n = g_list_length(sp->nodes);
4623 if (sp->closed) {
4624 n++;
4625 }
4627 //if the piece belongs to this subpath grab it
4628 //otherwise move onto the next subpath
4629 if (index < n) {
4630 e = sp->first;
4631 for (int i = 0; i < index; ++i) {
4632 e = e->n.other;
4633 }
4634 break;
4635 } else {
4636 if (sp->closed) {
4637 index -= (n+1);
4638 } else {
4639 index -= n;
4640 }
4641 }
4642 }
4644 return e;
4645 }
4647 /**
4648 * Returns plain text meaning of node type.
4649 */
4650 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4651 {
4652 unsigned retracted = 0;
4653 bool endnode = false;
4655 for (int which = -1; which <= 1; which += 2) {
4656 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4657 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4658 retracted ++;
4659 if (!side->other)
4660 endnode = true;
4661 }
4663 if (retracted == 0) {
4664 if (endnode) {
4665 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4666 return _("end node");
4667 } else {
4668 switch (node->type) {
4669 case Inkscape::NodePath::NODE_CUSP:
4670 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4671 return _("cusp");
4672 case Inkscape::NodePath::NODE_SMOOTH:
4673 // TRANSLATORS: "smooth" is an adjective here
4674 return _("smooth");
4675 case Inkscape::NodePath::NODE_SYMM:
4676 return _("symmetric");
4677 }
4678 }
4679 } else if (retracted == 1) {
4680 if (endnode) {
4681 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4682 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4683 } else {
4684 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4685 }
4686 } else {
4687 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4688 }
4690 return NULL;
4691 }
4693 /**
4694 * Handles content of statusbar as long as node tool is active.
4695 */
4696 void
4697 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4698 {
4699 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");
4700 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4702 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4703 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4704 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4705 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4707 SPDesktop *desktop = NULL;
4708 if (nodepath) {
4709 desktop = nodepath->desktop;
4710 } else {
4711 desktop = SP_ACTIVE_DESKTOP;
4712 }
4714 SPEventContext *ec = desktop->event_context;
4715 if (!ec) return;
4716 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4717 if (!mc) return;
4719 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4721 if (selected_nodes == 0) {
4722 Inkscape::Selection *sel = desktop->selection;
4723 if (!sel || sel->isEmpty()) {
4724 mc->setF(Inkscape::NORMAL_MESSAGE,
4725 _("Select a single object to edit its nodes or handles."));
4726 } else {
4727 if (nodepath) {
4728 mc->setF(Inkscape::NORMAL_MESSAGE,
4729 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.",
4730 "<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.",
4731 total_nodes),
4732 total_nodes);
4733 } else {
4734 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4735 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4736 } else {
4737 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4738 }
4739 }
4740 }
4741 } else if (nodepath && selected_nodes == 1) {
4742 mc->setF(Inkscape::NORMAL_MESSAGE,
4743 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4744 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4745 total_nodes),
4746 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4747 } else {
4748 if (selected_subpaths > 1) {
4749 mc->setF(Inkscape::NORMAL_MESSAGE,
4750 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4751 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4752 total_nodes),
4753 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4754 } else {
4755 mc->setF(Inkscape::NORMAL_MESSAGE,
4756 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4757 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4758 total_nodes),
4759 selected_nodes, total_nodes, when_selected);
4760 }
4761 }
4762 }
4764 /*
4765 * returns a *copy* of the curve of that object.
4766 */
4767 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4768 if (!object)
4769 return NULL;
4771 SPCurve *curve = NULL;
4772 if (SP_IS_PATH(object)) {
4773 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4774 curve = curve_new->copy();
4775 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4776 const gchar *svgd = object->repr->attribute(key);
4777 if (svgd) {
4778 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4779 SPCurve *curve_new = new SPCurve(pv);
4780 if (curve_new) {
4781 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4782 }
4783 }
4784 }
4786 return curve;
4787 }
4789 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4790 if (!np || !np->object || !curve)
4791 return;
4793 if (SP_IS_PATH(np->object)) {
4794 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4795 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4796 } else {
4797 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4798 }
4799 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4800 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4801 if (pathparam) {
4802 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4803 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4804 }
4805 }
4806 }
4808 SPCanvasItem *
4809 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4810 SPCurve *flash_curve = curve->copy();
4811 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4812 flash_curve->transform(i2d);
4813 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4814 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4815 // unless we also flash the nodes...
4816 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4817 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4818 sp_canvas_item_show(canvasitem);
4819 flash_curve->unref();
4820 return canvasitem;
4821 }
4823 SPCanvasItem *
4824 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4825 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4826 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4827 }
4829 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4830 np->show_helperpath = show;
4832 if (show) {
4833 SPCurve *helper_curve = np->curve->copy();
4834 helper_curve->transform(to_2geom(np->i2d));
4835 if (!np->helper_path) {
4836 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4837 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);
4838 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4839 sp_canvas_item_move_to_z(np->helper_path, 0);
4840 sp_canvas_item_show(np->helper_path);
4841 } else {
4842 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4843 }
4844 helper_curve->unref();
4845 } else {
4846 if (np->helper_path) {
4847 GtkObject *temp = np->helper_path;
4848 np->helper_path = NULL;
4849 gtk_object_destroy(temp);
4850 }
4851 }
4852 }
4854 /* sp_nodepath_make_straight_path:
4855 * Prevents user from curving the path by dragging a segment or activating handles etc.
4856 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4857 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4858 */
4859 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4860 np->straight_path = true;
4861 np->show_handles = false;
4862 g_message("add code to make the path straight.");
4863 // do sp_nodepath_convert_node_type on all nodes?
4864 // coding tip: search for this text : "Make selected segments lines"
4865 }
4868 /*
4869 Local Variables:
4870 mode:c++
4871 c-file-style:"stroustrup"
4872 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4873 indent-tabs-mode:nil
4874 fill-column:99
4875 End:
4876 */
4877 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :