aec5ff058b83417c50c9b3c8fb01b51d22fab4f1
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, and that its endpoint is the path's initialPoint because the path is closed
529 NR::Point pos = from_2geom(pit->initialPoint()) * np->i2d;
530 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
531 sp_nodepath_subpath_close(sp);
532 }
533 }
534 }
535 // should add initial point of curve with type of previous curve:
536 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,
537 NR::Point & ppos, NRPathcode & pcode)
538 {
539 if( dynamic_cast<Geom::LineSegment const*>(&c) ||
540 dynamic_cast<Geom::HLineSegment const*>(&c) ||
541 dynamic_cast<Geom::VLineSegment const*>(&c) )
542 {
543 NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
544 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
545 ppos = from_2geom(c.finalPoint());
546 pcode = NR_LINETO;
547 }
548 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
549 std::vector<Geom::Point> points = cubic_bezier->points();
550 NR::Point pos = from_2geom(points[0]) * np->i2d;
551 NR::Point npos = from_2geom(points[1]) * np->i2d;
552 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
553 ppos = from_2geom(points[2]) * np->i2d;
554 pcode = NR_CURVETO;
555 }
556 else {
557 //this case handles sbasis as well as all other curve types
558 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
560 for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
561 add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
562 }
563 }
564 }
567 /**
568 * Convert from sodipodi:nodetypes to new style type array.
569 */
570 static
571 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, gint length)
572 {
573 g_assert(length > 0);
575 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
577 gint pos = 0;
579 if (types) {
580 for (gint i = 0; types[i] && ( i < length ); i++) {
581 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
582 if (types[i] != '\0') {
583 switch (types[i]) {
584 case 's':
585 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
586 break;
587 case 'z':
588 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
589 break;
590 case 'c':
591 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
592 break;
593 default:
594 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
595 break;
596 }
597 }
598 }
599 }
601 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
603 return typestr;
604 }
606 /**
607 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
608 * updated but repr is not (for speed). Used during curve and node drag.
609 */
610 static void update_object(Inkscape::NodePath::Path *np)
611 {
612 g_assert(np);
614 np->curve->unref();
615 np->curve = create_curve(np);
617 sp_nodepath_set_curve(np, np->curve);
619 if (np->show_helperpath) {
620 SPCurve * helper_curve = np->curve->copy();
621 helper_curve->transform(np->i2d );
622 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
623 helper_curve->unref();
624 }
625 }
627 /**
628 * Update XML path node with data from path object.
629 */
630 static void update_repr_internal(Inkscape::NodePath::Path *np)
631 {
632 g_assert(np);
634 Inkscape::XML::Node *repr = np->object->repr;
636 np->curve->unref();
637 np->curve = create_curve(np);
639 gchar *typestr = create_typestr(np);
640 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
642 // determine if path has an effect applied and write to correct "d" attribute.
643 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
644 np->local_change++;
645 repr->setAttribute(np->repr_key, svgpath);
646 }
648 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
649 np->local_change++;
650 repr->setAttribute(np->repr_nodetypes_key, typestr);
651 }
653 g_free(svgpath);
654 g_free(typestr);
656 if (np->show_helperpath) {
657 SPCurve * helper_curve = np->curve->copy();
658 helper_curve->transform(np->i2d );
659 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
660 helper_curve->unref();
661 }
662 }
664 /**
665 * Update XML path node with data from path object, commit changes forever.
666 */
667 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
668 {
669 //fixme: np can be NULL, so check before proceeding
670 g_return_if_fail(np != NULL);
672 if (np->livarot_path) {
673 delete np->livarot_path;
674 np->livarot_path = NULL;
675 }
677 update_repr_internal(np);
678 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
680 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
681 annotation);
682 }
684 /**
685 * Update XML path node with data from path object, commit changes with undo.
686 */
687 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
688 {
689 if (np->livarot_path) {
690 delete np->livarot_path;
691 np->livarot_path = NULL;
692 }
694 update_repr_internal(np);
695 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
696 annotation);
697 }
699 /**
700 * Make duplicate of path, replace corresponding XML node in tree, commit.
701 */
702 static void stamp_repr(Inkscape::NodePath::Path *np)
703 {
704 g_assert(np);
706 Inkscape::XML::Node *old_repr = np->object->repr;
707 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
709 // remember the position of the item
710 gint pos = old_repr->position();
711 // remember parent
712 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
714 SPCurve *curve = create_curve(np);
715 gchar *typestr = create_typestr(np);
717 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
719 new_repr->setAttribute(np->repr_key, svgpath);
720 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
722 // add the new repr to the parent
723 parent->appendChild(new_repr);
724 // move to the saved position
725 new_repr->setPosition(pos > 0 ? pos : 0);
727 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
728 _("Stamp"));
730 Inkscape::GC::release(new_repr);
731 g_free(svgpath);
732 g_free(typestr);
733 curve->unref();
734 }
736 /**
737 * Create curve from path.
738 */
739 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
740 {
741 SPCurve *curve = new SPCurve();
743 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
744 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
745 curve->moveto(sp->first->pos * np->d2i);
746 Inkscape::NodePath::Node *n = sp->first->n.other;
747 while (n) {
748 NR::Point const end_pt = n->pos * np->d2i;
749 switch (n->code) {
750 case NR_LINETO:
751 curve->lineto(end_pt);
752 break;
753 case NR_CURVETO:
754 curve->curveto(n->p.other->n.pos * np->d2i,
755 n->p.pos * np->d2i,
756 end_pt);
757 break;
758 default:
759 g_assert_not_reached();
760 break;
761 }
762 if (n != sp->last) {
763 n = n->n.other;
764 } else {
765 n = NULL;
766 }
767 }
768 if (sp->closed) {
769 curve->closepath();
770 }
771 }
773 return curve;
774 }
776 /**
777 * Convert path type string to sodipodi:nodetypes style.
778 */
779 static gchar *create_typestr(Inkscape::NodePath::Path *np)
780 {
781 gchar *typestr = g_new(gchar, 32);
782 gint len = 32;
783 gint pos = 0;
785 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
786 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
788 if (pos >= len) {
789 typestr = g_renew(gchar, typestr, len + 32);
790 len += 32;
791 }
793 typestr[pos++] = 'c';
795 Inkscape::NodePath::Node *n;
796 n = sp->first->n.other;
797 while (n) {
798 gchar code;
800 switch (n->type) {
801 case Inkscape::NodePath::NODE_CUSP:
802 code = 'c';
803 break;
804 case Inkscape::NodePath::NODE_SMOOTH:
805 code = 's';
806 break;
807 case Inkscape::NodePath::NODE_SYMM:
808 code = 'z';
809 break;
810 default:
811 g_assert_not_reached();
812 code = '\0';
813 break;
814 }
816 if (pos >= len) {
817 typestr = g_renew(gchar, typestr, len + 32);
818 len += 32;
819 }
821 typestr[pos++] = code;
823 if (n != sp->last) {
824 n = n->n.other;
825 } else {
826 n = NULL;
827 }
828 }
829 }
831 if (pos >= len) {
832 typestr = g_renew(gchar, typestr, len + 1);
833 len += 1;
834 }
836 typestr[pos++] = '\0';
838 return typestr;
839 }
841 /**
842 * Returns current path in context. // later eliminate this function at all!
843 */
844 static Inkscape::NodePath::Path *sp_nodepath_current()
845 {
846 if (!SP_ACTIVE_DESKTOP) {
847 return NULL;
848 }
850 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
852 if (!SP_IS_NODE_CONTEXT(event_context)) {
853 return NULL;
854 }
856 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
857 }
861 /**
862 \brief Fills node and handle positions for three nodes, splitting line
863 marked by end at distance t.
864 */
865 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
866 {
867 g_assert(new_path != NULL);
868 g_assert(end != NULL);
870 g_assert(end->p.other == new_path);
871 Inkscape::NodePath::Node *start = new_path->p.other;
872 g_assert(start);
874 if (end->code == NR_LINETO) {
875 new_path->type =Inkscape::NodePath::NODE_CUSP;
876 new_path->code = NR_LINETO;
877 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
878 } else {
879 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
880 new_path->code = NR_CURVETO;
881 gdouble s = 1 - t;
882 for (int dim = 0; dim < 2; dim++) {
883 NR::Coord const f000 = start->pos[dim];
884 NR::Coord const f001 = start->n.pos[dim];
885 NR::Coord const f011 = end->p.pos[dim];
886 NR::Coord const f111 = end->pos[dim];
887 NR::Coord const f00t = s * f000 + t * f001;
888 NR::Coord const f01t = s * f001 + t * f011;
889 NR::Coord const f11t = s * f011 + t * f111;
890 NR::Coord const f0tt = s * f00t + t * f01t;
891 NR::Coord const f1tt = s * f01t + t * f11t;
892 NR::Coord const fttt = s * f0tt + t * f1tt;
893 start->n.pos[dim] = f00t;
894 new_path->p.pos[dim] = f0tt;
895 new_path->pos[dim] = fttt;
896 new_path->n.pos[dim] = f1tt;
897 end->p.pos[dim] = f11t;
898 }
899 }
900 }
902 /**
903 * Adds new node on direct line between two nodes, activates handles of all
904 * three nodes.
905 */
906 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
907 {
908 g_assert(end);
909 g_assert(end->subpath);
910 g_assert(g_list_find(end->subpath->nodes, end));
912 Inkscape::NodePath::Node *start = end->p.other;
913 g_assert( start->n.other == end );
914 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
915 end,
916 (NRPathcode)end->code == NR_LINETO?
917 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
918 (NRPathcode)end->code,
919 &start->pos, &start->pos, &start->n.pos);
920 sp_nodepath_line_midpoint(newnode, end, t);
922 sp_node_adjust_handles(start);
923 sp_node_update_handles(start);
924 sp_node_update_handles(newnode);
925 sp_node_adjust_handles(end);
926 sp_node_update_handles(end);
928 return newnode;
929 }
931 /**
932 \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
933 */
934 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
935 {
936 g_assert(node);
937 g_assert(node->subpath);
938 g_assert(g_list_find(node->subpath->nodes, node));
940 Inkscape::NodePath::SubPath *sp = node->subpath;
941 Inkscape::NodePath::Path *np = sp->nodepath;
943 if (sp->closed) {
944 sp_nodepath_subpath_open(sp, node);
945 return sp->first;
946 } else {
947 // no break for end nodes
948 if (node == sp->first) return NULL;
949 if (node == sp->last ) return NULL;
951 // create a new subpath
952 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
954 // duplicate the break node as start of the new subpath
955 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
957 // attach rest of curve to new node
958 g_assert(node->n.other);
959 newnode->n.other = node->n.other; node->n.other = NULL;
960 newnode->n.other->p.other = newnode;
961 newsubpath->last = sp->last;
962 sp->last = node;
963 node = newnode;
964 while (node->n.other) {
965 node = node->n.other;
966 node->subpath = newsubpath;
967 sp->nodes = g_list_remove(sp->nodes, node);
968 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
969 }
972 return newnode;
973 }
974 }
976 /**
977 * Duplicate node and connect to neighbours.
978 */
979 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
980 {
981 g_assert(node);
982 g_assert(node->subpath);
983 g_assert(g_list_find(node->subpath->nodes, node));
985 Inkscape::NodePath::SubPath *sp = node->subpath;
987 NRPathcode code = (NRPathcode) node->code;
988 if (code == NR_MOVETO) { // if node is the endnode,
989 node->code = NR_LINETO; // new one is inserted before it, so change that to line
990 }
992 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
994 if (!node->n.other || !node->p.other) // if node is an endnode, select it
995 return node;
996 else
997 return newnode; // otherwise select the newly created node
998 }
1000 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1001 {
1002 node->p.pos = (node->pos + (node->pos - node->n.pos));
1003 }
1005 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1006 {
1007 node->n.pos = (node->pos + (node->pos - node->p.pos));
1008 }
1010 /**
1011 * Change line type at node, with side effects on neighbours.
1012 */
1013 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1014 {
1015 g_assert(end);
1016 g_assert(end->subpath);
1017 g_assert(end->p.other);
1019 if (end->code == static_cast< guint > ( code ) )
1020 return;
1022 Inkscape::NodePath::Node *start = end->p.other;
1024 end->code = code;
1026 if (code == NR_LINETO) {
1027 if (start->code == NR_LINETO) {
1028 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1029 }
1030 if (end->n.other) {
1031 if (end->n.other->code == NR_LINETO) {
1032 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1033 }
1034 }
1035 } else {
1036 NR::Point delta = end->pos - start->pos;
1037 start->n.pos = start->pos + delta / 3;
1038 end->p.pos = end->pos - delta / 3;
1039 sp_node_adjust_handle(start, 1);
1040 sp_node_adjust_handle(end, -1);
1041 }
1043 sp_node_update_handles(start);
1044 sp_node_update_handles(end);
1045 }
1047 /**
1048 * Change node type, and its handles accordingly.
1049 */
1050 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1051 {
1052 g_assert(node);
1053 g_assert(node->subpath);
1055 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1056 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1057 type =Inkscape::NodePath::NODE_CUSP;
1058 }
1059 }
1061 node->type = type;
1063 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1064 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1065 node->knot->setSize (node->selected? 11 : 9);
1066 sp_knot_update_ctrl(node->knot);
1067 } else {
1068 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1069 node->knot->setSize (node->selected? 9 : 7);
1070 sp_knot_update_ctrl(node->knot);
1071 }
1073 // if one of handles is mouseovered, preserve its position
1074 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1075 sp_node_adjust_handle(node, 1);
1076 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1077 sp_node_adjust_handle(node, -1);
1078 } else {
1079 sp_node_adjust_handles(node);
1080 }
1082 sp_node_update_handles(node);
1084 sp_nodepath_update_statusbar(node->subpath->nodepath);
1086 return node;
1087 }
1089 bool
1090 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1091 {
1092 Inkscape::NodePath::Node *othernode = side->other;
1093 if (!othernode)
1094 return false;
1095 NRPathcode const code = sp_node_path_code_from_side(node, side);
1096 if (code == NR_LINETO)
1097 return true;
1098 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1099 if (&node->p == side) {
1100 other_to_me = &othernode->n;
1101 } else if (&node->n == side) {
1102 other_to_me = &othernode->p;
1103 }
1104 if (!other_to_me)
1105 return false;
1106 bool is_line =
1107 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1108 NR::L2(node->pos - side->pos) < 1e-6);
1109 return is_line;
1110 }
1112 /**
1113 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1114 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1115 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1116 * If already cusp and set to cusp, retracts handles.
1117 */
1118 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1119 {
1120 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1122 /*
1123 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1125 if (two_handles) {
1126 // do nothing, adjust_handles called via set_node_type will line them up
1127 } else if (one_handle) {
1128 if (opposite_to_handle_is_line) {
1129 if (lined_up) {
1130 // already half-smooth; pull opposite handle too making it fully smooth
1131 } else {
1132 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1133 }
1134 } else {
1135 // pull opposite handle in line with the existing one
1136 }
1137 } else if (no_handles) {
1138 if (both_segments_are_lines OR both_segments_are_curves) {
1139 //pull both handles
1140 } else {
1141 // pull the handle opposite to line segment, making node half-smooth
1142 }
1143 }
1144 */
1145 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1146 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1147 bool p_is_line = sp_node_side_is_line(node, &node->p);
1148 bool n_is_line = sp_node_side_is_line(node, &node->n);
1150 if (p_has_handle && n_has_handle) {
1151 // do nothing, adjust_handles will line them up
1152 } else if (p_has_handle || n_has_handle) {
1153 if (p_has_handle && n_is_line) {
1154 Radial line (node->n.other->pos - node->pos);
1155 Radial handle (node->pos - node->p.pos);
1156 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1157 // already half-smooth; pull opposite handle too making it fully smooth
1158 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1159 } else {
1160 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1161 }
1162 } else if (n_has_handle && p_is_line) {
1163 Radial line (node->p.other->pos - node->pos);
1164 Radial handle (node->pos - node->n.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->p.pos = node->pos + (node->p.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 (p_has_handle && node->n.other) {
1172 // pull n handle
1173 node->n.other->code = NR_CURVETO;
1174 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1175 NR::L2(node->p.pos - node->pos) :
1176 NR::L2(node->n.other->pos - node->pos) / 3;
1177 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1178 } else if (n_has_handle && node->p.other) {
1179 // pull p handle
1180 node->code = NR_CURVETO;
1181 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1182 NR::L2(node->n.pos - node->pos) :
1183 NR::L2(node->p.other->pos - node->pos) / 3;
1184 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1185 }
1186 } else if (!p_has_handle && !n_has_handle) {
1187 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1188 // no handles, but both segments are either lnes or curves:
1189 //pull both handles
1191 // convert both to curves:
1192 node->code = NR_CURVETO;
1193 node->n.other->code = NR_CURVETO;
1195 NR::Point leg_prev = node->pos - node->p.other->pos;
1196 NR::Point leg_next = node->pos - node->n.other->pos;
1198 double norm_leg_prev = L2(leg_prev);
1199 double norm_leg_next = L2(leg_next);
1201 NR::Point delta;
1202 if (norm_leg_next > 0.0) {
1203 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1204 (&delta)->normalize();
1205 }
1207 if (type == Inkscape::NodePath::NODE_SYMM) {
1208 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1209 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1210 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1211 } else {
1212 // length of handle is proportional to distance to adjacent node
1213 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1214 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1215 }
1217 } else {
1218 // pull the handle opposite to line segment, making it half-smooth
1219 if (p_is_line && node->n.other) {
1220 if (type != Inkscape::NodePath::NODE_SYMM) {
1221 // pull n handle
1222 node->n.other->code = NR_CURVETO;
1223 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1224 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1225 }
1226 } else if (n_is_line && node->p.other) {
1227 if (type != Inkscape::NodePath::NODE_SYMM) {
1228 // pull p handle
1229 node->code = NR_CURVETO;
1230 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1231 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1232 }
1233 }
1234 }
1235 }
1236 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1237 // cusping a cusp: retract nodes
1238 node->p.pos = node->pos;
1239 node->n.pos = node->pos;
1240 }
1242 sp_nodepath_set_node_type (node, type);
1243 }
1245 /**
1246 * Move node to point, and adjust its and neighbouring handles.
1247 */
1248 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1249 {
1250 NR::Point delta = p - node->pos;
1251 node->pos = p;
1253 node->p.pos += delta;
1254 node->n.pos += delta;
1256 Inkscape::NodePath::Node *node_p = NULL;
1257 Inkscape::NodePath::Node *node_n = NULL;
1259 if (node->p.other) {
1260 if (node->code == NR_LINETO) {
1261 sp_node_adjust_handle(node, 1);
1262 sp_node_adjust_handle(node->p.other, -1);
1263 node_p = node->p.other;
1264 }
1265 }
1266 if (node->n.other) {
1267 if (node->n.other->code == NR_LINETO) {
1268 sp_node_adjust_handle(node, -1);
1269 sp_node_adjust_handle(node->n.other, 1);
1270 node_n = node->n.other;
1271 }
1272 }
1274 // this function is only called from batch movers that will update display at the end
1275 // themselves, so here we just move all the knots without emitting move signals, for speed
1276 sp_node_update_handles(node, false);
1277 if (node_n) {
1278 sp_node_update_handles(node_n, false);
1279 }
1280 if (node_p) {
1281 sp_node_update_handles(node_p, false);
1282 }
1283 }
1285 /**
1286 * Call sp_node_moveto() for node selection and handle possible snapping.
1287 */
1288 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1289 bool const snap, bool constrained = false,
1290 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1291 {
1292 NR::Coord best = NR_HUGE;
1293 NR::Point delta(dx, dy);
1294 NR::Point best_pt = delta;
1295 Inkscape::SnappedPoint best_abs;
1297 if (snap) {
1298 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1299 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1300 * must provide that information. */
1302 // Build a list of the unselected nodes to which the snapper should snap
1303 std::vector<NR::Point> unselected_nodes;
1304 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1305 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1306 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1307 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1308 if (!node->selected) {
1309 unselected_nodes.push_back(node->pos);
1310 }
1311 }
1312 }
1314 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1316 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1317 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1318 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1319 Inkscape::SnappedPoint s;
1320 if (constrained) {
1321 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1322 dedicated_constraint.setPoint(n->pos);
1323 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1324 } else {
1325 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1326 }
1327 if (s.getSnapped() && (s.getDistance() < best)) {
1328 best = s.getDistance();
1329 best_abs = s;
1330 best_pt = s.getPoint() - n->pos;
1331 }
1332 }
1334 if (best_abs.getSnapped()) {
1335 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1336 } else {
1337 nodepath->desktop->snapindicator->remove_snappoint();
1338 }
1339 }
1341 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1342 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1343 sp_node_moveto(n, n->pos + best_pt);
1344 }
1346 // do not update repr here so that node dragging is acceptably fast
1347 update_object(nodepath);
1348 }
1350 /**
1351 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1352 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1353 near x = 0.
1354 */
1355 double
1356 sculpt_profile (double x, double alpha, guint profile)
1357 {
1358 if (x >= 1)
1359 return 0;
1360 if (x <= 0)
1361 return 1;
1363 switch (profile) {
1364 case SCULPT_PROFILE_LINEAR:
1365 return 1 - x;
1366 case SCULPT_PROFILE_BELL:
1367 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1368 case SCULPT_PROFILE_ELLIPTIC:
1369 return sqrt(1 - x*x);
1370 }
1372 return 1;
1373 }
1375 double
1376 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1377 {
1378 // extremely primitive for now, don't have time to look for the real one
1379 double lower = NR::L2(b - a);
1380 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1381 return (lower + upper)/2;
1382 }
1384 void
1385 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1386 {
1387 n->pos = n->origin + delta;
1388 n->n.pos = n->n.origin + delta_n;
1389 n->p.pos = n->p.origin + delta_p;
1390 sp_node_adjust_handles(n);
1391 sp_node_update_handles(n, false);
1392 }
1394 /**
1395 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1396 * on how far they are from the dragged node n.
1397 */
1398 static void
1399 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1400 {
1401 g_assert (n);
1402 g_assert (nodepath);
1403 g_assert (n->subpath->nodepath == nodepath);
1405 double pressure = n->knot->pressure;
1406 if (pressure == 0)
1407 pressure = 0.5; // default
1408 pressure = CLAMP (pressure, 0.2, 0.8);
1410 // map pressure to alpha = 1/5 ... 5
1411 double alpha = 1 - 2 * fabs(pressure - 0.5);
1412 if (pressure > 0.5)
1413 alpha = 1/alpha;
1415 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1417 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1418 // Only one subpath has selected nodes:
1419 // use linear mode, where the distance from n to node being dragged is calculated along the path
1421 double n_sel_range = 0, p_sel_range = 0;
1422 guint n_nodes = 0, p_nodes = 0;
1423 guint n_sel_nodes = 0, p_sel_nodes = 0;
1425 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1426 {
1427 double n_range = 0, p_range = 0;
1428 bool n_going = true, p_going = true;
1429 Inkscape::NodePath::Node *n_node = n;
1430 Inkscape::NodePath::Node *p_node = n;
1431 do {
1432 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1433 if (n_node && n_going)
1434 n_node = n_node->n.other;
1435 if (n_node == NULL) {
1436 n_going = false;
1437 } else {
1438 n_nodes ++;
1439 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1440 if (n_node->selected) {
1441 n_sel_nodes ++;
1442 n_sel_range = n_range;
1443 }
1444 if (n_node == p_node) {
1445 n_going = false;
1446 p_going = false;
1447 }
1448 }
1449 if (p_node && p_going)
1450 p_node = p_node->p.other;
1451 if (p_node == NULL) {
1452 p_going = false;
1453 } else {
1454 p_nodes ++;
1455 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1456 if (p_node->selected) {
1457 p_sel_nodes ++;
1458 p_sel_range = p_range;
1459 }
1460 if (p_node == n_node) {
1461 n_going = false;
1462 p_going = false;
1463 }
1464 }
1465 } while (n_going || p_going);
1466 }
1468 // Second pass: actually move nodes in this subpath
1469 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1470 {
1471 double n_range = 0, p_range = 0;
1472 bool n_going = true, p_going = true;
1473 Inkscape::NodePath::Node *n_node = n;
1474 Inkscape::NodePath::Node *p_node = n;
1475 do {
1476 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1477 if (n_node && n_going)
1478 n_node = n_node->n.other;
1479 if (n_node == NULL) {
1480 n_going = false;
1481 } else {
1482 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1483 if (n_node->selected) {
1484 sp_nodepath_move_node_and_handles (n_node,
1485 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1486 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1487 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1488 }
1489 if (n_node == p_node) {
1490 n_going = false;
1491 p_going = false;
1492 }
1493 }
1494 if (p_node && p_going)
1495 p_node = p_node->p.other;
1496 if (p_node == NULL) {
1497 p_going = false;
1498 } else {
1499 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1500 if (p_node->selected) {
1501 sp_nodepath_move_node_and_handles (p_node,
1502 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1503 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1504 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1505 }
1506 if (p_node == n_node) {
1507 n_going = false;
1508 p_going = false;
1509 }
1510 }
1511 } while (n_going || p_going);
1512 }
1514 } else {
1515 // Multiple subpaths have selected nodes:
1516 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1517 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1518 // fix the pear-like shape when sculpting e.g. a ring
1520 // First pass: calculate range
1521 gdouble direct_range = 0;
1522 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1523 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1524 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1525 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1526 if (node->selected) {
1527 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1528 }
1529 }
1530 }
1532 // Second pass: actually move nodes
1533 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1534 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1535 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1536 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1537 if (node->selected) {
1538 if (direct_range > 1e-6) {
1539 sp_nodepath_move_node_and_handles (node,
1540 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1541 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1542 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1543 } else {
1544 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1545 }
1547 }
1548 }
1549 }
1550 }
1552 // do not update repr here so that node dragging is acceptably fast
1553 update_object(nodepath);
1554 }
1557 /**
1558 * Move node selection to point, adjust its and neighbouring handles,
1559 * handle possible snapping, and commit the change with possible undo.
1560 */
1561 void
1562 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1563 {
1564 if (!nodepath) return;
1566 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1568 if (dx == 0) {
1569 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1570 } else if (dy == 0) {
1571 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1572 } else {
1573 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1574 }
1575 }
1577 /**
1578 * Move node selection off screen and commit the change.
1579 */
1580 void
1581 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1582 {
1583 // borrowed from sp_selection_move_screen in selection-chemistry.c
1584 // we find out the current zoom factor and divide deltas by it
1585 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1587 gdouble zoom = desktop->current_zoom();
1588 gdouble zdx = dx / zoom;
1589 gdouble zdy = dy / zoom;
1591 if (!nodepath) return;
1593 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1595 if (dx == 0) {
1596 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1597 } else if (dy == 0) {
1598 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1599 } else {
1600 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1601 }
1602 }
1604 /**
1605 * Move selected nodes to the absolute position given
1606 */
1607 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1608 {
1609 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1610 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1611 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1612 sp_node_moveto(n, npos);
1613 }
1615 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1616 }
1618 /**
1619 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1620 */
1621 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1622 {
1623 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1624 g_return_val_if_fail(nodepath->selected, no_coord);
1626 // determine coordinate of first selected node
1627 GList *nsel = nodepath->selected;
1628 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1629 NR::Coord coord = n->pos[axis];
1630 bool coincide = true;
1632 // compare it to the coordinates of all the other selected nodes
1633 for (GList *l = nsel->next; l != NULL; l = l->next) {
1634 n = (Inkscape::NodePath::Node *) l->data;
1635 if (n->pos[axis] != coord) {
1636 coincide = false;
1637 }
1638 }
1639 if (coincide) {
1640 return coord;
1641 } else {
1642 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1643 // currently we return the coordinate of the bounding box midpoint because I don't know how
1644 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1645 return bbox.midpoint()[axis];
1646 }
1647 }
1649 /** If they don't yet exist, creates knot and line for the given side of the node */
1650 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1651 {
1652 if (!side->knot) {
1653 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"));
1655 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1656 side->knot->setSize (7);
1657 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1658 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1659 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1660 sp_knot_update_ctrl(side->knot);
1662 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1663 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1664 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1665 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1666 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1667 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1668 }
1670 if (!side->line) {
1671 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1672 SP_TYPE_CTRLLINE, NULL);
1673 }
1674 }
1676 /**
1677 * Ensure the given handle of the node is visible/invisible, update its screen position
1678 */
1679 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1680 {
1681 g_assert(node != NULL);
1683 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1684 NRPathcode code = sp_node_path_code_from_side(node, side);
1686 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1688 if (show_handle) {
1689 if (!side->knot) { // No handle knot at all
1690 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1691 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1692 side->knot->pos = side->pos;
1693 if (side->knot->item)
1694 SP_CTRL(side->knot->item)->moveto(side->pos);
1695 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1696 sp_knot_show(side->knot);
1697 } else {
1698 if (side->knot->pos != side->pos) { // only if it's really moved
1699 if (fire_move_signals) {
1700 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1701 } else {
1702 sp_knot_moveto(side->knot, &side->pos);
1703 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1704 }
1705 }
1706 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1707 sp_knot_show(side->knot);
1708 }
1709 }
1710 sp_canvas_item_show(side->line);
1711 } else {
1712 if (side->knot) {
1713 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1714 sp_knot_hide(side->knot);
1715 }
1716 }
1717 if (side->line) {
1718 sp_canvas_item_hide(side->line);
1719 }
1720 }
1721 }
1723 /**
1724 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1725 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1726 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1727 * updated; otherwise, just move the knots silently (used in batch moves).
1728 */
1729 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1730 {
1731 g_assert(node != NULL);
1733 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1734 sp_knot_show(node->knot);
1735 }
1737 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1738 if (fire_move_signals)
1739 sp_knot_set_position(node->knot, &node->pos, 0);
1740 else
1741 sp_knot_moveto(node->knot, &node->pos);
1742 }
1744 gboolean show_handles = node->selected;
1745 if (node->p.other != NULL) {
1746 if (node->p.other->selected) show_handles = TRUE;
1747 }
1748 if (node->n.other != NULL) {
1749 if (node->n.other->selected) show_handles = TRUE;
1750 }
1752 if (node->subpath->nodepath->show_handles == false)
1753 show_handles = FALSE;
1755 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1756 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1757 }
1759 /**
1760 * Call sp_node_update_handles() for all nodes on subpath.
1761 */
1762 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1763 {
1764 g_assert(subpath != NULL);
1766 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1767 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1768 }
1769 }
1771 /**
1772 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1773 */
1774 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1775 {
1776 g_assert(nodepath != NULL);
1778 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1779 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1780 }
1781 }
1783 void
1784 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1785 {
1786 if (nodepath == NULL) return;
1788 nodepath->show_handles = show;
1789 sp_nodepath_update_handles(nodepath);
1790 }
1792 /**
1793 * Adds all selected nodes in nodepath to list.
1794 */
1795 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1796 {
1797 StlConv<Node *>::list(l, selected);
1798 /// \todo this adds a copying, rework when the selection becomes a stl list
1799 }
1801 /**
1802 * Align selected nodes on the specified axis.
1803 */
1804 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1805 {
1806 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1807 return;
1808 }
1810 if ( !nodepath->selected->next ) { // only one node selected
1811 return;
1812 }
1813 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1814 NR::Point dest(pNode->pos);
1815 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1816 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1817 if (pNode) {
1818 dest[axis] = pNode->pos[axis];
1819 sp_node_moveto(pNode, dest);
1820 }
1821 }
1823 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1824 }
1826 /// Helper struct.
1827 struct NodeSort
1828 {
1829 Inkscape::NodePath::Node *_node;
1830 NR::Coord _coord;
1831 /// \todo use vectorof pointers instead of calling copy ctor
1832 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1833 _node(node), _coord(node->pos[axis])
1834 {}
1836 };
1838 static bool operator<(NodeSort const &a, NodeSort const &b)
1839 {
1840 return (a._coord < b._coord);
1841 }
1843 /**
1844 * Distribute selected nodes on the specified axis.
1845 */
1846 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1847 {
1848 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1849 return;
1850 }
1852 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1853 return;
1854 }
1856 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1857 std::vector<NodeSort> sorted;
1858 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1859 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1860 if (pNode) {
1861 NodeSort n(pNode, axis);
1862 sorted.push_back(n);
1863 //dest[axis] = pNode->pos[axis];
1864 //sp_node_moveto(pNode, dest);
1865 }
1866 }
1867 std::sort(sorted.begin(), sorted.end());
1868 unsigned int len = sorted.size();
1869 //overall bboxes span
1870 float dist = (sorted.back()._coord -
1871 sorted.front()._coord);
1872 //new distance between each bbox
1873 float step = (dist) / (len - 1);
1874 float pos = sorted.front()._coord;
1875 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1876 it < sorted.end();
1877 it ++ )
1878 {
1879 NR::Point dest((*it)._node->pos);
1880 dest[axis] = pos;
1881 sp_node_moveto((*it)._node, dest);
1882 pos += step;
1883 }
1885 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1886 }
1889 /**
1890 * Call sp_nodepath_line_add_node() for all selected segments.
1891 */
1892 void
1893 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1894 {
1895 if (!nodepath) {
1896 return;
1897 }
1899 GList *nl = NULL;
1901 int n_added = 0;
1903 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1904 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1905 g_assert(t->selected);
1906 if (t->p.other && t->p.other->selected) {
1907 nl = g_list_prepend(nl, t);
1908 }
1909 }
1911 while (nl) {
1912 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1913 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1914 sp_nodepath_node_select(n, TRUE, FALSE);
1915 n_added ++;
1916 nl = g_list_remove(nl, t);
1917 }
1919 /** \todo fixme: adjust ? */
1920 sp_nodepath_update_handles(nodepath);
1922 if (n_added > 1) {
1923 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1924 } else if (n_added > 0) {
1925 sp_nodepath_update_repr(nodepath, _("Add node"));
1926 }
1928 sp_nodepath_update_statusbar(nodepath);
1929 }
1931 /**
1932 * Select segment nearest to point
1933 */
1934 void
1935 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1936 {
1937 if (!nodepath) {
1938 return;
1939 }
1941 sp_nodepath_ensure_livarot_path(nodepath);
1942 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1943 if (!maybe_position) {
1944 return;
1945 }
1946 Path::cut_position position = *maybe_position;
1948 //find segment to segment
1949 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1951 //fixme: this can return NULL, so check before proceeding.
1952 g_return_if_fail(e != NULL);
1954 gboolean force = FALSE;
1955 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1956 force = TRUE;
1957 }
1958 sp_nodepath_node_select(e, (gboolean) toggle, force);
1959 if (e->p.other)
1960 sp_nodepath_node_select(e->p.other, TRUE, force);
1962 sp_nodepath_update_handles(nodepath);
1964 sp_nodepath_update_statusbar(nodepath);
1965 }
1967 /**
1968 * Add a node nearest to point
1969 */
1970 void
1971 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1972 {
1973 if (!nodepath) {
1974 return;
1975 }
1977 sp_nodepath_ensure_livarot_path(nodepath);
1978 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1979 if (!maybe_position) {
1980 return;
1981 }
1982 Path::cut_position position = *maybe_position;
1984 //find segment to split
1985 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1987 //don't know why but t seems to flip for lines
1988 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1989 position.t = 1.0 - position.t;
1990 }
1991 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1992 sp_nodepath_node_select(n, FALSE, TRUE);
1994 /* fixme: adjust ? */
1995 sp_nodepath_update_handles(nodepath);
1997 sp_nodepath_update_repr(nodepath, _("Add node"));
1999 sp_nodepath_update_statusbar(nodepath);
2000 }
2002 /*
2003 * Adjusts a segment so that t moves by a certain delta for dragging
2004 * converts lines to curves
2005 *
2006 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2007 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2008 */
2009 void
2010 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2011 {
2012 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2014 //fixme: e and e->p can be NULL, so check for those before proceeding
2015 g_return_if_fail(e != NULL);
2016 g_return_if_fail(&e->p != NULL);
2018 /* feel good is an arbitrary parameter that distributes the delta between handles
2019 * if t of the drag point is less than 1/6 distance form the endpoint only
2020 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2021 */
2022 double feel_good;
2023 if (t <= 1.0 / 6.0)
2024 feel_good = 0;
2025 else if (t <= 0.5)
2026 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2027 else if (t <= 5.0 / 6.0)
2028 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2029 else
2030 feel_good = 1;
2032 //if we're dragging a line convert it to a curve
2033 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2034 sp_nodepath_set_line_type(e, NR_CURVETO);
2035 }
2037 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2038 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2039 e->p.other->n.pos += offsetcoord0;
2040 e->p.pos += offsetcoord1;
2042 // adjust handles of adjacent nodes where necessary
2043 sp_node_adjust_handle(e,1);
2044 sp_node_adjust_handle(e->p.other,-1);
2046 sp_nodepath_update_handles(e->subpath->nodepath);
2048 update_object(e->subpath->nodepath);
2050 sp_nodepath_update_statusbar(e->subpath->nodepath);
2051 }
2054 /**
2055 * Call sp_nodepath_break() for all selected segments.
2056 */
2057 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2058 {
2059 if (!nodepath) return;
2061 GList *tempin = g_list_copy(nodepath->selected);
2062 GList *temp = NULL;
2063 for (GList *l = tempin; l != NULL; l = l->next) {
2064 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2065 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2066 if (nn == NULL) continue; // no break, no new node
2067 temp = g_list_prepend(temp, nn);
2068 }
2069 g_list_free(tempin);
2071 if (temp) {
2072 sp_nodepath_deselect(nodepath);
2073 }
2074 for (GList *l = temp; l != NULL; l = l->next) {
2075 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2076 }
2078 sp_nodepath_update_handles(nodepath);
2080 sp_nodepath_update_repr(nodepath, _("Break path"));
2081 }
2083 /**
2084 * Duplicate the selected node(s).
2085 */
2086 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2087 {
2088 if (!nodepath) {
2089 return;
2090 }
2092 GList *temp = NULL;
2093 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2094 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2095 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2096 if (nn == NULL) continue; // could not duplicate
2097 temp = g_list_prepend(temp, nn);
2098 }
2100 if (temp) {
2101 sp_nodepath_deselect(nodepath);
2102 }
2103 for (GList *l = temp; l != NULL; l = l->next) {
2104 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2105 }
2107 sp_nodepath_update_handles(nodepath);
2109 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2110 }
2112 /**
2113 * Internal function to join two nodes by merging them into one.
2114 */
2115 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2116 {
2117 /* a and b are endpoints */
2119 // if one of the two nodes is mouseovered, fix its position
2120 NR::Point c;
2121 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2122 c = a->pos;
2123 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2124 c = b->pos;
2125 } else {
2126 // otherwise, move joined node to the midpoint
2127 c = (a->pos + b->pos) / 2;
2128 }
2130 if (a->subpath == b->subpath) {
2131 Inkscape::NodePath::SubPath *sp = a->subpath;
2132 sp_nodepath_subpath_close(sp);
2133 sp_node_moveto (sp->first, c);
2135 sp_nodepath_update_handles(sp->nodepath);
2136 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2137 return;
2138 }
2140 /* a and b are separate subpaths */
2141 Inkscape::NodePath::SubPath *sa = a->subpath;
2142 Inkscape::NodePath::SubPath *sb = b->subpath;
2143 NR::Point p;
2144 Inkscape::NodePath::Node *n;
2145 NRPathcode code;
2146 if (a == sa->first) {
2147 // we will now reverse sa, so that a is its last node, not first, and drop that node
2148 p = sa->first->n.pos;
2149 code = (NRPathcode)sa->first->n.other->code;
2150 // create new subpath
2151 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2152 // create a first moveto node on it
2153 n = sa->last;
2154 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2155 n = n->p.other;
2156 if (n == sa->first) n = NULL;
2157 while (n) {
2158 // copy the rest of the nodes from sa to t, going backwards
2159 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2160 n = n->p.other;
2161 if (n == sa->first) n = NULL;
2162 }
2163 // replace sa with t
2164 sp_nodepath_subpath_destroy(sa);
2165 sa = t;
2166 } else if (a == sa->last) {
2167 // a is already last, just drop it
2168 p = sa->last->p.pos;
2169 code = (NRPathcode)sa->last->code;
2170 sp_nodepath_node_destroy(sa->last);
2171 } else {
2172 code = NR_END;
2173 g_assert_not_reached();
2174 }
2176 if (b == sb->first) {
2177 // copy all nodes from b to a, forward
2178 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2179 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2180 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2181 }
2182 } else if (b == sb->last) {
2183 // copy all nodes from b to a, backward
2184 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2185 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2186 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2187 }
2188 } else {
2189 g_assert_not_reached();
2190 }
2191 /* and now destroy sb */
2193 sp_nodepath_subpath_destroy(sb);
2195 sp_nodepath_update_handles(sa->nodepath);
2197 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2199 sp_nodepath_update_statusbar(nodepath);
2200 }
2202 /**
2203 * Internal function to join two nodes by adding a segment between them.
2204 */
2205 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2206 {
2207 if (a->subpath == b->subpath) {
2208 Inkscape::NodePath::SubPath *sp = a->subpath;
2210 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2211 sp->closed = TRUE;
2213 sp->first->p.other = sp->last;
2214 sp->last->n.other = sp->first;
2216 sp_node_handle_mirror_p_to_n(sp->last);
2217 sp_node_handle_mirror_n_to_p(sp->first);
2219 sp->first->code = sp->last->code;
2220 sp->first = sp->last;
2222 sp_nodepath_update_handles(sp->nodepath);
2224 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2226 return;
2227 }
2229 /* a and b are separate subpaths */
2230 Inkscape::NodePath::SubPath *sa = a->subpath;
2231 Inkscape::NodePath::SubPath *sb = b->subpath;
2233 Inkscape::NodePath::Node *n;
2234 NR::Point p;
2235 NRPathcode code;
2236 if (a == sa->first) {
2237 code = (NRPathcode) sa->first->n.other->code;
2238 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2239 n = sa->last;
2240 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2241 for (n = n->p.other; n != NULL; n = n->p.other) {
2242 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2243 }
2244 sp_nodepath_subpath_destroy(sa);
2245 sa = t;
2246 } else if (a == sa->last) {
2247 code = (NRPathcode)sa->last->code;
2248 } else {
2249 code = NR_END;
2250 g_assert_not_reached();
2251 }
2253 if (b == sb->first) {
2254 n = sb->first;
2255 sp_node_handle_mirror_p_to_n(sa->last);
2256 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2257 sp_node_handle_mirror_n_to_p(sa->last);
2258 for (n = n->n.other; n != NULL; n = n->n.other) {
2259 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2260 }
2261 } else if (b == sb->last) {
2262 n = sb->last;
2263 sp_node_handle_mirror_p_to_n(sa->last);
2264 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2265 sp_node_handle_mirror_n_to_p(sa->last);
2266 for (n = n->p.other; n != NULL; n = n->p.other) {
2267 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2268 }
2269 } else {
2270 g_assert_not_reached();
2271 }
2272 /* and now destroy sb */
2274 sp_nodepath_subpath_destroy(sb);
2276 sp_nodepath_update_handles(sa->nodepath);
2278 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2279 }
2281 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2283 /**
2284 * Internal function to handle joining two nodes.
2285 */
2286 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2287 {
2288 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2290 if (g_list_length(nodepath->selected) != 2) {
2291 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2292 return;
2293 }
2295 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2296 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2298 g_assert(a != b);
2299 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2300 // someone tried to join an orphan node (i.e. a single-node subpath).
2301 // this is not worth an error message, just fail silently.
2302 return;
2303 }
2305 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2306 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2307 return;
2308 }
2310 switch(mode) {
2311 case NODE_JOIN_ENDPOINTS:
2312 do_node_selected_join(nodepath, a, b);
2313 break;
2314 case NODE_JOIN_SEGMENT:
2315 do_node_selected_join_segment(nodepath, a, b);
2316 break;
2317 }
2318 }
2320 /**
2321 * Join two nodes by merging them into one.
2322 */
2323 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2324 {
2325 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2326 }
2328 /**
2329 * Join two nodes by adding a segment between them.
2330 */
2331 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2332 {
2333 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2334 }
2336 /**
2337 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2338 */
2339 void sp_node_delete_preserve(GList *nodes_to_delete)
2340 {
2341 GSList *nodepaths = NULL;
2343 while (nodes_to_delete) {
2344 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2345 Inkscape::NodePath::SubPath *sp = node->subpath;
2346 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2347 Inkscape::NodePath::Node *sample_cursor = NULL;
2348 Inkscape::NodePath::Node *sample_end = NULL;
2349 Inkscape::NodePath::Node *delete_cursor = node;
2350 bool just_delete = false;
2352 //find the start of this contiguous selection
2353 //move left to the first node that is not selected
2354 //or the start of the non-closed path
2355 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2356 delete_cursor = curr;
2357 }
2359 //just delete at the beginning of an open path
2360 if (!delete_cursor->p.other) {
2361 sample_cursor = delete_cursor;
2362 just_delete = true;
2363 } else {
2364 sample_cursor = delete_cursor->p.other;
2365 }
2367 //calculate points for each segment
2368 int rate = 5;
2369 float period = 1.0 / rate;
2370 std::vector<NR::Point> data;
2371 if (!just_delete) {
2372 data.push_back(sample_cursor->pos);
2373 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2374 //just delete at the end of an open path
2375 if (!sp->closed && curr == sp->last) {
2376 just_delete = true;
2377 break;
2378 }
2380 //sample points on the contiguous selected segment
2381 NR::Point *bez;
2382 bez = new NR::Point [4];
2383 bez[0] = curr->pos;
2384 bez[1] = curr->n.pos;
2385 bez[2] = curr->n.other->p.pos;
2386 bez[3] = curr->n.other->pos;
2387 for (int i=1; i<rate; i++) {
2388 gdouble t = i * period;
2389 NR::Point p = bezier_pt(3, bez, t);
2390 data.push_back(p);
2391 }
2392 data.push_back(curr->n.other->pos);
2394 sample_end = curr->n.other;
2395 //break if we've come full circle or hit the end of the selection
2396 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2397 break;
2398 }
2399 }
2400 }
2402 if (!just_delete) {
2403 //calculate the best fitting single segment and adjust the endpoints
2404 NR::Point *adata;
2405 adata = new NR::Point [data.size()];
2406 copy(data.begin(), data.end(), adata);
2408 NR::Point *bez;
2409 bez = new NR::Point [4];
2410 //would decreasing error create a better fitting approximation?
2411 gdouble error = 1.0;
2412 gint ret;
2413 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2415 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2416 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2417 //the resulting nodes behave as expected.
2418 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2419 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2420 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2421 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2423 //adjust endpoints
2424 sample_cursor->n.pos = bez[1];
2425 sample_end->p.pos = bez[2];
2426 }
2428 //destroy this contiguous selection
2429 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2430 Inkscape::NodePath::Node *temp = delete_cursor;
2431 if (delete_cursor->n.other == delete_cursor) {
2432 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2433 delete_cursor = NULL;
2434 } else {
2435 delete_cursor = delete_cursor->n.other;
2436 }
2437 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2438 sp_nodepath_node_destroy(temp);
2439 }
2441 sp_nodepath_update_handles(nodepath);
2443 if (!g_slist_find(nodepaths, nodepath))
2444 nodepaths = g_slist_prepend (nodepaths, nodepath);
2445 }
2447 for (GSList *i = nodepaths; i; i = i->next) {
2448 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2449 // different nodepaths will give us one undo event per nodepath
2450 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2452 // if the entire nodepath is removed, delete the selected object.
2453 if (nodepath->subpaths == NULL ||
2454 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2455 //at least 2
2456 sp_nodepath_get_node_count(nodepath) < 2) {
2457 SPDocument *document = sp_desktop_document (nodepath->desktop);
2458 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2459 //delete this nodepath's object, not the entire selection! (though at this time, this
2460 //does not matter)
2461 sp_selection_delete();
2462 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2463 _("Delete nodes"));
2464 } else {
2465 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2466 sp_nodepath_update_statusbar(nodepath);
2467 }
2468 }
2470 g_slist_free (nodepaths);
2471 }
2473 /**
2474 * Delete one or more selected nodes.
2475 */
2476 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2477 {
2478 if (!nodepath) return;
2479 if (!nodepath->selected) return;
2481 /** \todo fixme: do it the right way */
2482 while (nodepath->selected) {
2483 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2484 sp_nodepath_node_destroy(node);
2485 }
2488 //clean up the nodepath (such as for trivial subpaths)
2489 sp_nodepath_cleanup(nodepath);
2491 sp_nodepath_update_handles(nodepath);
2493 // if the entire nodepath is removed, delete the selected object.
2494 if (nodepath->subpaths == NULL ||
2495 sp_nodepath_get_node_count(nodepath) < 2) {
2496 SPDocument *document = sp_desktop_document (nodepath->desktop);
2497 sp_selection_delete();
2498 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2499 _("Delete nodes"));
2500 return;
2501 }
2503 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2505 sp_nodepath_update_statusbar(nodepath);
2506 }
2508 /**
2509 * Delete one or more segments between two selected nodes.
2510 * This is the code for 'split'.
2511 */
2512 void
2513 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2514 {
2515 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2516 Inkscape::NodePath::Node *curr, *next; //Iterators
2518 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2520 if (g_list_length(nodepath->selected) != 2) {
2521 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2522 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2523 return;
2524 }
2526 //Selected nodes, not inclusive
2527 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2528 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2530 if ( ( a==b) || //same node
2531 (a->subpath != b->subpath ) || //not the same path
2532 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2533 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2534 {
2535 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2536 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2537 return;
2538 }
2540 //###########################################
2541 //# BEGIN EDITS
2542 //###########################################
2543 //##################################
2544 //# CLOSED PATH
2545 //##################################
2546 if (a->subpath->closed) {
2549 gboolean reversed = FALSE;
2551 //Since we can go in a circle, we need to find the shorter distance.
2552 // a->b or b->a
2553 start = end = NULL;
2554 int distance = 0;
2555 int minDistance = 0;
2556 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2557 if (curr==b) {
2558 //printf("a to b:%d\n", distance);
2559 start = a;//go from a to b
2560 end = b;
2561 minDistance = distance;
2562 //printf("A to B :\n");
2563 break;
2564 }
2565 distance++;
2566 }
2568 //try again, the other direction
2569 distance = 0;
2570 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2571 if (curr==a) {
2572 //printf("b to a:%d\n", distance);
2573 if (distance < minDistance) {
2574 start = b; //we go from b to a
2575 end = a;
2576 reversed = TRUE;
2577 //printf("B to A\n");
2578 }
2579 break;
2580 }
2581 distance++;
2582 }
2585 //Copy everything from 'end' to 'start' to a new subpath
2586 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2587 for (curr=end ; curr ; curr=curr->n.other) {
2588 NRPathcode code = (NRPathcode) curr->code;
2589 if (curr == end)
2590 code = NR_MOVETO;
2591 sp_nodepath_node_new(t, NULL,
2592 (Inkscape::NodePath::NodeType)curr->type, code,
2593 &curr->p.pos, &curr->pos, &curr->n.pos);
2594 if (curr == start)
2595 break;
2596 }
2597 sp_nodepath_subpath_destroy(a->subpath);
2600 }
2604 //##################################
2605 //# OPEN PATH
2606 //##################################
2607 else {
2609 //We need to get the direction of the list between A and B
2610 //Can we walk from a to b?
2611 start = end = NULL;
2612 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2613 if (curr==b) {
2614 start = a; //did it! we go from a to b
2615 end = b;
2616 //printf("A to B\n");
2617 break;
2618 }
2619 }
2620 if (!start) {//didn't work? let's try the other direction
2621 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2622 if (curr==a) {
2623 start = b; //did it! we go from b to a
2624 end = a;
2625 //printf("B to A\n");
2626 break;
2627 }
2628 }
2629 }
2630 if (!start) {
2631 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2632 _("Cannot find path between nodes."));
2633 return;
2634 }
2638 //Copy everything after 'end' to a new subpath
2639 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2640 for (curr=end ; curr ; curr=curr->n.other) {
2641 NRPathcode code = (NRPathcode) curr->code;
2642 if (curr == end)
2643 code = NR_MOVETO;
2644 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2645 &curr->p.pos, &curr->pos, &curr->n.pos);
2646 }
2648 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2649 for (curr = start->n.other ; curr ; curr=next) {
2650 next = curr->n.other;
2651 sp_nodepath_node_destroy(curr);
2652 }
2654 }
2655 //###########################################
2656 //# END EDITS
2657 //###########################################
2659 //clean up the nodepath (such as for trivial subpaths)
2660 sp_nodepath_cleanup(nodepath);
2662 sp_nodepath_update_handles(nodepath);
2664 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2666 sp_nodepath_update_statusbar(nodepath);
2667 }
2669 /**
2670 * Call sp_nodepath_set_line() for all selected segments.
2671 */
2672 void
2673 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2674 {
2675 if (nodepath == NULL) return;
2677 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2678 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2679 g_assert(n->selected);
2680 if (n->p.other && n->p.other->selected) {
2681 sp_nodepath_set_line_type(n, code);
2682 }
2683 }
2685 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2686 }
2688 /**
2689 * Call sp_nodepath_convert_node_type() for all selected nodes.
2690 */
2691 void
2692 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2693 {
2694 if (nodepath == NULL) return;
2696 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2698 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2699 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2700 }
2702 sp_nodepath_update_repr(nodepath, _("Change node type"));
2703 }
2705 /**
2706 * Change select status of node, update its own and neighbour handles.
2707 */
2708 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2709 {
2710 node->selected = selected;
2712 if (selected) {
2713 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2714 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2715 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2716 sp_knot_update_ctrl(node->knot);
2717 } else {
2718 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2719 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2720 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2721 sp_knot_update_ctrl(node->knot);
2722 }
2724 sp_node_update_handles(node);
2725 if (node->n.other) sp_node_update_handles(node->n.other);
2726 if (node->p.other) sp_node_update_handles(node->p.other);
2727 }
2729 /**
2730 \brief Select a node
2731 \param node The node to select
2732 \param incremental If true, add to selection, otherwise deselect others
2733 \param override If true, always select this node, otherwise toggle selected status
2734 */
2735 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2736 {
2737 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2739 if (incremental) {
2740 if (override) {
2741 if (!g_list_find(nodepath->selected, node)) {
2742 nodepath->selected = g_list_prepend(nodepath->selected, node);
2743 }
2744 sp_node_set_selected(node, TRUE);
2745 } else { // toggle
2746 if (node->selected) {
2747 g_assert(g_list_find(nodepath->selected, node));
2748 nodepath->selected = g_list_remove(nodepath->selected, node);
2749 } else {
2750 g_assert(!g_list_find(nodepath->selected, node));
2751 nodepath->selected = g_list_prepend(nodepath->selected, node);
2752 }
2753 sp_node_set_selected(node, !node->selected);
2754 }
2755 } else {
2756 sp_nodepath_deselect(nodepath);
2757 nodepath->selected = g_list_prepend(nodepath->selected, node);
2758 sp_node_set_selected(node, TRUE);
2759 }
2761 sp_nodepath_update_statusbar(nodepath);
2762 }
2765 /**
2766 \brief Deselect all nodes in the nodepath
2767 */
2768 void
2769 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2770 {
2771 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2773 while (nodepath->selected) {
2774 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2775 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2776 }
2777 sp_nodepath_update_statusbar(nodepath);
2778 }
2780 /**
2781 \brief Select or invert selection of all nodes in the nodepath
2782 */
2783 void
2784 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2785 {
2786 if (!nodepath) return;
2788 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2789 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2790 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2791 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2792 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2793 }
2794 }
2795 }
2797 /**
2798 * If nothing selected, does the same as sp_nodepath_select_all();
2799 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2800 * (i.e., similar to "select all in layer", with the "selected" subpaths
2801 * being treated as "layers" in the path).
2802 */
2803 void
2804 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2805 {
2806 if (!nodepath) return;
2808 if (g_list_length (nodepath->selected) == 0) {
2809 sp_nodepath_select_all (nodepath, invert);
2810 return;
2811 }
2813 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2814 GSList *subpaths = NULL;
2816 for (GList *l = copy; l != NULL; l = l->next) {
2817 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2818 Inkscape::NodePath::SubPath *subpath = n->subpath;
2819 if (!g_slist_find (subpaths, subpath))
2820 subpaths = g_slist_prepend (subpaths, subpath);
2821 }
2823 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2824 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2825 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2826 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2827 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2828 }
2829 }
2831 g_slist_free (subpaths);
2832 g_list_free (copy);
2833 }
2835 /**
2836 * \brief Select the node after the last selected; if none is selected,
2837 * select the first within path.
2838 */
2839 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2840 {
2841 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2843 Inkscape::NodePath::Node *last = NULL;
2844 if (nodepath->selected) {
2845 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2846 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2847 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2848 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2849 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2850 if (node->selected) {
2851 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2852 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2853 if (spl->next) { // there's a next subpath
2854 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2855 last = subpath_next->first;
2856 } else if (spl->prev) { // there's a previous subpath
2857 last = NULL; // to be set later to the first node of first subpath
2858 } else {
2859 last = node->n.other;
2860 }
2861 } else {
2862 last = node->n.other;
2863 }
2864 } else {
2865 if (node->n.other) {
2866 last = node->n.other;
2867 } else {
2868 if (spl->next) { // there's a next subpath
2869 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2870 last = subpath_next->first;
2871 } else if (spl->prev) { // there's a previous subpath
2872 last = NULL; // to be set later to the first node of first subpath
2873 } else {
2874 last = (Inkscape::NodePath::Node *) subpath->first;
2875 }
2876 }
2877 }
2878 }
2879 }
2880 }
2881 sp_nodepath_deselect(nodepath);
2882 }
2884 if (last) { // there's at least one more node after selected
2885 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2886 } else { // no more nodes, select the first one in first subpath
2887 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2888 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2889 }
2890 }
2892 /**
2893 * \brief Select the node before the first selected; if none is selected,
2894 * select the last within path
2895 */
2896 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2897 {
2898 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2900 Inkscape::NodePath::Node *last = NULL;
2901 if (nodepath->selected) {
2902 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2903 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2904 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2905 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2906 if (node->selected) {
2907 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2908 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2909 if (spl->prev) { // there's a prev subpath
2910 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2911 last = subpath_prev->last;
2912 } else if (spl->next) { // there's a next subpath
2913 last = NULL; // to be set later to the last node of last subpath
2914 } else {
2915 last = node->p.other;
2916 }
2917 } else {
2918 last = node->p.other;
2919 }
2920 } else {
2921 if (node->p.other) {
2922 last = node->p.other;
2923 } else {
2924 if (spl->prev) { // there's a prev subpath
2925 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2926 last = subpath_prev->last;
2927 } else if (spl->next) { // there's a next subpath
2928 last = NULL; // to be set later to the last node of last subpath
2929 } else {
2930 last = (Inkscape::NodePath::Node *) subpath->last;
2931 }
2932 }
2933 }
2934 }
2935 }
2936 }
2937 sp_nodepath_deselect(nodepath);
2938 }
2940 if (last) { // there's at least one more node before selected
2941 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2942 } else { // no more nodes, select the last one in last subpath
2943 GList *spl = g_list_last(nodepath->subpaths);
2944 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2945 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2946 }
2947 }
2949 /**
2950 * \brief Select all nodes that are within the rectangle.
2951 */
2952 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2953 {
2954 if (!incremental) {
2955 sp_nodepath_deselect(nodepath);
2956 }
2958 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2959 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2960 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2961 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2963 if (b.contains(node->pos)) {
2964 sp_nodepath_node_select(node, TRUE, TRUE);
2965 }
2966 }
2967 }
2968 }
2971 void
2972 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2973 {
2974 g_assert (n);
2975 g_assert (nodepath);
2976 g_assert (n->subpath->nodepath == nodepath);
2978 if (g_list_length (nodepath->selected) == 0) {
2979 if (grow > 0) {
2980 sp_nodepath_node_select(n, TRUE, TRUE);
2981 }
2982 return;
2983 }
2985 if (g_list_length (nodepath->selected) == 1) {
2986 if (grow < 0) {
2987 sp_nodepath_deselect (nodepath);
2988 return;
2989 }
2990 }
2992 double n_sel_range = 0, p_sel_range = 0;
2993 Inkscape::NodePath::Node *farthest_n_node = n;
2994 Inkscape::NodePath::Node *farthest_p_node = n;
2996 // Calculate ranges
2997 {
2998 double n_range = 0, p_range = 0;
2999 bool n_going = true, p_going = true;
3000 Inkscape::NodePath::Node *n_node = n;
3001 Inkscape::NodePath::Node *p_node = n;
3002 do {
3003 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3004 if (n_node && n_going)
3005 n_node = n_node->n.other;
3006 if (n_node == NULL) {
3007 n_going = false;
3008 } else {
3009 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3010 if (n_node->selected) {
3011 n_sel_range = n_range;
3012 farthest_n_node = n_node;
3013 }
3014 if (n_node == p_node) {
3015 n_going = false;
3016 p_going = false;
3017 }
3018 }
3019 if (p_node && p_going)
3020 p_node = p_node->p.other;
3021 if (p_node == NULL) {
3022 p_going = false;
3023 } else {
3024 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3025 if (p_node->selected) {
3026 p_sel_range = p_range;
3027 farthest_p_node = p_node;
3028 }
3029 if (p_node == n_node) {
3030 n_going = false;
3031 p_going = false;
3032 }
3033 }
3034 } while (n_going || p_going);
3035 }
3037 if (grow > 0) {
3038 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3039 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3040 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3041 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3042 }
3043 } else {
3044 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3045 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3046 } else if (farthest_p_node && farthest_p_node->selected) {
3047 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3048 }
3049 }
3050 }
3052 void
3053 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3054 {
3055 g_assert (n);
3056 g_assert (nodepath);
3057 g_assert (n->subpath->nodepath == nodepath);
3059 if (g_list_length (nodepath->selected) == 0) {
3060 if (grow > 0) {
3061 sp_nodepath_node_select(n, TRUE, TRUE);
3062 }
3063 return;
3064 }
3066 if (g_list_length (nodepath->selected) == 1) {
3067 if (grow < 0) {
3068 sp_nodepath_deselect (nodepath);
3069 return;
3070 }
3071 }
3073 Inkscape::NodePath::Node *farthest_selected = NULL;
3074 double farthest_dist = 0;
3076 Inkscape::NodePath::Node *closest_unselected = NULL;
3077 double closest_dist = NR_HUGE;
3079 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3080 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3081 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3082 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3083 if (node == n)
3084 continue;
3085 if (node->selected) {
3086 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3087 farthest_dist = NR::L2(node->pos - n->pos);
3088 farthest_selected = node;
3089 }
3090 } else {
3091 if (NR::L2(node->pos - n->pos) < closest_dist) {
3092 closest_dist = NR::L2(node->pos - n->pos);
3093 closest_unselected = node;
3094 }
3095 }
3096 }
3097 }
3099 if (grow > 0) {
3100 if (closest_unselected) {
3101 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3102 }
3103 } else {
3104 if (farthest_selected) {
3105 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3106 }
3107 }
3108 }
3111 /**
3112 \brief Saves all nodes' and handles' current positions in their origin members
3113 */
3114 void
3115 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3116 {
3117 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3118 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3119 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3120 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3121 n->origin = n->pos;
3122 n->p.origin = n->p.pos;
3123 n->n.origin = n->n.pos;
3124 }
3125 }
3126 }
3128 /**
3129 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3130 */
3131 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3132 {
3133 if (!nodepath->selected) {
3134 return NULL;
3135 }
3137 GList *r = NULL;
3138 guint i = 0;
3139 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3140 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3141 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3142 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3143 i++;
3144 if (node->selected) {
3145 r = g_list_append(r, GINT_TO_POINTER(i));
3146 }
3147 }
3148 }
3149 return r;
3150 }
3152 /**
3153 \brief Restores selection by selecting nodes whose positions are in the list
3154 */
3155 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3156 {
3157 sp_nodepath_deselect(nodepath);
3159 guint i = 0;
3160 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3161 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3162 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3163 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3164 i++;
3165 if (g_list_find(r, GINT_TO_POINTER(i))) {
3166 sp_nodepath_node_select(node, TRUE, TRUE);
3167 }
3168 }
3169 }
3170 }
3173 /**
3174 \brief Adjusts handle according to node type and line code.
3175 */
3176 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3177 {
3178 g_assert(node);
3180 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3181 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3183 // nothing to do if we are an end node
3184 if (me->other == NULL) return;
3185 if (other->other == NULL) return;
3187 // nothing to do if we are a cusp node
3188 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3190 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3191 NRPathcode mecode;
3192 if (which_adjust == 1) {
3193 mecode = (NRPathcode)me->other->code;
3194 } else {
3195 mecode = (NRPathcode)node->code;
3196 }
3197 if (mecode == NR_LINETO) return;
3199 if (sp_node_side_is_line(node, other)) {
3200 // other is a line, and we are either smooth or symm
3201 Inkscape::NodePath::Node *othernode = other->other;
3202 double len = NR::L2(me->pos - node->pos);
3203 NR::Point delta = node->pos - othernode->pos;
3204 double linelen = NR::L2(delta);
3205 if (linelen < 1e-18)
3206 return;
3207 me->pos = node->pos + (len / linelen)*delta;
3208 return;
3209 }
3211 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3212 // symmetrize
3213 me->pos = 2 * node->pos - other->pos;
3214 return;
3215 } else {
3216 // smoothify
3217 double len = NR::L2(me->pos - node->pos);
3218 NR::Point delta = other->pos - node->pos;
3219 double otherlen = NR::L2(delta);
3220 if (otherlen < 1e-18) return;
3221 me->pos = node->pos - (len / otherlen) * delta;
3222 }
3223 }
3225 /**
3226 \brief Adjusts both handles according to node type and line code
3227 */
3228 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3229 {
3230 g_assert(node);
3232 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3234 /* we are either smooth or symm */
3236 if (node->p.other == NULL) return;
3237 if (node->n.other == NULL) return;
3239 if (sp_node_side_is_line(node, &node->p)) {
3240 sp_node_adjust_handle(node, 1);
3241 return;
3242 }
3244 if (sp_node_side_is_line(node, &node->n)) {
3245 sp_node_adjust_handle(node, -1);
3246 return;
3247 }
3249 /* both are curves */
3250 NR::Point const delta( node->n.pos - node->p.pos );
3252 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3253 node->p.pos = node->pos - delta / 2;
3254 node->n.pos = node->pos + delta / 2;
3255 return;
3256 }
3258 /* We are smooth */
3259 double plen = NR::L2(node->p.pos - node->pos);
3260 if (plen < 1e-18) return;
3261 double nlen = NR::L2(node->n.pos - node->pos);
3262 if (nlen < 1e-18) return;
3263 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3264 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3265 }
3267 /**
3268 * Node event callback.
3269 */
3270 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3271 {
3272 gboolean ret = FALSE;
3273 switch (event->type) {
3274 case GDK_ENTER_NOTIFY:
3275 Inkscape::NodePath::Path::active_node = n;
3276 break;
3277 case GDK_LEAVE_NOTIFY:
3278 Inkscape::NodePath::Path::active_node = NULL;
3279 break;
3280 case GDK_SCROLL:
3281 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3282 switch (event->scroll.direction) {
3283 case GDK_SCROLL_UP:
3284 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3285 break;
3286 case GDK_SCROLL_DOWN:
3287 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3288 break;
3289 default:
3290 break;
3291 }
3292 ret = TRUE;
3293 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3294 switch (event->scroll.direction) {
3295 case GDK_SCROLL_UP:
3296 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3297 break;
3298 case GDK_SCROLL_DOWN:
3299 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3300 break;
3301 default:
3302 break;
3303 }
3304 ret = TRUE;
3305 }
3306 break;
3307 case GDK_KEY_PRESS:
3308 switch (get_group0_keyval (&event->key)) {
3309 case GDK_space:
3310 if (event->key.state & GDK_BUTTON1_MASK) {
3311 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3312 stamp_repr(nodepath);
3313 ret = TRUE;
3314 }
3315 break;
3316 case GDK_Page_Up:
3317 if (event->key.state & GDK_CONTROL_MASK) {
3318 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3319 } else {
3320 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3321 }
3322 break;
3323 case GDK_Page_Down:
3324 if (event->key.state & GDK_CONTROL_MASK) {
3325 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3326 } else {
3327 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3328 }
3329 break;
3330 default:
3331 break;
3332 }
3333 break;
3334 default:
3335 break;
3336 }
3338 return ret;
3339 }
3341 /**
3342 * Handle keypress on node; directly called.
3343 */
3344 gboolean node_key(GdkEvent *event)
3345 {
3346 Inkscape::NodePath::Path *np;
3348 // there is no way to verify nodes so set active_node to nil when deleting!!
3349 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3351 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3352 gint ret = FALSE;
3353 switch (get_group0_keyval (&event->key)) {
3354 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3355 case GDK_BackSpace:
3356 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3357 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3358 sp_nodepath_update_repr(np, _("Delete node"));
3359 Inkscape::NodePath::Path::active_node = NULL;
3360 ret = TRUE;
3361 break;
3362 case GDK_c:
3363 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3364 ret = TRUE;
3365 break;
3366 case GDK_s:
3367 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3368 ret = TRUE;
3369 break;
3370 case GDK_y:
3371 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3372 ret = TRUE;
3373 break;
3374 case GDK_b:
3375 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3376 ret = TRUE;
3377 break;
3378 }
3379 return ret;
3380 }
3381 return FALSE;
3382 }
3384 /**
3385 * Mouseclick on node callback.
3386 */
3387 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3388 {
3389 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3391 if (state & GDK_CONTROL_MASK) {
3392 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3394 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3395 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3396 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3397 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3398 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3399 } else {
3400 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3401 }
3402 sp_nodepath_update_repr(nodepath, _("Change node type"));
3403 sp_nodepath_update_statusbar(nodepath);
3405 } else { //ctrl+alt+click: delete node
3406 GList *node_to_delete = NULL;
3407 node_to_delete = g_list_append(node_to_delete, n);
3408 sp_node_delete_preserve(node_to_delete);
3409 }
3411 } else {
3412 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3413 }
3414 }
3416 /**
3417 * Mouse grabbed node callback.
3418 */
3419 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3420 {
3421 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3423 if (!n->selected) {
3424 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3425 }
3427 n->is_dragging = true;
3428 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3430 sp_nodepath_remember_origins (n->subpath->nodepath);
3431 }
3433 /**
3434 * Mouse ungrabbed node callback.
3435 */
3436 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3437 {
3438 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3440 n->dragging_out = NULL;
3441 n->is_dragging = false;
3442 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3444 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3445 }
3447 /**
3448 * The point on a line, given by its angle, closest to the given point.
3449 * \param p A point.
3450 * \param a Angle of the line; it is assumed to go through coordinate origin.
3451 * \param closest Pointer to the point struct where the result is stored.
3452 * \todo FIXME: use dot product perhaps?
3453 */
3454 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3455 {
3456 if (a == HUGE_VAL) { // vertical
3457 *closest = NR::Point(0, (*p)[NR::Y]);
3458 } else {
3459 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3460 (*closest)[NR::Y] = a * (*closest)[NR::X];
3461 }
3462 }
3464 /**
3465 * Distance from the point to a line given by its angle.
3466 * \param p A point.
3467 * \param a Angle of the line; it is assumed to go through coordinate origin.
3468 */
3469 static double point_line_distance(NR::Point *p, double a)
3470 {
3471 NR::Point c;
3472 point_line_closest(p, a, &c);
3473 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]));
3474 }
3476 /**
3477 * Callback for node "request" signal.
3478 * \todo fixme: This goes to "moved" event? (lauris)
3479 */
3480 static gboolean
3481 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3482 {
3483 double yn, xn, yp, xp;
3484 double an, ap, na, pa;
3485 double d_an, d_ap, d_na, d_pa;
3486 gboolean collinear = FALSE;
3487 NR::Point c;
3488 NR::Point pr;
3490 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3492 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3494 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3495 if ( (!n->subpath->nodepath->straight_path) &&
3496 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3497 || n->dragging_out ) )
3498 {
3499 NR::Point mouse = (*p);
3501 if (!n->dragging_out) {
3502 // This is the first drag-out event; find out which handle to drag out
3503 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3504 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3506 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3507 return FALSE;
3509 Inkscape::NodePath::NodeSide *opposite;
3510 if (appr_p > appr_n) { // closer to p
3511 n->dragging_out = &n->p;
3512 opposite = &n->n;
3513 n->code = NR_CURVETO;
3514 } else if (appr_p < appr_n) { // closer to n
3515 n->dragging_out = &n->n;
3516 opposite = &n->p;
3517 n->n.other->code = NR_CURVETO;
3518 } else { // p and n nodes are the same
3519 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3520 n->dragging_out = &n->p;
3521 opposite = &n->n;
3522 n->code = NR_CURVETO;
3523 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3524 n->dragging_out = &n->n;
3525 opposite = &n->p;
3526 n->n.other->code = NR_CURVETO;
3527 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3528 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);
3529 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);
3530 if (appr_other_p > appr_other_n) { // closer to other's p handle
3531 n->dragging_out = &n->n;
3532 opposite = &n->p;
3533 n->n.other->code = NR_CURVETO;
3534 } else { // closer to other's n handle
3535 n->dragging_out = &n->p;
3536 opposite = &n->n;
3537 n->code = NR_CURVETO;
3538 }
3539 }
3540 }
3542 // if there's another handle, make sure the one we drag out starts parallel to it
3543 if (opposite->pos != n->pos) {
3544 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3545 }
3547 // knots might not be created yet!
3548 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3549 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3550 }
3552 // pass this on to the handle-moved callback
3553 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3554 sp_node_update_handles(n);
3555 return TRUE;
3556 }
3558 if (state & GDK_CONTROL_MASK) { // constrained motion
3560 // calculate relative distances of handles
3561 // n handle:
3562 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3563 xn = n->n.pos[NR::X] - n->pos[NR::X];
3564 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3565 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3566 if (n->n.other) { // if there is the next point
3567 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3568 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3569 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3570 }
3571 }
3572 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3573 if (yn < 0) { xn = -xn; yn = -yn; }
3575 // p handle:
3576 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3577 xp = n->p.pos[NR::X] - n->pos[NR::X];
3578 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3579 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3580 if (n->p.other) {
3581 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3582 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3583 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3584 }
3585 }
3586 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3587 if (yp < 0) { xp = -xp; yp = -yp; }
3589 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3590 // sliding on handles, only if at least one of the handles is non-vertical
3591 // (otherwise it's the same as ctrl+drag anyway)
3593 // calculate angles of the handles
3594 if (xn == 0) {
3595 if (yn == 0) { // no handle, consider it the continuation of the other one
3596 an = 0;
3597 collinear = TRUE;
3598 }
3599 else an = 0; // vertical; set the angle to horizontal
3600 } else an = yn/xn;
3602 if (xp == 0) {
3603 if (yp == 0) { // no handle, consider it the continuation of the other one
3604 ap = an;
3605 }
3606 else ap = 0; // vertical; set the angle to horizontal
3607 } else ap = yp/xp;
3609 if (collinear) an = ap;
3611 // angles of the perpendiculars; HUGE_VAL means vertical
3612 if (an == 0) na = HUGE_VAL; else na = -1/an;
3613 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3615 // mouse point relative to the node's original pos
3616 pr = (*p) - n->origin;
3618 // distances to the four lines (two handles and two perpendiculars)
3619 d_an = point_line_distance(&pr, an);
3620 d_na = point_line_distance(&pr, na);
3621 d_ap = point_line_distance(&pr, ap);
3622 d_pa = point_line_distance(&pr, pa);
3624 // find out which line is the closest, save its closest point in c
3625 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3626 point_line_closest(&pr, an, &c);
3627 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3628 point_line_closest(&pr, ap, &c);
3629 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3630 point_line_closest(&pr, na, &c);
3631 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3632 point_line_closest(&pr, pa, &c);
3633 }
3635 // move the node to the closest point
3636 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3637 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3638 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3639 true);
3641 } else { // constraining to hor/vert
3643 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3644 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3645 (*p)[NR::X] - n->pos[NR::X],
3646 n->origin[NR::Y] - n->pos[NR::Y],
3647 true,
3648 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3649 } else { // snap to vert
3650 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3651 n->origin[NR::X] - n->pos[NR::X],
3652 (*p)[NR::Y] - n->pos[NR::Y],
3653 true,
3654 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3655 }
3656 }
3657 } else { // move freely
3658 if (n->is_dragging) {
3659 if (state & GDK_MOD1_MASK) { // sculpt
3660 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3661 } else {
3662 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3663 (*p)[NR::X] - n->pos[NR::X],
3664 (*p)[NR::Y] - n->pos[NR::Y],
3665 (state & GDK_SHIFT_MASK) == 0);
3666 }
3667 }
3668 }
3670 n->subpath->nodepath->desktop->scroll_to_point(p);
3672 return TRUE;
3673 }
3675 /**
3676 * Node handle clicked callback.
3677 */
3678 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3679 {
3680 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3682 if (state & GDK_CONTROL_MASK) { // "delete" handle
3683 if (n->p.knot == knot) {
3684 n->p.pos = n->pos;
3685 } else if (n->n.knot == knot) {
3686 n->n.pos = n->pos;
3687 }
3688 sp_node_update_handles(n);
3689 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3690 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3691 sp_nodepath_update_statusbar(nodepath);
3693 } else { // just select or add to selection, depending in Shift
3694 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3695 }
3696 }
3698 /**
3699 * Node handle grabbed callback.
3700 */
3701 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3702 {
3703 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3705 if (!n->selected) {
3706 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3707 }
3709 // remember the origin point of the handle
3710 if (n->p.knot == knot) {
3711 n->p.origin_radial = n->p.pos - n->pos;
3712 } else if (n->n.knot == knot) {
3713 n->n.origin_radial = n->n.pos - n->pos;
3714 } else {
3715 g_assert_not_reached();
3716 }
3718 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3719 }
3721 /**
3722 * Node handle ungrabbed callback.
3723 */
3724 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3725 {
3726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3728 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3729 if (n->p.knot == knot) {
3730 n->p.origin_radial.a = 0;
3731 sp_knot_set_position(knot, &n->p.pos, state);
3732 } else if (n->n.knot == knot) {
3733 n->n.origin_radial.a = 0;
3734 sp_knot_set_position(knot, &n->n.pos, state);
3735 } else {
3736 g_assert_not_reached();
3737 }
3739 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3740 }
3742 /**
3743 * Node handle "request" signal callback.
3744 */
3745 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3746 {
3747 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3749 Inkscape::NodePath::NodeSide *me, *opposite;
3750 gint which;
3751 if (n->p.knot == knot) {
3752 me = &n->p;
3753 opposite = &n->n;
3754 which = -1;
3755 } else if (n->n.knot == knot) {
3756 me = &n->n;
3757 opposite = &n->p;
3758 which = 1;
3759 } else {
3760 me = opposite = NULL;
3761 which = 0;
3762 g_assert_not_reached();
3763 }
3765 SPDesktop *desktop = n->subpath->nodepath->desktop;
3766 SnapManager &m = desktop->namedview->snap_manager;
3767 m.setup(desktop, n->subpath->nodepath->item);
3768 Inkscape::SnappedPoint s ;
3770 Inkscape::NodePath::Node *othernode = opposite->other;
3771 if (othernode) {
3772 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3773 /* We are smooth node adjacent with line */
3774 NR::Point const delta = *p - n->pos;
3775 NR::Coord const len = NR::L2(delta);
3776 Inkscape::NodePath::Node *othernode = opposite->other;
3777 NR::Point const ndelta = n->pos - othernode->pos;
3778 NR::Coord const linelen = NR::L2(ndelta);
3779 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3780 NR::Coord const scal = dot(delta, ndelta) / linelen;
3781 (*p) = n->pos + (scal / linelen) * ndelta;
3782 }
3783 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3784 } else {
3785 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3786 }
3787 } else {
3788 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3789 }
3791 s.getPoint(*p);
3793 sp_node_adjust_handle(n, -which);
3795 return FALSE;
3796 }
3798 /**
3799 * Node handle moved callback.
3800 */
3801 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3802 {
3803 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3805 Inkscape::NodePath::NodeSide *me;
3806 Inkscape::NodePath::NodeSide *other;
3807 if (n->p.knot == knot) {
3808 me = &n->p;
3809 other = &n->n;
3810 } else if (n->n.knot == knot) {
3811 me = &n->n;
3812 other = &n->p;
3813 } else {
3814 me = NULL;
3815 other = NULL;
3816 g_assert_not_reached();
3817 }
3819 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3820 Radial rme(me->pos - n->pos);
3821 Radial rother(other->pos - n->pos);
3822 Radial rnew(*p - n->pos);
3824 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3825 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3826 /* 0 interpreted as "no snapping". */
3828 // 1. Snap to the closest PI/snaps angle, starting from zero.
3829 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3831 // 2. Snap to the original angle, its opposite and perpendiculars
3832 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3833 /* The closest PI/2 angle, starting from original angle */
3834 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3836 // Snap to the closest.
3837 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3838 ? a_snapped
3839 : a_ortho );
3840 }
3842 // 3. Snap to the angle of the opposite line, if any
3843 Inkscape::NodePath::Node *othernode = other->other;
3844 if (othernode) {
3845 NR::Point other_to_snap(0,0);
3846 if (sp_node_side_is_line(n, other)) {
3847 other_to_snap = othernode->pos - n->pos;
3848 } else {
3849 other_to_snap = other->pos - n->pos;
3850 }
3851 if (NR::L2(other_to_snap) > 1e-3) {
3852 Radial rother_to_snap(other_to_snap);
3853 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3854 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3856 // Snap to the closest.
3857 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3858 ? a_snapped
3859 : a_oppo );
3860 }
3861 }
3863 rnew.a = a_snapped;
3864 }
3866 if (state & GDK_MOD1_MASK) {
3867 // lock handle length
3868 rnew.r = me->origin_radial.r;
3869 }
3871 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3872 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3873 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3874 rother.a += rnew.a - rme.a;
3875 other->pos = NR::Point(rother) + n->pos;
3876 if (other->knot) {
3877 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3878 sp_knot_moveto(other->knot, &other->pos);
3879 }
3880 }
3882 me->pos = NR::Point(rnew) + n->pos;
3883 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3885 // move knot, but without emitting the signal:
3886 // we cannot emit a "moved" signal because we're now processing it
3887 sp_knot_moveto(me->knot, &(me->pos));
3889 update_object(n->subpath->nodepath);
3891 /* status text */
3892 SPDesktop *desktop = n->subpath->nodepath->desktop;
3893 if (!desktop) return;
3894 SPEventContext *ec = desktop->event_context;
3895 if (!ec) return;
3896 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3897 if (!mc) return;
3899 double degrees = 180 / M_PI * rnew.a;
3900 if (degrees > 180) degrees -= 360;
3901 if (degrees < -180) degrees += 360;
3902 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3903 degrees = angle_to_compass (degrees);
3905 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3907 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3908 _("<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);
3910 g_string_free(length, TRUE);
3911 }
3913 /**
3914 * Node handle event callback.
3915 */
3916 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3917 {
3918 gboolean ret = FALSE;
3919 switch (event->type) {
3920 case GDK_KEY_PRESS:
3921 switch (get_group0_keyval (&event->key)) {
3922 case GDK_space:
3923 if (event->key.state & GDK_BUTTON1_MASK) {
3924 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3925 stamp_repr(nodepath);
3926 ret = TRUE;
3927 }
3928 break;
3929 default:
3930 break;
3931 }
3932 break;
3933 case GDK_ENTER_NOTIFY:
3934 // we use an experimentally determined threshold that seems to work fine
3935 if (NR::L2(n->pos - knot->pos) < 0.75)
3936 Inkscape::NodePath::Path::active_node = n;
3937 break;
3938 case GDK_LEAVE_NOTIFY:
3939 // we use an experimentally determined threshold that seems to work fine
3940 if (NR::L2(n->pos - knot->pos) < 0.75)
3941 Inkscape::NodePath::Path::active_node = NULL;
3942 break;
3943 default:
3944 break;
3945 }
3947 return ret;
3948 }
3950 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3951 Radial &rme, Radial &rother, gboolean const both)
3952 {
3953 rme.a += angle;
3954 if ( both
3955 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3956 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3957 {
3958 rother.a += angle;
3959 }
3960 }
3962 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3963 Radial &rme, Radial &rother, gboolean const both)
3964 {
3965 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3967 gdouble r;
3968 if ( both
3969 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3970 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3971 {
3972 r = MAX(rme.r, rother.r);
3973 } else {
3974 r = rme.r;
3975 }
3977 gdouble const weird_angle = atan2(norm_angle, r);
3978 /* Bulia says norm_angle is just the visible distance that the
3979 * object's end must travel on the screen. Left as 'angle' for want of
3980 * a better name.*/
3982 rme.a += weird_angle;
3983 if ( both
3984 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3985 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3986 {
3987 rother.a += weird_angle;
3988 }
3989 }
3991 /**
3992 * Rotate one node.
3993 */
3994 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3995 {
3996 Inkscape::NodePath::NodeSide *me, *other;
3997 bool both = false;
3999 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4000 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4002 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4003 me = &(n->p);
4004 other = &(n->n);
4005 } else if (!n->p.other) {
4006 me = &(n->n);
4007 other = &(n->p);
4008 } else {
4009 if (which > 0) { // right handle
4010 if (xn > xp) {
4011 me = &(n->n);
4012 other = &(n->p);
4013 } else {
4014 me = &(n->p);
4015 other = &(n->n);
4016 }
4017 } else if (which < 0){ // left handle
4018 if (xn <= xp) {
4019 me = &(n->n);
4020 other = &(n->p);
4021 } else {
4022 me = &(n->p);
4023 other = &(n->n);
4024 }
4025 } else { // both handles
4026 me = &(n->n);
4027 other = &(n->p);
4028 both = true;
4029 }
4030 }
4032 Radial rme(me->pos - n->pos);
4033 Radial rother(other->pos - n->pos);
4035 if (screen) {
4036 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4037 } else {
4038 node_rotate_one_internal (*n, angle, rme, rother, both);
4039 }
4041 me->pos = n->pos + NR::Point(rme);
4043 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4044 other->pos = n->pos + NR::Point(rother);
4045 }
4047 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4048 // so here we just move all the knots without emitting move signals, for speed
4049 sp_node_update_handles(n, false);
4050 }
4052 /**
4053 * Rotate selected nodes.
4054 */
4055 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4056 {
4057 if (!nodepath || !nodepath->selected) return;
4059 if (g_list_length(nodepath->selected) == 1) {
4060 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4061 node_rotate_one (n, angle, which, screen);
4062 } else {
4063 // rotate as an object:
4065 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4066 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4067 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4068 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4069 box.expandTo (n->pos); // contain all selected nodes
4070 }
4072 gdouble rot;
4073 if (screen) {
4074 gdouble const zoom = nodepath->desktop->current_zoom();
4075 gdouble const zmove = angle / zoom;
4076 gdouble const r = NR::L2(box.max() - box.midpoint());
4077 rot = atan2(zmove, r);
4078 } else {
4079 rot = angle;
4080 }
4082 NR::Point rot_center;
4083 if (Inkscape::NodePath::Path::active_node == NULL)
4084 rot_center = box.midpoint();
4085 else
4086 rot_center = Inkscape::NodePath::Path::active_node->pos;
4088 NR::Matrix t =
4089 NR::Matrix (NR::translate(-rot_center)) *
4090 NR::Matrix (NR::rotate(rot)) *
4091 NR::Matrix (NR::translate(rot_center));
4093 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4094 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4095 n->pos *= t;
4096 n->n.pos *= t;
4097 n->p.pos *= t;
4098 sp_node_update_handles(n, false);
4099 }
4100 }
4102 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4103 }
4105 /**
4106 * Scale one node.
4107 */
4108 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4109 {
4110 bool both = false;
4111 Inkscape::NodePath::NodeSide *me, *other;
4113 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4114 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4116 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4117 me = &(n->p);
4118 other = &(n->n);
4119 n->code = NR_CURVETO;
4120 } else if (!n->p.other) {
4121 me = &(n->n);
4122 other = &(n->p);
4123 if (n->n.other)
4124 n->n.other->code = NR_CURVETO;
4125 } else {
4126 if (which > 0) { // right handle
4127 if (xn > xp) {
4128 me = &(n->n);
4129 other = &(n->p);
4130 if (n->n.other)
4131 n->n.other->code = NR_CURVETO;
4132 } else {
4133 me = &(n->p);
4134 other = &(n->n);
4135 n->code = NR_CURVETO;
4136 }
4137 } else if (which < 0){ // left handle
4138 if (xn <= xp) {
4139 me = &(n->n);
4140 other = &(n->p);
4141 if (n->n.other)
4142 n->n.other->code = NR_CURVETO;
4143 } else {
4144 me = &(n->p);
4145 other = &(n->n);
4146 n->code = NR_CURVETO;
4147 }
4148 } else { // both handles
4149 me = &(n->n);
4150 other = &(n->p);
4151 both = true;
4152 n->code = NR_CURVETO;
4153 if (n->n.other)
4154 n->n.other->code = NR_CURVETO;
4155 }
4156 }
4158 Radial rme(me->pos - n->pos);
4159 Radial rother(other->pos - n->pos);
4161 rme.r += grow;
4162 if (rme.r < 0) rme.r = 0;
4163 if (rme.a == HUGE_VAL) {
4164 if (me->other) { // if direction is unknown, initialize it towards the next node
4165 Radial rme_next(me->other->pos - n->pos);
4166 rme.a = rme_next.a;
4167 } else { // if there's no next, initialize to 0
4168 rme.a = 0;
4169 }
4170 }
4171 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4172 rother.r += grow;
4173 if (rother.r < 0) rother.r = 0;
4174 if (rother.a == HUGE_VAL) {
4175 rother.a = rme.a + M_PI;
4176 }
4177 }
4179 me->pos = n->pos + NR::Point(rme);
4181 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4182 other->pos = n->pos + NR::Point(rother);
4183 }
4185 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4186 // so here we just move all the knots without emitting move signals, for speed
4187 sp_node_update_handles(n, false);
4188 }
4190 /**
4191 * Scale selected nodes.
4192 */
4193 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4194 {
4195 if (!nodepath || !nodepath->selected) return;
4197 if (g_list_length(nodepath->selected) == 1) {
4198 // scale handles of the single selected node
4199 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4200 node_scale_one (n, grow, which);
4201 } else {
4202 // scale nodes as an "object":
4204 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4205 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4206 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4207 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4208 box.expandTo (n->pos); // contain all selected nodes
4209 }
4211 double scale = (box.maxExtent() + grow)/box.maxExtent();
4213 NR::Point scale_center;
4214 if (Inkscape::NodePath::Path::active_node == NULL)
4215 scale_center = box.midpoint();
4216 else
4217 scale_center = Inkscape::NodePath::Path::active_node->pos;
4219 NR::Matrix t =
4220 NR::Matrix (NR::translate(-scale_center)) *
4221 NR::Matrix (NR::scale(scale, scale)) *
4222 NR::Matrix (NR::translate(scale_center));
4224 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4225 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4226 n->pos *= t;
4227 n->n.pos *= t;
4228 n->p.pos *= t;
4229 sp_node_update_handles(n, false);
4230 }
4231 }
4233 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4234 }
4236 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4237 {
4238 if (!nodepath) return;
4239 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4240 }
4242 /**
4243 * Flip selected nodes horizontally/vertically.
4244 */
4245 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4246 {
4247 if (!nodepath || !nodepath->selected) return;
4249 if (g_list_length(nodepath->selected) == 1 && !center) {
4250 // flip handles of the single selected node
4251 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4252 double temp = n->p.pos[axis];
4253 n->p.pos[axis] = n->n.pos[axis];
4254 n->n.pos[axis] = temp;
4255 sp_node_update_handles(n, false);
4256 } else {
4257 // scale nodes as an "object":
4259 NR::Rect box = sp_node_selected_bbox (nodepath);
4260 if (!center) {
4261 center = box.midpoint();
4262 }
4263 NR::Matrix t =
4264 NR::Matrix (NR::translate(- *center)) *
4265 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4266 NR::Matrix (NR::translate(*center));
4268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4269 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4270 n->pos *= t;
4271 n->n.pos *= t;
4272 n->p.pos *= t;
4273 sp_node_update_handles(n, false);
4274 }
4275 }
4277 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4278 }
4280 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4281 {
4282 g_assert (nodepath->selected);
4284 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4285 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4286 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4287 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4288 box.expandTo (n->pos); // contain all selected nodes
4289 }
4290 return box;
4291 }
4293 //-----------------------------------------------
4294 /**
4295 * Return new subpath under given nodepath.
4296 */
4297 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4298 {
4299 g_assert(nodepath);
4300 g_assert(nodepath->desktop);
4302 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4304 s->nodepath = nodepath;
4305 s->closed = FALSE;
4306 s->nodes = NULL;
4307 s->first = NULL;
4308 s->last = NULL;
4310 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4311 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4312 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4314 return s;
4315 }
4317 /**
4318 * Destroy nodes in subpath, then subpath itself.
4319 */
4320 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4321 {
4322 g_assert(subpath);
4323 g_assert(subpath->nodepath);
4324 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4326 while (subpath->nodes) {
4327 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4328 }
4330 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4332 g_free(subpath);
4333 }
4335 /**
4336 * Link head to tail in subpath.
4337 */
4338 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4339 {
4340 g_assert(!sp->closed);
4341 g_assert(sp->last != sp->first);
4342 g_assert(sp->first->code == NR_MOVETO);
4344 sp->closed = TRUE;
4346 //Link the head to the tail
4347 sp->first->p.other = sp->last;
4348 sp->last->n.other = sp->first;
4349 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4350 sp->first = sp->last;
4352 //Remove the extra end node
4353 sp_nodepath_node_destroy(sp->last->n.other);
4354 }
4356 /**
4357 * Open closed (loopy) subpath at node.
4358 */
4359 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4360 {
4361 g_assert(sp->closed);
4362 g_assert(n->subpath == sp);
4363 g_assert(sp->first == sp->last);
4365 /* We create new startpoint, current node will become last one */
4367 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4368 &n->pos, &n->pos, &n->n.pos);
4371 sp->closed = FALSE;
4373 //Unlink to make a head and tail
4374 sp->first = new_path;
4375 sp->last = n;
4376 n->n.other = NULL;
4377 new_path->p.other = NULL;
4378 }
4380 /**
4381 * Return new node in subpath with given properties.
4382 * \param pos Position of node.
4383 * \param ppos Handle position in previous direction
4384 * \param npos Handle position in previous direction
4385 */
4386 Inkscape::NodePath::Node *
4387 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)
4388 {
4389 g_assert(sp);
4390 g_assert(sp->nodepath);
4391 g_assert(sp->nodepath->desktop);
4393 if (nodechunk == NULL)
4394 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4396 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4398 n->subpath = sp;
4400 if (type != Inkscape::NodePath::NODE_NONE) {
4401 // use the type from sodipodi:nodetypes
4402 n->type = type;
4403 } else {
4404 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4405 // points are (almost) collinear
4406 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4407 // endnode, or a node with a retracted handle
4408 n->type = Inkscape::NodePath::NODE_CUSP;
4409 } else {
4410 n->type = Inkscape::NodePath::NODE_SMOOTH;
4411 }
4412 } else {
4413 n->type = Inkscape::NodePath::NODE_CUSP;
4414 }
4415 }
4417 n->code = code;
4418 n->selected = FALSE;
4419 n->pos = *pos;
4420 n->p.pos = *ppos;
4421 n->n.pos = *npos;
4423 n->dragging_out = NULL;
4425 Inkscape::NodePath::Node *prev;
4426 if (next) {
4427 //g_assert(g_list_find(sp->nodes, next));
4428 prev = next->p.other;
4429 } else {
4430 prev = sp->last;
4431 }
4433 if (prev)
4434 prev->n.other = n;
4435 else
4436 sp->first = n;
4438 if (next)
4439 next->p.other = n;
4440 else
4441 sp->last = n;
4443 n->p.other = prev;
4444 n->n.other = next;
4446 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"));
4447 sp_knot_set_position(n->knot, pos, 0);
4449 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4450 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4451 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4452 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4453 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4454 sp_knot_update_ctrl(n->knot);
4456 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4457 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4458 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4459 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4460 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4461 sp_knot_show(n->knot);
4463 // We only create handle knots and lines on demand
4464 n->p.knot = NULL;
4465 n->p.line = NULL;
4466 n->n.knot = NULL;
4467 n->n.line = NULL;
4469 sp->nodes = g_list_prepend(sp->nodes, n);
4471 return n;
4472 }
4474 /**
4475 * Destroy node and its knots, link neighbors in subpath.
4476 */
4477 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4478 {
4479 g_assert(node);
4480 g_assert(node->subpath);
4481 g_assert(SP_IS_KNOT(node->knot));
4483 Inkscape::NodePath::SubPath *sp = node->subpath;
4485 if (node->selected) { // first, deselect
4486 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4487 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4488 }
4490 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4492 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4493 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4494 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4495 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4496 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4497 g_object_unref(G_OBJECT(node->knot));
4499 if (node->p.knot) {
4500 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4501 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4502 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4503 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4504 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4505 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4506 g_object_unref(G_OBJECT(node->p.knot));
4507 node->p.knot = NULL;
4508 }
4510 if (node->n.knot) {
4511 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4512 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4513 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4514 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4515 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4516 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4517 g_object_unref(G_OBJECT(node->n.knot));
4518 node->n.knot = NULL;
4519 }
4521 if (node->p.line)
4522 gtk_object_destroy(GTK_OBJECT(node->p.line));
4523 if (node->n.line)
4524 gtk_object_destroy(GTK_OBJECT(node->n.line));
4526 if (sp->nodes) { // there are others nodes on the subpath
4527 if (sp->closed) {
4528 if (sp->first == node) {
4529 g_assert(sp->last == node);
4530 sp->first = node->n.other;
4531 sp->last = sp->first;
4532 }
4533 node->p.other->n.other = node->n.other;
4534 node->n.other->p.other = node->p.other;
4535 } else {
4536 if (sp->first == node) {
4537 sp->first = node->n.other;
4538 sp->first->code = NR_MOVETO;
4539 }
4540 if (sp->last == node) sp->last = node->p.other;
4541 if (node->p.other) node->p.other->n.other = node->n.other;
4542 if (node->n.other) node->n.other->p.other = node->p.other;
4543 }
4544 } else { // this was the last node on subpath
4545 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4546 }
4548 g_mem_chunk_free(nodechunk, node);
4549 }
4551 /**
4552 * Returns one of the node's two sides.
4553 * \param which Indicates which side.
4554 * \return Pointer to previous node side if which==-1, next if which==1.
4555 */
4556 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4557 {
4558 g_assert(node);
4560 switch (which) {
4561 case -1:
4562 return &node->p;
4563 case 1:
4564 return &node->n;
4565 default:
4566 break;
4567 }
4569 g_assert_not_reached();
4571 return NULL;
4572 }
4574 /**
4575 * Return the other side of the node, given one of its sides.
4576 */
4577 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4578 {
4579 g_assert(node);
4581 if (me == &node->p) return &node->n;
4582 if (me == &node->n) return &node->p;
4584 g_assert_not_reached();
4586 return NULL;
4587 }
4589 /**
4590 * Return NRPathcode on the given side of the node.
4591 */
4592 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4593 {
4594 g_assert(node);
4596 if (me == &node->p) {
4597 if (node->p.other) return (NRPathcode)node->code;
4598 return NR_MOVETO;
4599 }
4601 if (me == &node->n) {
4602 if (node->n.other) return (NRPathcode)node->n.other->code;
4603 return NR_MOVETO;
4604 }
4606 g_assert_not_reached();
4608 return NR_END;
4609 }
4611 /**
4612 * Return node with the given index
4613 */
4614 Inkscape::NodePath::Node *
4615 sp_nodepath_get_node_by_index(int index)
4616 {
4617 Inkscape::NodePath::Node *e = NULL;
4619 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4620 if (!nodepath) {
4621 return e;
4622 }
4624 //find segment
4625 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4627 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4628 int n = g_list_length(sp->nodes);
4629 if (sp->closed) {
4630 n++;
4631 }
4633 //if the piece belongs to this subpath grab it
4634 //otherwise move onto the next subpath
4635 if (index < n) {
4636 e = sp->first;
4637 for (int i = 0; i < index; ++i) {
4638 e = e->n.other;
4639 }
4640 break;
4641 } else {
4642 if (sp->closed) {
4643 index -= (n+1);
4644 } else {
4645 index -= n;
4646 }
4647 }
4648 }
4650 return e;
4651 }
4653 /**
4654 * Returns plain text meaning of node type.
4655 */
4656 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4657 {
4658 unsigned retracted = 0;
4659 bool endnode = false;
4661 for (int which = -1; which <= 1; which += 2) {
4662 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4663 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4664 retracted ++;
4665 if (!side->other)
4666 endnode = true;
4667 }
4669 if (retracted == 0) {
4670 if (endnode) {
4671 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4672 return _("end node");
4673 } else {
4674 switch (node->type) {
4675 case Inkscape::NodePath::NODE_CUSP:
4676 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4677 return _("cusp");
4678 case Inkscape::NodePath::NODE_SMOOTH:
4679 // TRANSLATORS: "smooth" is an adjective here
4680 return _("smooth");
4681 case Inkscape::NodePath::NODE_SYMM:
4682 return _("symmetric");
4683 }
4684 }
4685 } else if (retracted == 1) {
4686 if (endnode) {
4687 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4688 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4689 } else {
4690 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4691 }
4692 } else {
4693 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4694 }
4696 return NULL;
4697 }
4699 /**
4700 * Handles content of statusbar as long as node tool is active.
4701 */
4702 void
4703 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4704 {
4705 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");
4706 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4708 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4709 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4710 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4711 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4713 SPDesktop *desktop = NULL;
4714 if (nodepath) {
4715 desktop = nodepath->desktop;
4716 } else {
4717 desktop = SP_ACTIVE_DESKTOP;
4718 }
4720 SPEventContext *ec = desktop->event_context;
4721 if (!ec) return;
4722 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4723 if (!mc) return;
4725 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4727 if (selected_nodes == 0) {
4728 Inkscape::Selection *sel = desktop->selection;
4729 if (!sel || sel->isEmpty()) {
4730 mc->setF(Inkscape::NORMAL_MESSAGE,
4731 _("Select a single object to edit its nodes or handles."));
4732 } else {
4733 if (nodepath) {
4734 mc->setF(Inkscape::NORMAL_MESSAGE,
4735 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.",
4736 "<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.",
4737 total_nodes),
4738 total_nodes);
4739 } else {
4740 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4741 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4742 } else {
4743 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4744 }
4745 }
4746 }
4747 } else if (nodepath && selected_nodes == 1) {
4748 mc->setF(Inkscape::NORMAL_MESSAGE,
4749 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4750 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4751 total_nodes),
4752 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4753 } else {
4754 if (selected_subpaths > 1) {
4755 mc->setF(Inkscape::NORMAL_MESSAGE,
4756 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4757 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4758 total_nodes),
4759 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4760 } else {
4761 mc->setF(Inkscape::NORMAL_MESSAGE,
4762 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4763 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4764 total_nodes),
4765 selected_nodes, total_nodes, when_selected);
4766 }
4767 }
4768 }
4770 /*
4771 * returns a *copy* of the curve of that object.
4772 */
4773 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4774 if (!object)
4775 return NULL;
4777 SPCurve *curve = NULL;
4778 if (SP_IS_PATH(object)) {
4779 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4780 curve = curve_new->copy();
4781 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4782 const gchar *svgd = object->repr->attribute(key);
4783 if (svgd) {
4784 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4785 SPCurve *curve_new = new SPCurve(pv);
4786 if (curve_new) {
4787 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4788 }
4789 }
4790 }
4792 return curve;
4793 }
4795 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4796 if (!np || !np->object || !curve)
4797 return;
4799 if (SP_IS_PATH(np->object)) {
4800 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4801 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4802 } else {
4803 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4804 }
4805 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4806 // FIXME: this writing to string and then reading from string is bound to be slow.
4807 // create a method to convert from curve directly to 2geom...
4808 gchar *svgpath = sp_svg_write_path( np->curve->get_pathvector() );
4809 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4810 g_free(svgpath);
4812 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4813 }
4814 }
4816 SPCanvasItem *
4817 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4818 SPCurve *flash_curve = curve->copy();
4819 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4820 flash_curve->transform(i2d);
4821 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4822 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4823 // unless we also flash the nodes...
4824 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4825 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4826 sp_canvas_item_show(canvasitem);
4827 flash_curve->unref();
4828 return canvasitem;
4829 }
4831 SPCanvasItem *
4832 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4833 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4834 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4835 }
4837 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4838 np->show_helperpath = show;
4840 if (show) {
4841 SPCurve *helper_curve = np->curve->copy();
4842 helper_curve->transform(np->i2d );
4843 if (!np->helper_path) {
4844 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4845 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);
4846 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4847 sp_canvas_item_move_to_z(np->helper_path, 0);
4848 sp_canvas_item_show(np->helper_path);
4849 } else {
4850 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4851 }
4852 helper_curve->unref();
4853 } else {
4854 if (np->helper_path) {
4855 GtkObject *temp = np->helper_path;
4856 np->helper_path = NULL;
4857 gtk_object_destroy(temp);
4858 }
4859 }
4860 }
4862 /* sp_nodepath_make_straight_path:
4863 * Prevents user from curving the path by dragging a segment or activating handles etc.
4864 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4865 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4866 */
4867 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4868 np->straight_path = true;
4869 np->show_handles = false;
4870 g_message("add code to make the path straight.");
4871 // do sp_nodepath_convert_node_type on all nodes?
4872 // coding tip: search for this text : "Make selected segments lines"
4873 }
4876 /*
4877 Local Variables:
4878 mode:c++
4879 c-file-style:"stroustrup"
4880 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4881 indent-tabs-mode:nil
4882 fill-column:99
4883 End:
4884 */
4885 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :