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 * This code is in public domain
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 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)
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(curve->bpath);
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_DT_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_DT_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(curve->bpath);
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_DT_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_MOSEOVER(node->p.knot)) {
840 sp_node_adjust_handle(node, 1);
841 } else if (node->n.knot && SP_KNOT_IS_MOSEOVER(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[2] = { NR_HUGE, NR_HUGE };
925 NR::Point delta(dx, dy);
926 NR::Point best_pt = delta;
928 if (snap) {
929 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
930 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
931 NR::Point p = n->pos + delta;
932 for (int dim = 0; dim < 2; dim++) {
933 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
934 Inkscape::Snapper::SNAP_POINT, p,
935 NR::Dim2(dim), nodepath->path);
936 if (dist < best[dim]) {
937 best[dim] = dist;
938 best_pt[dim] = p[dim] - n->pos[dim];
939 }
940 }
941 }
942 }
944 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
945 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
946 sp_node_moveto(n, n->pos + best_pt);
947 }
949 // do not update repr here so that node dragging is acceptably fast
950 update_object(nodepath);
951 }
953 /**
954 * Move node selection to point, adjust its and neighbouring handles,
955 * handle possible snapping, and commit the change with possible undo.
956 */
957 void
958 sp_node_selected_move(gdouble dx, gdouble dy)
959 {
960 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
961 if (!nodepath) return;
963 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
965 if (dx == 0) {
966 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
967 } else if (dy == 0) {
968 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
969 } else {
970 sp_nodepath_update_repr(nodepath);
971 }
972 }
974 /**
975 * Move node selection off screen and commit the change.
976 */
977 void
978 sp_node_selected_move_screen(gdouble dx, gdouble dy)
979 {
980 // borrowed from sp_selection_move_screen in selection-chemistry.c
981 // we find out the current zoom factor and divide deltas by it
982 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
984 gdouble zoom = desktop->current_zoom();
985 gdouble zdx = dx / zoom;
986 gdouble zdy = dy / zoom;
988 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
989 if (!nodepath) return;
991 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
993 if (dx == 0) {
994 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
995 } else if (dy == 0) {
996 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
997 } else {
998 sp_nodepath_update_repr(nodepath);
999 }
1000 }
1002 /** If they don't yet exist, creates knot and line for the given side of the node */
1003 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1004 {
1005 if (!side->knot) {
1006 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"));
1008 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1009 side->knot->setSize (7);
1010 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1011 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1012 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1013 sp_knot_update_ctrl(side->knot);
1015 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1016 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1017 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1018 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1019 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1020 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1021 }
1023 if (!side->line) {
1024 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1025 SP_TYPE_CTRLLINE, NULL);
1026 }
1027 }
1029 /**
1030 * Ensure the given handle of the node is visible/invisible, update its screen position
1031 */
1032 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1033 {
1034 g_assert(node != NULL);
1036 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1037 NRPathcode code = sp_node_path_code_from_side(node, side);
1039 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1041 if (show_handle) {
1042 if (!side->knot) { // No handle knot at all
1043 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1044 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1045 side->knot->pos = side->pos;
1046 if (side->knot->item)
1047 SP_CTRL(side->knot->item)->moveto(side->pos);
1048 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1049 sp_knot_show(side->knot);
1050 } else {
1051 if (side->knot->pos != side->pos) { // only if it's really moved
1052 if (fire_move_signals) {
1053 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1054 } else {
1055 sp_knot_moveto(side->knot, &side->pos);
1056 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1057 }
1058 }
1059 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1060 sp_knot_show(side->knot);
1061 }
1062 }
1063 sp_canvas_item_show(side->line);
1064 } else {
1065 if (side->knot) {
1066 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1067 sp_knot_hide(side->knot);
1068 }
1069 }
1070 if (side->line) {
1071 sp_canvas_item_hide(side->line);
1072 }
1073 }
1074 }
1076 /**
1077 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1078 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1079 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1080 * updated; otherwise, just move the knots silently (used in batch moves).
1081 */
1082 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1083 {
1084 g_assert(node != NULL);
1086 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1087 sp_knot_show(node->knot);
1088 }
1090 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1091 if (fire_move_signals)
1092 sp_knot_set_position(node->knot, &node->pos, 0);
1093 else
1094 sp_knot_moveto(node->knot, &node->pos);
1095 }
1097 gboolean show_handles = node->selected;
1098 if (node->p.other != NULL) {
1099 if (node->p.other->selected) show_handles = TRUE;
1100 }
1101 if (node->n.other != NULL) {
1102 if (node->n.other->selected) show_handles = TRUE;
1103 }
1105 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1106 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1107 }
1109 /**
1110 * Call sp_node_update_handles() for all nodes on subpath.
1111 */
1112 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1113 {
1114 g_assert(subpath != NULL);
1116 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1117 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1118 }
1119 }
1121 /**
1122 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1123 */
1124 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1125 {
1126 g_assert(nodepath != NULL);
1128 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1129 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1130 }
1131 }
1133 /**
1134 * Adds all selected nodes in nodepath to list.
1135 */
1136 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1137 {
1138 StlConv<Node *>::list(l, selected);
1139 /// \todo this adds a copying, rework when the selection becomes a stl list
1140 }
1142 /**
1143 * Align selected nodes on the specified axis.
1144 */
1145 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1146 {
1147 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1148 return;
1149 }
1151 if ( !nodepath->selected->next ) { // only one node selected
1152 return;
1153 }
1154 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1155 NR::Point dest(pNode->pos);
1156 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1157 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1158 if (pNode) {
1159 dest[axis] = pNode->pos[axis];
1160 sp_node_moveto(pNode, dest);
1161 }
1162 }
1164 sp_nodepath_update_repr(nodepath);
1165 }
1167 /// Helper struct.
1168 struct NodeSort
1169 {
1170 Inkscape::NodePath::Node *_node;
1171 NR::Coord _coord;
1172 /// \todo use vectorof pointers instead of calling copy ctor
1173 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1174 _node(node), _coord(node->pos[axis])
1175 {}
1177 };
1179 static bool operator<(NodeSort const &a, NodeSort const &b)
1180 {
1181 return (a._coord < b._coord);
1182 }
1184 /**
1185 * Distribute selected nodes on the specified axis.
1186 */
1187 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1188 {
1189 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1190 return;
1191 }
1193 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1194 return;
1195 }
1197 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1198 std::vector<NodeSort> sorted;
1199 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1200 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1201 if (pNode) {
1202 NodeSort n(pNode, axis);
1203 sorted.push_back(n);
1204 //dest[axis] = pNode->pos[axis];
1205 //sp_node_moveto(pNode, dest);
1206 }
1207 }
1208 std::sort(sorted.begin(), sorted.end());
1209 unsigned int len = sorted.size();
1210 //overall bboxes span
1211 float dist = (sorted.back()._coord -
1212 sorted.front()._coord);
1213 //new distance between each bbox
1214 float step = (dist) / (len - 1);
1215 float pos = sorted.front()._coord;
1216 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1217 it < sorted.end();
1218 it ++ )
1219 {
1220 NR::Point dest((*it)._node->pos);
1221 dest[axis] = pos;
1222 sp_node_moveto((*it)._node, dest);
1223 pos += step;
1224 }
1226 sp_nodepath_update_repr(nodepath);
1227 }
1230 /**
1231 * Call sp_nodepath_line_add_node() for all selected segments.
1232 */
1233 void
1234 sp_node_selected_add_node(void)
1235 {
1236 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1237 if (!nodepath) {
1238 return;
1239 }
1241 GList *nl = NULL;
1243 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1244 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1245 g_assert(t->selected);
1246 if (t->p.other && t->p.other->selected) {
1247 nl = g_list_prepend(nl, t);
1248 }
1249 }
1251 while (nl) {
1252 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1253 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1254 sp_nodepath_node_select(n, TRUE, FALSE);
1255 nl = g_list_remove(nl, t);
1256 }
1258 /** \todo fixme: adjust ? */
1259 sp_nodepath_update_handles(nodepath);
1261 sp_nodepath_update_repr(nodepath);
1263 sp_nodepath_update_statusbar(nodepath);
1264 }
1266 /**
1267 * Select segment nearest to point
1268 */
1269 void
1270 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1271 {
1272 if (!nodepath) {
1273 return;
1274 }
1276 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1278 //find segment to segment
1279 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1281 gboolean force = FALSE;
1282 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1283 force = TRUE;
1284 }
1285 sp_nodepath_node_select(e, (gboolean) toggle, force);
1286 if (e->p.other)
1287 sp_nodepath_node_select(e->p.other, TRUE, force);
1289 sp_nodepath_update_handles(nodepath);
1291 sp_nodepath_update_statusbar(nodepath);
1292 }
1294 /**
1295 * Add a node nearest to point
1296 */
1297 void
1298 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1299 {
1300 if (!nodepath) {
1301 return;
1302 }
1304 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1306 //find segment to split
1307 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1309 //don't know why but t seems to flip for lines
1310 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1311 position.t = 1.0 - position.t;
1312 }
1313 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1314 sp_nodepath_node_select(n, FALSE, TRUE);
1316 /* fixme: adjust ? */
1317 sp_nodepath_update_handles(nodepath);
1319 sp_nodepath_update_repr(nodepath);
1321 sp_nodepath_update_statusbar(nodepath);
1322 }
1324 /*
1325 * Adjusts a segment so that t moves by a certain delta for dragging
1326 * converts lines to curves
1327 *
1328 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1329 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1330 */
1331 void
1332 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1333 {
1334 /* feel good is an arbitrary parameter that distributes the delta between handles
1335 * if t of the drag point is less than 1/6 distance form the endpoint only
1336 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1337 */
1338 double feel_good;
1339 if (t <= 1.0 / 6.0)
1340 feel_good = 0;
1341 else if (t <= 0.5)
1342 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1343 else if (t <= 5.0 / 6.0)
1344 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1345 else
1346 feel_good = 1;
1348 //if we're dragging a line convert it to a curve
1349 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1350 sp_nodepath_set_line_type(e, NR_CURVETO);
1351 }
1353 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1354 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1355 e->p.other->n.pos += offsetcoord0;
1356 e->p.pos += offsetcoord1;
1358 // adjust handles of adjacent nodes where necessary
1359 sp_node_adjust_handle(e,1);
1360 sp_node_adjust_handle(e->p.other,-1);
1362 sp_nodepath_update_handles(e->subpath->nodepath);
1364 update_object(e->subpath->nodepath);
1366 sp_nodepath_update_statusbar(e->subpath->nodepath);
1367 }
1370 /**
1371 * Call sp_nodepath_break() for all selected segments.
1372 */
1373 void sp_node_selected_break()
1374 {
1375 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1376 if (!nodepath) return;
1378 GList *temp = NULL;
1379 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1380 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1381 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1382 if (nn == NULL) continue; // no break, no new node
1383 temp = g_list_prepend(temp, nn);
1384 }
1386 if (temp) {
1387 sp_nodepath_deselect(nodepath);
1388 }
1389 for (GList *l = temp; l != NULL; l = l->next) {
1390 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1391 }
1393 sp_nodepath_update_handles(nodepath);
1395 sp_nodepath_update_repr(nodepath);
1396 }
1398 /**
1399 * Duplicate the selected node(s).
1400 */
1401 void sp_node_selected_duplicate()
1402 {
1403 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1404 if (!nodepath) {
1405 return;
1406 }
1408 GList *temp = NULL;
1409 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1410 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1411 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1412 if (nn == NULL) continue; // could not duplicate
1413 temp = g_list_prepend(temp, nn);
1414 }
1416 if (temp) {
1417 sp_nodepath_deselect(nodepath);
1418 }
1419 for (GList *l = temp; l != NULL; l = l->next) {
1420 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1421 }
1423 sp_nodepath_update_handles(nodepath);
1425 sp_nodepath_update_repr(nodepath);
1426 }
1428 /**
1429 * Join two nodes by merging them into one.
1430 */
1431 void sp_node_selected_join()
1432 {
1433 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1434 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1436 if (g_list_length(nodepath->selected) != 2) {
1437 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1438 return;
1439 }
1441 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1442 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1444 g_assert(a != b);
1445 g_assert(a->p.other || a->n.other);
1446 g_assert(b->p.other || b->n.other);
1448 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1449 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1450 return;
1451 }
1453 /* a and b are endpoints */
1455 NR::Point c = (a->pos + b->pos) / 2;
1457 if (a->subpath == b->subpath) {
1458 Inkscape::NodePath::SubPath *sp = a->subpath;
1459 sp_nodepath_subpath_close(sp);
1461 sp_nodepath_update_handles(sp->nodepath);
1463 sp_nodepath_update_repr(nodepath);
1465 return;
1466 }
1468 /* a and b are separate subpaths */
1469 Inkscape::NodePath::SubPath *sa = a->subpath;
1470 Inkscape::NodePath::SubPath *sb = b->subpath;
1471 NR::Point p;
1472 Inkscape::NodePath::Node *n;
1473 NRPathcode code;
1474 if (a == sa->first) {
1475 p = sa->first->n.pos;
1476 code = (NRPathcode)sa->first->n.other->code;
1477 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1478 n = sa->last;
1479 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1480 n = n->p.other;
1481 while (n) {
1482 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1483 n = n->p.other;
1484 if (n == sa->first) n = NULL;
1485 }
1486 sp_nodepath_subpath_destroy(sa);
1487 sa = t;
1488 } else if (a == sa->last) {
1489 p = sa->last->p.pos;
1490 code = (NRPathcode)sa->last->code;
1491 sp_nodepath_node_destroy(sa->last);
1492 } else {
1493 code = NR_END;
1494 g_assert_not_reached();
1495 }
1497 if (b == sb->first) {
1498 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1499 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1500 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1501 }
1502 } else if (b == sb->last) {
1503 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1504 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1505 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1506 }
1507 } else {
1508 g_assert_not_reached();
1509 }
1510 /* and now destroy sb */
1512 sp_nodepath_subpath_destroy(sb);
1514 sp_nodepath_update_handles(sa->nodepath);
1516 sp_nodepath_update_repr(nodepath);
1518 sp_nodepath_update_statusbar(nodepath);
1519 }
1521 /**
1522 * Join two nodes by adding a segment between them.
1523 */
1524 void sp_node_selected_join_segment()
1525 {
1526 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1527 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1529 if (g_list_length(nodepath->selected) != 2) {
1530 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1531 return;
1532 }
1534 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1535 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1537 g_assert(a != b);
1538 g_assert(a->p.other || a->n.other);
1539 g_assert(b->p.other || b->n.other);
1541 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1542 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1543 return;
1544 }
1546 if (a->subpath == b->subpath) {
1547 Inkscape::NodePath::SubPath *sp = a->subpath;
1549 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1550 sp->closed = TRUE;
1552 sp->first->p.other = sp->last;
1553 sp->last->n.other = sp->first;
1555 sp_node_handle_mirror_p_to_n(sp->last);
1556 sp_node_handle_mirror_n_to_p(sp->first);
1558 sp->first->code = sp->last->code;
1559 sp->first = sp->last;
1561 sp_nodepath_update_handles(sp->nodepath);
1563 sp_nodepath_update_repr(nodepath);
1565 return;
1566 }
1568 /* a and b are separate subpaths */
1569 Inkscape::NodePath::SubPath *sa = a->subpath;
1570 Inkscape::NodePath::SubPath *sb = b->subpath;
1572 Inkscape::NodePath::Node *n;
1573 NR::Point p;
1574 NRPathcode code;
1575 if (a == sa->first) {
1576 code = (NRPathcode) sa->first->n.other->code;
1577 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1578 n = sa->last;
1579 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1580 for (n = n->p.other; n != NULL; n = n->p.other) {
1581 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1582 }
1583 sp_nodepath_subpath_destroy(sa);
1584 sa = t;
1585 } else if (a == sa->last) {
1586 code = (NRPathcode)sa->last->code;
1587 } else {
1588 code = NR_END;
1589 g_assert_not_reached();
1590 }
1592 if (b == sb->first) {
1593 n = sb->first;
1594 sp_node_handle_mirror_p_to_n(sa->last);
1595 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1596 sp_node_handle_mirror_n_to_p(sa->last);
1597 for (n = n->n.other; n != NULL; n = n->n.other) {
1598 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1599 }
1600 } else if (b == sb->last) {
1601 n = sb->last;
1602 sp_node_handle_mirror_p_to_n(sa->last);
1603 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1604 sp_node_handle_mirror_n_to_p(sa->last);
1605 for (n = n->p.other; n != NULL; n = n->p.other) {
1606 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1607 }
1608 } else {
1609 g_assert_not_reached();
1610 }
1611 /* and now destroy sb */
1613 sp_nodepath_subpath_destroy(sb);
1615 sp_nodepath_update_handles(sa->nodepath);
1617 sp_nodepath_update_repr(nodepath);
1618 }
1620 /**
1621 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1622 */
1623 void sp_node_delete_preserve(GList *nodes_to_delete)
1624 {
1626 while (nodes_to_delete) {
1627 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1628 Inkscape::NodePath::SubPath *sp = node->subpath;
1629 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1630 Inkscape::NodePath::Node *sample_cursor = NULL;
1631 Inkscape::NodePath::Node *sample_end = NULL;
1632 Inkscape::NodePath::Node *delete_cursor = node;
1633 bool just_delete = false;
1635 //find the start of this contiguous selection
1636 //move left to the first node that is not selected
1637 //or the start of the non-closed path
1638 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1639 delete_cursor = curr;
1640 }
1642 //just delete at the beginning of an open path
1643 if (!delete_cursor->p.other) {
1644 sample_cursor = delete_cursor;
1645 just_delete = true;
1646 } else {
1647 sample_cursor = delete_cursor->p.other;
1648 }
1650 //calculate points for each segment
1651 int rate = 5;
1652 float period = 1.0 / rate;
1653 std::vector<NR::Point> data;
1654 if (!just_delete) {
1655 data.push_back(sample_cursor->pos);
1656 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1657 //just delete at the end of an open path
1658 if (!sp->closed && curr->n.other == sp->last) {
1659 just_delete = true;
1660 break;
1661 }
1663 //sample points on the contiguous selected segment
1664 NR::Point *bez;
1665 bez = new NR::Point [4];
1666 bez[0] = curr->pos;
1667 bez[1] = curr->n.pos;
1668 bez[2] = curr->n.other->p.pos;
1669 bez[3] = curr->n.other->pos;
1670 for (int i=1; i<rate; i++) {
1671 gdouble t = i * period;
1672 NR::Point p = bezier_pt(3, bez, t);
1673 data.push_back(p);
1674 }
1675 data.push_back(curr->n.other->pos);
1677 sample_end = curr->n.other;
1678 //break if we've come full circle or hit the end of the selection
1679 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1680 break;
1681 }
1682 }
1683 }
1685 if (!just_delete) {
1686 //calculate the best fitting single segment and adjust the endpoints
1687 NR::Point *adata;
1688 adata = new NR::Point [data.size()];
1689 copy(data.begin(), data.end(), adata);
1691 NR::Point *bez;
1692 bez = new NR::Point [4];
1693 //would decreasing error create a better fitting approximation?
1694 gdouble error = 1.0;
1695 gint ret;
1696 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1698 //adjust endpoints
1699 sample_cursor->n.pos = bez[1];
1700 sample_end->p.pos = bez[2];
1701 }
1703 //destroy this contiguous selection
1704 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1705 Inkscape::NodePath::Node *temp = delete_cursor;
1706 if (delete_cursor->n.other == delete_cursor) {
1707 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1708 delete_cursor = NULL;
1709 } else {
1710 delete_cursor = delete_cursor->n.other;
1711 }
1712 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1713 sp_nodepath_node_destroy(temp);
1714 }
1716 //clean up the nodepath (such as for trivial subpaths)
1717 sp_nodepath_cleanup(nodepath);
1719 sp_nodepath_update_handles(nodepath);
1721 // if the entire nodepath is removed, delete the selected object.
1722 if (nodepath->subpaths == NULL ||
1723 sp_nodepath_get_node_count(nodepath) < 2) {
1724 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1725 sp_nodepath_destroy(nodepath);
1726 g_list_free(nodes_to_delete);
1727 nodes_to_delete = NULL;
1728 //is the next line necessary?
1729 sp_selection_delete();
1730 sp_document_done (document);
1731 return;
1732 }
1734 sp_nodepath_update_repr(nodepath);
1736 sp_nodepath_update_statusbar(nodepath);
1737 }
1738 }
1740 /**
1741 * Delete one or more selected nodes.
1742 */
1743 void sp_node_selected_delete()
1744 {
1745 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1746 if (!nodepath) return;
1747 if (!nodepath->selected) return;
1749 /** \todo fixme: do it the right way */
1750 while (nodepath->selected) {
1751 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1752 sp_nodepath_node_destroy(node);
1753 }
1756 //clean up the nodepath (such as for trivial subpaths)
1757 sp_nodepath_cleanup(nodepath);
1759 sp_nodepath_update_handles(nodepath);
1761 // if the entire nodepath is removed, delete the selected object.
1762 if (nodepath->subpaths == NULL ||
1763 sp_nodepath_get_node_count(nodepath) < 2) {
1764 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1765 sp_nodepath_destroy(nodepath);
1766 sp_selection_delete();
1767 sp_document_done (document);
1768 return;
1769 }
1771 sp_nodepath_update_repr(nodepath);
1773 sp_nodepath_update_statusbar(nodepath);
1774 }
1776 /**
1777 * Delete one or more segments between two selected nodes.
1778 * This is the code for 'split'.
1779 */
1780 void
1781 sp_node_selected_delete_segment(void)
1782 {
1783 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1784 Inkscape::NodePath::Node *curr, *next; //Iterators
1786 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1787 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1789 if (g_list_length(nodepath->selected) != 2) {
1790 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1791 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1792 return;
1793 }
1795 //Selected nodes, not inclusive
1796 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1797 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1799 if ( ( a==b) || //same node
1800 (a->subpath != b->subpath ) || //not the same path
1801 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1802 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1803 {
1804 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1805 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1806 return;
1807 }
1809 //###########################################
1810 //# BEGIN EDITS
1811 //###########################################
1812 //##################################
1813 //# CLOSED PATH
1814 //##################################
1815 if (a->subpath->closed) {
1818 gboolean reversed = FALSE;
1820 //Since we can go in a circle, we need to find the shorter distance.
1821 // a->b or b->a
1822 start = end = NULL;
1823 int distance = 0;
1824 int minDistance = 0;
1825 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1826 if (curr==b) {
1827 //printf("a to b:%d\n", distance);
1828 start = a;//go from a to b
1829 end = b;
1830 minDistance = distance;
1831 //printf("A to B :\n");
1832 break;
1833 }
1834 distance++;
1835 }
1837 //try again, the other direction
1838 distance = 0;
1839 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1840 if (curr==a) {
1841 //printf("b to a:%d\n", distance);
1842 if (distance < minDistance) {
1843 start = b; //we go from b to a
1844 end = a;
1845 reversed = TRUE;
1846 //printf("B to A\n");
1847 }
1848 break;
1849 }
1850 distance++;
1851 }
1854 //Copy everything from 'end' to 'start' to a new subpath
1855 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1856 for (curr=end ; curr ; curr=curr->n.other) {
1857 NRPathcode code = (NRPathcode) curr->code;
1858 if (curr == end)
1859 code = NR_MOVETO;
1860 sp_nodepath_node_new(t, NULL,
1861 (Inkscape::NodePath::NodeType)curr->type, code,
1862 &curr->p.pos, &curr->pos, &curr->n.pos);
1863 if (curr == start)
1864 break;
1865 }
1866 sp_nodepath_subpath_destroy(a->subpath);
1869 }
1873 //##################################
1874 //# OPEN PATH
1875 //##################################
1876 else {
1878 //We need to get the direction of the list between A and B
1879 //Can we walk from a to b?
1880 start = end = NULL;
1881 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1882 if (curr==b) {
1883 start = a; //did it! we go from a to b
1884 end = b;
1885 //printf("A to B\n");
1886 break;
1887 }
1888 }
1889 if (!start) {//didn't work? let's try the other direction
1890 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1891 if (curr==a) {
1892 start = b; //did it! we go from b to a
1893 end = a;
1894 //printf("B to A\n");
1895 break;
1896 }
1897 }
1898 }
1899 if (!start) {
1900 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1901 _("Cannot find path between nodes."));
1902 return;
1903 }
1907 //Copy everything after 'end' to a new subpath
1908 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1909 for (curr=end ; curr ; curr=curr->n.other) {
1910 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1911 &curr->p.pos, &curr->pos, &curr->n.pos);
1912 }
1914 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1915 for (curr = start->n.other ; curr ; curr=next) {
1916 next = curr->n.other;
1917 sp_nodepath_node_destroy(curr);
1918 }
1920 }
1921 //###########################################
1922 //# END EDITS
1923 //###########################################
1925 //clean up the nodepath (such as for trivial subpaths)
1926 sp_nodepath_cleanup(nodepath);
1928 sp_nodepath_update_handles(nodepath);
1930 sp_nodepath_update_repr(nodepath);
1932 // if the entire nodepath is removed, delete the selected object.
1933 if (nodepath->subpaths == NULL ||
1934 sp_nodepath_get_node_count(nodepath) < 2) {
1935 sp_nodepath_destroy(nodepath);
1936 sp_selection_delete();
1937 return;
1938 }
1940 sp_nodepath_update_statusbar(nodepath);
1941 }
1943 /**
1944 * Call sp_nodepath_set_line() for all selected segments.
1945 */
1946 void
1947 sp_node_selected_set_line_type(NRPathcode code)
1948 {
1949 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1950 if (nodepath == NULL) return;
1952 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1953 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1954 g_assert(n->selected);
1955 if (n->p.other && n->p.other->selected) {
1956 sp_nodepath_set_line_type(n, code);
1957 }
1958 }
1960 sp_nodepath_update_repr(nodepath);
1961 }
1963 /**
1964 * Call sp_nodepath_convert_node_type() for all selected nodes.
1965 */
1966 void
1967 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1968 {
1969 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1970 if (nodepath == NULL) return;
1972 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1973 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1974 }
1976 sp_nodepath_update_repr(nodepath);
1977 }
1979 /**
1980 * Change select status of node, update its own and neighbour handles.
1981 */
1982 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1983 {
1984 node->selected = selected;
1986 if (selected) {
1987 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1988 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1989 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1990 sp_knot_update_ctrl(node->knot);
1991 } else {
1992 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1993 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1994 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1995 sp_knot_update_ctrl(node->knot);
1996 }
1998 sp_node_update_handles(node);
1999 if (node->n.other) sp_node_update_handles(node->n.other);
2000 if (node->p.other) sp_node_update_handles(node->p.other);
2001 }
2003 /**
2004 \brief Select a node
2005 \param node The node to select
2006 \param incremental If true, add to selection, otherwise deselect others
2007 \param override If true, always select this node, otherwise toggle selected status
2008 */
2009 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2010 {
2011 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2013 if (incremental) {
2014 if (override) {
2015 if (!g_list_find(nodepath->selected, node)) {
2016 nodepath->selected = g_list_prepend(nodepath->selected, node);
2017 }
2018 sp_node_set_selected(node, TRUE);
2019 } else { // toggle
2020 if (node->selected) {
2021 g_assert(g_list_find(nodepath->selected, node));
2022 nodepath->selected = g_list_remove(nodepath->selected, node);
2023 } else {
2024 g_assert(!g_list_find(nodepath->selected, node));
2025 nodepath->selected = g_list_prepend(nodepath->selected, node);
2026 }
2027 sp_node_set_selected(node, !node->selected);
2028 }
2029 } else {
2030 sp_nodepath_deselect(nodepath);
2031 nodepath->selected = g_list_prepend(nodepath->selected, node);
2032 sp_node_set_selected(node, TRUE);
2033 }
2035 sp_nodepath_update_statusbar(nodepath);
2036 }
2039 /**
2040 \brief Deselect all nodes in the nodepath
2041 */
2042 void
2043 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2044 {
2045 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2047 while (nodepath->selected) {
2048 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2049 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2050 }
2051 sp_nodepath_update_statusbar(nodepath);
2052 }
2054 /**
2055 \brief Select or invert selection of all nodes in the nodepath
2056 */
2057 void
2058 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2059 {
2060 if (!nodepath) return;
2062 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2063 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2064 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2065 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2066 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2067 }
2068 }
2069 }
2071 /**
2072 * If nothing selected, does the same as sp_nodepath_select_all();
2073 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2074 * (i.e., similar to "select all in layer", with the "selected" subpaths
2075 * being treated as "layers" in the path).
2076 */
2077 void
2078 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2079 {
2080 if (!nodepath) return;
2082 if (g_list_length (nodepath->selected) == 0) {
2083 sp_nodepath_select_all (nodepath, invert);
2084 return;
2085 }
2087 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2088 GSList *subpaths = NULL;
2090 for (GList *l = copy; l != NULL; l = l->next) {
2091 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2092 Inkscape::NodePath::SubPath *subpath = n->subpath;
2093 if (!g_slist_find (subpaths, subpath))
2094 subpaths = g_slist_prepend (subpaths, subpath);
2095 }
2097 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2098 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2099 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2100 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2101 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2102 }
2103 }
2105 g_slist_free (subpaths);
2106 g_list_free (copy);
2107 }
2109 /**
2110 * \brief Select the node after the last selected; if none is selected,
2111 * select the first within path.
2112 */
2113 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2114 {
2115 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2117 Inkscape::NodePath::Node *last = NULL;
2118 if (nodepath->selected) {
2119 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2120 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2121 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2122 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2123 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2124 if (node->selected) {
2125 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2126 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2127 if (spl->next) { // there's a next subpath
2128 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2129 last = subpath_next->first;
2130 } else if (spl->prev) { // there's a previous subpath
2131 last = NULL; // to be set later to the first node of first subpath
2132 } else {
2133 last = node->n.other;
2134 }
2135 } else {
2136 last = node->n.other;
2137 }
2138 } else {
2139 if (node->n.other) {
2140 last = node->n.other;
2141 } else {
2142 if (spl->next) { // there's a next subpath
2143 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2144 last = subpath_next->first;
2145 } else if (spl->prev) { // there's a previous subpath
2146 last = NULL; // to be set later to the first node of first subpath
2147 } else {
2148 last = (Inkscape::NodePath::Node *) subpath->first;
2149 }
2150 }
2151 }
2152 }
2153 }
2154 }
2155 sp_nodepath_deselect(nodepath);
2156 }
2158 if (last) { // there's at least one more node after selected
2159 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2160 } else { // no more nodes, select the first one in first subpath
2161 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2162 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2163 }
2164 }
2166 /**
2167 * \brief Select the node before the first selected; if none is selected,
2168 * select the last within path
2169 */
2170 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2171 {
2172 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2174 Inkscape::NodePath::Node *last = NULL;
2175 if (nodepath->selected) {
2176 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2177 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2178 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2179 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2180 if (node->selected) {
2181 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2182 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2183 if (spl->prev) { // there's a prev subpath
2184 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2185 last = subpath_prev->last;
2186 } else if (spl->next) { // there's a next subpath
2187 last = NULL; // to be set later to the last node of last subpath
2188 } else {
2189 last = node->p.other;
2190 }
2191 } else {
2192 last = node->p.other;
2193 }
2194 } else {
2195 if (node->p.other) {
2196 last = node->p.other;
2197 } else {
2198 if (spl->prev) { // there's a prev subpath
2199 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2200 last = subpath_prev->last;
2201 } else if (spl->next) { // there's a next subpath
2202 last = NULL; // to be set later to the last node of last subpath
2203 } else {
2204 last = (Inkscape::NodePath::Node *) subpath->last;
2205 }
2206 }
2207 }
2208 }
2209 }
2210 }
2211 sp_nodepath_deselect(nodepath);
2212 }
2214 if (last) { // there's at least one more node before selected
2215 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2216 } else { // no more nodes, select the last one in last subpath
2217 GList *spl = g_list_last(nodepath->subpaths);
2218 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2219 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2220 }
2221 }
2223 /**
2224 * \brief Select all nodes that are within the rectangle.
2225 */
2226 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2227 {
2228 if (!incremental) {
2229 sp_nodepath_deselect(nodepath);
2230 }
2232 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2233 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2234 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2235 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2237 if (b.contains(node->pos)) {
2238 sp_nodepath_node_select(node, TRUE, TRUE);
2239 }
2240 }
2241 }
2242 }
2244 /**
2245 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2246 */
2247 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2248 {
2249 if (!nodepath->selected) {
2250 return NULL;
2251 }
2253 GList *r = NULL;
2254 guint i = 0;
2255 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2256 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2257 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2258 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2259 i++;
2260 if (node->selected) {
2261 r = g_list_append(r, GINT_TO_POINTER(i));
2262 }
2263 }
2264 }
2265 return r;
2266 }
2268 /**
2269 \brief Restores selection by selecting nodes whose positions are in the list
2270 */
2271 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2272 {
2273 sp_nodepath_deselect(nodepath);
2275 guint i = 0;
2276 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2277 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2278 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2279 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2280 i++;
2281 if (g_list_find(r, GINT_TO_POINTER(i))) {
2282 sp_nodepath_node_select(node, TRUE, TRUE);
2283 }
2284 }
2285 }
2287 }
2289 /**
2290 \brief Adjusts handle according to node type and line code.
2291 */
2292 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2293 {
2294 double len, otherlen, linelen;
2296 g_assert(node);
2298 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2299 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2301 /** \todo fixme: */
2302 if (me->other == NULL) return;
2303 if (other->other == NULL) return;
2305 /* I have line */
2307 NRPathcode mecode, ocode;
2308 if (which_adjust == 1) {
2309 mecode = (NRPathcode)me->other->code;
2310 ocode = (NRPathcode)node->code;
2311 } else {
2312 mecode = (NRPathcode)node->code;
2313 ocode = (NRPathcode)other->other->code;
2314 }
2316 if (mecode == NR_LINETO) return;
2318 /* I am curve */
2320 if (other->other == NULL) return;
2322 /* Other has line */
2324 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2326 NR::Point delta;
2327 if (ocode == NR_LINETO) {
2328 /* other is lineto, we are either smooth or symm */
2329 Inkscape::NodePath::Node *othernode = other->other;
2330 len = NR::L2(me->pos - node->pos);
2331 delta = node->pos - othernode->pos;
2332 linelen = NR::L2(delta);
2333 if (linelen < 1e-18)
2334 return;
2335 me->pos = node->pos + (len / linelen)*delta;
2336 return;
2337 }
2339 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2341 me->pos = 2 * node->pos - other->pos;
2342 return;
2343 }
2345 /* We are smooth */
2347 len = NR::L2(me->pos - node->pos);
2348 delta = other->pos - node->pos;
2349 otherlen = NR::L2(delta);
2350 if (otherlen < 1e-18) return;
2352 me->pos = node->pos - (len / otherlen) * delta;
2353 }
2355 /**
2356 \brief Adjusts both handles according to node type and line code
2357 */
2358 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2359 {
2360 g_assert(node);
2362 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2364 /* we are either smooth or symm */
2366 if (node->p.other == NULL) return;
2368 if (node->n.other == NULL) return;
2370 if (node->code == NR_LINETO) {
2371 if (node->n.other->code == NR_LINETO) return;
2372 sp_node_adjust_handle(node, 1);
2373 return;
2374 }
2376 if (node->n.other->code == NR_LINETO) {
2377 if (node->code == NR_LINETO) return;
2378 sp_node_adjust_handle(node, -1);
2379 return;
2380 }
2382 /* both are curves */
2383 NR::Point const delta( node->n.pos - node->p.pos );
2385 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2386 node->p.pos = node->pos - delta / 2;
2387 node->n.pos = node->pos + delta / 2;
2388 return;
2389 }
2391 /* We are smooth */
2392 double plen = NR::L2(node->p.pos - node->pos);
2393 if (plen < 1e-18) return;
2394 double nlen = NR::L2(node->n.pos - node->pos);
2395 if (nlen < 1e-18) return;
2396 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2397 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2398 }
2400 /**
2401 * Node event callback.
2402 */
2403 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2404 {
2405 gboolean ret = FALSE;
2406 switch (event->type) {
2407 case GDK_ENTER_NOTIFY:
2408 active_node = n;
2409 break;
2410 case GDK_LEAVE_NOTIFY:
2411 active_node = NULL;
2412 break;
2413 case GDK_KEY_PRESS:
2414 switch (get_group0_keyval (&event->key)) {
2415 case GDK_space:
2416 if (event->key.state & GDK_BUTTON1_MASK) {
2417 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2418 stamp_repr(nodepath);
2419 ret = TRUE;
2420 }
2421 break;
2422 default:
2423 break;
2424 }
2425 break;
2426 default:
2427 break;
2428 }
2430 return ret;
2431 }
2433 /**
2434 * Handle keypress on node; directly called.
2435 */
2436 gboolean node_key(GdkEvent *event)
2437 {
2438 Inkscape::NodePath::Path *np;
2440 // there is no way to verify nodes so set active_node to nil when deleting!!
2441 if (active_node == NULL) return FALSE;
2443 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2444 gint ret = FALSE;
2445 switch (get_group0_keyval (&event->key)) {
2446 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2447 case GDK_BackSpace:
2448 np = active_node->subpath->nodepath;
2449 sp_nodepath_node_destroy(active_node);
2450 sp_nodepath_update_repr(np);
2451 active_node = NULL;
2452 ret = TRUE;
2453 break;
2454 case GDK_c:
2455 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2456 ret = TRUE;
2457 break;
2458 case GDK_s:
2459 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2460 ret = TRUE;
2461 break;
2462 case GDK_y:
2463 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2464 ret = TRUE;
2465 break;
2466 case GDK_b:
2467 sp_nodepath_node_break(active_node);
2468 ret = TRUE;
2469 break;
2470 }
2471 return ret;
2472 }
2473 return FALSE;
2474 }
2476 /**
2477 * Mouseclick on node callback.
2478 */
2479 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2480 {
2481 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2483 if (state & GDK_CONTROL_MASK) {
2484 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2486 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2487 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2488 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2489 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2490 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2491 } else {
2492 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2493 }
2494 sp_nodepath_update_repr(nodepath);
2495 sp_nodepath_update_statusbar(nodepath);
2497 } else { //ctrl+alt+click: delete node
2498 GList *node_to_delete = NULL;
2499 node_to_delete = g_list_append(node_to_delete, n);
2500 sp_node_delete_preserve(node_to_delete);
2501 }
2503 } else {
2504 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2505 }
2506 }
2508 /**
2509 * Mouse grabbed node callback.
2510 */
2511 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2512 {
2513 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2515 n->origin = knot->pos;
2517 if (!n->selected) {
2518 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2519 }
2520 }
2522 /**
2523 * Mouse ungrabbed node callback.
2524 */
2525 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2526 {
2527 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2529 n->dragging_out = NULL;
2531 sp_nodepath_update_repr(n->subpath->nodepath);
2532 }
2534 /**
2535 * The point on a line, given by its angle, closest to the given point.
2536 * \param p A point.
2537 * \param a Angle of the line; it is assumed to go through coordinate origin.
2538 * \param closest Pointer to the point struct where the result is stored.
2539 * \todo FIXME: use dot product perhaps?
2540 */
2541 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2542 {
2543 if (a == HUGE_VAL) { // vertical
2544 *closest = NR::Point(0, (*p)[NR::Y]);
2545 } else {
2546 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2547 (*closest)[NR::Y] = a * (*closest)[NR::X];
2548 }
2549 }
2551 /**
2552 * Distance from the point to a line given by its angle.
2553 * \param p A point.
2554 * \param a Angle of the line; it is assumed to go through coordinate origin.
2555 */
2556 static double point_line_distance(NR::Point *p, double a)
2557 {
2558 NR::Point c;
2559 point_line_closest(p, a, &c);
2560 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]));
2561 }
2563 /**
2564 * Callback for node "request" signal.
2565 * \todo fixme: This goes to "moved" event? (lauris)
2566 */
2567 static gboolean
2568 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2569 {
2570 double yn, xn, yp, xp;
2571 double an, ap, na, pa;
2572 double d_an, d_ap, d_na, d_pa;
2573 gboolean collinear = FALSE;
2574 NR::Point c;
2575 NR::Point pr;
2577 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2579 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2580 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2582 NR::Point mouse = (*p);
2584 if (!n->dragging_out) {
2585 // This is the first drag-out event; find out which handle to drag out
2586 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2587 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2589 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2590 return FALSE;
2592 Inkscape::NodePath::NodeSide *opposite;
2593 if (appr_p > appr_n) { // closer to p
2594 n->dragging_out = &n->p;
2595 opposite = &n->n;
2596 n->code = NR_CURVETO;
2597 } else if (appr_p < appr_n) { // closer to n
2598 n->dragging_out = &n->n;
2599 opposite = &n->p;
2600 n->n.other->code = NR_CURVETO;
2601 } else { // p and n nodes are the same
2602 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2603 n->dragging_out = &n->p;
2604 opposite = &n->n;
2605 n->code = NR_CURVETO;
2606 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2607 n->dragging_out = &n->n;
2608 opposite = &n->p;
2609 n->n.other->code = NR_CURVETO;
2610 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2611 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);
2612 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);
2613 if (appr_other_p > appr_other_n) { // closer to other's p handle
2614 n->dragging_out = &n->n;
2615 opposite = &n->p;
2616 n->n.other->code = NR_CURVETO;
2617 } else { // closer to other's n handle
2618 n->dragging_out = &n->p;
2619 opposite = &n->n;
2620 n->code = NR_CURVETO;
2621 }
2622 }
2623 }
2625 // if there's another handle, make sure the one we drag out starts parallel to it
2626 if (opposite->pos != n->pos) {
2627 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2628 }
2630 // knots might not be created yet!
2631 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2632 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2633 }
2635 // pass this on to the handle-moved callback
2636 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2637 sp_node_update_handles(n);
2638 return TRUE;
2639 }
2641 if (state & GDK_CONTROL_MASK) { // constrained motion
2643 // calculate relative distances of handles
2644 // n handle:
2645 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2646 xn = n->n.pos[NR::X] - n->pos[NR::X];
2647 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2648 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2649 if (n->n.other) { // if there is the next point
2650 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2651 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2652 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2653 }
2654 }
2655 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2656 if (yn < 0) { xn = -xn; yn = -yn; }
2658 // p handle:
2659 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2660 xp = n->p.pos[NR::X] - n->pos[NR::X];
2661 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2662 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2663 if (n->p.other) {
2664 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2665 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2666 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2667 }
2668 }
2669 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2670 if (yp < 0) { xp = -xp; yp = -yp; }
2672 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2673 // sliding on handles, only if at least one of the handles is non-vertical
2674 // (otherwise it's the same as ctrl+drag anyway)
2676 // calculate angles of the handles
2677 if (xn == 0) {
2678 if (yn == 0) { // no handle, consider it the continuation of the other one
2679 an = 0;
2680 collinear = TRUE;
2681 }
2682 else an = 0; // vertical; set the angle to horizontal
2683 } else an = yn/xn;
2685 if (xp == 0) {
2686 if (yp == 0) { // no handle, consider it the continuation of the other one
2687 ap = an;
2688 }
2689 else ap = 0; // vertical; set the angle to horizontal
2690 } else ap = yp/xp;
2692 if (collinear) an = ap;
2694 // angles of the perpendiculars; HUGE_VAL means vertical
2695 if (an == 0) na = HUGE_VAL; else na = -1/an;
2696 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2698 //g_print("an %g ap %g\n", an, ap);
2700 // mouse point relative to the node's original pos
2701 pr = (*p) - n->origin;
2703 // distances to the four lines (two handles and two perpendiculars)
2704 d_an = point_line_distance(&pr, an);
2705 d_na = point_line_distance(&pr, na);
2706 d_ap = point_line_distance(&pr, ap);
2707 d_pa = point_line_distance(&pr, pa);
2709 // find out which line is the closest, save its closest point in c
2710 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2711 point_line_closest(&pr, an, &c);
2712 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2713 point_line_closest(&pr, ap, &c);
2714 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2715 point_line_closest(&pr, na, &c);
2716 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2717 point_line_closest(&pr, pa, &c);
2718 }
2720 // move the node to the closest point
2721 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2722 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2723 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2725 } else { // constraining to hor/vert
2727 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2728 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2729 } else { // snap to vert
2730 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2731 }
2732 }
2733 } else { // move freely
2734 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2735 (*p)[NR::X] - n->pos[NR::X],
2736 (*p)[NR::Y] - n->pos[NR::Y],
2737 (state & GDK_SHIFT_MASK) == 0);
2738 }
2740 n->subpath->nodepath->desktop->scroll_to_point(p);
2742 return TRUE;
2743 }
2745 /**
2746 * Node handle clicked callback.
2747 */
2748 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2749 {
2750 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2752 if (state & GDK_CONTROL_MASK) { // "delete" handle
2753 if (n->p.knot == knot) {
2754 n->p.pos = n->pos;
2755 } else if (n->n.knot == knot) {
2756 n->n.pos = n->pos;
2757 }
2758 sp_node_update_handles(n);
2759 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2760 sp_nodepath_update_repr(nodepath);
2761 sp_nodepath_update_statusbar(nodepath);
2763 } else { // just select or add to selection, depending in Shift
2764 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2765 }
2766 }
2768 /**
2769 * Node handle grabbed callback.
2770 */
2771 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2772 {
2773 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2775 if (!n->selected) {
2776 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2777 }
2779 // remember the origin point of the handle
2780 if (n->p.knot == knot) {
2781 n->p.origin = n->p.pos - n->pos;
2782 } else if (n->n.knot == knot) {
2783 n->n.origin = n->n.pos - n->pos;
2784 } else {
2785 g_assert_not_reached();
2786 }
2788 }
2790 /**
2791 * Node handle ungrabbed callback.
2792 */
2793 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2794 {
2795 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2797 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2798 if (n->p.knot == knot) {
2799 n->p.origin.a = 0;
2800 sp_knot_set_position(knot, &n->p.pos, state);
2801 } else if (n->n.knot == knot) {
2802 n->n.origin.a = 0;
2803 sp_knot_set_position(knot, &n->n.pos, state);
2804 } else {
2805 g_assert_not_reached();
2806 }
2808 sp_nodepath_update_repr(n->subpath->nodepath);
2809 }
2811 /**
2812 * Node handle "request" signal callback.
2813 */
2814 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2815 {
2816 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2818 Inkscape::NodePath::NodeSide *me, *opposite;
2819 gint which;
2820 if (n->p.knot == knot) {
2821 me = &n->p;
2822 opposite = &n->n;
2823 which = -1;
2824 } else if (n->n.knot == knot) {
2825 me = &n->n;
2826 opposite = &n->p;
2827 which = 1;
2828 } else {
2829 me = opposite = NULL;
2830 which = 0;
2831 g_assert_not_reached();
2832 }
2834 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2836 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2838 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2839 /* We are smooth node adjacent with line */
2840 NR::Point const delta = *p - n->pos;
2841 NR::Coord const len = NR::L2(delta);
2842 Inkscape::NodePath::Node *othernode = opposite->other;
2843 NR::Point const ndelta = n->pos - othernode->pos;
2844 NR::Coord const linelen = NR::L2(ndelta);
2845 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2846 NR::Coord const scal = dot(delta, ndelta) / linelen;
2847 (*p) = n->pos + (scal / linelen) * ndelta;
2848 }
2849 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2850 } else {
2851 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2852 }
2854 sp_node_adjust_handle(n, -which);
2856 return FALSE;
2857 }
2859 /**
2860 * Node handle moved callback.
2861 */
2862 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2863 {
2864 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2866 Inkscape::NodePath::NodeSide *me;
2867 Inkscape::NodePath::NodeSide *other;
2868 if (n->p.knot == knot) {
2869 me = &n->p;
2870 other = &n->n;
2871 } else if (n->n.knot == knot) {
2872 me = &n->n;
2873 other = &n->p;
2874 } else {
2875 me = NULL;
2876 other = NULL;
2877 g_assert_not_reached();
2878 }
2880 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2881 Radial rme(me->pos - n->pos);
2882 Radial rother(other->pos - n->pos);
2883 Radial rnew(*p - n->pos);
2885 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2886 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2887 /* 0 interpreted as "no snapping". */
2889 // The closest PI/snaps angle, starting from zero.
2890 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2891 if (me->origin.a == HUGE_VAL) {
2892 // ortho doesn't exist: original handle was zero length.
2893 rnew.a = a_snapped;
2894 } else {
2895 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2896 * its opposite and perpendiculars). */
2897 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2899 // Snap to the closest.
2900 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2901 ? a_snapped
2902 : a_ortho );
2903 }
2904 }
2906 if (state & GDK_MOD1_MASK) {
2907 // lock handle length
2908 rnew.r = me->origin.r;
2909 }
2911 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2912 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2913 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2914 rother.a += rnew.a - rme.a;
2915 other->pos = NR::Point(rother) + n->pos;
2916 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2917 sp_knot_set_position(other->knot, &other->pos, 0);
2918 }
2920 me->pos = NR::Point(rnew) + n->pos;
2921 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2923 // this is what sp_knot_set_position does, but without emitting the signal:
2924 // we cannot emit a "moved" signal because we're now processing it
2925 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2927 knot->desktop->set_coordinate_status(me->pos);
2929 update_object(n->subpath->nodepath);
2931 /* status text */
2932 SPDesktop *desktop = n->subpath->nodepath->desktop;
2933 if (!desktop) return;
2934 SPEventContext *ec = desktop->event_context;
2935 if (!ec) return;
2936 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2937 if (!mc) return;
2939 double degrees = 180 / M_PI * rnew.a;
2940 if (degrees > 180) degrees -= 360;
2941 if (degrees < -180) degrees += 360;
2942 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2943 degrees = angle_to_compass (degrees);
2945 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2947 mc->setF(Inkscape::NORMAL_MESSAGE,
2948 _("<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);
2950 g_string_free(length, TRUE);
2951 }
2953 /**
2954 * Node handle event callback.
2955 */
2956 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2957 {
2958 gboolean ret = FALSE;
2959 switch (event->type) {
2960 case GDK_KEY_PRESS:
2961 switch (get_group0_keyval (&event->key)) {
2962 case GDK_space:
2963 if (event->key.state & GDK_BUTTON1_MASK) {
2964 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2965 stamp_repr(nodepath);
2966 ret = TRUE;
2967 }
2968 break;
2969 default:
2970 break;
2971 }
2972 break;
2973 default:
2974 break;
2975 }
2977 return ret;
2978 }
2980 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2981 Radial &rme, Radial &rother, gboolean const both)
2982 {
2983 rme.a += angle;
2984 if ( both
2985 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2986 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2987 {
2988 rother.a += angle;
2989 }
2990 }
2992 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2993 Radial &rme, Radial &rother, gboolean const both)
2994 {
2995 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2997 gdouble r;
2998 if ( both
2999 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3000 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3001 {
3002 r = MAX(rme.r, rother.r);
3003 } else {
3004 r = rme.r;
3005 }
3007 gdouble const weird_angle = atan2(norm_angle, r);
3008 /* Bulia says norm_angle is just the visible distance that the
3009 * object's end must travel on the screen. Left as 'angle' for want of
3010 * a better name.*/
3012 rme.a += weird_angle;
3013 if ( both
3014 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3015 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3016 {
3017 rother.a += weird_angle;
3018 }
3019 }
3021 /**
3022 * Rotate one node.
3023 */
3024 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3025 {
3026 Inkscape::NodePath::NodeSide *me, *other;
3027 bool both = false;
3029 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3030 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3032 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3033 me = &(n->p);
3034 other = &(n->n);
3035 } else if (!n->p.other) {
3036 me = &(n->n);
3037 other = &(n->p);
3038 } else {
3039 if (which > 0) { // right handle
3040 if (xn > xp) {
3041 me = &(n->n);
3042 other = &(n->p);
3043 } else {
3044 me = &(n->p);
3045 other = &(n->n);
3046 }
3047 } else if (which < 0){ // left handle
3048 if (xn <= xp) {
3049 me = &(n->n);
3050 other = &(n->p);
3051 } else {
3052 me = &(n->p);
3053 other = &(n->n);
3054 }
3055 } else { // both handles
3056 me = &(n->n);
3057 other = &(n->p);
3058 both = true;
3059 }
3060 }
3062 Radial rme(me->pos - n->pos);
3063 Radial rother(other->pos - n->pos);
3065 if (screen) {
3066 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3067 } else {
3068 node_rotate_one_internal (*n, angle, rme, rother, both);
3069 }
3071 me->pos = n->pos + NR::Point(rme);
3073 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3074 other->pos = n->pos + NR::Point(rother);
3075 }
3077 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3078 // so here we just move all the knots without emitting move signals, for speed
3079 sp_node_update_handles(n, false);
3080 }
3082 /**
3083 * Rotate selected nodes.
3084 */
3085 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3086 {
3087 if (!nodepath || !nodepath->selected) return;
3089 if (g_list_length(nodepath->selected) == 1) {
3090 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3091 node_rotate_one (n, angle, which, screen);
3092 } else {
3093 // rotate as an object:
3095 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3096 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3097 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3098 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3099 box.expandTo (n->pos); // contain all selected nodes
3100 }
3102 gdouble rot;
3103 if (screen) {
3104 gdouble const zoom = nodepath->desktop->current_zoom();
3105 gdouble const zmove = angle / zoom;
3106 gdouble const r = NR::L2(box.max() - box.midpoint());
3107 rot = atan2(zmove, r);
3108 } else {
3109 rot = angle;
3110 }
3112 NR::Matrix t =
3113 NR::Matrix (NR::translate(-box.midpoint())) *
3114 NR::Matrix (NR::rotate(rot)) *
3115 NR::Matrix (NR::translate(box.midpoint()));
3117 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3118 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3119 n->pos *= t;
3120 n->n.pos *= t;
3121 n->p.pos *= t;
3122 sp_node_update_handles(n, false);
3123 }
3124 }
3126 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3127 }
3129 /**
3130 * Scale one node.
3131 */
3132 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3133 {
3134 bool both = false;
3135 Inkscape::NodePath::NodeSide *me, *other;
3137 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3138 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3140 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3141 me = &(n->p);
3142 other = &(n->n);
3143 n->code = NR_CURVETO;
3144 } else if (!n->p.other) {
3145 me = &(n->n);
3146 other = &(n->p);
3147 if (n->n.other)
3148 n->n.other->code = NR_CURVETO;
3149 } else {
3150 if (which > 0) { // right handle
3151 if (xn > xp) {
3152 me = &(n->n);
3153 other = &(n->p);
3154 if (n->n.other)
3155 n->n.other->code = NR_CURVETO;
3156 } else {
3157 me = &(n->p);
3158 other = &(n->n);
3159 n->code = NR_CURVETO;
3160 }
3161 } else if (which < 0){ // left handle
3162 if (xn <= xp) {
3163 me = &(n->n);
3164 other = &(n->p);
3165 if (n->n.other)
3166 n->n.other->code = NR_CURVETO;
3167 } else {
3168 me = &(n->p);
3169 other = &(n->n);
3170 n->code = NR_CURVETO;
3171 }
3172 } else { // both handles
3173 me = &(n->n);
3174 other = &(n->p);
3175 both = true;
3176 n->code = NR_CURVETO;
3177 if (n->n.other)
3178 n->n.other->code = NR_CURVETO;
3179 }
3180 }
3182 Radial rme(me->pos - n->pos);
3183 Radial rother(other->pos - n->pos);
3185 rme.r += grow;
3186 if (rme.r < 0) rme.r = 0;
3187 if (rme.a == HUGE_VAL) {
3188 if (me->other) { // if direction is unknown, initialize it towards the next node
3189 Radial rme_next(me->other->pos - n->pos);
3190 rme.a = rme_next.a;
3191 } else { // if there's no next, initialize to 0
3192 rme.a = 0;
3193 }
3194 }
3195 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3196 rother.r += grow;
3197 if (rother.r < 0) rother.r = 0;
3198 if (rother.a == HUGE_VAL) {
3199 rother.a = rme.a + M_PI;
3200 }
3201 }
3203 me->pos = n->pos + NR::Point(rme);
3205 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3206 other->pos = n->pos + NR::Point(rother);
3207 }
3209 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3210 // so here we just move all the knots without emitting move signals, for speed
3211 sp_node_update_handles(n, false);
3212 }
3214 /**
3215 * Scale selected nodes.
3216 */
3217 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3218 {
3219 if (!nodepath || !nodepath->selected) return;
3221 if (g_list_length(nodepath->selected) == 1) {
3222 // scale handles of the single selected node
3223 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3224 node_scale_one (n, grow, which);
3225 } else {
3226 // scale nodes as an "object":
3228 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3229 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3230 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3231 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3232 box.expandTo (n->pos); // contain all selected nodes
3233 }
3235 double scale = (box.maxExtent() + grow)/box.maxExtent();
3237 NR::Matrix t =
3238 NR::Matrix (NR::translate(-box.midpoint())) *
3239 NR::Matrix (NR::scale(scale, scale)) *
3240 NR::Matrix (NR::translate(box.midpoint()));
3242 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3243 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3244 n->pos *= t;
3245 n->n.pos *= t;
3246 n->p.pos *= t;
3247 sp_node_update_handles(n, false);
3248 }
3249 }
3251 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3252 }
3254 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3255 {
3256 if (!nodepath) return;
3257 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3258 }
3260 /**
3261 * Flip selected nodes horizontally/vertically.
3262 */
3263 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3264 {
3265 if (!nodepath || !nodepath->selected) return;
3267 if (g_list_length(nodepath->selected) == 1) {
3268 // flip handles of the single selected node
3269 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3270 double temp = n->p.pos[axis];
3271 n->p.pos[axis] = n->n.pos[axis];
3272 n->n.pos[axis] = temp;
3273 sp_node_update_handles(n, false);
3274 } else {
3275 // scale nodes as an "object":
3277 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3278 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3279 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3280 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3281 box.expandTo (n->pos); // contain all selected nodes
3282 }
3284 NR::Matrix t =
3285 NR::Matrix (NR::translate(-box.midpoint())) *
3286 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3287 NR::Matrix (NR::translate(box.midpoint()));
3289 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3290 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3291 n->pos *= t;
3292 n->n.pos *= t;
3293 n->p.pos *= t;
3294 sp_node_update_handles(n, false);
3295 }
3296 }
3298 sp_nodepath_update_repr(nodepath);
3299 }
3301 //-----------------------------------------------
3302 /**
3303 * Return new subpath under given nodepath.
3304 */
3305 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3306 {
3307 g_assert(nodepath);
3308 g_assert(nodepath->desktop);
3310 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3312 s->nodepath = nodepath;
3313 s->closed = FALSE;
3314 s->nodes = NULL;
3315 s->first = NULL;
3316 s->last = NULL;
3318 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3319 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3320 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3322 return s;
3323 }
3325 /**
3326 * Destroy nodes in subpath, then subpath itself.
3327 */
3328 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3329 {
3330 g_assert(subpath);
3331 g_assert(subpath->nodepath);
3332 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3334 while (subpath->nodes) {
3335 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3336 }
3338 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3340 g_free(subpath);
3341 }
3343 /**
3344 * Link head to tail in subpath.
3345 */
3346 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3347 {
3348 g_assert(!sp->closed);
3349 g_assert(sp->last != sp->first);
3350 g_assert(sp->first->code == NR_MOVETO);
3352 sp->closed = TRUE;
3354 //Link the head to the tail
3355 sp->first->p.other = sp->last;
3356 sp->last->n.other = sp->first;
3357 sp->last->n.pos = sp->first->n.pos;
3358 sp->first = sp->last;
3360 //Remove the extra end node
3361 sp_nodepath_node_destroy(sp->last->n.other);
3362 }
3364 /**
3365 * Open closed (loopy) subpath at node.
3366 */
3367 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3368 {
3369 g_assert(sp->closed);
3370 g_assert(n->subpath == sp);
3371 g_assert(sp->first == sp->last);
3373 /* We create new startpoint, current node will become last one */
3375 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3376 &n->pos, &n->pos, &n->n.pos);
3379 sp->closed = FALSE;
3381 //Unlink to make a head and tail
3382 sp->first = new_path;
3383 sp->last = n;
3384 n->n.other = NULL;
3385 new_path->p.other = NULL;
3386 }
3388 /**
3389 * Returns area in triangle given by points; may be negative.
3390 */
3391 inline double
3392 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3393 {
3394 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]);
3395 }
3397 /**
3398 * Return new node in subpath with given properties.
3399 * \param pos Position of node.
3400 * \param ppos Handle position in previous direction
3401 * \param npos Handle position in previous direction
3402 */
3403 Inkscape::NodePath::Node *
3404 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)
3405 {
3406 g_assert(sp);
3407 g_assert(sp->nodepath);
3408 g_assert(sp->nodepath->desktop);
3410 if (nodechunk == NULL)
3411 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3413 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3415 n->subpath = sp;
3417 if (type != Inkscape::NodePath::NODE_NONE) {
3418 // use the type from sodipodi:nodetypes
3419 n->type = type;
3420 } else {
3421 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3422 // points are (almost) collinear
3423 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3424 // endnode, or a node with a retracted handle
3425 n->type = Inkscape::NodePath::NODE_CUSP;
3426 } else {
3427 n->type = Inkscape::NodePath::NODE_SMOOTH;
3428 }
3429 } else {
3430 n->type = Inkscape::NodePath::NODE_CUSP;
3431 }
3432 }
3434 n->code = code;
3435 n->selected = FALSE;
3436 n->pos = *pos;
3437 n->p.pos = *ppos;
3438 n->n.pos = *npos;
3440 n->dragging_out = NULL;
3442 Inkscape::NodePath::Node *prev;
3443 if (next) {
3444 //g_assert(g_list_find(sp->nodes, next));
3445 prev = next->p.other;
3446 } else {
3447 prev = sp->last;
3448 }
3450 if (prev)
3451 prev->n.other = n;
3452 else
3453 sp->first = n;
3455 if (next)
3456 next->p.other = n;
3457 else
3458 sp->last = n;
3460 n->p.other = prev;
3461 n->n.other = next;
3463 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"));
3464 sp_knot_set_position(n->knot, pos, 0);
3466 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3467 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3468 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3469 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3470 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3471 sp_knot_update_ctrl(n->knot);
3473 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3474 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3475 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3476 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3477 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3478 sp_knot_show(n->knot);
3480 // We only create handle knots and lines on demand
3481 n->p.knot = NULL;
3482 n->p.line = NULL;
3483 n->n.knot = NULL;
3484 n->n.line = NULL;
3486 sp->nodes = g_list_prepend(sp->nodes, n);
3488 return n;
3489 }
3491 /**
3492 * Destroy node and its knots, link neighbors in subpath.
3493 */
3494 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3495 {
3496 g_assert(node);
3497 g_assert(node->subpath);
3498 g_assert(SP_IS_KNOT(node->knot));
3500 Inkscape::NodePath::SubPath *sp = node->subpath;
3502 if (node->selected) { // first, deselect
3503 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3504 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3505 }
3507 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3509 g_object_unref(G_OBJECT(node->knot));
3510 if (node->p.knot)
3511 g_object_unref(G_OBJECT(node->p.knot));
3512 if (node->n.knot)
3513 g_object_unref(G_OBJECT(node->n.knot));
3515 if (node->p.line)
3516 gtk_object_destroy(GTK_OBJECT(node->p.line));
3517 if (node->n.line)
3518 gtk_object_destroy(GTK_OBJECT(node->n.line));
3520 if (sp->nodes) { // there are others nodes on the subpath
3521 if (sp->closed) {
3522 if (sp->first == node) {
3523 g_assert(sp->last == node);
3524 sp->first = node->n.other;
3525 sp->last = sp->first;
3526 }
3527 node->p.other->n.other = node->n.other;
3528 node->n.other->p.other = node->p.other;
3529 } else {
3530 if (sp->first == node) {
3531 sp->first = node->n.other;
3532 sp->first->code = NR_MOVETO;
3533 }
3534 if (sp->last == node) sp->last = node->p.other;
3535 if (node->p.other) node->p.other->n.other = node->n.other;
3536 if (node->n.other) node->n.other->p.other = node->p.other;
3537 }
3538 } else { // this was the last node on subpath
3539 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3540 }
3542 g_mem_chunk_free(nodechunk, node);
3543 }
3545 /**
3546 * Returns one of the node's two sides.
3547 * \param which Indicates which side.
3548 * \return Pointer to previous node side if which==-1, next if which==1.
3549 */
3550 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3551 {
3552 g_assert(node);
3554 switch (which) {
3555 case -1:
3556 return &node->p;
3557 case 1:
3558 return &node->n;
3559 default:
3560 break;
3561 }
3563 g_assert_not_reached();
3565 return NULL;
3566 }
3568 /**
3569 * Return the other side of the node, given one of its sides.
3570 */
3571 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3572 {
3573 g_assert(node);
3575 if (me == &node->p) return &node->n;
3576 if (me == &node->n) return &node->p;
3578 g_assert_not_reached();
3580 return NULL;
3581 }
3583 /**
3584 * Return NRPathcode on the given side of the node.
3585 */
3586 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3587 {
3588 g_assert(node);
3590 if (me == &node->p) {
3591 if (node->p.other) return (NRPathcode)node->code;
3592 return NR_MOVETO;
3593 }
3595 if (me == &node->n) {
3596 if (node->n.other) return (NRPathcode)node->n.other->code;
3597 return NR_MOVETO;
3598 }
3600 g_assert_not_reached();
3602 return NR_END;
3603 }
3605 /**
3606 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3607 */
3608 Inkscape::NodePath::Node *
3609 sp_nodepath_get_node_by_index(int index)
3610 {
3611 Inkscape::NodePath::Node *e = NULL;
3613 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3614 if (!nodepath) {
3615 return e;
3616 }
3618 //find segment
3619 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3621 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3622 int n = g_list_length(sp->nodes);
3623 if (sp->closed) {
3624 n++;
3625 }
3627 //if the piece belongs to this subpath grab it
3628 //otherwise move onto the next subpath
3629 if (index < n) {
3630 e = sp->first;
3631 for (int i = 0; i < index; ++i) {
3632 e = e->n.other;
3633 }
3634 break;
3635 } else {
3636 if (sp->closed) {
3637 index -= (n+1);
3638 } else {
3639 index -= n;
3640 }
3641 }
3642 }
3644 return e;
3645 }
3647 /**
3648 * Returns plain text meaning of node type.
3649 */
3650 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3651 {
3652 unsigned retracted = 0;
3653 bool endnode = false;
3655 for (int which = -1; which <= 1; which += 2) {
3656 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3657 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3658 retracted ++;
3659 if (!side->other)
3660 endnode = true;
3661 }
3663 if (retracted == 0) {
3664 if (endnode) {
3665 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3666 return _("end node");
3667 } else {
3668 switch (node->type) {
3669 case Inkscape::NodePath::NODE_CUSP:
3670 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3671 return _("cusp");
3672 case Inkscape::NodePath::NODE_SMOOTH:
3673 // TRANSLATORS: "smooth" is an adjective here
3674 return _("smooth");
3675 case Inkscape::NodePath::NODE_SYMM:
3676 return _("symmetric");
3677 }
3678 }
3679 } else if (retracted == 1) {
3680 if (endnode) {
3681 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3682 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3683 } else {
3684 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3685 }
3686 } else {
3687 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3688 }
3690 return NULL;
3691 }
3693 /**
3694 * Handles content of statusbar as long as node tool is active.
3695 */
3696 void
3697 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3698 {
3699 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3700 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3702 gint total = 0;
3703 gint selected = 0;
3704 SPDesktop *desktop = NULL;
3706 if (nodepath) {
3707 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3708 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3709 total += g_list_length(subpath->nodes);
3710 }
3711 selected = g_list_length(nodepath->selected);
3712 desktop = nodepath->desktop;
3713 } else {
3714 desktop = SP_ACTIVE_DESKTOP;
3715 }
3717 SPEventContext *ec = desktop->event_context;
3718 if (!ec) return;
3719 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3720 if (!mc) return;
3722 if (selected == 0) {
3723 Inkscape::Selection *sel = desktop->selection;
3724 if (!sel || sel->isEmpty()) {
3725 mc->setF(Inkscape::NORMAL_MESSAGE,
3726 _("Select a single object to edit its nodes or handles."));
3727 } else {
3728 if (nodepath) {
3729 mc->setF(Inkscape::NORMAL_MESSAGE,
3730 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.",
3731 "<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.",
3732 total),
3733 total);
3734 } else {
3735 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3736 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3737 } else {
3738 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3739 }
3740 }
3741 }
3742 } else if (nodepath && selected == 1) {
3743 mc->setF(Inkscape::NORMAL_MESSAGE,
3744 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3745 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3746 total),
3747 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3748 } else {
3749 mc->setF(Inkscape::NORMAL_MESSAGE,
3750 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3751 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3752 total),
3753 selected, total, when_selected);
3754 }
3755 }
3758 /*
3759 Local Variables:
3760 mode:c++
3761 c-file-style:"stroustrup"
3762 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3763 indent-tabs-mode:nil
3764 fill-column:99
3765 End:
3766 */
3767 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :