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/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include "libnr/nr-matrix-ops.h"
41 #include "splivarot.h"
42 #include "svg/svg.h"
43 #include "display/bezier-utils.h"
44 #include <vector>
45 #include <algorithm>
47 class NR::Matrix;
49 /// \todo
50 /// evil evil evil. FIXME: conflict of two different Path classes!
51 /// There is a conflict in the namespace between two classes named Path.
52 /// #include "sp-flowtext.h"
53 /// #include "sp-flowregion.h"
55 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
56 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
57 GType sp_flowregion_get_type (void);
58 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
59 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
60 GType sp_flowtext_get_type (void);
61 // end evil workaround
63 #include "helper/stlport.h"
66 /// \todo fixme: Implement these via preferences */
68 #define NODE_FILL 0xbfbfbf00
69 #define NODE_STROKE 0x000000ff
70 #define NODE_FILL_HI 0xff000000
71 #define NODE_STROKE_HI 0x000000ff
72 #define NODE_FILL_SEL 0x0000ffff
73 #define NODE_STROKE_SEL 0x000000ff
74 #define NODE_FILL_SEL_HI 0xff000000
75 #define NODE_STROKE_SEL_HI 0x000000ff
76 #define KNOT_FILL 0xffffffff
77 #define KNOT_STROKE 0x000000ff
78 #define KNOT_FILL_HI 0xff000000
79 #define KNOT_STROKE_HI 0x000000ff
81 static GMemChunk *nodechunk = NULL;
83 /* Creation from object */
85 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
86 static gchar *parse_nodetypes(gchar const *types, gint length);
88 /* Object updating */
90 static void stamp_repr(Inkscape::NodePath::Path *np);
91 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
92 static gchar *create_typestr(Inkscape::NodePath::Path *np);
94 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
96 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
98 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
100 /* Adjust handle placement, if the node or the other handle is moved */
101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
102 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
104 /* Node event callbacks */
105 static void node_clicked(SPKnot *knot, guint state, gpointer data);
106 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
107 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
108 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
110 /* Handle event callbacks */
111 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
112 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
114 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
115 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
118 /* Constructors and destructors */
120 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
121 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
122 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
123 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
124 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
125 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
126 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
128 /* Helpers */
130 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
131 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 // active_node indicates mouseover node
135 static Inkscape::NodePath::Node *active_node = NULL;
137 /**
138 * \brief Creates new nodepath from item
139 */
140 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
141 {
142 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
144 /** \todo
145 * FIXME: remove this. We don't want to edit paths inside flowtext.
146 * Instead we will build our flowtext with cloned paths, so that the
147 * real paths are outside the flowtext and thus editable as usual.
148 */
149 if (SP_IS_FLOWTEXT(item)) {
150 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
151 if SP_IS_FLOWREGION(child) {
152 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
153 if (grandchild && SP_IS_PATH(grandchild)) {
154 item = SP_ITEM(grandchild);
155 break;
156 }
157 }
158 }
159 }
161 if (!SP_IS_PATH(item))
162 return NULL;
163 SPPath *path = SP_PATH(item);
164 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
165 if (curve == NULL)
166 return NULL;
168 NArtBpath *bpath = sp_curve_first_bpath(curve);
169 gint length = curve->end;
170 if (length == 0)
171 return NULL; // prevent crash for one-node paths
173 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
174 gchar *typestr = parse_nodetypes(nodetypes, length);
176 //Create new nodepath
177 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
178 if (!np)
179 return NULL;
181 // Set defaults
182 np->desktop = desktop;
183 np->path = path;
184 np->subpaths = NULL;
185 np->selected = NULL;
186 np->nodeContext = NULL; //Let the context that makes this set it
187 np->livarot_path = NULL;
188 np->local_change = 0;
190 // we need to update item's transform from the repr here,
191 // because they may be out of sync when we respond
192 // to a change in repr by regenerating nodepath --bb
193 sp_object_read_attr(SP_OBJECT(item), "transform");
195 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
196 np->d2i = np->i2d.inverse();
197 np->repr = repr;
199 // create the subpath(s) from the bpath
200 NArtBpath *b = bpath;
201 while (b->code != NR_END) {
202 b = subpath_from_bpath(np, b, typestr + (b - bpath));
203 }
205 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
206 np->subpaths = g_list_reverse(np->subpaths);
208 g_free(typestr);
209 sp_curve_unref(curve);
211 // create the livarot representation from the same item
212 np->livarot_path = Path_for_item(item, true, true);
213 if (np->livarot_path)
214 np->livarot_path->ConvertWithBackData(0.01);
216 return np;
217 }
219 /**
220 * Destroys nodepath's subpaths, then itself, also tell context about it.
221 */
222 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
224 if (!np) //soft fail, like delete
225 return;
227 while (np->subpaths) {
228 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
229 }
231 //Inform the context that made me, if any, that I am gone.
232 if (np->nodeContext)
233 np->nodeContext->nodepath = NULL;
235 g_assert(!np->selected);
237 if (np->livarot_path) {
238 delete np->livarot_path;
239 np->livarot_path = NULL;
240 }
242 np->desktop = NULL;
244 g_free(np);
245 }
248 /**
249 * Return the node count of a given NodeSubPath.
250 */
251 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
252 {
253 if (!subpath)
254 return 0;
255 gint nodeCount = g_list_length(subpath->nodes);
256 return nodeCount;
257 }
259 /**
260 * Return the node count of a given NodePath.
261 */
262 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
263 {
264 if (!np)
265 return 0;
266 gint nodeCount = 0;
267 for (GList *item = np->subpaths ; item ; item=item->next) {
268 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
269 nodeCount += g_list_length(subpath->nodes);
270 }
271 return nodeCount;
272 }
275 /**
276 * Clean up a nodepath after editing.
277 *
278 * Currently we are deleting trivial subpaths.
279 */
280 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
281 {
282 GList *badSubPaths = NULL;
284 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
285 for (GList *l = nodepath->subpaths; l ; l=l->next) {
286 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
287 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
288 badSubPaths = g_list_append(badSubPaths, sp);
289 }
291 //Delete them. This second step is because sp_nodepath_subpath_destroy()
292 //also removes the subpath from nodepath->subpaths
293 for (GList *l = badSubPaths; l ; l=l->next) {
294 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
295 sp_nodepath_subpath_destroy(sp);
296 }
298 g_list_free(badSubPaths);
299 }
301 /**
302 * Create new nodepath from b, make it subpath of np.
303 * \param t The node type.
304 * \todo Fixme: t should be a proper type, rather than gchar
305 */
306 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
307 {
308 NR::Point ppos, pos, npos;
310 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
312 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
313 bool const closed = (b->code == NR_MOVETO);
315 pos = NR::Point(b->x3, b->y3) * np->i2d;
316 if (b[1].code == NR_CURVETO) {
317 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
318 } else {
319 npos = pos;
320 }
321 Inkscape::NodePath::Node *n;
322 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
323 g_assert(sp->first == n);
324 g_assert(sp->last == n);
326 b++;
327 t++;
328 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
329 pos = NR::Point(b->x3, b->y3) * np->i2d;
330 if (b->code == NR_CURVETO) {
331 ppos = NR::Point(b->x2, b->y2) * np->i2d;
332 } else {
333 ppos = pos;
334 }
335 if (b[1].code == NR_CURVETO) {
336 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
337 } else {
338 npos = pos;
339 }
340 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
341 b++;
342 t++;
343 }
345 if (closed) sp_nodepath_subpath_close(sp);
347 return b;
348 }
350 /**
351 * Convert from sodipodi:nodetypes to new style type string.
352 */
353 static gchar *parse_nodetypes(gchar const *types, gint length)
354 {
355 g_assert(length > 0);
357 gchar *typestr = g_new(gchar, length + 1);
359 gint pos = 0;
361 if (types) {
362 for (gint i = 0; types[i] && ( i < length ); i++) {
363 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
364 if (types[i] != '\0') {
365 switch (types[i]) {
366 case 's':
367 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
368 break;
369 case 'z':
370 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
371 break;
372 case 'c':
373 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
374 break;
375 default:
376 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
377 break;
378 }
379 }
380 }
381 }
383 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
385 return typestr;
386 }
388 /**
389 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
390 * updated but repr is not (for speed). Used during curve and node drag.
391 */
392 static void update_object(Inkscape::NodePath::Path *np)
393 {
394 g_assert(np);
396 SPCurve *curve = create_curve(np);
398 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
400 sp_curve_unref(curve);
401 }
403 /**
404 * Update XML path node with data from path object.
405 */
406 static void update_repr_internal(Inkscape::NodePath::Path *np)
407 {
408 g_assert(np);
410 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
412 SPCurve *curve = create_curve(np);
413 gchar *typestr = create_typestr(np);
414 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
416 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
417 np->local_change++;
418 repr->setAttribute("d", svgpath);
419 }
421 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
422 np->local_change++;
423 repr->setAttribute("sodipodi:nodetypes", typestr);
424 }
426 g_free(svgpath);
427 g_free(typestr);
428 sp_curve_unref(curve);
429 }
431 /**
432 * Update XML path node with data from path object, commit changes forever.
433 */
434 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
435 {
436 update_repr_internal(np);
437 sp_document_done(sp_desktop_document(np->desktop));
439 if (np->livarot_path) {
440 delete np->livarot_path;
441 np->livarot_path = NULL;
442 }
444 if (np->path && SP_IS_ITEM(np->path)) {
445 np->livarot_path = Path_for_item (np->path, true, true);
446 if (np->livarot_path)
447 np->livarot_path->ConvertWithBackData(0.01);
448 }
449 }
451 /**
452 * Update XML path node with data from path object, commit changes with undo.
453 */
454 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
455 {
456 update_repr_internal(np);
457 sp_document_maybe_done(sp_desktop_document(np->desktop), key);
459 if (np->livarot_path) {
460 delete np->livarot_path;
461 np->livarot_path = NULL;
462 }
464 if (np->path && SP_IS_ITEM(np->path)) {
465 np->livarot_path = Path_for_item (np->path, true, true);
466 if (np->livarot_path)
467 np->livarot_path->ConvertWithBackData(0.01);
468 }
469 }
471 /**
472 * Make duplicate of path, replace corresponding XML node in tree, commit.
473 */
474 static void stamp_repr(Inkscape::NodePath::Path *np)
475 {
476 g_assert(np);
478 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
479 Inkscape::XML::Node *new_repr = old_repr->duplicate();
481 // remember the position of the item
482 gint pos = old_repr->position();
483 // remember parent
484 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
486 SPCurve *curve = create_curve(np);
487 gchar *typestr = create_typestr(np);
489 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
491 new_repr->setAttribute("d", svgpath);
492 new_repr->setAttribute("sodipodi:nodetypes", typestr);
494 // add the new repr to the parent
495 parent->appendChild(new_repr);
496 // move to the saved position
497 new_repr->setPosition(pos > 0 ? pos : 0);
499 sp_document_done(sp_desktop_document(np->desktop));
501 Inkscape::GC::release(new_repr);
502 g_free(svgpath);
503 g_free(typestr);
504 sp_curve_unref(curve);
505 }
507 /**
508 * Create curve from path.
509 */
510 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
511 {
512 SPCurve *curve = sp_curve_new();
514 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
515 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
516 sp_curve_moveto(curve,
517 sp->first->pos * np->d2i);
518 Inkscape::NodePath::Node *n = sp->first->n.other;
519 while (n) {
520 NR::Point const end_pt = n->pos * np->d2i;
521 switch (n->code) {
522 case NR_LINETO:
523 sp_curve_lineto(curve, end_pt);
524 break;
525 case NR_CURVETO:
526 sp_curve_curveto(curve,
527 n->p.other->n.pos * np->d2i,
528 n->p.pos * np->d2i,
529 end_pt);
530 break;
531 default:
532 g_assert_not_reached();
533 break;
534 }
535 if (n != sp->last) {
536 n = n->n.other;
537 } else {
538 n = NULL;
539 }
540 }
541 if (sp->closed) {
542 sp_curve_closepath(curve);
543 }
544 }
546 return curve;
547 }
549 /**
550 * Convert path type string to sodipodi:nodetypes style.
551 */
552 static gchar *create_typestr(Inkscape::NodePath::Path *np)
553 {
554 gchar *typestr = g_new(gchar, 32);
555 gint len = 32;
556 gint pos = 0;
558 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
559 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
561 if (pos >= len) {
562 typestr = g_renew(gchar, typestr, len + 32);
563 len += 32;
564 }
566 typestr[pos++] = 'c';
568 Inkscape::NodePath::Node *n;
569 n = sp->first->n.other;
570 while (n) {
571 gchar code;
573 switch (n->type) {
574 case Inkscape::NodePath::NODE_CUSP:
575 code = 'c';
576 break;
577 case Inkscape::NodePath::NODE_SMOOTH:
578 code = 's';
579 break;
580 case Inkscape::NodePath::NODE_SYMM:
581 code = 'z';
582 break;
583 default:
584 g_assert_not_reached();
585 code = '\0';
586 break;
587 }
589 if (pos >= len) {
590 typestr = g_renew(gchar, typestr, len + 32);
591 len += 32;
592 }
594 typestr[pos++] = code;
596 if (n != sp->last) {
597 n = n->n.other;
598 } else {
599 n = NULL;
600 }
601 }
602 }
604 if (pos >= len) {
605 typestr = g_renew(gchar, typestr, len + 1);
606 len += 1;
607 }
609 typestr[pos++] = '\0';
611 return typestr;
612 }
614 /**
615 * Returns current path in context.
616 */
617 static Inkscape::NodePath::Path *sp_nodepath_current()
618 {
619 if (!SP_ACTIVE_DESKTOP) {
620 return NULL;
621 }
623 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
625 if (!SP_IS_NODE_CONTEXT(event_context)) {
626 return NULL;
627 }
629 return SP_NODE_CONTEXT(event_context)->nodepath;
630 }
634 /**
635 \brief Fills node and handle positions for three nodes, splitting line
636 marked by end at distance t.
637 */
638 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
639 {
640 g_assert(new_path != NULL);
641 g_assert(end != NULL);
643 g_assert(end->p.other == new_path);
644 Inkscape::NodePath::Node *start = new_path->p.other;
645 g_assert(start);
647 if (end->code == NR_LINETO) {
648 new_path->type =Inkscape::NodePath::NODE_CUSP;
649 new_path->code = NR_LINETO;
650 new_path->pos = (t * start->pos + (1 - t) * end->pos);
651 } else {
652 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
653 new_path->code = NR_CURVETO;
654 gdouble s = 1 - t;
655 for (int dim = 0; dim < 2; dim++) {
656 NR::Coord const f000 = start->pos[dim];
657 NR::Coord const f001 = start->n.pos[dim];
658 NR::Coord const f011 = end->p.pos[dim];
659 NR::Coord const f111 = end->pos[dim];
660 NR::Coord const f00t = s * f000 + t * f001;
661 NR::Coord const f01t = s * f001 + t * f011;
662 NR::Coord const f11t = s * f011 + t * f111;
663 NR::Coord const f0tt = s * f00t + t * f01t;
664 NR::Coord const f1tt = s * f01t + t * f11t;
665 NR::Coord const fttt = s * f0tt + t * f1tt;
666 start->n.pos[dim] = f00t;
667 new_path->p.pos[dim] = f0tt;
668 new_path->pos[dim] = fttt;
669 new_path->n.pos[dim] = f1tt;
670 end->p.pos[dim] = f11t;
671 }
672 }
673 }
675 /**
676 * Adds new node on direct line between two nodes, activates handles of all
677 * three nodes.
678 */
679 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
680 {
681 g_assert(end);
682 g_assert(end->subpath);
683 g_assert(g_list_find(end->subpath->nodes, end));
685 Inkscape::NodePath::Node *start = end->p.other;
686 g_assert( start->n.other == end );
687 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
688 end,
689 Inkscape::NodePath::NODE_SMOOTH,
690 (NRPathcode)end->code,
691 &start->pos, &start->pos, &start->n.pos);
692 sp_nodepath_line_midpoint(newnode, end, t);
694 sp_node_update_handles(start);
695 sp_node_update_handles(newnode);
696 sp_node_update_handles(end);
698 return newnode;
699 }
701 /**
702 \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
703 */
704 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
705 {
706 g_assert(node);
707 g_assert(node->subpath);
708 g_assert(g_list_find(node->subpath->nodes, node));
710 Inkscape::NodePath::SubPath *sp = node->subpath;
711 Inkscape::NodePath::Path *np = sp->nodepath;
713 if (sp->closed) {
714 sp_nodepath_subpath_open(sp, node);
715 return sp->first;
716 } else {
717 // no break for end nodes
718 if (node == sp->first) return NULL;
719 if (node == sp->last ) return NULL;
721 // create a new subpath
722 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
724 // duplicate the break node as start of the new subpath
725 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
727 while (node->n.other) { // copy the remaining nodes into the new subpath
728 Inkscape::NodePath::Node *n = node->n.other;
729 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
730 if (n->selected) {
731 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
732 }
733 sp_nodepath_node_destroy(n); // remove the point on the original subpath
734 }
736 return newnode;
737 }
738 }
740 /**
741 * Duplicate node and connect to neighbours.
742 */
743 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
744 {
745 g_assert(node);
746 g_assert(node->subpath);
747 g_assert(g_list_find(node->subpath->nodes, node));
749 Inkscape::NodePath::SubPath *sp = node->subpath;
751 NRPathcode code = (NRPathcode) node->code;
752 if (code == NR_MOVETO) { // if node is the endnode,
753 node->code = NR_LINETO; // new one is inserted before it, so change that to line
754 }
756 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
758 if (!node->n.other || !node->p.other) // if node is an endnode, select it
759 return node;
760 else
761 return newnode; // otherwise select the newly created node
762 }
764 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
765 {
766 node->p.pos = (node->pos + (node->pos - node->n.pos));
767 }
769 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
770 {
771 node->n.pos = (node->pos + (node->pos - node->p.pos));
772 }
774 /**
775 * Change line type at node, with side effects on neighbours.
776 */
777 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
778 {
779 g_assert(end);
780 g_assert(end->subpath);
781 g_assert(end->p.other);
783 if (end->code == static_cast< guint > ( code ) )
784 return;
786 Inkscape::NodePath::Node *start = end->p.other;
788 end->code = code;
790 if (code == NR_LINETO) {
791 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
792 if (end->n.other) {
793 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
794 }
795 sp_node_adjust_handle(start, -1);
796 sp_node_adjust_handle(end, 1);
797 } else {
798 NR::Point delta = end->pos - start->pos;
799 start->n.pos = start->pos + delta / 3;
800 end->p.pos = end->pos - delta / 3;
801 sp_node_adjust_handle(start, 1);
802 sp_node_adjust_handle(end, -1);
803 }
805 sp_node_update_handles(start);
806 sp_node_update_handles(end);
807 }
809 /**
810 * Change node type, and its handles accordingly.
811 */
812 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
813 {
814 g_assert(node);
815 g_assert(node->subpath);
817 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
818 return node;
820 if ((node->p.other != NULL) && (node->n.other != NULL)) {
821 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
822 type =Inkscape::NodePath::NODE_CUSP;
823 }
824 }
826 node->type = type;
828 if (node->type == Inkscape::NodePath::NODE_CUSP) {
829 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
830 node->knot->setSize (node->selected? 11 : 9);
831 sp_knot_update_ctrl(node->knot);
832 } else {
833 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
834 node->knot->setSize (node->selected? 9 : 7);
835 sp_knot_update_ctrl(node->knot);
836 }
838 // if one of handles is mouseovered, preserve its position
839 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
840 sp_node_adjust_handle(node, 1);
841 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
842 sp_node_adjust_handle(node, -1);
843 } else {
844 sp_node_adjust_handles(node);
845 }
847 sp_node_update_handles(node);
849 sp_nodepath_update_statusbar(node->subpath->nodepath);
851 return node;
852 }
854 /**
855 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
856 * adjacent segments from lines to curves.
857 */
858 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
859 {
860 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
861 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
862 // convert adjacent segment BEFORE to curve
863 node->code = NR_CURVETO;
864 NR::Point delta;
865 if (node->n.other != NULL)
866 delta = node->n.other->pos - node->p.other->pos;
867 else
868 delta = node->pos - node->p.other->pos;
869 node->p.pos = node->pos - delta / 4;
870 sp_node_update_handles(node);
871 }
873 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
874 // convert adjacent segment AFTER to curve
875 node->n.other->code = NR_CURVETO;
876 NR::Point delta;
877 if (node->p.other != NULL)
878 delta = node->p.other->pos - node->n.other->pos;
879 else
880 delta = node->pos - node->n.other->pos;
881 node->n.pos = node->pos - delta / 4;
882 sp_node_update_handles(node);
883 }
884 }
886 sp_nodepath_set_node_type (node, type);
887 }
889 /**
890 * Move node to point, and adjust its and neighbouring handles.
891 */
892 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
893 {
894 NR::Point delta = p - node->pos;
895 node->pos = p;
897 node->p.pos += delta;
898 node->n.pos += delta;
900 if (node->p.other) {
901 if (node->code == NR_LINETO) {
902 sp_node_adjust_handle(node, 1);
903 sp_node_adjust_handle(node->p.other, -1);
904 }
905 }
906 if (node->n.other) {
907 if (node->n.other->code == NR_LINETO) {
908 sp_node_adjust_handle(node, -1);
909 sp_node_adjust_handle(node->n.other, 1);
910 }
911 }
913 // this function is only called from batch movers that will update display at the end
914 // themselves, so here we just move all the knots without emitting move signals, for speed
915 sp_node_update_handles(node, false);
916 }
918 /**
919 * Call sp_node_moveto() for node selection and handle possible snapping.
920 */
921 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
922 bool const snap = true)
923 {
924 NR::Coord best = NR_HUGE;
925 NR::Point delta(dx, dy);
926 NR::Point best_pt = delta;
928 if (snap) {
929 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
931 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
932 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
933 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
934 if (s.getDistance() < best) {
935 best = s.getDistance();
936 best_pt = s.getPoint() - n->pos;
937 }
938 }
939 }
941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
942 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
943 sp_node_moveto(n, n->pos + best_pt);
944 }
946 // do not update repr here so that node dragging is acceptably fast
947 update_object(nodepath);
948 }
950 /**
951 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
952 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
953 near x = 0.
954 */
955 double
956 sculpt_profile (double x, double alpha)
957 {
958 if (x >= 1)
959 return 0;
960 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
961 }
963 double
964 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
965 {
966 // extremely primitive for now, don't have time to look for the real one
967 double lower = NR::L2(b - a);
968 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
969 return (lower + upper)/2;
970 }
972 void
973 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
974 {
975 n->pos = n->origin + delta;
976 n->n.pos = n->n.origin + delta_n;
977 n->p.pos = n->p.origin + delta_p;
978 sp_node_adjust_handles(n);
979 sp_node_update_handles(n, false);
980 }
982 /**
983 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
984 * on how far they are from the dragged node n.
985 */
986 static void
987 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
988 {
989 g_assert (n);
990 g_assert (nodepath);
991 g_assert (n->subpath->nodepath == nodepath);
993 double pressure = n->knot->pressure;
994 if (pressure == 0)
995 pressure = 0.5; // default
996 pressure = CLAMP (pressure, 0.2, 0.8);
998 // map pressure to alpha = 1/5 ... 5
999 double alpha = 1 - 2 * fabs(pressure - 0.5);
1000 if (pressure > 0.5)
1001 alpha = 1/alpha;
1003 double n_sel_range = 0, p_sel_range = 0;
1004 guint n_nodes = 0, p_nodes = 0;
1005 guint n_sel_nodes = 0, p_sel_nodes = 0;
1007 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1008 {
1009 double n_range = 0, p_range = 0;
1010 bool n_going = true, p_going = true;
1011 Inkscape::NodePath::Node *n_node = n;
1012 Inkscape::NodePath::Node *p_node = n;
1013 do {
1014 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1015 if (n_node && n_going)
1016 n_node = n_node->n.other;
1017 if (n_node == NULL) {
1018 n_going = false;
1019 } else {
1020 n_nodes ++;
1021 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1022 if (n_node->selected) {
1023 n_sel_nodes ++;
1024 n_sel_range = n_range;
1025 }
1026 if (n_node == p_node) {
1027 n_going = false;
1028 p_going = false;
1029 }
1030 }
1031 if (p_node && p_going)
1032 p_node = p_node->p.other;
1033 if (p_node == NULL) {
1034 p_going = false;
1035 } else {
1036 p_nodes ++;
1037 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1038 if (p_node->selected) {
1039 p_sel_nodes ++;
1040 p_sel_range = p_range;
1041 }
1042 if (p_node == n_node) {
1043 n_going = false;
1044 p_going = false;
1045 }
1046 }
1047 } while (n_going || p_going);
1048 }
1050 // Second pass: actually move nodes
1051 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1052 {
1053 double n_range = 0, p_range = 0;
1054 bool n_going = true, p_going = true;
1055 Inkscape::NodePath::Node *n_node = n;
1056 Inkscape::NodePath::Node *p_node = n;
1057 do {
1058 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1059 if (n_node && n_going)
1060 n_node = n_node->n.other;
1061 if (n_node == NULL) {
1062 n_going = false;
1063 } else {
1064 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1065 if (n_node->selected) {
1066 sp_nodepath_move_node_and_handles (n_node,
1067 sculpt_profile (n_range / n_sel_range, alpha) * delta,
1068 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha) * delta,
1069 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha) * delta);
1070 }
1071 if (n_node == p_node) {
1072 n_going = false;
1073 p_going = false;
1074 }
1075 }
1076 if (p_node && p_going)
1077 p_node = p_node->p.other;
1078 if (p_node == NULL) {
1079 p_going = false;
1080 } else {
1081 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1082 if (p_node->selected) {
1083 sp_nodepath_move_node_and_handles (p_node,
1084 sculpt_profile (p_range / p_sel_range, alpha) * delta,
1085 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha) * delta,
1086 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha) * delta);
1087 }
1088 if (p_node == n_node) {
1089 n_going = false;
1090 p_going = false;
1091 }
1092 }
1093 } while (n_going || p_going);
1094 }
1096 // do not update repr here so that node dragging is acceptably fast
1097 update_object(nodepath);
1098 }
1101 /**
1102 * Move node selection to point, adjust its and neighbouring handles,
1103 * handle possible snapping, and commit the change with possible undo.
1104 */
1105 void
1106 sp_node_selected_move(gdouble dx, gdouble dy)
1107 {
1108 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1109 if (!nodepath) return;
1111 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1113 if (dx == 0) {
1114 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1115 } else if (dy == 0) {
1116 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1117 } else {
1118 sp_nodepath_update_repr(nodepath);
1119 }
1120 }
1122 /**
1123 * Move node selection off screen and commit the change.
1124 */
1125 void
1126 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1127 {
1128 // borrowed from sp_selection_move_screen in selection-chemistry.c
1129 // we find out the current zoom factor and divide deltas by it
1130 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1132 gdouble zoom = desktop->current_zoom();
1133 gdouble zdx = dx / zoom;
1134 gdouble zdy = dy / zoom;
1136 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1137 if (!nodepath) return;
1139 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1141 if (dx == 0) {
1142 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1143 } else if (dy == 0) {
1144 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1145 } else {
1146 sp_nodepath_update_repr(nodepath);
1147 }
1148 }
1150 /** If they don't yet exist, creates knot and line for the given side of the node */
1151 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1152 {
1153 if (!side->knot) {
1154 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"));
1156 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1157 side->knot->setSize (7);
1158 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1159 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1160 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1161 sp_knot_update_ctrl(side->knot);
1163 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1164 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1165 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1166 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1167 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1168 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1169 }
1171 if (!side->line) {
1172 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1173 SP_TYPE_CTRLLINE, NULL);
1174 }
1175 }
1177 /**
1178 * Ensure the given handle of the node is visible/invisible, update its screen position
1179 */
1180 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1181 {
1182 g_assert(node != NULL);
1184 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1185 NRPathcode code = sp_node_path_code_from_side(node, side);
1187 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1189 if (show_handle) {
1190 if (!side->knot) { // No handle knot at all
1191 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1192 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1193 side->knot->pos = side->pos;
1194 if (side->knot->item)
1195 SP_CTRL(side->knot->item)->moveto(side->pos);
1196 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1197 sp_knot_show(side->knot);
1198 } else {
1199 if (side->knot->pos != side->pos) { // only if it's really moved
1200 if (fire_move_signals) {
1201 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1202 } else {
1203 sp_knot_moveto(side->knot, &side->pos);
1204 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1205 }
1206 }
1207 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1208 sp_knot_show(side->knot);
1209 }
1210 }
1211 sp_canvas_item_show(side->line);
1212 } else {
1213 if (side->knot) {
1214 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1215 sp_knot_hide(side->knot);
1216 }
1217 }
1218 if (side->line) {
1219 sp_canvas_item_hide(side->line);
1220 }
1221 }
1222 }
1224 /**
1225 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1226 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1227 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1228 * updated; otherwise, just move the knots silently (used in batch moves).
1229 */
1230 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1231 {
1232 g_assert(node != NULL);
1234 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1235 sp_knot_show(node->knot);
1236 }
1238 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1239 if (fire_move_signals)
1240 sp_knot_set_position(node->knot, &node->pos, 0);
1241 else
1242 sp_knot_moveto(node->knot, &node->pos);
1243 }
1245 gboolean show_handles = node->selected;
1246 if (node->p.other != NULL) {
1247 if (node->p.other->selected) show_handles = TRUE;
1248 }
1249 if (node->n.other != NULL) {
1250 if (node->n.other->selected) show_handles = TRUE;
1251 }
1253 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1254 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1255 }
1257 /**
1258 * Call sp_node_update_handles() for all nodes on subpath.
1259 */
1260 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1261 {
1262 g_assert(subpath != NULL);
1264 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1265 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1266 }
1267 }
1269 /**
1270 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1271 */
1272 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1273 {
1274 g_assert(nodepath != NULL);
1276 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1277 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1278 }
1279 }
1281 /**
1282 * Adds all selected nodes in nodepath to list.
1283 */
1284 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1285 {
1286 StlConv<Node *>::list(l, selected);
1287 /// \todo this adds a copying, rework when the selection becomes a stl list
1288 }
1290 /**
1291 * Align selected nodes on the specified axis.
1292 */
1293 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1294 {
1295 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1296 return;
1297 }
1299 if ( !nodepath->selected->next ) { // only one node selected
1300 return;
1301 }
1302 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1303 NR::Point dest(pNode->pos);
1304 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1305 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1306 if (pNode) {
1307 dest[axis] = pNode->pos[axis];
1308 sp_node_moveto(pNode, dest);
1309 }
1310 }
1312 sp_nodepath_update_repr(nodepath);
1313 }
1315 /// Helper struct.
1316 struct NodeSort
1317 {
1318 Inkscape::NodePath::Node *_node;
1319 NR::Coord _coord;
1320 /// \todo use vectorof pointers instead of calling copy ctor
1321 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1322 _node(node), _coord(node->pos[axis])
1323 {}
1325 };
1327 static bool operator<(NodeSort const &a, NodeSort const &b)
1328 {
1329 return (a._coord < b._coord);
1330 }
1332 /**
1333 * Distribute selected nodes on the specified axis.
1334 */
1335 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1336 {
1337 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1338 return;
1339 }
1341 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1342 return;
1343 }
1345 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1346 std::vector<NodeSort> sorted;
1347 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1348 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1349 if (pNode) {
1350 NodeSort n(pNode, axis);
1351 sorted.push_back(n);
1352 //dest[axis] = pNode->pos[axis];
1353 //sp_node_moveto(pNode, dest);
1354 }
1355 }
1356 std::sort(sorted.begin(), sorted.end());
1357 unsigned int len = sorted.size();
1358 //overall bboxes span
1359 float dist = (sorted.back()._coord -
1360 sorted.front()._coord);
1361 //new distance between each bbox
1362 float step = (dist) / (len - 1);
1363 float pos = sorted.front()._coord;
1364 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1365 it < sorted.end();
1366 it ++ )
1367 {
1368 NR::Point dest((*it)._node->pos);
1369 dest[axis] = pos;
1370 sp_node_moveto((*it)._node, dest);
1371 pos += step;
1372 }
1374 sp_nodepath_update_repr(nodepath);
1375 }
1378 /**
1379 * Call sp_nodepath_line_add_node() for all selected segments.
1380 */
1381 void
1382 sp_node_selected_add_node(void)
1383 {
1384 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1385 if (!nodepath) {
1386 return;
1387 }
1389 GList *nl = NULL;
1391 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1392 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1393 g_assert(t->selected);
1394 if (t->p.other && t->p.other->selected) {
1395 nl = g_list_prepend(nl, t);
1396 }
1397 }
1399 while (nl) {
1400 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1401 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1402 sp_nodepath_node_select(n, TRUE, FALSE);
1403 nl = g_list_remove(nl, t);
1404 }
1406 /** \todo fixme: adjust ? */
1407 sp_nodepath_update_handles(nodepath);
1409 sp_nodepath_update_repr(nodepath);
1411 sp_nodepath_update_statusbar(nodepath);
1412 }
1414 /**
1415 * Select segment nearest to point
1416 */
1417 void
1418 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1419 {
1420 if (!nodepath) {
1421 return;
1422 }
1424 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1426 //find segment to segment
1427 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1429 gboolean force = FALSE;
1430 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1431 force = TRUE;
1432 }
1433 sp_nodepath_node_select(e, (gboolean) toggle, force);
1434 if (e->p.other)
1435 sp_nodepath_node_select(e->p.other, TRUE, force);
1437 sp_nodepath_update_handles(nodepath);
1439 sp_nodepath_update_statusbar(nodepath);
1440 }
1442 /**
1443 * Add a node nearest to point
1444 */
1445 void
1446 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1447 {
1448 if (!nodepath) {
1449 return;
1450 }
1452 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1454 //find segment to split
1455 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1457 //don't know why but t seems to flip for lines
1458 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1459 position.t = 1.0 - position.t;
1460 }
1461 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1462 sp_nodepath_node_select(n, FALSE, TRUE);
1464 /* fixme: adjust ? */
1465 sp_nodepath_update_handles(nodepath);
1467 sp_nodepath_update_repr(nodepath);
1469 sp_nodepath_update_statusbar(nodepath);
1470 }
1472 /*
1473 * Adjusts a segment so that t moves by a certain delta for dragging
1474 * converts lines to curves
1475 *
1476 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1477 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1478 */
1479 void
1480 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1481 {
1482 /* feel good is an arbitrary parameter that distributes the delta between handles
1483 * if t of the drag point is less than 1/6 distance form the endpoint only
1484 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1485 */
1486 double feel_good;
1487 if (t <= 1.0 / 6.0)
1488 feel_good = 0;
1489 else if (t <= 0.5)
1490 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1491 else if (t <= 5.0 / 6.0)
1492 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1493 else
1494 feel_good = 1;
1496 //if we're dragging a line convert it to a curve
1497 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1498 sp_nodepath_set_line_type(e, NR_CURVETO);
1499 }
1501 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1502 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1503 e->p.other->n.pos += offsetcoord0;
1504 e->p.pos += offsetcoord1;
1506 // adjust handles of adjacent nodes where necessary
1507 sp_node_adjust_handle(e,1);
1508 sp_node_adjust_handle(e->p.other,-1);
1510 sp_nodepath_update_handles(e->subpath->nodepath);
1512 update_object(e->subpath->nodepath);
1514 sp_nodepath_update_statusbar(e->subpath->nodepath);
1515 }
1518 /**
1519 * Call sp_nodepath_break() for all selected segments.
1520 */
1521 void sp_node_selected_break()
1522 {
1523 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1524 if (!nodepath) return;
1526 GList *temp = NULL;
1527 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1528 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1529 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1530 if (nn == NULL) continue; // no break, no new node
1531 temp = g_list_prepend(temp, nn);
1532 }
1534 if (temp) {
1535 sp_nodepath_deselect(nodepath);
1536 }
1537 for (GList *l = temp; l != NULL; l = l->next) {
1538 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1539 }
1541 sp_nodepath_update_handles(nodepath);
1543 sp_nodepath_update_repr(nodepath);
1544 }
1546 /**
1547 * Duplicate the selected node(s).
1548 */
1549 void sp_node_selected_duplicate()
1550 {
1551 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1552 if (!nodepath) {
1553 return;
1554 }
1556 GList *temp = NULL;
1557 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1558 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1559 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1560 if (nn == NULL) continue; // could not duplicate
1561 temp = g_list_prepend(temp, nn);
1562 }
1564 if (temp) {
1565 sp_nodepath_deselect(nodepath);
1566 }
1567 for (GList *l = temp; l != NULL; l = l->next) {
1568 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1569 }
1571 sp_nodepath_update_handles(nodepath);
1573 sp_nodepath_update_repr(nodepath);
1574 }
1576 /**
1577 * Join two nodes by merging them into one.
1578 */
1579 void sp_node_selected_join()
1580 {
1581 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1582 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1584 if (g_list_length(nodepath->selected) != 2) {
1585 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1586 return;
1587 }
1589 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1590 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1592 g_assert(a != b);
1593 g_assert(a->p.other || a->n.other);
1594 g_assert(b->p.other || b->n.other);
1596 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1597 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1598 return;
1599 }
1601 /* a and b are endpoints */
1603 NR::Point c;
1604 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1605 c = a->pos;
1606 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1607 c = b->pos;
1608 } else {
1609 c = (a->pos + b->pos) / 2;
1610 }
1612 if (a->subpath == b->subpath) {
1613 Inkscape::NodePath::SubPath *sp = a->subpath;
1614 sp_nodepath_subpath_close(sp);
1615 sp_node_moveto (sp->first, c);
1617 sp_nodepath_update_handles(sp->nodepath);
1618 sp_nodepath_update_repr(nodepath);
1619 return;
1620 }
1622 /* a and b are separate subpaths */
1623 Inkscape::NodePath::SubPath *sa = a->subpath;
1624 Inkscape::NodePath::SubPath *sb = b->subpath;
1625 NR::Point p;
1626 Inkscape::NodePath::Node *n;
1627 NRPathcode code;
1628 if (a == sa->first) {
1629 p = sa->first->n.pos;
1630 code = (NRPathcode)sa->first->n.other->code;
1631 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1632 n = sa->last;
1633 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1634 n = n->p.other;
1635 while (n) {
1636 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1637 n = n->p.other;
1638 if (n == sa->first) n = NULL;
1639 }
1640 sp_nodepath_subpath_destroy(sa);
1641 sa = t;
1642 } else if (a == sa->last) {
1643 p = sa->last->p.pos;
1644 code = (NRPathcode)sa->last->code;
1645 sp_nodepath_node_destroy(sa->last);
1646 } else {
1647 code = NR_END;
1648 g_assert_not_reached();
1649 }
1651 if (b == sb->first) {
1652 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1653 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1654 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1655 }
1656 } else if (b == sb->last) {
1657 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1658 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1659 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1660 }
1661 } else {
1662 g_assert_not_reached();
1663 }
1664 /* and now destroy sb */
1666 sp_nodepath_subpath_destroy(sb);
1668 sp_nodepath_update_handles(sa->nodepath);
1670 sp_nodepath_update_repr(nodepath);
1672 sp_nodepath_update_statusbar(nodepath);
1673 }
1675 /**
1676 * Join two nodes by adding a segment between them.
1677 */
1678 void sp_node_selected_join_segment()
1679 {
1680 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1681 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1683 if (g_list_length(nodepath->selected) != 2) {
1684 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1685 return;
1686 }
1688 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1689 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1691 g_assert(a != b);
1692 g_assert(a->p.other || a->n.other);
1693 g_assert(b->p.other || b->n.other);
1695 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1696 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1697 return;
1698 }
1700 if (a->subpath == b->subpath) {
1701 Inkscape::NodePath::SubPath *sp = a->subpath;
1703 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1704 sp->closed = TRUE;
1706 sp->first->p.other = sp->last;
1707 sp->last->n.other = sp->first;
1709 sp_node_handle_mirror_p_to_n(sp->last);
1710 sp_node_handle_mirror_n_to_p(sp->first);
1712 sp->first->code = sp->last->code;
1713 sp->first = sp->last;
1715 sp_nodepath_update_handles(sp->nodepath);
1717 sp_nodepath_update_repr(nodepath);
1719 return;
1720 }
1722 /* a and b are separate subpaths */
1723 Inkscape::NodePath::SubPath *sa = a->subpath;
1724 Inkscape::NodePath::SubPath *sb = b->subpath;
1726 Inkscape::NodePath::Node *n;
1727 NR::Point p;
1728 NRPathcode code;
1729 if (a == sa->first) {
1730 code = (NRPathcode) sa->first->n.other->code;
1731 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1732 n = sa->last;
1733 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1734 for (n = n->p.other; n != NULL; n = n->p.other) {
1735 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1736 }
1737 sp_nodepath_subpath_destroy(sa);
1738 sa = t;
1739 } else if (a == sa->last) {
1740 code = (NRPathcode)sa->last->code;
1741 } else {
1742 code = NR_END;
1743 g_assert_not_reached();
1744 }
1746 if (b == sb->first) {
1747 n = sb->first;
1748 sp_node_handle_mirror_p_to_n(sa->last);
1749 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1750 sp_node_handle_mirror_n_to_p(sa->last);
1751 for (n = n->n.other; n != NULL; n = n->n.other) {
1752 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1753 }
1754 } else if (b == sb->last) {
1755 n = sb->last;
1756 sp_node_handle_mirror_p_to_n(sa->last);
1757 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1758 sp_node_handle_mirror_n_to_p(sa->last);
1759 for (n = n->p.other; n != NULL; n = n->p.other) {
1760 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1761 }
1762 } else {
1763 g_assert_not_reached();
1764 }
1765 /* and now destroy sb */
1767 sp_nodepath_subpath_destroy(sb);
1769 sp_nodepath_update_handles(sa->nodepath);
1771 sp_nodepath_update_repr(nodepath);
1772 }
1774 /**
1775 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1776 */
1777 void sp_node_delete_preserve(GList *nodes_to_delete)
1778 {
1779 GSList *nodepaths = NULL;
1781 while (nodes_to_delete) {
1782 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1783 Inkscape::NodePath::SubPath *sp = node->subpath;
1784 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1785 Inkscape::NodePath::Node *sample_cursor = NULL;
1786 Inkscape::NodePath::Node *sample_end = NULL;
1787 Inkscape::NodePath::Node *delete_cursor = node;
1788 bool just_delete = false;
1790 //find the start of this contiguous selection
1791 //move left to the first node that is not selected
1792 //or the start of the non-closed path
1793 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1794 delete_cursor = curr;
1795 }
1797 //just delete at the beginning of an open path
1798 if (!delete_cursor->p.other) {
1799 sample_cursor = delete_cursor;
1800 just_delete = true;
1801 } else {
1802 sample_cursor = delete_cursor->p.other;
1803 }
1805 //calculate points for each segment
1806 int rate = 5;
1807 float period = 1.0 / rate;
1808 std::vector<NR::Point> data;
1809 if (!just_delete) {
1810 data.push_back(sample_cursor->pos);
1811 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1812 //just delete at the end of an open path
1813 if (!sp->closed && curr->n.other == sp->last) {
1814 just_delete = true;
1815 break;
1816 }
1818 //sample points on the contiguous selected segment
1819 NR::Point *bez;
1820 bez = new NR::Point [4];
1821 bez[0] = curr->pos;
1822 bez[1] = curr->n.pos;
1823 bez[2] = curr->n.other->p.pos;
1824 bez[3] = curr->n.other->pos;
1825 for (int i=1; i<rate; i++) {
1826 gdouble t = i * period;
1827 NR::Point p = bezier_pt(3, bez, t);
1828 data.push_back(p);
1829 }
1830 data.push_back(curr->n.other->pos);
1832 sample_end = curr->n.other;
1833 //break if we've come full circle or hit the end of the selection
1834 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1835 break;
1836 }
1837 }
1838 }
1840 if (!just_delete) {
1841 //calculate the best fitting single segment and adjust the endpoints
1842 NR::Point *adata;
1843 adata = new NR::Point [data.size()];
1844 copy(data.begin(), data.end(), adata);
1846 NR::Point *bez;
1847 bez = new NR::Point [4];
1848 //would decreasing error create a better fitting approximation?
1849 gdouble error = 1.0;
1850 gint ret;
1851 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1853 //adjust endpoints
1854 sample_cursor->n.pos = bez[1];
1855 sample_end->p.pos = bez[2];
1856 }
1858 //destroy this contiguous selection
1859 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1860 Inkscape::NodePath::Node *temp = delete_cursor;
1861 if (delete_cursor->n.other == delete_cursor) {
1862 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1863 delete_cursor = NULL;
1864 } else {
1865 delete_cursor = delete_cursor->n.other;
1866 }
1867 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1868 sp_nodepath_node_destroy(temp);
1869 }
1871 sp_nodepath_update_handles(nodepath);
1873 // if the entire nodepath is removed, delete the selected object.
1874 if (nodepath->subpaths == NULL ||
1875 sp_nodepath_get_node_count(nodepath) < 2) {
1876 SPDocument *document = sp_desktop_document (nodepath->desktop);
1877 sp_nodepath_destroy(nodepath);
1878 g_list_free(nodes_to_delete);
1879 nodes_to_delete = NULL;
1880 //is the next line necessary?
1881 sp_selection_delete();
1882 sp_document_done (document);
1883 return;
1884 }
1886 if (!g_slist_find(nodepaths, nodepath))
1887 nodepaths = g_slist_prepend (nodepaths, nodepath);
1888 }
1890 for (GSList *i = nodepaths; i; i = i->next) {
1891 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1892 sp_nodepath_update_repr(nodepath);
1893 sp_nodepath_update_statusbar(nodepath);
1894 }
1895 }
1897 /**
1898 * Delete one or more selected nodes.
1899 */
1900 void sp_node_selected_delete()
1901 {
1902 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1903 if (!nodepath) return;
1904 if (!nodepath->selected) return;
1906 /** \todo fixme: do it the right way */
1907 while (nodepath->selected) {
1908 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1909 sp_nodepath_node_destroy(node);
1910 }
1913 //clean up the nodepath (such as for trivial subpaths)
1914 sp_nodepath_cleanup(nodepath);
1916 sp_nodepath_update_handles(nodepath);
1918 // if the entire nodepath is removed, delete the selected object.
1919 if (nodepath->subpaths == NULL ||
1920 sp_nodepath_get_node_count(nodepath) < 2) {
1921 SPDocument *document = sp_desktop_document (nodepath->desktop);
1922 sp_nodepath_destroy(nodepath);
1923 sp_selection_delete();
1924 sp_document_done (document);
1925 return;
1926 }
1928 sp_nodepath_update_repr(nodepath);
1930 sp_nodepath_update_statusbar(nodepath);
1931 }
1933 /**
1934 * Delete one or more segments between two selected nodes.
1935 * This is the code for 'split'.
1936 */
1937 void
1938 sp_node_selected_delete_segment(void)
1939 {
1940 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1941 Inkscape::NodePath::Node *curr, *next; //Iterators
1943 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1944 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1946 if (g_list_length(nodepath->selected) != 2) {
1947 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1948 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1949 return;
1950 }
1952 //Selected nodes, not inclusive
1953 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1954 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1956 if ( ( a==b) || //same node
1957 (a->subpath != b->subpath ) || //not the same path
1958 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1959 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1960 {
1961 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1962 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1963 return;
1964 }
1966 //###########################################
1967 //# BEGIN EDITS
1968 //###########################################
1969 //##################################
1970 //# CLOSED PATH
1971 //##################################
1972 if (a->subpath->closed) {
1975 gboolean reversed = FALSE;
1977 //Since we can go in a circle, we need to find the shorter distance.
1978 // a->b or b->a
1979 start = end = NULL;
1980 int distance = 0;
1981 int minDistance = 0;
1982 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1983 if (curr==b) {
1984 //printf("a to b:%d\n", distance);
1985 start = a;//go from a to b
1986 end = b;
1987 minDistance = distance;
1988 //printf("A to B :\n");
1989 break;
1990 }
1991 distance++;
1992 }
1994 //try again, the other direction
1995 distance = 0;
1996 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1997 if (curr==a) {
1998 //printf("b to a:%d\n", distance);
1999 if (distance < minDistance) {
2000 start = b; //we go from b to a
2001 end = a;
2002 reversed = TRUE;
2003 //printf("B to A\n");
2004 }
2005 break;
2006 }
2007 distance++;
2008 }
2011 //Copy everything from 'end' to 'start' to a new subpath
2012 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2013 for (curr=end ; curr ; curr=curr->n.other) {
2014 NRPathcode code = (NRPathcode) curr->code;
2015 if (curr == end)
2016 code = NR_MOVETO;
2017 sp_nodepath_node_new(t, NULL,
2018 (Inkscape::NodePath::NodeType)curr->type, code,
2019 &curr->p.pos, &curr->pos, &curr->n.pos);
2020 if (curr == start)
2021 break;
2022 }
2023 sp_nodepath_subpath_destroy(a->subpath);
2026 }
2030 //##################################
2031 //# OPEN PATH
2032 //##################################
2033 else {
2035 //We need to get the direction of the list between A and B
2036 //Can we walk from a to b?
2037 start = end = NULL;
2038 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2039 if (curr==b) {
2040 start = a; //did it! we go from a to b
2041 end = b;
2042 //printf("A to B\n");
2043 break;
2044 }
2045 }
2046 if (!start) {//didn't work? let's try the other direction
2047 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2048 if (curr==a) {
2049 start = b; //did it! we go from b to a
2050 end = a;
2051 //printf("B to A\n");
2052 break;
2053 }
2054 }
2055 }
2056 if (!start) {
2057 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2058 _("Cannot find path between nodes."));
2059 return;
2060 }
2064 //Copy everything after 'end' to a new subpath
2065 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2066 for (curr=end ; curr ; curr=curr->n.other) {
2067 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2068 &curr->p.pos, &curr->pos, &curr->n.pos);
2069 }
2071 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2072 for (curr = start->n.other ; curr ; curr=next) {
2073 next = curr->n.other;
2074 sp_nodepath_node_destroy(curr);
2075 }
2077 }
2078 //###########################################
2079 //# END EDITS
2080 //###########################################
2082 //clean up the nodepath (such as for trivial subpaths)
2083 sp_nodepath_cleanup(nodepath);
2085 sp_nodepath_update_handles(nodepath);
2087 sp_nodepath_update_repr(nodepath);
2089 // if the entire nodepath is removed, delete the selected object.
2090 if (nodepath->subpaths == NULL ||
2091 sp_nodepath_get_node_count(nodepath) < 2) {
2092 sp_nodepath_destroy(nodepath);
2093 sp_selection_delete();
2094 return;
2095 }
2097 sp_nodepath_update_statusbar(nodepath);
2098 }
2100 /**
2101 * Call sp_nodepath_set_line() for all selected segments.
2102 */
2103 void
2104 sp_node_selected_set_line_type(NRPathcode code)
2105 {
2106 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2107 if (nodepath == NULL) return;
2109 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2110 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2111 g_assert(n->selected);
2112 if (n->p.other && n->p.other->selected) {
2113 sp_nodepath_set_line_type(n, code);
2114 }
2115 }
2117 sp_nodepath_update_repr(nodepath);
2118 }
2120 /**
2121 * Call sp_nodepath_convert_node_type() for all selected nodes.
2122 */
2123 void
2124 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2125 {
2126 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2127 if (nodepath == NULL) return;
2129 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2130 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2131 }
2133 sp_nodepath_update_repr(nodepath);
2134 }
2136 /**
2137 * Change select status of node, update its own and neighbour handles.
2138 */
2139 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2140 {
2141 node->selected = selected;
2143 if (selected) {
2144 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2145 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2146 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2147 sp_knot_update_ctrl(node->knot);
2148 } else {
2149 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2150 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2151 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2152 sp_knot_update_ctrl(node->knot);
2153 }
2155 sp_node_update_handles(node);
2156 if (node->n.other) sp_node_update_handles(node->n.other);
2157 if (node->p.other) sp_node_update_handles(node->p.other);
2158 }
2160 /**
2161 \brief Select a node
2162 \param node The node to select
2163 \param incremental If true, add to selection, otherwise deselect others
2164 \param override If true, always select this node, otherwise toggle selected status
2165 */
2166 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2167 {
2168 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2170 if (incremental) {
2171 if (override) {
2172 if (!g_list_find(nodepath->selected, node)) {
2173 nodepath->selected = g_list_prepend(nodepath->selected, node);
2174 }
2175 sp_node_set_selected(node, TRUE);
2176 } else { // toggle
2177 if (node->selected) {
2178 g_assert(g_list_find(nodepath->selected, node));
2179 nodepath->selected = g_list_remove(nodepath->selected, node);
2180 } else {
2181 g_assert(!g_list_find(nodepath->selected, node));
2182 nodepath->selected = g_list_prepend(nodepath->selected, node);
2183 }
2184 sp_node_set_selected(node, !node->selected);
2185 }
2186 } else {
2187 sp_nodepath_deselect(nodepath);
2188 nodepath->selected = g_list_prepend(nodepath->selected, node);
2189 sp_node_set_selected(node, TRUE);
2190 }
2192 sp_nodepath_update_statusbar(nodepath);
2193 }
2196 /**
2197 \brief Deselect all nodes in the nodepath
2198 */
2199 void
2200 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2201 {
2202 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2204 while (nodepath->selected) {
2205 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2206 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2207 }
2208 sp_nodepath_update_statusbar(nodepath);
2209 }
2211 /**
2212 \brief Select or invert selection of all nodes in the nodepath
2213 */
2214 void
2215 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2216 {
2217 if (!nodepath) return;
2219 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2220 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2221 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2222 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2223 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2224 }
2225 }
2226 }
2228 /**
2229 * If nothing selected, does the same as sp_nodepath_select_all();
2230 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2231 * (i.e., similar to "select all in layer", with the "selected" subpaths
2232 * being treated as "layers" in the path).
2233 */
2234 void
2235 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2236 {
2237 if (!nodepath) return;
2239 if (g_list_length (nodepath->selected) == 0) {
2240 sp_nodepath_select_all (nodepath, invert);
2241 return;
2242 }
2244 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2245 GSList *subpaths = NULL;
2247 for (GList *l = copy; l != NULL; l = l->next) {
2248 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2249 Inkscape::NodePath::SubPath *subpath = n->subpath;
2250 if (!g_slist_find (subpaths, subpath))
2251 subpaths = g_slist_prepend (subpaths, subpath);
2252 }
2254 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2255 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2256 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2257 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2258 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2259 }
2260 }
2262 g_slist_free (subpaths);
2263 g_list_free (copy);
2264 }
2266 /**
2267 * \brief Select the node after the last selected; if none is selected,
2268 * select the first within path.
2269 */
2270 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2271 {
2272 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2274 Inkscape::NodePath::Node *last = NULL;
2275 if (nodepath->selected) {
2276 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2277 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2278 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2279 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2280 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2281 if (node->selected) {
2282 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2283 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2284 if (spl->next) { // there's a next subpath
2285 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2286 last = subpath_next->first;
2287 } else if (spl->prev) { // there's a previous subpath
2288 last = NULL; // to be set later to the first node of first subpath
2289 } else {
2290 last = node->n.other;
2291 }
2292 } else {
2293 last = node->n.other;
2294 }
2295 } else {
2296 if (node->n.other) {
2297 last = node->n.other;
2298 } else {
2299 if (spl->next) { // there's a next subpath
2300 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2301 last = subpath_next->first;
2302 } else if (spl->prev) { // there's a previous subpath
2303 last = NULL; // to be set later to the first node of first subpath
2304 } else {
2305 last = (Inkscape::NodePath::Node *) subpath->first;
2306 }
2307 }
2308 }
2309 }
2310 }
2311 }
2312 sp_nodepath_deselect(nodepath);
2313 }
2315 if (last) { // there's at least one more node after selected
2316 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2317 } else { // no more nodes, select the first one in first subpath
2318 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2319 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2320 }
2321 }
2323 /**
2324 * \brief Select the node before the first selected; if none is selected,
2325 * select the last within path
2326 */
2327 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2328 {
2329 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2331 Inkscape::NodePath::Node *last = NULL;
2332 if (nodepath->selected) {
2333 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2334 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2335 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2336 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2337 if (node->selected) {
2338 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2339 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2340 if (spl->prev) { // there's a prev subpath
2341 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2342 last = subpath_prev->last;
2343 } else if (spl->next) { // there's a next subpath
2344 last = NULL; // to be set later to the last node of last subpath
2345 } else {
2346 last = node->p.other;
2347 }
2348 } else {
2349 last = node->p.other;
2350 }
2351 } else {
2352 if (node->p.other) {
2353 last = node->p.other;
2354 } else {
2355 if (spl->prev) { // there's a prev subpath
2356 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2357 last = subpath_prev->last;
2358 } else if (spl->next) { // there's a next subpath
2359 last = NULL; // to be set later to the last node of last subpath
2360 } else {
2361 last = (Inkscape::NodePath::Node *) subpath->last;
2362 }
2363 }
2364 }
2365 }
2366 }
2367 }
2368 sp_nodepath_deselect(nodepath);
2369 }
2371 if (last) { // there's at least one more node before selected
2372 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2373 } else { // no more nodes, select the last one in last subpath
2374 GList *spl = g_list_last(nodepath->subpaths);
2375 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2376 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2377 }
2378 }
2380 /**
2381 * \brief Select all nodes that are within the rectangle.
2382 */
2383 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2384 {
2385 if (!incremental) {
2386 sp_nodepath_deselect(nodepath);
2387 }
2389 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2390 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2391 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2392 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2394 if (b.contains(node->pos)) {
2395 sp_nodepath_node_select(node, TRUE, TRUE);
2396 }
2397 }
2398 }
2399 }
2402 /**
2403 \brief Saves all nodes' and handles' current positions in their origin members
2404 */
2405 void
2406 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2407 {
2408 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2409 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2410 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2411 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2412 n->origin = n->pos;
2413 n->p.origin = n->p.pos;
2414 n->n.origin = n->n.pos;
2415 }
2416 }
2417 }
2419 /**
2420 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2421 */
2422 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2423 {
2424 if (!nodepath->selected) {
2425 return NULL;
2426 }
2428 GList *r = NULL;
2429 guint i = 0;
2430 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2431 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2432 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2433 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2434 i++;
2435 if (node->selected) {
2436 r = g_list_append(r, GINT_TO_POINTER(i));
2437 }
2438 }
2439 }
2440 return r;
2441 }
2443 /**
2444 \brief Restores selection by selecting nodes whose positions are in the list
2445 */
2446 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2447 {
2448 sp_nodepath_deselect(nodepath);
2450 guint i = 0;
2451 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2452 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2453 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2454 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2455 i++;
2456 if (g_list_find(r, GINT_TO_POINTER(i))) {
2457 sp_nodepath_node_select(node, TRUE, TRUE);
2458 }
2459 }
2460 }
2462 }
2464 /**
2465 \brief Adjusts handle according to node type and line code.
2466 */
2467 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2468 {
2469 double len, otherlen, linelen;
2471 g_assert(node);
2473 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2474 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2476 /** \todo fixme: */
2477 if (me->other == NULL) return;
2478 if (other->other == NULL) return;
2480 /* I have line */
2482 NRPathcode mecode, ocode;
2483 if (which_adjust == 1) {
2484 mecode = (NRPathcode)me->other->code;
2485 ocode = (NRPathcode)node->code;
2486 } else {
2487 mecode = (NRPathcode)node->code;
2488 ocode = (NRPathcode)other->other->code;
2489 }
2491 if (mecode == NR_LINETO) return;
2493 /* I am curve */
2495 if (other->other == NULL) return;
2497 /* Other has line */
2499 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2501 NR::Point delta;
2502 if (ocode == NR_LINETO) {
2503 /* other is lineto, we are either smooth or symm */
2504 Inkscape::NodePath::Node *othernode = other->other;
2505 len = NR::L2(me->pos - node->pos);
2506 delta = node->pos - othernode->pos;
2507 linelen = NR::L2(delta);
2508 if (linelen < 1e-18)
2509 return;
2510 me->pos = node->pos + (len / linelen)*delta;
2511 return;
2512 }
2514 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2516 me->pos = 2 * node->pos - other->pos;
2517 return;
2518 }
2520 /* We are smooth */
2522 len = NR::L2(me->pos - node->pos);
2523 delta = other->pos - node->pos;
2524 otherlen = NR::L2(delta);
2525 if (otherlen < 1e-18) return;
2527 me->pos = node->pos - (len / otherlen) * delta;
2528 }
2530 /**
2531 \brief Adjusts both handles according to node type and line code
2532 */
2533 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2534 {
2535 g_assert(node);
2537 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2539 /* we are either smooth or symm */
2541 if (node->p.other == NULL) return;
2543 if (node->n.other == NULL) return;
2545 if (node->code == NR_LINETO) {
2546 if (node->n.other->code == NR_LINETO) return;
2547 sp_node_adjust_handle(node, 1);
2548 return;
2549 }
2551 if (node->n.other->code == NR_LINETO) {
2552 if (node->code == NR_LINETO) return;
2553 sp_node_adjust_handle(node, -1);
2554 return;
2555 }
2557 /* both are curves */
2558 NR::Point const delta( node->n.pos - node->p.pos );
2560 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2561 node->p.pos = node->pos - delta / 2;
2562 node->n.pos = node->pos + delta / 2;
2563 return;
2564 }
2566 /* We are smooth */
2567 double plen = NR::L2(node->p.pos - node->pos);
2568 if (plen < 1e-18) return;
2569 double nlen = NR::L2(node->n.pos - node->pos);
2570 if (nlen < 1e-18) return;
2571 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2572 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2573 }
2575 /**
2576 * Node event callback.
2577 */
2578 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2579 {
2580 gboolean ret = FALSE;
2581 switch (event->type) {
2582 case GDK_ENTER_NOTIFY:
2583 active_node = n;
2584 break;
2585 case GDK_LEAVE_NOTIFY:
2586 active_node = NULL;
2587 break;
2588 case GDK_KEY_PRESS:
2589 switch (get_group0_keyval (&event->key)) {
2590 case GDK_space:
2591 if (event->key.state & GDK_BUTTON1_MASK) {
2592 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2593 stamp_repr(nodepath);
2594 ret = TRUE;
2595 }
2596 break;
2597 default:
2598 break;
2599 }
2600 break;
2601 default:
2602 break;
2603 }
2605 return ret;
2606 }
2608 /**
2609 * Handle keypress on node; directly called.
2610 */
2611 gboolean node_key(GdkEvent *event)
2612 {
2613 Inkscape::NodePath::Path *np;
2615 // there is no way to verify nodes so set active_node to nil when deleting!!
2616 if (active_node == NULL) return FALSE;
2618 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2619 gint ret = FALSE;
2620 switch (get_group0_keyval (&event->key)) {
2621 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2622 case GDK_BackSpace:
2623 np = active_node->subpath->nodepath;
2624 sp_nodepath_node_destroy(active_node);
2625 sp_nodepath_update_repr(np);
2626 active_node = NULL;
2627 ret = TRUE;
2628 break;
2629 case GDK_c:
2630 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2631 ret = TRUE;
2632 break;
2633 case GDK_s:
2634 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2635 ret = TRUE;
2636 break;
2637 case GDK_y:
2638 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2639 ret = TRUE;
2640 break;
2641 case GDK_b:
2642 sp_nodepath_node_break(active_node);
2643 ret = TRUE;
2644 break;
2645 }
2646 return ret;
2647 }
2648 return FALSE;
2649 }
2651 /**
2652 * Mouseclick on node callback.
2653 */
2654 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2655 {
2656 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2658 if (state & GDK_CONTROL_MASK) {
2659 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2661 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2662 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2663 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2664 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2665 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2666 } else {
2667 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2668 }
2669 sp_nodepath_update_repr(nodepath);
2670 sp_nodepath_update_statusbar(nodepath);
2672 } else { //ctrl+alt+click: delete node
2673 GList *node_to_delete = NULL;
2674 node_to_delete = g_list_append(node_to_delete, n);
2675 sp_node_delete_preserve(node_to_delete);
2676 }
2678 } else {
2679 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2680 }
2681 }
2683 /**
2684 * Mouse grabbed node callback.
2685 */
2686 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2687 {
2688 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2690 if (!n->selected) {
2691 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2692 }
2694 sp_nodepath_remember_origins (n->subpath->nodepath);
2695 }
2697 /**
2698 * Mouse ungrabbed node callback.
2699 */
2700 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2701 {
2702 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2704 n->dragging_out = NULL;
2706 sp_nodepath_update_repr(n->subpath->nodepath);
2707 }
2709 /**
2710 * The point on a line, given by its angle, closest to the given point.
2711 * \param p A point.
2712 * \param a Angle of the line; it is assumed to go through coordinate origin.
2713 * \param closest Pointer to the point struct where the result is stored.
2714 * \todo FIXME: use dot product perhaps?
2715 */
2716 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2717 {
2718 if (a == HUGE_VAL) { // vertical
2719 *closest = NR::Point(0, (*p)[NR::Y]);
2720 } else {
2721 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2722 (*closest)[NR::Y] = a * (*closest)[NR::X];
2723 }
2724 }
2726 /**
2727 * Distance from the point to a line given by its angle.
2728 * \param p A point.
2729 * \param a Angle of the line; it is assumed to go through coordinate origin.
2730 */
2731 static double point_line_distance(NR::Point *p, double a)
2732 {
2733 NR::Point c;
2734 point_line_closest(p, a, &c);
2735 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]));
2736 }
2738 /**
2739 * Callback for node "request" signal.
2740 * \todo fixme: This goes to "moved" event? (lauris)
2741 */
2742 static gboolean
2743 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2744 {
2745 double yn, xn, yp, xp;
2746 double an, ap, na, pa;
2747 double d_an, d_ap, d_na, d_pa;
2748 gboolean collinear = FALSE;
2749 NR::Point c;
2750 NR::Point pr;
2752 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2754 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2755 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2757 NR::Point mouse = (*p);
2759 if (!n->dragging_out) {
2760 // This is the first drag-out event; find out which handle to drag out
2761 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2762 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2764 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2765 return FALSE;
2767 Inkscape::NodePath::NodeSide *opposite;
2768 if (appr_p > appr_n) { // closer to p
2769 n->dragging_out = &n->p;
2770 opposite = &n->n;
2771 n->code = NR_CURVETO;
2772 } else if (appr_p < appr_n) { // closer to n
2773 n->dragging_out = &n->n;
2774 opposite = &n->p;
2775 n->n.other->code = NR_CURVETO;
2776 } else { // p and n nodes are the same
2777 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2778 n->dragging_out = &n->p;
2779 opposite = &n->n;
2780 n->code = NR_CURVETO;
2781 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2782 n->dragging_out = &n->n;
2783 opposite = &n->p;
2784 n->n.other->code = NR_CURVETO;
2785 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2786 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);
2787 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);
2788 if (appr_other_p > appr_other_n) { // closer to other's p handle
2789 n->dragging_out = &n->n;
2790 opposite = &n->p;
2791 n->n.other->code = NR_CURVETO;
2792 } else { // closer to other's n handle
2793 n->dragging_out = &n->p;
2794 opposite = &n->n;
2795 n->code = NR_CURVETO;
2796 }
2797 }
2798 }
2800 // if there's another handle, make sure the one we drag out starts parallel to it
2801 if (opposite->pos != n->pos) {
2802 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2803 }
2805 // knots might not be created yet!
2806 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2807 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2808 }
2810 // pass this on to the handle-moved callback
2811 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2812 sp_node_update_handles(n);
2813 return TRUE;
2814 }
2816 if (state & GDK_CONTROL_MASK) { // constrained motion
2818 // calculate relative distances of handles
2819 // n handle:
2820 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2821 xn = n->n.pos[NR::X] - n->pos[NR::X];
2822 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2823 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2824 if (n->n.other) { // if there is the next point
2825 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2826 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2827 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2828 }
2829 }
2830 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2831 if (yn < 0) { xn = -xn; yn = -yn; }
2833 // p handle:
2834 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2835 xp = n->p.pos[NR::X] - n->pos[NR::X];
2836 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2837 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2838 if (n->p.other) {
2839 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2840 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2841 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2842 }
2843 }
2844 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2845 if (yp < 0) { xp = -xp; yp = -yp; }
2847 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2848 // sliding on handles, only if at least one of the handles is non-vertical
2849 // (otherwise it's the same as ctrl+drag anyway)
2851 // calculate angles of the handles
2852 if (xn == 0) {
2853 if (yn == 0) { // no handle, consider it the continuation of the other one
2854 an = 0;
2855 collinear = TRUE;
2856 }
2857 else an = 0; // vertical; set the angle to horizontal
2858 } else an = yn/xn;
2860 if (xp == 0) {
2861 if (yp == 0) { // no handle, consider it the continuation of the other one
2862 ap = an;
2863 }
2864 else ap = 0; // vertical; set the angle to horizontal
2865 } else ap = yp/xp;
2867 if (collinear) an = ap;
2869 // angles of the perpendiculars; HUGE_VAL means vertical
2870 if (an == 0) na = HUGE_VAL; else na = -1/an;
2871 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2873 // mouse point relative to the node's original pos
2874 pr = (*p) - n->origin;
2876 // distances to the four lines (two handles and two perpendiculars)
2877 d_an = point_line_distance(&pr, an);
2878 d_na = point_line_distance(&pr, na);
2879 d_ap = point_line_distance(&pr, ap);
2880 d_pa = point_line_distance(&pr, pa);
2882 // find out which line is the closest, save its closest point in c
2883 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2884 point_line_closest(&pr, an, &c);
2885 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2886 point_line_closest(&pr, ap, &c);
2887 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2888 point_line_closest(&pr, na, &c);
2889 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2890 point_line_closest(&pr, pa, &c);
2891 }
2893 // move the node to the closest point
2894 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2895 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2896 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2898 } else { // constraining to hor/vert
2900 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2901 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2902 } else { // snap to vert
2903 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2904 }
2905 }
2906 } else { // move freely
2907 if (state & GDK_MOD1_MASK) { // sculpt
2908 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2909 } else {
2910 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2911 (*p)[NR::X] - n->pos[NR::X],
2912 (*p)[NR::Y] - n->pos[NR::Y],
2913 (state & GDK_SHIFT_MASK) == 0);
2914 }
2915 }
2917 n->subpath->nodepath->desktop->scroll_to_point(p);
2919 return TRUE;
2920 }
2922 /**
2923 * Node handle clicked callback.
2924 */
2925 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2926 {
2927 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2929 if (state & GDK_CONTROL_MASK) { // "delete" handle
2930 if (n->p.knot == knot) {
2931 n->p.pos = n->pos;
2932 } else if (n->n.knot == knot) {
2933 n->n.pos = n->pos;
2934 }
2935 sp_node_update_handles(n);
2936 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2937 sp_nodepath_update_repr(nodepath);
2938 sp_nodepath_update_statusbar(nodepath);
2940 } else { // just select or add to selection, depending in Shift
2941 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2942 }
2943 }
2945 /**
2946 * Node handle grabbed callback.
2947 */
2948 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2949 {
2950 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2952 if (!n->selected) {
2953 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2954 }
2956 // remember the origin point of the handle
2957 if (n->p.knot == knot) {
2958 n->p.origin_radial = n->p.pos - n->pos;
2959 } else if (n->n.knot == knot) {
2960 n->n.origin_radial = n->n.pos - n->pos;
2961 } else {
2962 g_assert_not_reached();
2963 }
2965 }
2967 /**
2968 * Node handle ungrabbed callback.
2969 */
2970 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2971 {
2972 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2974 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2975 if (n->p.knot == knot) {
2976 n->p.origin_radial.a = 0;
2977 sp_knot_set_position(knot, &n->p.pos, state);
2978 } else if (n->n.knot == knot) {
2979 n->n.origin_radial.a = 0;
2980 sp_knot_set_position(knot, &n->n.pos, state);
2981 } else {
2982 g_assert_not_reached();
2983 }
2985 sp_nodepath_update_repr(n->subpath->nodepath);
2986 }
2988 /**
2989 * Node handle "request" signal callback.
2990 */
2991 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2992 {
2993 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2995 Inkscape::NodePath::NodeSide *me, *opposite;
2996 gint which;
2997 if (n->p.knot == knot) {
2998 me = &n->p;
2999 opposite = &n->n;
3000 which = -1;
3001 } else if (n->n.knot == knot) {
3002 me = &n->n;
3003 opposite = &n->p;
3004 which = 1;
3005 } else {
3006 me = opposite = NULL;
3007 which = 0;
3008 g_assert_not_reached();
3009 }
3011 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3013 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3015 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3016 /* We are smooth node adjacent with line */
3017 NR::Point const delta = *p - n->pos;
3018 NR::Coord const len = NR::L2(delta);
3019 Inkscape::NodePath::Node *othernode = opposite->other;
3020 NR::Point const ndelta = n->pos - othernode->pos;
3021 NR::Coord const linelen = NR::L2(ndelta);
3022 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3023 NR::Coord const scal = dot(delta, ndelta) / linelen;
3024 (*p) = n->pos + (scal / linelen) * ndelta;
3025 }
3026 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3027 } else {
3028 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3029 }
3031 sp_node_adjust_handle(n, -which);
3033 return FALSE;
3034 }
3036 /**
3037 * Node handle moved callback.
3038 */
3039 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3040 {
3041 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3043 Inkscape::NodePath::NodeSide *me;
3044 Inkscape::NodePath::NodeSide *other;
3045 if (n->p.knot == knot) {
3046 me = &n->p;
3047 other = &n->n;
3048 } else if (n->n.knot == knot) {
3049 me = &n->n;
3050 other = &n->p;
3051 } else {
3052 me = NULL;
3053 other = NULL;
3054 g_assert_not_reached();
3055 }
3057 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3058 Radial rme(me->pos - n->pos);
3059 Radial rother(other->pos - n->pos);
3060 Radial rnew(*p - n->pos);
3062 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3063 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3064 /* 0 interpreted as "no snapping". */
3066 // The closest PI/snaps angle, starting from zero.
3067 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3068 if (me->origin_radial.a == HUGE_VAL) {
3069 // ortho doesn't exist: original handle was zero length.
3070 rnew.a = a_snapped;
3071 } else {
3072 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3073 * its opposite and perpendiculars). */
3074 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3076 // Snap to the closest.
3077 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3078 ? a_snapped
3079 : a_ortho );
3080 }
3081 }
3083 if (state & GDK_MOD1_MASK) {
3084 // lock handle length
3085 rnew.r = me->origin_radial.r;
3086 }
3088 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3089 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3090 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3091 rother.a += rnew.a - rme.a;
3092 other->pos = NR::Point(rother) + n->pos;
3093 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3094 sp_knot_set_position(other->knot, &other->pos, 0);
3095 }
3097 me->pos = NR::Point(rnew) + n->pos;
3098 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3100 // this is what sp_knot_set_position does, but without emitting the signal:
3101 // we cannot emit a "moved" signal because we're now processing it
3102 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3104 knot->desktop->set_coordinate_status(me->pos);
3106 update_object(n->subpath->nodepath);
3108 /* status text */
3109 SPDesktop *desktop = n->subpath->nodepath->desktop;
3110 if (!desktop) return;
3111 SPEventContext *ec = desktop->event_context;
3112 if (!ec) return;
3113 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3114 if (!mc) return;
3116 double degrees = 180 / M_PI * rnew.a;
3117 if (degrees > 180) degrees -= 360;
3118 if (degrees < -180) degrees += 360;
3119 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3120 degrees = angle_to_compass (degrees);
3122 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3124 mc->setF(Inkscape::NORMAL_MESSAGE,
3125 _("<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);
3127 g_string_free(length, TRUE);
3128 }
3130 /**
3131 * Node handle event callback.
3132 */
3133 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3134 {
3135 gboolean ret = FALSE;
3136 switch (event->type) {
3137 case GDK_KEY_PRESS:
3138 switch (get_group0_keyval (&event->key)) {
3139 case GDK_space:
3140 if (event->key.state & GDK_BUTTON1_MASK) {
3141 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3142 stamp_repr(nodepath);
3143 ret = TRUE;
3144 }
3145 break;
3146 default:
3147 break;
3148 }
3149 break;
3150 default:
3151 break;
3152 }
3154 return ret;
3155 }
3157 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3158 Radial &rme, Radial &rother, gboolean const both)
3159 {
3160 rme.a += angle;
3161 if ( both
3162 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3163 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3164 {
3165 rother.a += angle;
3166 }
3167 }
3169 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3170 Radial &rme, Radial &rother, gboolean const both)
3171 {
3172 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3174 gdouble r;
3175 if ( both
3176 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3177 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3178 {
3179 r = MAX(rme.r, rother.r);
3180 } else {
3181 r = rme.r;
3182 }
3184 gdouble const weird_angle = atan2(norm_angle, r);
3185 /* Bulia says norm_angle is just the visible distance that the
3186 * object's end must travel on the screen. Left as 'angle' for want of
3187 * a better name.*/
3189 rme.a += weird_angle;
3190 if ( both
3191 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3192 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3193 {
3194 rother.a += weird_angle;
3195 }
3196 }
3198 /**
3199 * Rotate one node.
3200 */
3201 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3202 {
3203 Inkscape::NodePath::NodeSide *me, *other;
3204 bool both = false;
3206 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3207 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3209 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3210 me = &(n->p);
3211 other = &(n->n);
3212 } else if (!n->p.other) {
3213 me = &(n->n);
3214 other = &(n->p);
3215 } else {
3216 if (which > 0) { // right handle
3217 if (xn > xp) {
3218 me = &(n->n);
3219 other = &(n->p);
3220 } else {
3221 me = &(n->p);
3222 other = &(n->n);
3223 }
3224 } else if (which < 0){ // left handle
3225 if (xn <= xp) {
3226 me = &(n->n);
3227 other = &(n->p);
3228 } else {
3229 me = &(n->p);
3230 other = &(n->n);
3231 }
3232 } else { // both handles
3233 me = &(n->n);
3234 other = &(n->p);
3235 both = true;
3236 }
3237 }
3239 Radial rme(me->pos - n->pos);
3240 Radial rother(other->pos - n->pos);
3242 if (screen) {
3243 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3244 } else {
3245 node_rotate_one_internal (*n, angle, rme, rother, both);
3246 }
3248 me->pos = n->pos + NR::Point(rme);
3250 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3251 other->pos = n->pos + NR::Point(rother);
3252 }
3254 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3255 // so here we just move all the knots without emitting move signals, for speed
3256 sp_node_update_handles(n, false);
3257 }
3259 /**
3260 * Rotate selected nodes.
3261 */
3262 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3263 {
3264 if (!nodepath || !nodepath->selected) return;
3266 if (g_list_length(nodepath->selected) == 1) {
3267 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3268 node_rotate_one (n, angle, which, screen);
3269 } else {
3270 // rotate as an object:
3272 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3273 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3274 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3275 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3276 box.expandTo (n->pos); // contain all selected nodes
3277 }
3279 gdouble rot;
3280 if (screen) {
3281 gdouble const zoom = nodepath->desktop->current_zoom();
3282 gdouble const zmove = angle / zoom;
3283 gdouble const r = NR::L2(box.max() - box.midpoint());
3284 rot = atan2(zmove, r);
3285 } else {
3286 rot = angle;
3287 }
3289 NR::Matrix t =
3290 NR::Matrix (NR::translate(-box.midpoint())) *
3291 NR::Matrix (NR::rotate(rot)) *
3292 NR::Matrix (NR::translate(box.midpoint()));
3294 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3295 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3296 n->pos *= t;
3297 n->n.pos *= t;
3298 n->p.pos *= t;
3299 sp_node_update_handles(n, false);
3300 }
3301 }
3303 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3304 }
3306 /**
3307 * Scale one node.
3308 */
3309 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3310 {
3311 bool both = false;
3312 Inkscape::NodePath::NodeSide *me, *other;
3314 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3315 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3317 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3318 me = &(n->p);
3319 other = &(n->n);
3320 n->code = NR_CURVETO;
3321 } else if (!n->p.other) {
3322 me = &(n->n);
3323 other = &(n->p);
3324 if (n->n.other)
3325 n->n.other->code = NR_CURVETO;
3326 } else {
3327 if (which > 0) { // right handle
3328 if (xn > xp) {
3329 me = &(n->n);
3330 other = &(n->p);
3331 if (n->n.other)
3332 n->n.other->code = NR_CURVETO;
3333 } else {
3334 me = &(n->p);
3335 other = &(n->n);
3336 n->code = NR_CURVETO;
3337 }
3338 } else if (which < 0){ // left handle
3339 if (xn <= xp) {
3340 me = &(n->n);
3341 other = &(n->p);
3342 if (n->n.other)
3343 n->n.other->code = NR_CURVETO;
3344 } else {
3345 me = &(n->p);
3346 other = &(n->n);
3347 n->code = NR_CURVETO;
3348 }
3349 } else { // both handles
3350 me = &(n->n);
3351 other = &(n->p);
3352 both = true;
3353 n->code = NR_CURVETO;
3354 if (n->n.other)
3355 n->n.other->code = NR_CURVETO;
3356 }
3357 }
3359 Radial rme(me->pos - n->pos);
3360 Radial rother(other->pos - n->pos);
3362 rme.r += grow;
3363 if (rme.r < 0) rme.r = 0;
3364 if (rme.a == HUGE_VAL) {
3365 if (me->other) { // if direction is unknown, initialize it towards the next node
3366 Radial rme_next(me->other->pos - n->pos);
3367 rme.a = rme_next.a;
3368 } else { // if there's no next, initialize to 0
3369 rme.a = 0;
3370 }
3371 }
3372 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3373 rother.r += grow;
3374 if (rother.r < 0) rother.r = 0;
3375 if (rother.a == HUGE_VAL) {
3376 rother.a = rme.a + M_PI;
3377 }
3378 }
3380 me->pos = n->pos + NR::Point(rme);
3382 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3383 other->pos = n->pos + NR::Point(rother);
3384 }
3386 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3387 // so here we just move all the knots without emitting move signals, for speed
3388 sp_node_update_handles(n, false);
3389 }
3391 /**
3392 * Scale selected nodes.
3393 */
3394 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3395 {
3396 if (!nodepath || !nodepath->selected) return;
3398 if (g_list_length(nodepath->selected) == 1) {
3399 // scale handles of the single selected node
3400 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3401 node_scale_one (n, grow, which);
3402 } else {
3403 // scale nodes as an "object":
3405 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3406 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3407 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3408 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3409 box.expandTo (n->pos); // contain all selected nodes
3410 }
3412 double scale = (box.maxExtent() + grow)/box.maxExtent();
3414 NR::Matrix t =
3415 NR::Matrix (NR::translate(-box.midpoint())) *
3416 NR::Matrix (NR::scale(scale, scale)) *
3417 NR::Matrix (NR::translate(box.midpoint()));
3419 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3420 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3421 n->pos *= t;
3422 n->n.pos *= t;
3423 n->p.pos *= t;
3424 sp_node_update_handles(n, false);
3425 }
3426 }
3428 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3429 }
3431 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3432 {
3433 if (!nodepath) return;
3434 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3435 }
3437 /**
3438 * Flip selected nodes horizontally/vertically.
3439 */
3440 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3441 {
3442 if (!nodepath || !nodepath->selected) return;
3444 if (g_list_length(nodepath->selected) == 1) {
3445 // flip handles of the single selected node
3446 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3447 double temp = n->p.pos[axis];
3448 n->p.pos[axis] = n->n.pos[axis];
3449 n->n.pos[axis] = temp;
3450 sp_node_update_handles(n, false);
3451 } else {
3452 // scale nodes as an "object":
3454 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3455 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3456 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3457 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3458 box.expandTo (n->pos); // contain all selected nodes
3459 }
3461 NR::Matrix t =
3462 NR::Matrix (NR::translate(-box.midpoint())) *
3463 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3464 NR::Matrix (NR::translate(box.midpoint()));
3466 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3467 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3468 n->pos *= t;
3469 n->n.pos *= t;
3470 n->p.pos *= t;
3471 sp_node_update_handles(n, false);
3472 }
3473 }
3475 sp_nodepath_update_repr(nodepath);
3476 }
3478 //-----------------------------------------------
3479 /**
3480 * Return new subpath under given nodepath.
3481 */
3482 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3483 {
3484 g_assert(nodepath);
3485 g_assert(nodepath->desktop);
3487 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3489 s->nodepath = nodepath;
3490 s->closed = FALSE;
3491 s->nodes = NULL;
3492 s->first = NULL;
3493 s->last = NULL;
3495 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3496 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3497 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3499 return s;
3500 }
3502 /**
3503 * Destroy nodes in subpath, then subpath itself.
3504 */
3505 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3506 {
3507 g_assert(subpath);
3508 g_assert(subpath->nodepath);
3509 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3511 while (subpath->nodes) {
3512 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3513 }
3515 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3517 g_free(subpath);
3518 }
3520 /**
3521 * Link head to tail in subpath.
3522 */
3523 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3524 {
3525 g_assert(!sp->closed);
3526 g_assert(sp->last != sp->first);
3527 g_assert(sp->first->code == NR_MOVETO);
3529 sp->closed = TRUE;
3531 //Link the head to the tail
3532 sp->first->p.other = sp->last;
3533 sp->last->n.other = sp->first;
3534 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3535 sp->first = sp->last;
3537 //Remove the extra end node
3538 sp_nodepath_node_destroy(sp->last->n.other);
3539 }
3541 /**
3542 * Open closed (loopy) subpath at node.
3543 */
3544 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3545 {
3546 g_assert(sp->closed);
3547 g_assert(n->subpath == sp);
3548 g_assert(sp->first == sp->last);
3550 /* We create new startpoint, current node will become last one */
3552 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3553 &n->pos, &n->pos, &n->n.pos);
3556 sp->closed = FALSE;
3558 //Unlink to make a head and tail
3559 sp->first = new_path;
3560 sp->last = n;
3561 n->n.other = NULL;
3562 new_path->p.other = NULL;
3563 }
3565 /**
3566 * Returns area in triangle given by points; may be negative.
3567 */
3568 inline double
3569 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3570 {
3571 return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
3572 }
3574 /**
3575 * Return new node in subpath with given properties.
3576 * \param pos Position of node.
3577 * \param ppos Handle position in previous direction
3578 * \param npos Handle position in previous direction
3579 */
3580 Inkscape::NodePath::Node *
3581 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)
3582 {
3583 g_assert(sp);
3584 g_assert(sp->nodepath);
3585 g_assert(sp->nodepath->desktop);
3587 if (nodechunk == NULL)
3588 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3590 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3592 n->subpath = sp;
3594 if (type != Inkscape::NodePath::NODE_NONE) {
3595 // use the type from sodipodi:nodetypes
3596 n->type = type;
3597 } else {
3598 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3599 // points are (almost) collinear
3600 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3601 // endnode, or a node with a retracted handle
3602 n->type = Inkscape::NodePath::NODE_CUSP;
3603 } else {
3604 n->type = Inkscape::NodePath::NODE_SMOOTH;
3605 }
3606 } else {
3607 n->type = Inkscape::NodePath::NODE_CUSP;
3608 }
3609 }
3611 n->code = code;
3612 n->selected = FALSE;
3613 n->pos = *pos;
3614 n->p.pos = *ppos;
3615 n->n.pos = *npos;
3617 n->dragging_out = NULL;
3619 Inkscape::NodePath::Node *prev;
3620 if (next) {
3621 //g_assert(g_list_find(sp->nodes, next));
3622 prev = next->p.other;
3623 } else {
3624 prev = sp->last;
3625 }
3627 if (prev)
3628 prev->n.other = n;
3629 else
3630 sp->first = n;
3632 if (next)
3633 next->p.other = n;
3634 else
3635 sp->last = n;
3637 n->p.other = prev;
3638 n->n.other = next;
3640 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"));
3641 sp_knot_set_position(n->knot, pos, 0);
3643 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3644 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3645 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3646 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3647 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3648 sp_knot_update_ctrl(n->knot);
3650 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3651 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3652 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3653 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3654 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3655 sp_knot_show(n->knot);
3657 // We only create handle knots and lines on demand
3658 n->p.knot = NULL;
3659 n->p.line = NULL;
3660 n->n.knot = NULL;
3661 n->n.line = NULL;
3663 sp->nodes = g_list_prepend(sp->nodes, n);
3665 return n;
3666 }
3668 /**
3669 * Destroy node and its knots, link neighbors in subpath.
3670 */
3671 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3672 {
3673 g_assert(node);
3674 g_assert(node->subpath);
3675 g_assert(SP_IS_KNOT(node->knot));
3677 Inkscape::NodePath::SubPath *sp = node->subpath;
3679 if (node->selected) { // first, deselect
3680 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3681 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3682 }
3684 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3686 g_object_unref(G_OBJECT(node->knot));
3687 if (node->p.knot)
3688 g_object_unref(G_OBJECT(node->p.knot));
3689 if (node->n.knot)
3690 g_object_unref(G_OBJECT(node->n.knot));
3692 if (node->p.line)
3693 gtk_object_destroy(GTK_OBJECT(node->p.line));
3694 if (node->n.line)
3695 gtk_object_destroy(GTK_OBJECT(node->n.line));
3697 if (sp->nodes) { // there are others nodes on the subpath
3698 if (sp->closed) {
3699 if (sp->first == node) {
3700 g_assert(sp->last == node);
3701 sp->first = node->n.other;
3702 sp->last = sp->first;
3703 }
3704 node->p.other->n.other = node->n.other;
3705 node->n.other->p.other = node->p.other;
3706 } else {
3707 if (sp->first == node) {
3708 sp->first = node->n.other;
3709 sp->first->code = NR_MOVETO;
3710 }
3711 if (sp->last == node) sp->last = node->p.other;
3712 if (node->p.other) node->p.other->n.other = node->n.other;
3713 if (node->n.other) node->n.other->p.other = node->p.other;
3714 }
3715 } else { // this was the last node on subpath
3716 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3717 }
3719 g_mem_chunk_free(nodechunk, node);
3720 }
3722 /**
3723 * Returns one of the node's two sides.
3724 * \param which Indicates which side.
3725 * \return Pointer to previous node side if which==-1, next if which==1.
3726 */
3727 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3728 {
3729 g_assert(node);
3731 switch (which) {
3732 case -1:
3733 return &node->p;
3734 case 1:
3735 return &node->n;
3736 default:
3737 break;
3738 }
3740 g_assert_not_reached();
3742 return NULL;
3743 }
3745 /**
3746 * Return the other side of the node, given one of its sides.
3747 */
3748 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3749 {
3750 g_assert(node);
3752 if (me == &node->p) return &node->n;
3753 if (me == &node->n) return &node->p;
3755 g_assert_not_reached();
3757 return NULL;
3758 }
3760 /**
3761 * Return NRPathcode on the given side of the node.
3762 */
3763 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3764 {
3765 g_assert(node);
3767 if (me == &node->p) {
3768 if (node->p.other) return (NRPathcode)node->code;
3769 return NR_MOVETO;
3770 }
3772 if (me == &node->n) {
3773 if (node->n.other) return (NRPathcode)node->n.other->code;
3774 return NR_MOVETO;
3775 }
3777 g_assert_not_reached();
3779 return NR_END;
3780 }
3782 /**
3783 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3784 */
3785 Inkscape::NodePath::Node *
3786 sp_nodepath_get_node_by_index(int index)
3787 {
3788 Inkscape::NodePath::Node *e = NULL;
3790 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3791 if (!nodepath) {
3792 return e;
3793 }
3795 //find segment
3796 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3798 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3799 int n = g_list_length(sp->nodes);
3800 if (sp->closed) {
3801 n++;
3802 }
3804 //if the piece belongs to this subpath grab it
3805 //otherwise move onto the next subpath
3806 if (index < n) {
3807 e = sp->first;
3808 for (int i = 0; i < index; ++i) {
3809 e = e->n.other;
3810 }
3811 break;
3812 } else {
3813 if (sp->closed) {
3814 index -= (n+1);
3815 } else {
3816 index -= n;
3817 }
3818 }
3819 }
3821 return e;
3822 }
3824 /**
3825 * Returns plain text meaning of node type.
3826 */
3827 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3828 {
3829 unsigned retracted = 0;
3830 bool endnode = false;
3832 for (int which = -1; which <= 1; which += 2) {
3833 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3834 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3835 retracted ++;
3836 if (!side->other)
3837 endnode = true;
3838 }
3840 if (retracted == 0) {
3841 if (endnode) {
3842 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3843 return _("end node");
3844 } else {
3845 switch (node->type) {
3846 case Inkscape::NodePath::NODE_CUSP:
3847 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3848 return _("cusp");
3849 case Inkscape::NodePath::NODE_SMOOTH:
3850 // TRANSLATORS: "smooth" is an adjective here
3851 return _("smooth");
3852 case Inkscape::NodePath::NODE_SYMM:
3853 return _("symmetric");
3854 }
3855 }
3856 } else if (retracted == 1) {
3857 if (endnode) {
3858 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3859 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3860 } else {
3861 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3862 }
3863 } else {
3864 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3865 }
3867 return NULL;
3868 }
3870 /**
3871 * Handles content of statusbar as long as node tool is active.
3872 */
3873 void
3874 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3875 {
3876 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3877 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3879 gint total = 0;
3880 gint selected = 0;
3881 SPDesktop *desktop = NULL;
3883 if (nodepath) {
3884 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3885 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3886 total += g_list_length(subpath->nodes);
3887 }
3888 selected = g_list_length(nodepath->selected);
3889 desktop = nodepath->desktop;
3890 } else {
3891 desktop = SP_ACTIVE_DESKTOP;
3892 }
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 if (selected == 0) {
3900 Inkscape::Selection *sel = desktop->selection;
3901 if (!sel || sel->isEmpty()) {
3902 mc->setF(Inkscape::NORMAL_MESSAGE,
3903 _("Select a single object to edit its nodes or handles."));
3904 } else {
3905 if (nodepath) {
3906 mc->setF(Inkscape::NORMAL_MESSAGE,
3907 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.",
3908 "<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.",
3909 total),
3910 total);
3911 } else {
3912 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3913 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3914 } else {
3915 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3916 }
3917 }
3918 }
3919 } else if (nodepath && selected == 1) {
3920 mc->setF(Inkscape::NORMAL_MESSAGE,
3921 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3922 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3923 total),
3924 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3925 } else {
3926 mc->setF(Inkscape::NORMAL_MESSAGE,
3927 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3928 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3929 total),
3930 selected, total, when_selected);
3931 }
3932 }
3935 /*
3936 Local Variables:
3937 mode:c++
3938 c-file-style:"stroustrup"
3939 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3940 indent-tabs-mode:nil
3941 fill-column:99
3942 End:
3943 */
3944 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :