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 (!g_slist_find(nodepaths, nodepath))
1874 nodepaths = g_slist_prepend (nodepaths, nodepath);
1875 }
1877 for (GSList *i = nodepaths; i; i = i->next) {
1878 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1879 // different nodepaths will give us one undo event per nodepath
1880 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1881 // if the entire nodepath is removed, delete the selected object.
1882 if (nodepath->subpaths == NULL ||
1883 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1884 //at least 2
1885 sp_nodepath_get_node_count(nodepath) < 2) {
1886 SPDocument *document = sp_desktop_document (nodepath->desktop);
1887 sp_nodepath_destroy(nodepath);
1888 g_list_free(nodes_to_delete);
1889 nodes_to_delete = NULL;
1890 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
1891 //delete this nodepath's object, not the entire selection! (though at this time, this
1892 //does not matter)
1893 sp_selection_delete();
1894 sp_document_done (document);
1895 } else {
1896 sp_nodepath_update_repr(nodepath);
1897 sp_nodepath_update_statusbar(nodepath);
1898 }
1899 }
1901 g_slist_free (nodepaths);
1902 }
1904 /**
1905 * Delete one or more selected nodes.
1906 */
1907 void sp_node_selected_delete()
1908 {
1909 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1910 if (!nodepath) return;
1911 if (!nodepath->selected) return;
1913 /** \todo fixme: do it the right way */
1914 while (nodepath->selected) {
1915 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1916 sp_nodepath_node_destroy(node);
1917 }
1920 //clean up the nodepath (such as for trivial subpaths)
1921 sp_nodepath_cleanup(nodepath);
1923 sp_nodepath_update_handles(nodepath);
1925 // if the entire nodepath is removed, delete the selected object.
1926 if (nodepath->subpaths == NULL ||
1927 sp_nodepath_get_node_count(nodepath) < 2) {
1928 SPDocument *document = sp_desktop_document (nodepath->desktop);
1929 sp_nodepath_destroy(nodepath);
1930 sp_selection_delete();
1931 sp_document_done (document);
1932 return;
1933 }
1935 sp_nodepath_update_repr(nodepath);
1937 sp_nodepath_update_statusbar(nodepath);
1938 }
1940 /**
1941 * Delete one or more segments between two selected nodes.
1942 * This is the code for 'split'.
1943 */
1944 void
1945 sp_node_selected_delete_segment(void)
1946 {
1947 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1948 Inkscape::NodePath::Node *curr, *next; //Iterators
1950 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1951 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1953 if (g_list_length(nodepath->selected) != 2) {
1954 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1955 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1956 return;
1957 }
1959 //Selected nodes, not inclusive
1960 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1961 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1963 if ( ( a==b) || //same node
1964 (a->subpath != b->subpath ) || //not the same path
1965 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1966 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1967 {
1968 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1969 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1970 return;
1971 }
1973 //###########################################
1974 //# BEGIN EDITS
1975 //###########################################
1976 //##################################
1977 //# CLOSED PATH
1978 //##################################
1979 if (a->subpath->closed) {
1982 gboolean reversed = FALSE;
1984 //Since we can go in a circle, we need to find the shorter distance.
1985 // a->b or b->a
1986 start = end = NULL;
1987 int distance = 0;
1988 int minDistance = 0;
1989 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1990 if (curr==b) {
1991 //printf("a to b:%d\n", distance);
1992 start = a;//go from a to b
1993 end = b;
1994 minDistance = distance;
1995 //printf("A to B :\n");
1996 break;
1997 }
1998 distance++;
1999 }
2001 //try again, the other direction
2002 distance = 0;
2003 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2004 if (curr==a) {
2005 //printf("b to a:%d\n", distance);
2006 if (distance < minDistance) {
2007 start = b; //we go from b to a
2008 end = a;
2009 reversed = TRUE;
2010 //printf("B to A\n");
2011 }
2012 break;
2013 }
2014 distance++;
2015 }
2018 //Copy everything from 'end' to 'start' to a new subpath
2019 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2020 for (curr=end ; curr ; curr=curr->n.other) {
2021 NRPathcode code = (NRPathcode) curr->code;
2022 if (curr == end)
2023 code = NR_MOVETO;
2024 sp_nodepath_node_new(t, NULL,
2025 (Inkscape::NodePath::NodeType)curr->type, code,
2026 &curr->p.pos, &curr->pos, &curr->n.pos);
2027 if (curr == start)
2028 break;
2029 }
2030 sp_nodepath_subpath_destroy(a->subpath);
2033 }
2037 //##################################
2038 //# OPEN PATH
2039 //##################################
2040 else {
2042 //We need to get the direction of the list between A and B
2043 //Can we walk from a to b?
2044 start = end = NULL;
2045 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2046 if (curr==b) {
2047 start = a; //did it! we go from a to b
2048 end = b;
2049 //printf("A to B\n");
2050 break;
2051 }
2052 }
2053 if (!start) {//didn't work? let's try the other direction
2054 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2055 if (curr==a) {
2056 start = b; //did it! we go from b to a
2057 end = a;
2058 //printf("B to A\n");
2059 break;
2060 }
2061 }
2062 }
2063 if (!start) {
2064 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2065 _("Cannot find path between nodes."));
2066 return;
2067 }
2071 //Copy everything after 'end' to a new subpath
2072 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2073 for (curr=end ; curr ; curr=curr->n.other) {
2074 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2075 &curr->p.pos, &curr->pos, &curr->n.pos);
2076 }
2078 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2079 for (curr = start->n.other ; curr ; curr=next) {
2080 next = curr->n.other;
2081 sp_nodepath_node_destroy(curr);
2082 }
2084 }
2085 //###########################################
2086 //# END EDITS
2087 //###########################################
2089 //clean up the nodepath (such as for trivial subpaths)
2090 sp_nodepath_cleanup(nodepath);
2092 sp_nodepath_update_handles(nodepath);
2094 sp_nodepath_update_repr(nodepath);
2096 // if the entire nodepath is removed, delete the selected object.
2097 if (nodepath->subpaths == NULL ||
2098 sp_nodepath_get_node_count(nodepath) < 2) {
2099 sp_nodepath_destroy(nodepath);
2100 sp_selection_delete();
2101 return;
2102 }
2104 sp_nodepath_update_statusbar(nodepath);
2105 }
2107 /**
2108 * Call sp_nodepath_set_line() for all selected segments.
2109 */
2110 void
2111 sp_node_selected_set_line_type(NRPathcode code)
2112 {
2113 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2114 if (nodepath == NULL) return;
2116 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2117 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2118 g_assert(n->selected);
2119 if (n->p.other && n->p.other->selected) {
2120 sp_nodepath_set_line_type(n, code);
2121 }
2122 }
2124 sp_nodepath_update_repr(nodepath);
2125 }
2127 /**
2128 * Call sp_nodepath_convert_node_type() for all selected nodes.
2129 */
2130 void
2131 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2132 {
2133 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2134 if (nodepath == NULL) return;
2136 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2137 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2138 }
2140 sp_nodepath_update_repr(nodepath);
2141 }
2143 /**
2144 * Change select status of node, update its own and neighbour handles.
2145 */
2146 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2147 {
2148 node->selected = selected;
2150 if (selected) {
2151 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2152 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2153 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2154 sp_knot_update_ctrl(node->knot);
2155 } else {
2156 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2157 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2158 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2159 sp_knot_update_ctrl(node->knot);
2160 }
2162 sp_node_update_handles(node);
2163 if (node->n.other) sp_node_update_handles(node->n.other);
2164 if (node->p.other) sp_node_update_handles(node->p.other);
2165 }
2167 /**
2168 \brief Select a node
2169 \param node The node to select
2170 \param incremental If true, add to selection, otherwise deselect others
2171 \param override If true, always select this node, otherwise toggle selected status
2172 */
2173 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2174 {
2175 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2177 if (incremental) {
2178 if (override) {
2179 if (!g_list_find(nodepath->selected, node)) {
2180 nodepath->selected = g_list_prepend(nodepath->selected, node);
2181 }
2182 sp_node_set_selected(node, TRUE);
2183 } else { // toggle
2184 if (node->selected) {
2185 g_assert(g_list_find(nodepath->selected, node));
2186 nodepath->selected = g_list_remove(nodepath->selected, node);
2187 } else {
2188 g_assert(!g_list_find(nodepath->selected, node));
2189 nodepath->selected = g_list_prepend(nodepath->selected, node);
2190 }
2191 sp_node_set_selected(node, !node->selected);
2192 }
2193 } else {
2194 sp_nodepath_deselect(nodepath);
2195 nodepath->selected = g_list_prepend(nodepath->selected, node);
2196 sp_node_set_selected(node, TRUE);
2197 }
2199 sp_nodepath_update_statusbar(nodepath);
2200 }
2203 /**
2204 \brief Deselect all nodes in the nodepath
2205 */
2206 void
2207 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2208 {
2209 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2211 while (nodepath->selected) {
2212 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2213 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2214 }
2215 sp_nodepath_update_statusbar(nodepath);
2216 }
2218 /**
2219 \brief Select or invert selection of all nodes in the nodepath
2220 */
2221 void
2222 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2223 {
2224 if (!nodepath) return;
2226 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2227 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2228 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2229 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2230 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2231 }
2232 }
2233 }
2235 /**
2236 * If nothing selected, does the same as sp_nodepath_select_all();
2237 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2238 * (i.e., similar to "select all in layer", with the "selected" subpaths
2239 * being treated as "layers" in the path).
2240 */
2241 void
2242 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2243 {
2244 if (!nodepath) return;
2246 if (g_list_length (nodepath->selected) == 0) {
2247 sp_nodepath_select_all (nodepath, invert);
2248 return;
2249 }
2251 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2252 GSList *subpaths = NULL;
2254 for (GList *l = copy; l != NULL; l = l->next) {
2255 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2256 Inkscape::NodePath::SubPath *subpath = n->subpath;
2257 if (!g_slist_find (subpaths, subpath))
2258 subpaths = g_slist_prepend (subpaths, subpath);
2259 }
2261 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2262 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2263 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2264 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2265 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2266 }
2267 }
2269 g_slist_free (subpaths);
2270 g_list_free (copy);
2271 }
2273 /**
2274 * \brief Select the node after the last selected; if none is selected,
2275 * select the first within path.
2276 */
2277 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2278 {
2279 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2281 Inkscape::NodePath::Node *last = NULL;
2282 if (nodepath->selected) {
2283 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2284 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2285 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2286 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2287 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2288 if (node->selected) {
2289 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2290 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2291 if (spl->next) { // there's a next subpath
2292 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2293 last = subpath_next->first;
2294 } else if (spl->prev) { // there's a previous subpath
2295 last = NULL; // to be set later to the first node of first subpath
2296 } else {
2297 last = node->n.other;
2298 }
2299 } else {
2300 last = node->n.other;
2301 }
2302 } else {
2303 if (node->n.other) {
2304 last = node->n.other;
2305 } else {
2306 if (spl->next) { // there's a next subpath
2307 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2308 last = subpath_next->first;
2309 } else if (spl->prev) { // there's a previous subpath
2310 last = NULL; // to be set later to the first node of first subpath
2311 } else {
2312 last = (Inkscape::NodePath::Node *) subpath->first;
2313 }
2314 }
2315 }
2316 }
2317 }
2318 }
2319 sp_nodepath_deselect(nodepath);
2320 }
2322 if (last) { // there's at least one more node after selected
2323 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2324 } else { // no more nodes, select the first one in first subpath
2325 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2326 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2327 }
2328 }
2330 /**
2331 * \brief Select the node before the first selected; if none is selected,
2332 * select the last within path
2333 */
2334 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2335 {
2336 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2338 Inkscape::NodePath::Node *last = NULL;
2339 if (nodepath->selected) {
2340 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2341 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2342 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2343 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2344 if (node->selected) {
2345 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2346 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2347 if (spl->prev) { // there's a prev subpath
2348 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2349 last = subpath_prev->last;
2350 } else if (spl->next) { // there's a next subpath
2351 last = NULL; // to be set later to the last node of last subpath
2352 } else {
2353 last = node->p.other;
2354 }
2355 } else {
2356 last = node->p.other;
2357 }
2358 } else {
2359 if (node->p.other) {
2360 last = node->p.other;
2361 } else {
2362 if (spl->prev) { // there's a prev subpath
2363 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2364 last = subpath_prev->last;
2365 } else if (spl->next) { // there's a next subpath
2366 last = NULL; // to be set later to the last node of last subpath
2367 } else {
2368 last = (Inkscape::NodePath::Node *) subpath->last;
2369 }
2370 }
2371 }
2372 }
2373 }
2374 }
2375 sp_nodepath_deselect(nodepath);
2376 }
2378 if (last) { // there's at least one more node before selected
2379 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2380 } else { // no more nodes, select the last one in last subpath
2381 GList *spl = g_list_last(nodepath->subpaths);
2382 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2383 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2384 }
2385 }
2387 /**
2388 * \brief Select all nodes that are within the rectangle.
2389 */
2390 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2391 {
2392 if (!incremental) {
2393 sp_nodepath_deselect(nodepath);
2394 }
2396 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2397 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2398 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2399 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2401 if (b.contains(node->pos)) {
2402 sp_nodepath_node_select(node, TRUE, TRUE);
2403 }
2404 }
2405 }
2406 }
2409 /**
2410 \brief Saves all nodes' and handles' current positions in their origin members
2411 */
2412 void
2413 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2414 {
2415 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2416 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2417 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2418 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2419 n->origin = n->pos;
2420 n->p.origin = n->p.pos;
2421 n->n.origin = n->n.pos;
2422 }
2423 }
2424 }
2426 /**
2427 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2428 */
2429 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2430 {
2431 if (!nodepath->selected) {
2432 return NULL;
2433 }
2435 GList *r = NULL;
2436 guint i = 0;
2437 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2438 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2439 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2440 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2441 i++;
2442 if (node->selected) {
2443 r = g_list_append(r, GINT_TO_POINTER(i));
2444 }
2445 }
2446 }
2447 return r;
2448 }
2450 /**
2451 \brief Restores selection by selecting nodes whose positions are in the list
2452 */
2453 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2454 {
2455 sp_nodepath_deselect(nodepath);
2457 guint i = 0;
2458 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2459 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2460 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2461 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2462 i++;
2463 if (g_list_find(r, GINT_TO_POINTER(i))) {
2464 sp_nodepath_node_select(node, TRUE, TRUE);
2465 }
2466 }
2467 }
2469 }
2471 /**
2472 \brief Adjusts handle according to node type and line code.
2473 */
2474 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2475 {
2476 double len, otherlen, linelen;
2478 g_assert(node);
2480 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2481 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2483 /** \todo fixme: */
2484 if (me->other == NULL) return;
2485 if (other->other == NULL) return;
2487 /* I have line */
2489 NRPathcode mecode, ocode;
2490 if (which_adjust == 1) {
2491 mecode = (NRPathcode)me->other->code;
2492 ocode = (NRPathcode)node->code;
2493 } else {
2494 mecode = (NRPathcode)node->code;
2495 ocode = (NRPathcode)other->other->code;
2496 }
2498 if (mecode == NR_LINETO) return;
2500 /* I am curve */
2502 if (other->other == NULL) return;
2504 /* Other has line */
2506 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2508 NR::Point delta;
2509 if (ocode == NR_LINETO) {
2510 /* other is lineto, we are either smooth or symm */
2511 Inkscape::NodePath::Node *othernode = other->other;
2512 len = NR::L2(me->pos - node->pos);
2513 delta = node->pos - othernode->pos;
2514 linelen = NR::L2(delta);
2515 if (linelen < 1e-18)
2516 return;
2517 me->pos = node->pos + (len / linelen)*delta;
2518 return;
2519 }
2521 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2523 me->pos = 2 * node->pos - other->pos;
2524 return;
2525 }
2527 /* We are smooth */
2529 len = NR::L2(me->pos - node->pos);
2530 delta = other->pos - node->pos;
2531 otherlen = NR::L2(delta);
2532 if (otherlen < 1e-18) return;
2534 me->pos = node->pos - (len / otherlen) * delta;
2535 }
2537 /**
2538 \brief Adjusts both handles according to node type and line code
2539 */
2540 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2541 {
2542 g_assert(node);
2544 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2546 /* we are either smooth or symm */
2548 if (node->p.other == NULL) return;
2550 if (node->n.other == NULL) return;
2552 if (node->code == NR_LINETO) {
2553 if (node->n.other->code == NR_LINETO) return;
2554 sp_node_adjust_handle(node, 1);
2555 return;
2556 }
2558 if (node->n.other->code == NR_LINETO) {
2559 if (node->code == NR_LINETO) return;
2560 sp_node_adjust_handle(node, -1);
2561 return;
2562 }
2564 /* both are curves */
2565 NR::Point const delta( node->n.pos - node->p.pos );
2567 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2568 node->p.pos = node->pos - delta / 2;
2569 node->n.pos = node->pos + delta / 2;
2570 return;
2571 }
2573 /* We are smooth */
2574 double plen = NR::L2(node->p.pos - node->pos);
2575 if (plen < 1e-18) return;
2576 double nlen = NR::L2(node->n.pos - node->pos);
2577 if (nlen < 1e-18) return;
2578 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2579 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2580 }
2582 /**
2583 * Node event callback.
2584 */
2585 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2586 {
2587 gboolean ret = FALSE;
2588 switch (event->type) {
2589 case GDK_ENTER_NOTIFY:
2590 active_node = n;
2591 break;
2592 case GDK_LEAVE_NOTIFY:
2593 active_node = NULL;
2594 break;
2595 case GDK_KEY_PRESS:
2596 switch (get_group0_keyval (&event->key)) {
2597 case GDK_space:
2598 if (event->key.state & GDK_BUTTON1_MASK) {
2599 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2600 stamp_repr(nodepath);
2601 ret = TRUE;
2602 }
2603 break;
2604 default:
2605 break;
2606 }
2607 break;
2608 default:
2609 break;
2610 }
2612 return ret;
2613 }
2615 /**
2616 * Handle keypress on node; directly called.
2617 */
2618 gboolean node_key(GdkEvent *event)
2619 {
2620 Inkscape::NodePath::Path *np;
2622 // there is no way to verify nodes so set active_node to nil when deleting!!
2623 if (active_node == NULL) return FALSE;
2625 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2626 gint ret = FALSE;
2627 switch (get_group0_keyval (&event->key)) {
2628 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2629 case GDK_BackSpace:
2630 np = active_node->subpath->nodepath;
2631 sp_nodepath_node_destroy(active_node);
2632 sp_nodepath_update_repr(np);
2633 active_node = NULL;
2634 ret = TRUE;
2635 break;
2636 case GDK_c:
2637 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2638 ret = TRUE;
2639 break;
2640 case GDK_s:
2641 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2642 ret = TRUE;
2643 break;
2644 case GDK_y:
2645 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2646 ret = TRUE;
2647 break;
2648 case GDK_b:
2649 sp_nodepath_node_break(active_node);
2650 ret = TRUE;
2651 break;
2652 }
2653 return ret;
2654 }
2655 return FALSE;
2656 }
2658 /**
2659 * Mouseclick on node callback.
2660 */
2661 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2662 {
2663 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2665 if (state & GDK_CONTROL_MASK) {
2666 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2668 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2669 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2670 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2671 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2672 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2673 } else {
2674 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2675 }
2676 sp_nodepath_update_repr(nodepath);
2677 sp_nodepath_update_statusbar(nodepath);
2679 } else { //ctrl+alt+click: delete node
2680 GList *node_to_delete = NULL;
2681 node_to_delete = g_list_append(node_to_delete, n);
2682 sp_node_delete_preserve(node_to_delete);
2683 }
2685 } else {
2686 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2687 }
2688 }
2690 /**
2691 * Mouse grabbed node callback.
2692 */
2693 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2694 {
2695 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2697 if (!n->selected) {
2698 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2699 }
2701 sp_nodepath_remember_origins (n->subpath->nodepath);
2702 }
2704 /**
2705 * Mouse ungrabbed node callback.
2706 */
2707 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2708 {
2709 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2711 n->dragging_out = NULL;
2713 sp_nodepath_update_repr(n->subpath->nodepath);
2714 }
2716 /**
2717 * The point on a line, given by its angle, closest to the given point.
2718 * \param p A point.
2719 * \param a Angle of the line; it is assumed to go through coordinate origin.
2720 * \param closest Pointer to the point struct where the result is stored.
2721 * \todo FIXME: use dot product perhaps?
2722 */
2723 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2724 {
2725 if (a == HUGE_VAL) { // vertical
2726 *closest = NR::Point(0, (*p)[NR::Y]);
2727 } else {
2728 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2729 (*closest)[NR::Y] = a * (*closest)[NR::X];
2730 }
2731 }
2733 /**
2734 * Distance from the point to a line given by its angle.
2735 * \param p A point.
2736 * \param a Angle of the line; it is assumed to go through coordinate origin.
2737 */
2738 static double point_line_distance(NR::Point *p, double a)
2739 {
2740 NR::Point c;
2741 point_line_closest(p, a, &c);
2742 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]));
2743 }
2745 /**
2746 * Callback for node "request" signal.
2747 * \todo fixme: This goes to "moved" event? (lauris)
2748 */
2749 static gboolean
2750 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2751 {
2752 double yn, xn, yp, xp;
2753 double an, ap, na, pa;
2754 double d_an, d_ap, d_na, d_pa;
2755 gboolean collinear = FALSE;
2756 NR::Point c;
2757 NR::Point pr;
2759 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2761 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2762 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2764 NR::Point mouse = (*p);
2766 if (!n->dragging_out) {
2767 // This is the first drag-out event; find out which handle to drag out
2768 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2769 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2771 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2772 return FALSE;
2774 Inkscape::NodePath::NodeSide *opposite;
2775 if (appr_p > appr_n) { // closer to p
2776 n->dragging_out = &n->p;
2777 opposite = &n->n;
2778 n->code = NR_CURVETO;
2779 } else if (appr_p < appr_n) { // closer to n
2780 n->dragging_out = &n->n;
2781 opposite = &n->p;
2782 n->n.other->code = NR_CURVETO;
2783 } else { // p and n nodes are the same
2784 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2785 n->dragging_out = &n->p;
2786 opposite = &n->n;
2787 n->code = NR_CURVETO;
2788 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2789 n->dragging_out = &n->n;
2790 opposite = &n->p;
2791 n->n.other->code = NR_CURVETO;
2792 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2793 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);
2794 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);
2795 if (appr_other_p > appr_other_n) { // closer to other's p handle
2796 n->dragging_out = &n->n;
2797 opposite = &n->p;
2798 n->n.other->code = NR_CURVETO;
2799 } else { // closer to other's n handle
2800 n->dragging_out = &n->p;
2801 opposite = &n->n;
2802 n->code = NR_CURVETO;
2803 }
2804 }
2805 }
2807 // if there's another handle, make sure the one we drag out starts parallel to it
2808 if (opposite->pos != n->pos) {
2809 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2810 }
2812 // knots might not be created yet!
2813 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2814 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2815 }
2817 // pass this on to the handle-moved callback
2818 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2819 sp_node_update_handles(n);
2820 return TRUE;
2821 }
2823 if (state & GDK_CONTROL_MASK) { // constrained motion
2825 // calculate relative distances of handles
2826 // n handle:
2827 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2828 xn = n->n.pos[NR::X] - n->pos[NR::X];
2829 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2830 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2831 if (n->n.other) { // if there is the next point
2832 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2833 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2834 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2835 }
2836 }
2837 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2838 if (yn < 0) { xn = -xn; yn = -yn; }
2840 // p handle:
2841 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2842 xp = n->p.pos[NR::X] - n->pos[NR::X];
2843 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2844 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2845 if (n->p.other) {
2846 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2847 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2848 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2849 }
2850 }
2851 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2852 if (yp < 0) { xp = -xp; yp = -yp; }
2854 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2855 // sliding on handles, only if at least one of the handles is non-vertical
2856 // (otherwise it's the same as ctrl+drag anyway)
2858 // calculate angles of the handles
2859 if (xn == 0) {
2860 if (yn == 0) { // no handle, consider it the continuation of the other one
2861 an = 0;
2862 collinear = TRUE;
2863 }
2864 else an = 0; // vertical; set the angle to horizontal
2865 } else an = yn/xn;
2867 if (xp == 0) {
2868 if (yp == 0) { // no handle, consider it the continuation of the other one
2869 ap = an;
2870 }
2871 else ap = 0; // vertical; set the angle to horizontal
2872 } else ap = yp/xp;
2874 if (collinear) an = ap;
2876 // angles of the perpendiculars; HUGE_VAL means vertical
2877 if (an == 0) na = HUGE_VAL; else na = -1/an;
2878 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2880 // mouse point relative to the node's original pos
2881 pr = (*p) - n->origin;
2883 // distances to the four lines (two handles and two perpendiculars)
2884 d_an = point_line_distance(&pr, an);
2885 d_na = point_line_distance(&pr, na);
2886 d_ap = point_line_distance(&pr, ap);
2887 d_pa = point_line_distance(&pr, pa);
2889 // find out which line is the closest, save its closest point in c
2890 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2891 point_line_closest(&pr, an, &c);
2892 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2893 point_line_closest(&pr, ap, &c);
2894 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2895 point_line_closest(&pr, na, &c);
2896 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2897 point_line_closest(&pr, pa, &c);
2898 }
2900 // move the node to the closest point
2901 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2902 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2903 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2905 } else { // constraining to hor/vert
2907 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2908 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2909 } else { // snap to vert
2910 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2911 }
2912 }
2913 } else { // move freely
2914 if (state & GDK_MOD1_MASK) { // sculpt
2915 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
2916 } else {
2917 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2918 (*p)[NR::X] - n->pos[NR::X],
2919 (*p)[NR::Y] - n->pos[NR::Y],
2920 (state & GDK_SHIFT_MASK) == 0);
2921 }
2922 }
2924 n->subpath->nodepath->desktop->scroll_to_point(p);
2926 return TRUE;
2927 }
2929 /**
2930 * Node handle clicked callback.
2931 */
2932 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2933 {
2934 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2936 if (state & GDK_CONTROL_MASK) { // "delete" handle
2937 if (n->p.knot == knot) {
2938 n->p.pos = n->pos;
2939 } else if (n->n.knot == knot) {
2940 n->n.pos = n->pos;
2941 }
2942 sp_node_update_handles(n);
2943 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2944 sp_nodepath_update_repr(nodepath);
2945 sp_nodepath_update_statusbar(nodepath);
2947 } else { // just select or add to selection, depending in Shift
2948 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2949 }
2950 }
2952 /**
2953 * Node handle grabbed callback.
2954 */
2955 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2956 {
2957 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2959 if (!n->selected) {
2960 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2961 }
2963 // remember the origin point of the handle
2964 if (n->p.knot == knot) {
2965 n->p.origin_radial = n->p.pos - n->pos;
2966 } else if (n->n.knot == knot) {
2967 n->n.origin_radial = n->n.pos - n->pos;
2968 } else {
2969 g_assert_not_reached();
2970 }
2972 }
2974 /**
2975 * Node handle ungrabbed callback.
2976 */
2977 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2978 {
2979 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2981 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2982 if (n->p.knot == knot) {
2983 n->p.origin_radial.a = 0;
2984 sp_knot_set_position(knot, &n->p.pos, state);
2985 } else if (n->n.knot == knot) {
2986 n->n.origin_radial.a = 0;
2987 sp_knot_set_position(knot, &n->n.pos, state);
2988 } else {
2989 g_assert_not_reached();
2990 }
2992 sp_nodepath_update_repr(n->subpath->nodepath);
2993 }
2995 /**
2996 * Node handle "request" signal callback.
2997 */
2998 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2999 {
3000 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3002 Inkscape::NodePath::NodeSide *me, *opposite;
3003 gint which;
3004 if (n->p.knot == knot) {
3005 me = &n->p;
3006 opposite = &n->n;
3007 which = -1;
3008 } else if (n->n.knot == knot) {
3009 me = &n->n;
3010 opposite = &n->p;
3011 which = 1;
3012 } else {
3013 me = opposite = NULL;
3014 which = 0;
3015 g_assert_not_reached();
3016 }
3018 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3020 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3022 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3023 /* We are smooth node adjacent with line */
3024 NR::Point const delta = *p - n->pos;
3025 NR::Coord const len = NR::L2(delta);
3026 Inkscape::NodePath::Node *othernode = opposite->other;
3027 NR::Point const ndelta = n->pos - othernode->pos;
3028 NR::Coord const linelen = NR::L2(ndelta);
3029 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3030 NR::Coord const scal = dot(delta, ndelta) / linelen;
3031 (*p) = n->pos + (scal / linelen) * ndelta;
3032 }
3033 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3034 } else {
3035 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3036 }
3038 sp_node_adjust_handle(n, -which);
3040 return FALSE;
3041 }
3043 /**
3044 * Node handle moved callback.
3045 */
3046 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3047 {
3048 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3050 Inkscape::NodePath::NodeSide *me;
3051 Inkscape::NodePath::NodeSide *other;
3052 if (n->p.knot == knot) {
3053 me = &n->p;
3054 other = &n->n;
3055 } else if (n->n.knot == knot) {
3056 me = &n->n;
3057 other = &n->p;
3058 } else {
3059 me = NULL;
3060 other = NULL;
3061 g_assert_not_reached();
3062 }
3064 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3065 Radial rme(me->pos - n->pos);
3066 Radial rother(other->pos - n->pos);
3067 Radial rnew(*p - n->pos);
3069 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3070 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3071 /* 0 interpreted as "no snapping". */
3073 // The closest PI/snaps angle, starting from zero.
3074 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3075 if (me->origin_radial.a == HUGE_VAL) {
3076 // ortho doesn't exist: original handle was zero length.
3077 rnew.a = a_snapped;
3078 } else {
3079 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3080 * its opposite and perpendiculars). */
3081 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3083 // Snap to the closest.
3084 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3085 ? a_snapped
3086 : a_ortho );
3087 }
3088 }
3090 if (state & GDK_MOD1_MASK) {
3091 // lock handle length
3092 rnew.r = me->origin_radial.r;
3093 }
3095 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3096 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3097 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3098 rother.a += rnew.a - rme.a;
3099 other->pos = NR::Point(rother) + n->pos;
3100 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3101 sp_knot_set_position(other->knot, &other->pos, 0);
3102 }
3104 me->pos = NR::Point(rnew) + n->pos;
3105 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3107 // this is what sp_knot_set_position does, but without emitting the signal:
3108 // we cannot emit a "moved" signal because we're now processing it
3109 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3111 knot->desktop->set_coordinate_status(me->pos);
3113 update_object(n->subpath->nodepath);
3115 /* status text */
3116 SPDesktop *desktop = n->subpath->nodepath->desktop;
3117 if (!desktop) return;
3118 SPEventContext *ec = desktop->event_context;
3119 if (!ec) return;
3120 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3121 if (!mc) return;
3123 double degrees = 180 / M_PI * rnew.a;
3124 if (degrees > 180) degrees -= 360;
3125 if (degrees < -180) degrees += 360;
3126 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3127 degrees = angle_to_compass (degrees);
3129 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3131 mc->setF(Inkscape::NORMAL_MESSAGE,
3132 _("<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);
3134 g_string_free(length, TRUE);
3135 }
3137 /**
3138 * Node handle event callback.
3139 */
3140 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3141 {
3142 gboolean ret = FALSE;
3143 switch (event->type) {
3144 case GDK_KEY_PRESS:
3145 switch (get_group0_keyval (&event->key)) {
3146 case GDK_space:
3147 if (event->key.state & GDK_BUTTON1_MASK) {
3148 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3149 stamp_repr(nodepath);
3150 ret = TRUE;
3151 }
3152 break;
3153 default:
3154 break;
3155 }
3156 break;
3157 default:
3158 break;
3159 }
3161 return ret;
3162 }
3164 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3165 Radial &rme, Radial &rother, gboolean const both)
3166 {
3167 rme.a += angle;
3168 if ( both
3169 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3170 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3171 {
3172 rother.a += angle;
3173 }
3174 }
3176 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3177 Radial &rme, Radial &rother, gboolean const both)
3178 {
3179 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3181 gdouble r;
3182 if ( both
3183 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3184 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3185 {
3186 r = MAX(rme.r, rother.r);
3187 } else {
3188 r = rme.r;
3189 }
3191 gdouble const weird_angle = atan2(norm_angle, r);
3192 /* Bulia says norm_angle is just the visible distance that the
3193 * object's end must travel on the screen. Left as 'angle' for want of
3194 * a better name.*/
3196 rme.a += weird_angle;
3197 if ( both
3198 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3199 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3200 {
3201 rother.a += weird_angle;
3202 }
3203 }
3205 /**
3206 * Rotate one node.
3207 */
3208 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3209 {
3210 Inkscape::NodePath::NodeSide *me, *other;
3211 bool both = false;
3213 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3214 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3216 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3217 me = &(n->p);
3218 other = &(n->n);
3219 } else if (!n->p.other) {
3220 me = &(n->n);
3221 other = &(n->p);
3222 } else {
3223 if (which > 0) { // right handle
3224 if (xn > xp) {
3225 me = &(n->n);
3226 other = &(n->p);
3227 } else {
3228 me = &(n->p);
3229 other = &(n->n);
3230 }
3231 } else if (which < 0){ // left handle
3232 if (xn <= xp) {
3233 me = &(n->n);
3234 other = &(n->p);
3235 } else {
3236 me = &(n->p);
3237 other = &(n->n);
3238 }
3239 } else { // both handles
3240 me = &(n->n);
3241 other = &(n->p);
3242 both = true;
3243 }
3244 }
3246 Radial rme(me->pos - n->pos);
3247 Radial rother(other->pos - n->pos);
3249 if (screen) {
3250 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3251 } else {
3252 node_rotate_one_internal (*n, angle, rme, rother, both);
3253 }
3255 me->pos = n->pos + NR::Point(rme);
3257 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3258 other->pos = n->pos + NR::Point(rother);
3259 }
3261 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3262 // so here we just move all the knots without emitting move signals, for speed
3263 sp_node_update_handles(n, false);
3264 }
3266 /**
3267 * Rotate selected nodes.
3268 */
3269 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3270 {
3271 if (!nodepath || !nodepath->selected) return;
3273 if (g_list_length(nodepath->selected) == 1) {
3274 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3275 node_rotate_one (n, angle, which, screen);
3276 } else {
3277 // rotate as an object:
3279 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3280 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3281 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3282 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3283 box.expandTo (n->pos); // contain all selected nodes
3284 }
3286 gdouble rot;
3287 if (screen) {
3288 gdouble const zoom = nodepath->desktop->current_zoom();
3289 gdouble const zmove = angle / zoom;
3290 gdouble const r = NR::L2(box.max() - box.midpoint());
3291 rot = atan2(zmove, r);
3292 } else {
3293 rot = angle;
3294 }
3296 NR::Matrix t =
3297 NR::Matrix (NR::translate(-box.midpoint())) *
3298 NR::Matrix (NR::rotate(rot)) *
3299 NR::Matrix (NR::translate(box.midpoint()));
3301 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3302 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3303 n->pos *= t;
3304 n->n.pos *= t;
3305 n->p.pos *= t;
3306 sp_node_update_handles(n, false);
3307 }
3308 }
3310 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3311 }
3313 /**
3314 * Scale one node.
3315 */
3316 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3317 {
3318 bool both = false;
3319 Inkscape::NodePath::NodeSide *me, *other;
3321 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3322 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3324 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3325 me = &(n->p);
3326 other = &(n->n);
3327 n->code = NR_CURVETO;
3328 } else if (!n->p.other) {
3329 me = &(n->n);
3330 other = &(n->p);
3331 if (n->n.other)
3332 n->n.other->code = NR_CURVETO;
3333 } else {
3334 if (which > 0) { // right handle
3335 if (xn > xp) {
3336 me = &(n->n);
3337 other = &(n->p);
3338 if (n->n.other)
3339 n->n.other->code = NR_CURVETO;
3340 } else {
3341 me = &(n->p);
3342 other = &(n->n);
3343 n->code = NR_CURVETO;
3344 }
3345 } else if (which < 0){ // left handle
3346 if (xn <= xp) {
3347 me = &(n->n);
3348 other = &(n->p);
3349 if (n->n.other)
3350 n->n.other->code = NR_CURVETO;
3351 } else {
3352 me = &(n->p);
3353 other = &(n->n);
3354 n->code = NR_CURVETO;
3355 }
3356 } else { // both handles
3357 me = &(n->n);
3358 other = &(n->p);
3359 both = true;
3360 n->code = NR_CURVETO;
3361 if (n->n.other)
3362 n->n.other->code = NR_CURVETO;
3363 }
3364 }
3366 Radial rme(me->pos - n->pos);
3367 Radial rother(other->pos - n->pos);
3369 rme.r += grow;
3370 if (rme.r < 0) rme.r = 0;
3371 if (rme.a == HUGE_VAL) {
3372 if (me->other) { // if direction is unknown, initialize it towards the next node
3373 Radial rme_next(me->other->pos - n->pos);
3374 rme.a = rme_next.a;
3375 } else { // if there's no next, initialize to 0
3376 rme.a = 0;
3377 }
3378 }
3379 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3380 rother.r += grow;
3381 if (rother.r < 0) rother.r = 0;
3382 if (rother.a == HUGE_VAL) {
3383 rother.a = rme.a + M_PI;
3384 }
3385 }
3387 me->pos = n->pos + NR::Point(rme);
3389 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3390 other->pos = n->pos + NR::Point(rother);
3391 }
3393 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3394 // so here we just move all the knots without emitting move signals, for speed
3395 sp_node_update_handles(n, false);
3396 }
3398 /**
3399 * Scale selected nodes.
3400 */
3401 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3402 {
3403 if (!nodepath || !nodepath->selected) return;
3405 if (g_list_length(nodepath->selected) == 1) {
3406 // scale handles of the single selected node
3407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3408 node_scale_one (n, grow, which);
3409 } else {
3410 // scale nodes as an "object":
3412 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3413 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3414 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3415 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3416 box.expandTo (n->pos); // contain all selected nodes
3417 }
3419 double scale = (box.maxExtent() + grow)/box.maxExtent();
3421 NR::Matrix t =
3422 NR::Matrix (NR::translate(-box.midpoint())) *
3423 NR::Matrix (NR::scale(scale, scale)) *
3424 NR::Matrix (NR::translate(box.midpoint()));
3426 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3427 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3428 n->pos *= t;
3429 n->n.pos *= t;
3430 n->p.pos *= t;
3431 sp_node_update_handles(n, false);
3432 }
3433 }
3435 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3436 }
3438 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3439 {
3440 if (!nodepath) return;
3441 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3442 }
3444 /**
3445 * Flip selected nodes horizontally/vertically.
3446 */
3447 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3448 {
3449 if (!nodepath || !nodepath->selected) return;
3451 if (g_list_length(nodepath->selected) == 1) {
3452 // flip handles of the single selected node
3453 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3454 double temp = n->p.pos[axis];
3455 n->p.pos[axis] = n->n.pos[axis];
3456 n->n.pos[axis] = temp;
3457 sp_node_update_handles(n, false);
3458 } else {
3459 // scale nodes as an "object":
3461 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3462 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3463 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3464 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3465 box.expandTo (n->pos); // contain all selected nodes
3466 }
3468 NR::Matrix t =
3469 NR::Matrix (NR::translate(-box.midpoint())) *
3470 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3471 NR::Matrix (NR::translate(box.midpoint()));
3473 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3474 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3475 n->pos *= t;
3476 n->n.pos *= t;
3477 n->p.pos *= t;
3478 sp_node_update_handles(n, false);
3479 }
3480 }
3482 sp_nodepath_update_repr(nodepath);
3483 }
3485 //-----------------------------------------------
3486 /**
3487 * Return new subpath under given nodepath.
3488 */
3489 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3490 {
3491 g_assert(nodepath);
3492 g_assert(nodepath->desktop);
3494 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3496 s->nodepath = nodepath;
3497 s->closed = FALSE;
3498 s->nodes = NULL;
3499 s->first = NULL;
3500 s->last = NULL;
3502 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3503 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3504 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3506 return s;
3507 }
3509 /**
3510 * Destroy nodes in subpath, then subpath itself.
3511 */
3512 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3513 {
3514 g_assert(subpath);
3515 g_assert(subpath->nodepath);
3516 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3518 while (subpath->nodes) {
3519 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3520 }
3522 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3524 g_free(subpath);
3525 }
3527 /**
3528 * Link head to tail in subpath.
3529 */
3530 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3531 {
3532 g_assert(!sp->closed);
3533 g_assert(sp->last != sp->first);
3534 g_assert(sp->first->code == NR_MOVETO);
3536 sp->closed = TRUE;
3538 //Link the head to the tail
3539 sp->first->p.other = sp->last;
3540 sp->last->n.other = sp->first;
3541 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3542 sp->first = sp->last;
3544 //Remove the extra end node
3545 sp_nodepath_node_destroy(sp->last->n.other);
3546 }
3548 /**
3549 * Open closed (loopy) subpath at node.
3550 */
3551 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3552 {
3553 g_assert(sp->closed);
3554 g_assert(n->subpath == sp);
3555 g_assert(sp->first == sp->last);
3557 /* We create new startpoint, current node will become last one */
3559 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3560 &n->pos, &n->pos, &n->n.pos);
3563 sp->closed = FALSE;
3565 //Unlink to make a head and tail
3566 sp->first = new_path;
3567 sp->last = n;
3568 n->n.other = NULL;
3569 new_path->p.other = NULL;
3570 }
3572 /**
3573 * Returns area in triangle given by points; may be negative.
3574 */
3575 inline double
3576 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3577 {
3578 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]);
3579 }
3581 /**
3582 * Return new node in subpath with given properties.
3583 * \param pos Position of node.
3584 * \param ppos Handle position in previous direction
3585 * \param npos Handle position in previous direction
3586 */
3587 Inkscape::NodePath::Node *
3588 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)
3589 {
3590 g_assert(sp);
3591 g_assert(sp->nodepath);
3592 g_assert(sp->nodepath->desktop);
3594 if (nodechunk == NULL)
3595 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3597 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3599 n->subpath = sp;
3601 if (type != Inkscape::NodePath::NODE_NONE) {
3602 // use the type from sodipodi:nodetypes
3603 n->type = type;
3604 } else {
3605 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3606 // points are (almost) collinear
3607 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3608 // endnode, or a node with a retracted handle
3609 n->type = Inkscape::NodePath::NODE_CUSP;
3610 } else {
3611 n->type = Inkscape::NodePath::NODE_SMOOTH;
3612 }
3613 } else {
3614 n->type = Inkscape::NodePath::NODE_CUSP;
3615 }
3616 }
3618 n->code = code;
3619 n->selected = FALSE;
3620 n->pos = *pos;
3621 n->p.pos = *ppos;
3622 n->n.pos = *npos;
3624 n->dragging_out = NULL;
3626 Inkscape::NodePath::Node *prev;
3627 if (next) {
3628 //g_assert(g_list_find(sp->nodes, next));
3629 prev = next->p.other;
3630 } else {
3631 prev = sp->last;
3632 }
3634 if (prev)
3635 prev->n.other = n;
3636 else
3637 sp->first = n;
3639 if (next)
3640 next->p.other = n;
3641 else
3642 sp->last = n;
3644 n->p.other = prev;
3645 n->n.other = next;
3647 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"));
3648 sp_knot_set_position(n->knot, pos, 0);
3650 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3651 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3652 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3653 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3654 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3655 sp_knot_update_ctrl(n->knot);
3657 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3658 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3659 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3660 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3661 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3662 sp_knot_show(n->knot);
3664 // We only create handle knots and lines on demand
3665 n->p.knot = NULL;
3666 n->p.line = NULL;
3667 n->n.knot = NULL;
3668 n->n.line = NULL;
3670 sp->nodes = g_list_prepend(sp->nodes, n);
3672 return n;
3673 }
3675 /**
3676 * Destroy node and its knots, link neighbors in subpath.
3677 */
3678 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3679 {
3680 g_assert(node);
3681 g_assert(node->subpath);
3682 g_assert(SP_IS_KNOT(node->knot));
3684 Inkscape::NodePath::SubPath *sp = node->subpath;
3686 if (node->selected) { // first, deselect
3687 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3688 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3689 }
3691 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3693 g_object_unref(G_OBJECT(node->knot));
3694 if (node->p.knot)
3695 g_object_unref(G_OBJECT(node->p.knot));
3696 if (node->n.knot)
3697 g_object_unref(G_OBJECT(node->n.knot));
3699 if (node->p.line)
3700 gtk_object_destroy(GTK_OBJECT(node->p.line));
3701 if (node->n.line)
3702 gtk_object_destroy(GTK_OBJECT(node->n.line));
3704 if (sp->nodes) { // there are others nodes on the subpath
3705 if (sp->closed) {
3706 if (sp->first == node) {
3707 g_assert(sp->last == node);
3708 sp->first = node->n.other;
3709 sp->last = sp->first;
3710 }
3711 node->p.other->n.other = node->n.other;
3712 node->n.other->p.other = node->p.other;
3713 } else {
3714 if (sp->first == node) {
3715 sp->first = node->n.other;
3716 sp->first->code = NR_MOVETO;
3717 }
3718 if (sp->last == node) sp->last = node->p.other;
3719 if (node->p.other) node->p.other->n.other = node->n.other;
3720 if (node->n.other) node->n.other->p.other = node->p.other;
3721 }
3722 } else { // this was the last node on subpath
3723 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3724 }
3726 g_mem_chunk_free(nodechunk, node);
3727 }
3729 /**
3730 * Returns one of the node's two sides.
3731 * \param which Indicates which side.
3732 * \return Pointer to previous node side if which==-1, next if which==1.
3733 */
3734 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3735 {
3736 g_assert(node);
3738 switch (which) {
3739 case -1:
3740 return &node->p;
3741 case 1:
3742 return &node->n;
3743 default:
3744 break;
3745 }
3747 g_assert_not_reached();
3749 return NULL;
3750 }
3752 /**
3753 * Return the other side of the node, given one of its sides.
3754 */
3755 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3756 {
3757 g_assert(node);
3759 if (me == &node->p) return &node->n;
3760 if (me == &node->n) return &node->p;
3762 g_assert_not_reached();
3764 return NULL;
3765 }
3767 /**
3768 * Return NRPathcode on the given side of the node.
3769 */
3770 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3771 {
3772 g_assert(node);
3774 if (me == &node->p) {
3775 if (node->p.other) return (NRPathcode)node->code;
3776 return NR_MOVETO;
3777 }
3779 if (me == &node->n) {
3780 if (node->n.other) return (NRPathcode)node->n.other->code;
3781 return NR_MOVETO;
3782 }
3784 g_assert_not_reached();
3786 return NR_END;
3787 }
3789 /**
3790 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3791 */
3792 Inkscape::NodePath::Node *
3793 sp_nodepath_get_node_by_index(int index)
3794 {
3795 Inkscape::NodePath::Node *e = NULL;
3797 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3798 if (!nodepath) {
3799 return e;
3800 }
3802 //find segment
3803 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3805 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3806 int n = g_list_length(sp->nodes);
3807 if (sp->closed) {
3808 n++;
3809 }
3811 //if the piece belongs to this subpath grab it
3812 //otherwise move onto the next subpath
3813 if (index < n) {
3814 e = sp->first;
3815 for (int i = 0; i < index; ++i) {
3816 e = e->n.other;
3817 }
3818 break;
3819 } else {
3820 if (sp->closed) {
3821 index -= (n+1);
3822 } else {
3823 index -= n;
3824 }
3825 }
3826 }
3828 return e;
3829 }
3831 /**
3832 * Returns plain text meaning of node type.
3833 */
3834 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3835 {
3836 unsigned retracted = 0;
3837 bool endnode = false;
3839 for (int which = -1; which <= 1; which += 2) {
3840 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3841 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3842 retracted ++;
3843 if (!side->other)
3844 endnode = true;
3845 }
3847 if (retracted == 0) {
3848 if (endnode) {
3849 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3850 return _("end node");
3851 } else {
3852 switch (node->type) {
3853 case Inkscape::NodePath::NODE_CUSP:
3854 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3855 return _("cusp");
3856 case Inkscape::NodePath::NODE_SMOOTH:
3857 // TRANSLATORS: "smooth" is an adjective here
3858 return _("smooth");
3859 case Inkscape::NodePath::NODE_SYMM:
3860 return _("symmetric");
3861 }
3862 }
3863 } else if (retracted == 1) {
3864 if (endnode) {
3865 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3866 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3867 } else {
3868 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3869 }
3870 } else {
3871 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3872 }
3874 return NULL;
3875 }
3877 /**
3878 * Handles content of statusbar as long as node tool is active.
3879 */
3880 void
3881 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3882 {
3883 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3884 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3886 gint total = 0;
3887 gint selected = 0;
3888 SPDesktop *desktop = NULL;
3890 if (nodepath) {
3891 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3892 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3893 total += g_list_length(subpath->nodes);
3894 }
3895 selected = g_list_length(nodepath->selected);
3896 desktop = nodepath->desktop;
3897 } else {
3898 desktop = SP_ACTIVE_DESKTOP;
3899 }
3901 SPEventContext *ec = desktop->event_context;
3902 if (!ec) return;
3903 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3904 if (!mc) return;
3906 if (selected == 0) {
3907 Inkscape::Selection *sel = desktop->selection;
3908 if (!sel || sel->isEmpty()) {
3909 mc->setF(Inkscape::NORMAL_MESSAGE,
3910 _("Select a single object to edit its nodes or handles."));
3911 } else {
3912 if (nodepath) {
3913 mc->setF(Inkscape::NORMAL_MESSAGE,
3914 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.",
3915 "<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.",
3916 total),
3917 total);
3918 } else {
3919 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3920 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3921 } else {
3922 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3923 }
3924 }
3925 }
3926 } else if (nodepath && selected == 1) {
3927 mc->setF(Inkscape::NORMAL_MESSAGE,
3928 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3929 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3930 total),
3931 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3932 } else {
3933 mc->setF(Inkscape::NORMAL_MESSAGE,
3934 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3935 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3936 total),
3937 selected, total, when_selected);
3938 }
3939 }
3942 /*
3943 Local Variables:
3944 mode:c++
3945 c-file-style:"stroustrup"
3946 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3947 indent-tabs-mode:nil
3948 fill-column:99
3949 End:
3950 */
3951 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :