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