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