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(SP_CURVE_BPATH(curve));
416 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
417 np->local_change++;
418 repr->setAttribute("d", svgpath);
419 }
421 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
422 np->local_change++;
423 repr->setAttribute("sodipodi:nodetypes", typestr);
424 }
426 g_free(svgpath);
427 g_free(typestr);
428 sp_curve_unref(curve);
429 }
431 /**
432 * Update XML path node with data from path object, commit changes forever.
433 */
434 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
435 {
436 update_repr_internal(np);
437 sp_document_done(sp_desktop_document(np->desktop));
439 if (np->livarot_path) {
440 delete np->livarot_path;
441 np->livarot_path = NULL;
442 }
444 if (np->path && SP_IS_ITEM(np->path)) {
445 np->livarot_path = Path_for_item (np->path, true, true);
446 if (np->livarot_path)
447 np->livarot_path->ConvertWithBackData(0.01);
448 }
449 }
451 /**
452 * Update XML path node with data from path object, commit changes with undo.
453 */
454 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
455 {
456 update_repr_internal(np);
457 sp_document_maybe_done(sp_desktop_document(np->desktop), key);
459 if (np->livarot_path) {
460 delete np->livarot_path;
461 np->livarot_path = NULL;
462 }
464 if (np->path && SP_IS_ITEM(np->path)) {
465 np->livarot_path = Path_for_item (np->path, true, true);
466 if (np->livarot_path)
467 np->livarot_path->ConvertWithBackData(0.01);
468 }
469 }
471 /**
472 * Make duplicate of path, replace corresponding XML node in tree, commit.
473 */
474 static void stamp_repr(Inkscape::NodePath::Path *np)
475 {
476 g_assert(np);
478 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
479 Inkscape::XML::Node *new_repr = old_repr->duplicate();
481 // remember the position of the item
482 gint pos = old_repr->position();
483 // remember parent
484 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
486 SPCurve *curve = create_curve(np);
487 gchar *typestr = create_typestr(np);
489 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
491 new_repr->setAttribute("d", svgpath);
492 new_repr->setAttribute("sodipodi:nodetypes", typestr);
494 // add the new repr to the parent
495 parent->appendChild(new_repr);
496 // move to the saved position
497 new_repr->setPosition(pos > 0 ? pos : 0);
499 sp_document_done(sp_desktop_document(np->desktop));
501 Inkscape::GC::release(new_repr);
502 g_free(svgpath);
503 g_free(typestr);
504 sp_curve_unref(curve);
505 }
507 /**
508 * Create curve from path.
509 */
510 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
511 {
512 SPCurve *curve = sp_curve_new();
514 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
515 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
516 sp_curve_moveto(curve,
517 sp->first->pos * np->d2i);
518 Inkscape::NodePath::Node *n = sp->first->n.other;
519 while (n) {
520 NR::Point const end_pt = n->pos * np->d2i;
521 switch (n->code) {
522 case NR_LINETO:
523 sp_curve_lineto(curve, end_pt);
524 break;
525 case NR_CURVETO:
526 sp_curve_curveto(curve,
527 n->p.other->n.pos * np->d2i,
528 n->p.pos * np->d2i,
529 end_pt);
530 break;
531 default:
532 g_assert_not_reached();
533 break;
534 }
535 if (n != sp->last) {
536 n = n->n.other;
537 } else {
538 n = NULL;
539 }
540 }
541 if (sp->closed) {
542 sp_curve_closepath(curve);
543 }
544 }
546 return curve;
547 }
549 /**
550 * Convert path type string to sodipodi:nodetypes style.
551 */
552 static gchar *create_typestr(Inkscape::NodePath::Path *np)
553 {
554 gchar *typestr = g_new(gchar, 32);
555 gint len = 32;
556 gint pos = 0;
558 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
559 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
561 if (pos >= len) {
562 typestr = g_renew(gchar, typestr, len + 32);
563 len += 32;
564 }
566 typestr[pos++] = 'c';
568 Inkscape::NodePath::Node *n;
569 n = sp->first->n.other;
570 while (n) {
571 gchar code;
573 switch (n->type) {
574 case Inkscape::NodePath::NODE_CUSP:
575 code = 'c';
576 break;
577 case Inkscape::NodePath::NODE_SMOOTH:
578 code = 's';
579 break;
580 case Inkscape::NodePath::NODE_SYMM:
581 code = 'z';
582 break;
583 default:
584 g_assert_not_reached();
585 code = '\0';
586 break;
587 }
589 if (pos >= len) {
590 typestr = g_renew(gchar, typestr, len + 32);
591 len += 32;
592 }
594 typestr[pos++] = code;
596 if (n != sp->last) {
597 n = n->n.other;
598 } else {
599 n = NULL;
600 }
601 }
602 }
604 if (pos >= len) {
605 typestr = g_renew(gchar, typestr, len + 1);
606 len += 1;
607 }
609 typestr[pos++] = '\0';
611 return typestr;
612 }
614 /**
615 * Returns current path in context.
616 */
617 static Inkscape::NodePath::Path *sp_nodepath_current()
618 {
619 if (!SP_ACTIVE_DESKTOP) {
620 return NULL;
621 }
623 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
625 if (!SP_IS_NODE_CONTEXT(event_context)) {
626 return NULL;
627 }
629 return SP_NODE_CONTEXT(event_context)->nodepath;
630 }
634 /**
635 \brief Fills node and handle positions for three nodes, splitting line
636 marked by end at distance t.
637 */
638 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
639 {
640 g_assert(new_path != NULL);
641 g_assert(end != NULL);
643 g_assert(end->p.other == new_path);
644 Inkscape::NodePath::Node *start = new_path->p.other;
645 g_assert(start);
647 if (end->code == NR_LINETO) {
648 new_path->type =Inkscape::NodePath::NODE_CUSP;
649 new_path->code = NR_LINETO;
650 new_path->pos = (t * start->pos + (1 - t) * end->pos);
651 } else {
652 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
653 new_path->code = NR_CURVETO;
654 gdouble s = 1 - t;
655 for (int dim = 0; dim < 2; dim++) {
656 NR::Coord const f000 = start->pos[dim];
657 NR::Coord const f001 = start->n.pos[dim];
658 NR::Coord const f011 = end->p.pos[dim];
659 NR::Coord const f111 = end->pos[dim];
660 NR::Coord const f00t = s * f000 + t * f001;
661 NR::Coord const f01t = s * f001 + t * f011;
662 NR::Coord const f11t = s * f011 + t * f111;
663 NR::Coord const f0tt = s * f00t + t * f01t;
664 NR::Coord const f1tt = s * f01t + t * f11t;
665 NR::Coord const fttt = s * f0tt + t * f1tt;
666 start->n.pos[dim] = f00t;
667 new_path->p.pos[dim] = f0tt;
668 new_path->pos[dim] = fttt;
669 new_path->n.pos[dim] = f1tt;
670 end->p.pos[dim] = f11t;
671 }
672 }
673 }
675 /**
676 * Adds new node on direct line between two nodes, activates handles of all
677 * three nodes.
678 */
679 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
680 {
681 g_assert(end);
682 g_assert(end->subpath);
683 g_assert(g_list_find(end->subpath->nodes, end));
685 Inkscape::NodePath::Node *start = end->p.other;
686 g_assert( start->n.other == end );
687 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
688 end,
689 Inkscape::NodePath::NODE_SMOOTH,
690 (NRPathcode)end->code,
691 &start->pos, &start->pos, &start->n.pos);
692 sp_nodepath_line_midpoint(newnode, end, t);
694 sp_node_update_handles(start);
695 sp_node_update_handles(newnode);
696 sp_node_update_handles(end);
698 return newnode;
699 }
701 /**
702 \brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
703 */
704 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
705 {
706 g_assert(node);
707 g_assert(node->subpath);
708 g_assert(g_list_find(node->subpath->nodes, node));
710 Inkscape::NodePath::SubPath *sp = node->subpath;
711 Inkscape::NodePath::Path *np = sp->nodepath;
713 if (sp->closed) {
714 sp_nodepath_subpath_open(sp, node);
715 return sp->first;
716 } else {
717 // no break for end nodes
718 if (node == sp->first) return NULL;
719 if (node == sp->last ) return NULL;
721 // create a new subpath
722 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
724 // duplicate the break node as start of the new subpath
725 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
727 while (node->n.other) { // copy the remaining nodes into the new subpath
728 Inkscape::NodePath::Node *n = node->n.other;
729 Inkscape::NodePath::Node *nn = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
730 if (n->selected) {
731 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
732 }
733 sp_nodepath_node_destroy(n); // remove the point on the original subpath
734 }
736 return newnode;
737 }
738 }
740 /**
741 * Duplicate node and connect to neighbours.
742 */
743 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
744 {
745 g_assert(node);
746 g_assert(node->subpath);
747 g_assert(g_list_find(node->subpath->nodes, node));
749 Inkscape::NodePath::SubPath *sp = node->subpath;
751 NRPathcode code = (NRPathcode) node->code;
752 if (code == NR_MOVETO) { // if node is the endnode,
753 node->code = NR_LINETO; // new one is inserted before it, so change that to line
754 }
756 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
758 if (!node->n.other || !node->p.other) // if node is an endnode, select it
759 return node;
760 else
761 return newnode; // otherwise select the newly created node
762 }
764 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
765 {
766 node->p.pos = (node->pos + (node->pos - node->n.pos));
767 }
769 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
770 {
771 node->n.pos = (node->pos + (node->pos - node->p.pos));
772 }
774 /**
775 * Change line type at node, with side effects on neighbours.
776 */
777 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
778 {
779 g_assert(end);
780 g_assert(end->subpath);
781 g_assert(end->p.other);
783 if (end->code == static_cast< guint > ( code ) )
784 return;
786 Inkscape::NodePath::Node *start = end->p.other;
788 end->code = code;
790 if (code == NR_LINETO) {
791 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
792 if (end->n.other) {
793 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
794 }
795 sp_node_adjust_handle(start, -1);
796 sp_node_adjust_handle(end, 1);
797 } else {
798 NR::Point delta = end->pos - start->pos;
799 start->n.pos = start->pos + delta / 3;
800 end->p.pos = end->pos - delta / 3;
801 sp_node_adjust_handle(start, 1);
802 sp_node_adjust_handle(end, -1);
803 }
805 sp_node_update_handles(start);
806 sp_node_update_handles(end);
807 }
809 /**
810 * Change node type, and its handles accordingly.
811 */
812 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
813 {
814 g_assert(node);
815 g_assert(node->subpath);
817 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
818 return node;
820 if ((node->p.other != NULL) && (node->n.other != NULL)) {
821 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
822 type =Inkscape::NodePath::NODE_CUSP;
823 }
824 }
826 node->type = type;
828 if (node->type == Inkscape::NodePath::NODE_CUSP) {
829 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
830 node->knot->setSize (node->selected? 11 : 9);
831 sp_knot_update_ctrl(node->knot);
832 } else {
833 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
834 node->knot->setSize (node->selected? 9 : 7);
835 sp_knot_update_ctrl(node->knot);
836 }
838 // if one of handles is mouseovered, preserve its position
839 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
840 sp_node_adjust_handle(node, 1);
841 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
842 sp_node_adjust_handle(node, -1);
843 } else {
844 sp_node_adjust_handles(node);
845 }
847 sp_node_update_handles(node);
849 sp_nodepath_update_statusbar(node->subpath->nodepath);
851 return node;
852 }
854 /**
855 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
856 * adjacent segments from lines to curves.
857 */
858 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
859 {
860 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
861 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
862 // convert adjacent segment BEFORE to curve
863 node->code = NR_CURVETO;
864 NR::Point delta;
865 if (node->n.other != NULL)
866 delta = node->n.other->pos - node->p.other->pos;
867 else
868 delta = node->pos - node->p.other->pos;
869 node->p.pos = node->pos - delta / 4;
870 sp_node_update_handles(node);
871 }
873 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
874 // convert adjacent segment AFTER to curve
875 node->n.other->code = NR_CURVETO;
876 NR::Point delta;
877 if (node->p.other != NULL)
878 delta = node->p.other->pos - node->n.other->pos;
879 else
880 delta = node->pos - node->n.other->pos;
881 node->n.pos = node->pos - delta / 4;
882 sp_node_update_handles(node);
883 }
884 }
886 sp_nodepath_set_node_type (node, type);
887 }
889 /**
890 * Move node to point, and adjust its and neighbouring handles.
891 */
892 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
893 {
894 NR::Point delta = p - node->pos;
895 node->pos = p;
897 node->p.pos += delta;
898 node->n.pos += delta;
900 if (node->p.other) {
901 if (node->code == NR_LINETO) {
902 sp_node_adjust_handle(node, 1);
903 sp_node_adjust_handle(node->p.other, -1);
904 }
905 }
906 if (node->n.other) {
907 if (node->n.other->code == NR_LINETO) {
908 sp_node_adjust_handle(node, -1);
909 sp_node_adjust_handle(node->n.other, 1);
910 }
911 }
913 // this function is only called from batch movers that will update display at the end
914 // themselves, so here we just move all the knots without emitting move signals, for speed
915 sp_node_update_handles(node, false);
916 }
918 /**
919 * Call sp_node_moveto() for node selection and handle possible snapping.
920 */
921 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
922 bool const snap = true)
923 {
924 NR::Coord best = NR_HUGE;
925 NR::Point delta(dx, dy);
926 NR::Point best_pt = delta;
928 if (snap) {
929 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
931 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
932 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
933 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
934 if (s.getDistance() < best) {
935 best = s.getDistance();
936 best_pt = s.getPoint() - n->pos;
937 }
938 }
939 }
941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
942 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
943 sp_node_moveto(n, n->pos + best_pt);
944 }
946 // do not update repr here so that node dragging is acceptably fast
947 update_object(nodepath);
948 }
950 /**
951 * Move node selection to point, adjust its and neighbouring handles,
952 * handle possible snapping, and commit the change with possible undo.
953 */
954 void
955 sp_node_selected_move(gdouble dx, gdouble dy)
956 {
957 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
958 if (!nodepath) return;
960 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
962 if (dx == 0) {
963 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
964 } else if (dy == 0) {
965 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
966 } else {
967 sp_nodepath_update_repr(nodepath);
968 }
969 }
971 /**
972 * Move node selection off screen and commit the change.
973 */
974 void
975 sp_node_selected_move_screen(gdouble dx, gdouble dy)
976 {
977 // borrowed from sp_selection_move_screen in selection-chemistry.c
978 // we find out the current zoom factor and divide deltas by it
979 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
981 gdouble zoom = desktop->current_zoom();
982 gdouble zdx = dx / zoom;
983 gdouble zdy = dy / zoom;
985 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
986 if (!nodepath) return;
988 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
990 if (dx == 0) {
991 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
992 } else if (dy == 0) {
993 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
994 } else {
995 sp_nodepath_update_repr(nodepath);
996 }
997 }
999 /** If they don't yet exist, creates knot and line for the given side of the node */
1000 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1001 {
1002 if (!side->knot) {
1003 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"));
1005 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1006 side->knot->setSize (7);
1007 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1008 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1009 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1010 sp_knot_update_ctrl(side->knot);
1012 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1013 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1014 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1015 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1016 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1017 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1018 }
1020 if (!side->line) {
1021 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1022 SP_TYPE_CTRLLINE, NULL);
1023 }
1024 }
1026 /**
1027 * Ensure the given handle of the node is visible/invisible, update its screen position
1028 */
1029 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1030 {
1031 g_assert(node != NULL);
1033 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1034 NRPathcode code = sp_node_path_code_from_side(node, side);
1036 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1038 if (show_handle) {
1039 if (!side->knot) { // No handle knot at all
1040 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1041 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1042 side->knot->pos = side->pos;
1043 if (side->knot->item)
1044 SP_CTRL(side->knot->item)->moveto(side->pos);
1045 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1046 sp_knot_show(side->knot);
1047 } else {
1048 if (side->knot->pos != side->pos) { // only if it's really moved
1049 if (fire_move_signals) {
1050 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1051 } else {
1052 sp_knot_moveto(side->knot, &side->pos);
1053 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1054 }
1055 }
1056 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1057 sp_knot_show(side->knot);
1058 }
1059 }
1060 sp_canvas_item_show(side->line);
1061 } else {
1062 if (side->knot) {
1063 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1064 sp_knot_hide(side->knot);
1065 }
1066 }
1067 if (side->line) {
1068 sp_canvas_item_hide(side->line);
1069 }
1070 }
1071 }
1073 /**
1074 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1075 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1076 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1077 * updated; otherwise, just move the knots silently (used in batch moves).
1078 */
1079 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1080 {
1081 g_assert(node != NULL);
1083 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1084 sp_knot_show(node->knot);
1085 }
1087 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1088 if (fire_move_signals)
1089 sp_knot_set_position(node->knot, &node->pos, 0);
1090 else
1091 sp_knot_moveto(node->knot, &node->pos);
1092 }
1094 gboolean show_handles = node->selected;
1095 if (node->p.other != NULL) {
1096 if (node->p.other->selected) show_handles = TRUE;
1097 }
1098 if (node->n.other != NULL) {
1099 if (node->n.other->selected) show_handles = TRUE;
1100 }
1102 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1103 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1104 }
1106 /**
1107 * Call sp_node_update_handles() for all nodes on subpath.
1108 */
1109 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1110 {
1111 g_assert(subpath != NULL);
1113 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1114 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1115 }
1116 }
1118 /**
1119 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1120 */
1121 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1122 {
1123 g_assert(nodepath != NULL);
1125 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1126 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1127 }
1128 }
1130 /**
1131 * Adds all selected nodes in nodepath to list.
1132 */
1133 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1134 {
1135 StlConv<Node *>::list(l, selected);
1136 /// \todo this adds a copying, rework when the selection becomes a stl list
1137 }
1139 /**
1140 * Align selected nodes on the specified axis.
1141 */
1142 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1143 {
1144 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1145 return;
1146 }
1148 if ( !nodepath->selected->next ) { // only one node selected
1149 return;
1150 }
1151 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1152 NR::Point dest(pNode->pos);
1153 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1154 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1155 if (pNode) {
1156 dest[axis] = pNode->pos[axis];
1157 sp_node_moveto(pNode, dest);
1158 }
1159 }
1161 sp_nodepath_update_repr(nodepath);
1162 }
1164 /// Helper struct.
1165 struct NodeSort
1166 {
1167 Inkscape::NodePath::Node *_node;
1168 NR::Coord _coord;
1169 /// \todo use vectorof pointers instead of calling copy ctor
1170 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1171 _node(node), _coord(node->pos[axis])
1172 {}
1174 };
1176 static bool operator<(NodeSort const &a, NodeSort const &b)
1177 {
1178 return (a._coord < b._coord);
1179 }
1181 /**
1182 * Distribute selected nodes on the specified axis.
1183 */
1184 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1185 {
1186 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1187 return;
1188 }
1190 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1191 return;
1192 }
1194 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1195 std::vector<NodeSort> sorted;
1196 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1197 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1198 if (pNode) {
1199 NodeSort n(pNode, axis);
1200 sorted.push_back(n);
1201 //dest[axis] = pNode->pos[axis];
1202 //sp_node_moveto(pNode, dest);
1203 }
1204 }
1205 std::sort(sorted.begin(), sorted.end());
1206 unsigned int len = sorted.size();
1207 //overall bboxes span
1208 float dist = (sorted.back()._coord -
1209 sorted.front()._coord);
1210 //new distance between each bbox
1211 float step = (dist) / (len - 1);
1212 float pos = sorted.front()._coord;
1213 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1214 it < sorted.end();
1215 it ++ )
1216 {
1217 NR::Point dest((*it)._node->pos);
1218 dest[axis] = pos;
1219 sp_node_moveto((*it)._node, dest);
1220 pos += step;
1221 }
1223 sp_nodepath_update_repr(nodepath);
1224 }
1227 /**
1228 * Call sp_nodepath_line_add_node() for all selected segments.
1229 */
1230 void
1231 sp_node_selected_add_node(void)
1232 {
1233 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1234 if (!nodepath) {
1235 return;
1236 }
1238 GList *nl = NULL;
1240 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1241 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1242 g_assert(t->selected);
1243 if (t->p.other && t->p.other->selected) {
1244 nl = g_list_prepend(nl, t);
1245 }
1246 }
1248 while (nl) {
1249 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1250 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1251 sp_nodepath_node_select(n, TRUE, FALSE);
1252 nl = g_list_remove(nl, t);
1253 }
1255 /** \todo fixme: adjust ? */
1256 sp_nodepath_update_handles(nodepath);
1258 sp_nodepath_update_repr(nodepath);
1260 sp_nodepath_update_statusbar(nodepath);
1261 }
1263 /**
1264 * Select segment nearest to point
1265 */
1266 void
1267 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1268 {
1269 if (!nodepath) {
1270 return;
1271 }
1273 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1275 //find segment to segment
1276 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1278 gboolean force = FALSE;
1279 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1280 force = TRUE;
1281 }
1282 sp_nodepath_node_select(e, (gboolean) toggle, force);
1283 if (e->p.other)
1284 sp_nodepath_node_select(e->p.other, TRUE, force);
1286 sp_nodepath_update_handles(nodepath);
1288 sp_nodepath_update_statusbar(nodepath);
1289 }
1291 /**
1292 * Add a node nearest to point
1293 */
1294 void
1295 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1296 {
1297 if (!nodepath) {
1298 return;
1299 }
1301 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1303 //find segment to split
1304 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1306 //don't know why but t seems to flip for lines
1307 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1308 position.t = 1.0 - position.t;
1309 }
1310 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1311 sp_nodepath_node_select(n, FALSE, TRUE);
1313 /* fixme: adjust ? */
1314 sp_nodepath_update_handles(nodepath);
1316 sp_nodepath_update_repr(nodepath);
1318 sp_nodepath_update_statusbar(nodepath);
1319 }
1321 /*
1322 * Adjusts a segment so that t moves by a certain delta for dragging
1323 * converts lines to curves
1324 *
1325 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1326 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1327 */
1328 void
1329 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1330 {
1331 /* feel good is an arbitrary parameter that distributes the delta between handles
1332 * if t of the drag point is less than 1/6 distance form the endpoint only
1333 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1334 */
1335 double feel_good;
1336 if (t <= 1.0 / 6.0)
1337 feel_good = 0;
1338 else if (t <= 0.5)
1339 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1340 else if (t <= 5.0 / 6.0)
1341 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1342 else
1343 feel_good = 1;
1345 //if we're dragging a line convert it to a curve
1346 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1347 sp_nodepath_set_line_type(e, NR_CURVETO);
1348 }
1350 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1351 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1352 e->p.other->n.pos += offsetcoord0;
1353 e->p.pos += offsetcoord1;
1355 // adjust handles of adjacent nodes where necessary
1356 sp_node_adjust_handle(e,1);
1357 sp_node_adjust_handle(e->p.other,-1);
1359 sp_nodepath_update_handles(e->subpath->nodepath);
1361 update_object(e->subpath->nodepath);
1363 sp_nodepath_update_statusbar(e->subpath->nodepath);
1364 }
1367 /**
1368 * Call sp_nodepath_break() for all selected segments.
1369 */
1370 void sp_node_selected_break()
1371 {
1372 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1373 if (!nodepath) return;
1375 GList *temp = NULL;
1376 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1377 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1378 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1379 if (nn == NULL) continue; // no break, no new node
1380 temp = g_list_prepend(temp, nn);
1381 }
1383 if (temp) {
1384 sp_nodepath_deselect(nodepath);
1385 }
1386 for (GList *l = temp; l != NULL; l = l->next) {
1387 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1388 }
1390 sp_nodepath_update_handles(nodepath);
1392 sp_nodepath_update_repr(nodepath);
1393 }
1395 /**
1396 * Duplicate the selected node(s).
1397 */
1398 void sp_node_selected_duplicate()
1399 {
1400 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1401 if (!nodepath) {
1402 return;
1403 }
1405 GList *temp = NULL;
1406 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1408 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1409 if (nn == NULL) continue; // could not duplicate
1410 temp = g_list_prepend(temp, nn);
1411 }
1413 if (temp) {
1414 sp_nodepath_deselect(nodepath);
1415 }
1416 for (GList *l = temp; l != NULL; l = l->next) {
1417 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1418 }
1420 sp_nodepath_update_handles(nodepath);
1422 sp_nodepath_update_repr(nodepath);
1423 }
1425 /**
1426 * Join two nodes by merging them into one.
1427 */
1428 void sp_node_selected_join()
1429 {
1430 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1431 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1433 if (g_list_length(nodepath->selected) != 2) {
1434 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1435 return;
1436 }
1438 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1439 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1441 g_assert(a != b);
1442 g_assert(a->p.other || a->n.other);
1443 g_assert(b->p.other || b->n.other);
1445 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1446 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1447 return;
1448 }
1450 /* a and b are endpoints */
1452 NR::Point c;
1453 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1454 c = a->pos;
1455 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1456 c = b->pos;
1457 } else {
1458 c = (a->pos + b->pos) / 2;
1459 }
1461 if (a->subpath == b->subpath) {
1462 Inkscape::NodePath::SubPath *sp = a->subpath;
1463 sp_nodepath_subpath_close(sp);
1464 sp_node_moveto (sp->first, c);
1466 sp_nodepath_update_handles(sp->nodepath);
1467 sp_nodepath_update_repr(nodepath);
1468 return;
1469 }
1471 /* a and b are separate subpaths */
1472 Inkscape::NodePath::SubPath *sa = a->subpath;
1473 Inkscape::NodePath::SubPath *sb = b->subpath;
1474 NR::Point p;
1475 Inkscape::NodePath::Node *n;
1476 NRPathcode code;
1477 if (a == sa->first) {
1478 p = sa->first->n.pos;
1479 code = (NRPathcode)sa->first->n.other->code;
1480 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1481 n = sa->last;
1482 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1483 n = n->p.other;
1484 while (n) {
1485 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1486 n = n->p.other;
1487 if (n == sa->first) n = NULL;
1488 }
1489 sp_nodepath_subpath_destroy(sa);
1490 sa = t;
1491 } else if (a == sa->last) {
1492 p = sa->last->p.pos;
1493 code = (NRPathcode)sa->last->code;
1494 sp_nodepath_node_destroy(sa->last);
1495 } else {
1496 code = NR_END;
1497 g_assert_not_reached();
1498 }
1500 if (b == sb->first) {
1501 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1502 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1503 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1504 }
1505 } else if (b == sb->last) {
1506 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1507 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1508 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1509 }
1510 } else {
1511 g_assert_not_reached();
1512 }
1513 /* and now destroy sb */
1515 sp_nodepath_subpath_destroy(sb);
1517 sp_nodepath_update_handles(sa->nodepath);
1519 sp_nodepath_update_repr(nodepath);
1521 sp_nodepath_update_statusbar(nodepath);
1522 }
1524 /**
1525 * Join two nodes by adding a segment between them.
1526 */
1527 void sp_node_selected_join_segment()
1528 {
1529 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1530 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1532 if (g_list_length(nodepath->selected) != 2) {
1533 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1534 return;
1535 }
1537 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1538 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1540 g_assert(a != b);
1541 g_assert(a->p.other || a->n.other);
1542 g_assert(b->p.other || b->n.other);
1544 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1545 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1546 return;
1547 }
1549 if (a->subpath == b->subpath) {
1550 Inkscape::NodePath::SubPath *sp = a->subpath;
1552 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1553 sp->closed = TRUE;
1555 sp->first->p.other = sp->last;
1556 sp->last->n.other = sp->first;
1558 sp_node_handle_mirror_p_to_n(sp->last);
1559 sp_node_handle_mirror_n_to_p(sp->first);
1561 sp->first->code = sp->last->code;
1562 sp->first = sp->last;
1564 sp_nodepath_update_handles(sp->nodepath);
1566 sp_nodepath_update_repr(nodepath);
1568 return;
1569 }
1571 /* a and b are separate subpaths */
1572 Inkscape::NodePath::SubPath *sa = a->subpath;
1573 Inkscape::NodePath::SubPath *sb = b->subpath;
1575 Inkscape::NodePath::Node *n;
1576 NR::Point p;
1577 NRPathcode code;
1578 if (a == sa->first) {
1579 code = (NRPathcode) sa->first->n.other->code;
1580 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1581 n = sa->last;
1582 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1583 for (n = n->p.other; n != NULL; n = n->p.other) {
1584 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1585 }
1586 sp_nodepath_subpath_destroy(sa);
1587 sa = t;
1588 } else if (a == sa->last) {
1589 code = (NRPathcode)sa->last->code;
1590 } else {
1591 code = NR_END;
1592 g_assert_not_reached();
1593 }
1595 if (b == sb->first) {
1596 n = sb->first;
1597 sp_node_handle_mirror_p_to_n(sa->last);
1598 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1599 sp_node_handle_mirror_n_to_p(sa->last);
1600 for (n = n->n.other; n != NULL; n = n->n.other) {
1601 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1602 }
1603 } else if (b == sb->last) {
1604 n = sb->last;
1605 sp_node_handle_mirror_p_to_n(sa->last);
1606 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1607 sp_node_handle_mirror_n_to_p(sa->last);
1608 for (n = n->p.other; n != NULL; n = n->p.other) {
1609 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1610 }
1611 } else {
1612 g_assert_not_reached();
1613 }
1614 /* and now destroy sb */
1616 sp_nodepath_subpath_destroy(sb);
1618 sp_nodepath_update_handles(sa->nodepath);
1620 sp_nodepath_update_repr(nodepath);
1621 }
1623 /**
1624 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1625 */
1626 void sp_node_delete_preserve(GList *nodes_to_delete)
1627 {
1629 while (nodes_to_delete) {
1630 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1631 Inkscape::NodePath::SubPath *sp = node->subpath;
1632 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1633 Inkscape::NodePath::Node *sample_cursor = NULL;
1634 Inkscape::NodePath::Node *sample_end = NULL;
1635 Inkscape::NodePath::Node *delete_cursor = node;
1636 bool just_delete = false;
1638 //find the start of this contiguous selection
1639 //move left to the first node that is not selected
1640 //or the start of the non-closed path
1641 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1642 delete_cursor = curr;
1643 }
1645 //just delete at the beginning of an open path
1646 if (!delete_cursor->p.other) {
1647 sample_cursor = delete_cursor;
1648 just_delete = true;
1649 } else {
1650 sample_cursor = delete_cursor->p.other;
1651 }
1653 //calculate points for each segment
1654 int rate = 5;
1655 float period = 1.0 / rate;
1656 std::vector<NR::Point> data;
1657 if (!just_delete) {
1658 data.push_back(sample_cursor->pos);
1659 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1660 //just delete at the end of an open path
1661 if (!sp->closed && curr->n.other == sp->last) {
1662 just_delete = true;
1663 break;
1664 }
1666 //sample points on the contiguous selected segment
1667 NR::Point *bez;
1668 bez = new NR::Point [4];
1669 bez[0] = curr->pos;
1670 bez[1] = curr->n.pos;
1671 bez[2] = curr->n.other->p.pos;
1672 bez[3] = curr->n.other->pos;
1673 for (int i=1; i<rate; i++) {
1674 gdouble t = i * period;
1675 NR::Point p = bezier_pt(3, bez, t);
1676 data.push_back(p);
1677 }
1678 data.push_back(curr->n.other->pos);
1680 sample_end = curr->n.other;
1681 //break if we've come full circle or hit the end of the selection
1682 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1683 break;
1684 }
1685 }
1686 }
1688 if (!just_delete) {
1689 //calculate the best fitting single segment and adjust the endpoints
1690 NR::Point *adata;
1691 adata = new NR::Point [data.size()];
1692 copy(data.begin(), data.end(), adata);
1694 NR::Point *bez;
1695 bez = new NR::Point [4];
1696 //would decreasing error create a better fitting approximation?
1697 gdouble error = 1.0;
1698 gint ret;
1699 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1701 //adjust endpoints
1702 sample_cursor->n.pos = bez[1];
1703 sample_end->p.pos = bez[2];
1704 }
1706 //destroy this contiguous selection
1707 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1708 Inkscape::NodePath::Node *temp = delete_cursor;
1709 if (delete_cursor->n.other == delete_cursor) {
1710 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1711 delete_cursor = NULL;
1712 } else {
1713 delete_cursor = delete_cursor->n.other;
1714 }
1715 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1716 sp_nodepath_node_destroy(temp);
1717 }
1719 //clean up the nodepath (such as for trivial subpaths)
1720 sp_nodepath_cleanup(nodepath);
1722 sp_nodepath_update_handles(nodepath);
1724 // if the entire nodepath is removed, delete the selected object.
1725 if (nodepath->subpaths == NULL ||
1726 sp_nodepath_get_node_count(nodepath) < 2) {
1727 SPDocument *document = sp_desktop_document (nodepath->desktop);
1728 sp_nodepath_destroy(nodepath);
1729 g_list_free(nodes_to_delete);
1730 nodes_to_delete = NULL;
1731 //is the next line necessary?
1732 sp_selection_delete();
1733 sp_document_done (document);
1734 return;
1735 }
1737 sp_nodepath_update_repr(nodepath);
1739 sp_nodepath_update_statusbar(nodepath);
1740 }
1741 }
1743 /**
1744 * Delete one or more selected nodes.
1745 */
1746 void sp_node_selected_delete()
1747 {
1748 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1749 if (!nodepath) return;
1750 if (!nodepath->selected) return;
1752 /** \todo fixme: do it the right way */
1753 while (nodepath->selected) {
1754 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1755 sp_nodepath_node_destroy(node);
1756 }
1759 //clean up the nodepath (such as for trivial subpaths)
1760 sp_nodepath_cleanup(nodepath);
1762 sp_nodepath_update_handles(nodepath);
1764 // if the entire nodepath is removed, delete the selected object.
1765 if (nodepath->subpaths == NULL ||
1766 sp_nodepath_get_node_count(nodepath) < 2) {
1767 SPDocument *document = sp_desktop_document (nodepath->desktop);
1768 sp_nodepath_destroy(nodepath);
1769 sp_selection_delete();
1770 sp_document_done (document);
1771 return;
1772 }
1774 sp_nodepath_update_repr(nodepath);
1776 sp_nodepath_update_statusbar(nodepath);
1777 }
1779 /**
1780 * Delete one or more segments between two selected nodes.
1781 * This is the code for 'split'.
1782 */
1783 void
1784 sp_node_selected_delete_segment(void)
1785 {
1786 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1787 Inkscape::NodePath::Node *curr, *next; //Iterators
1789 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1790 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1792 if (g_list_length(nodepath->selected) != 2) {
1793 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1794 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1795 return;
1796 }
1798 //Selected nodes, not inclusive
1799 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1800 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1802 if ( ( a==b) || //same node
1803 (a->subpath != b->subpath ) || //not the same path
1804 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1805 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1806 {
1807 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1808 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1809 return;
1810 }
1812 //###########################################
1813 //# BEGIN EDITS
1814 //###########################################
1815 //##################################
1816 //# CLOSED PATH
1817 //##################################
1818 if (a->subpath->closed) {
1821 gboolean reversed = FALSE;
1823 //Since we can go in a circle, we need to find the shorter distance.
1824 // a->b or b->a
1825 start = end = NULL;
1826 int distance = 0;
1827 int minDistance = 0;
1828 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1829 if (curr==b) {
1830 //printf("a to b:%d\n", distance);
1831 start = a;//go from a to b
1832 end = b;
1833 minDistance = distance;
1834 //printf("A to B :\n");
1835 break;
1836 }
1837 distance++;
1838 }
1840 //try again, the other direction
1841 distance = 0;
1842 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1843 if (curr==a) {
1844 //printf("b to a:%d\n", distance);
1845 if (distance < minDistance) {
1846 start = b; //we go from b to a
1847 end = a;
1848 reversed = TRUE;
1849 //printf("B to A\n");
1850 }
1851 break;
1852 }
1853 distance++;
1854 }
1857 //Copy everything from 'end' to 'start' to a new subpath
1858 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1859 for (curr=end ; curr ; curr=curr->n.other) {
1860 NRPathcode code = (NRPathcode) curr->code;
1861 if (curr == end)
1862 code = NR_MOVETO;
1863 sp_nodepath_node_new(t, NULL,
1864 (Inkscape::NodePath::NodeType)curr->type, code,
1865 &curr->p.pos, &curr->pos, &curr->n.pos);
1866 if (curr == start)
1867 break;
1868 }
1869 sp_nodepath_subpath_destroy(a->subpath);
1872 }
1876 //##################################
1877 //# OPEN PATH
1878 //##################################
1879 else {
1881 //We need to get the direction of the list between A and B
1882 //Can we walk from a to b?
1883 start = end = NULL;
1884 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1885 if (curr==b) {
1886 start = a; //did it! we go from a to b
1887 end = b;
1888 //printf("A to B\n");
1889 break;
1890 }
1891 }
1892 if (!start) {//didn't work? let's try the other direction
1893 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1894 if (curr==a) {
1895 start = b; //did it! we go from b to a
1896 end = a;
1897 //printf("B to A\n");
1898 break;
1899 }
1900 }
1901 }
1902 if (!start) {
1903 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1904 _("Cannot find path between nodes."));
1905 return;
1906 }
1910 //Copy everything after 'end' to a new subpath
1911 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1912 for (curr=end ; curr ; curr=curr->n.other) {
1913 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1914 &curr->p.pos, &curr->pos, &curr->n.pos);
1915 }
1917 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1918 for (curr = start->n.other ; curr ; curr=next) {
1919 next = curr->n.other;
1920 sp_nodepath_node_destroy(curr);
1921 }
1923 }
1924 //###########################################
1925 //# END EDITS
1926 //###########################################
1928 //clean up the nodepath (such as for trivial subpaths)
1929 sp_nodepath_cleanup(nodepath);
1931 sp_nodepath_update_handles(nodepath);
1933 sp_nodepath_update_repr(nodepath);
1935 // if the entire nodepath is removed, delete the selected object.
1936 if (nodepath->subpaths == NULL ||
1937 sp_nodepath_get_node_count(nodepath) < 2) {
1938 sp_nodepath_destroy(nodepath);
1939 sp_selection_delete();
1940 return;
1941 }
1943 sp_nodepath_update_statusbar(nodepath);
1944 }
1946 /**
1947 * Call sp_nodepath_set_line() for all selected segments.
1948 */
1949 void
1950 sp_node_selected_set_line_type(NRPathcode code)
1951 {
1952 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1953 if (nodepath == NULL) return;
1955 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1956 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1957 g_assert(n->selected);
1958 if (n->p.other && n->p.other->selected) {
1959 sp_nodepath_set_line_type(n, code);
1960 }
1961 }
1963 sp_nodepath_update_repr(nodepath);
1964 }
1966 /**
1967 * Call sp_nodepath_convert_node_type() for all selected nodes.
1968 */
1969 void
1970 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1971 {
1972 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1973 if (nodepath == NULL) return;
1975 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1976 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1977 }
1979 sp_nodepath_update_repr(nodepath);
1980 }
1982 /**
1983 * Change select status of node, update its own and neighbour handles.
1984 */
1985 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1986 {
1987 node->selected = selected;
1989 if (selected) {
1990 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1991 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1992 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1993 sp_knot_update_ctrl(node->knot);
1994 } else {
1995 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1996 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1997 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1998 sp_knot_update_ctrl(node->knot);
1999 }
2001 sp_node_update_handles(node);
2002 if (node->n.other) sp_node_update_handles(node->n.other);
2003 if (node->p.other) sp_node_update_handles(node->p.other);
2004 }
2006 /**
2007 \brief Select a node
2008 \param node The node to select
2009 \param incremental If true, add to selection, otherwise deselect others
2010 \param override If true, always select this node, otherwise toggle selected status
2011 */
2012 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2013 {
2014 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2016 if (incremental) {
2017 if (override) {
2018 if (!g_list_find(nodepath->selected, node)) {
2019 nodepath->selected = g_list_prepend(nodepath->selected, node);
2020 }
2021 sp_node_set_selected(node, TRUE);
2022 } else { // toggle
2023 if (node->selected) {
2024 g_assert(g_list_find(nodepath->selected, node));
2025 nodepath->selected = g_list_remove(nodepath->selected, node);
2026 } else {
2027 g_assert(!g_list_find(nodepath->selected, node));
2028 nodepath->selected = g_list_prepend(nodepath->selected, node);
2029 }
2030 sp_node_set_selected(node, !node->selected);
2031 }
2032 } else {
2033 sp_nodepath_deselect(nodepath);
2034 nodepath->selected = g_list_prepend(nodepath->selected, node);
2035 sp_node_set_selected(node, TRUE);
2036 }
2038 sp_nodepath_update_statusbar(nodepath);
2039 }
2042 /**
2043 \brief Deselect all nodes in the nodepath
2044 */
2045 void
2046 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2047 {
2048 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2050 while (nodepath->selected) {
2051 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2052 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2053 }
2054 sp_nodepath_update_statusbar(nodepath);
2055 }
2057 /**
2058 \brief Select or invert selection of all nodes in the nodepath
2059 */
2060 void
2061 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2062 {
2063 if (!nodepath) return;
2065 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2066 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2067 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2068 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2069 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2070 }
2071 }
2072 }
2074 /**
2075 * If nothing selected, does the same as sp_nodepath_select_all();
2076 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2077 * (i.e., similar to "select all in layer", with the "selected" subpaths
2078 * being treated as "layers" in the path).
2079 */
2080 void
2081 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2082 {
2083 if (!nodepath) return;
2085 if (g_list_length (nodepath->selected) == 0) {
2086 sp_nodepath_select_all (nodepath, invert);
2087 return;
2088 }
2090 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2091 GSList *subpaths = NULL;
2093 for (GList *l = copy; l != NULL; l = l->next) {
2094 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2095 Inkscape::NodePath::SubPath *subpath = n->subpath;
2096 if (!g_slist_find (subpaths, subpath))
2097 subpaths = g_slist_prepend (subpaths, subpath);
2098 }
2100 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2101 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2102 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2103 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2104 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2105 }
2106 }
2108 g_slist_free (subpaths);
2109 g_list_free (copy);
2110 }
2112 /**
2113 * \brief Select the node after the last selected; if none is selected,
2114 * select the first within path.
2115 */
2116 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2117 {
2118 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2120 Inkscape::NodePath::Node *last = NULL;
2121 if (nodepath->selected) {
2122 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2123 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2124 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2125 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2126 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2127 if (node->selected) {
2128 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2129 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2130 if (spl->next) { // there's a next subpath
2131 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2132 last = subpath_next->first;
2133 } else if (spl->prev) { // there's a previous subpath
2134 last = NULL; // to be set later to the first node of first subpath
2135 } else {
2136 last = node->n.other;
2137 }
2138 } else {
2139 last = node->n.other;
2140 }
2141 } else {
2142 if (node->n.other) {
2143 last = node->n.other;
2144 } else {
2145 if (spl->next) { // there's a next subpath
2146 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2147 last = subpath_next->first;
2148 } else if (spl->prev) { // there's a previous subpath
2149 last = NULL; // to be set later to the first node of first subpath
2150 } else {
2151 last = (Inkscape::NodePath::Node *) subpath->first;
2152 }
2153 }
2154 }
2155 }
2156 }
2157 }
2158 sp_nodepath_deselect(nodepath);
2159 }
2161 if (last) { // there's at least one more node after selected
2162 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2163 } else { // no more nodes, select the first one in first subpath
2164 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2165 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2166 }
2167 }
2169 /**
2170 * \brief Select the node before the first selected; if none is selected,
2171 * select the last within path
2172 */
2173 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2174 {
2175 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2177 Inkscape::NodePath::Node *last = NULL;
2178 if (nodepath->selected) {
2179 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2180 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2181 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2182 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2183 if (node->selected) {
2184 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2185 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2186 if (spl->prev) { // there's a prev subpath
2187 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2188 last = subpath_prev->last;
2189 } else if (spl->next) { // there's a next subpath
2190 last = NULL; // to be set later to the last node of last subpath
2191 } else {
2192 last = node->p.other;
2193 }
2194 } else {
2195 last = node->p.other;
2196 }
2197 } else {
2198 if (node->p.other) {
2199 last = node->p.other;
2200 } else {
2201 if (spl->prev) { // there's a prev subpath
2202 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2203 last = subpath_prev->last;
2204 } else if (spl->next) { // there's a next subpath
2205 last = NULL; // to be set later to the last node of last subpath
2206 } else {
2207 last = (Inkscape::NodePath::Node *) subpath->last;
2208 }
2209 }
2210 }
2211 }
2212 }
2213 }
2214 sp_nodepath_deselect(nodepath);
2215 }
2217 if (last) { // there's at least one more node before selected
2218 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2219 } else { // no more nodes, select the last one in last subpath
2220 GList *spl = g_list_last(nodepath->subpaths);
2221 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2222 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2223 }
2224 }
2226 /**
2227 * \brief Select all nodes that are within the rectangle.
2228 */
2229 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2230 {
2231 if (!incremental) {
2232 sp_nodepath_deselect(nodepath);
2233 }
2235 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2236 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2237 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2238 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2240 if (b.contains(node->pos)) {
2241 sp_nodepath_node_select(node, TRUE, TRUE);
2242 }
2243 }
2244 }
2245 }
2247 /**
2248 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2249 */
2250 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2251 {
2252 if (!nodepath->selected) {
2253 return NULL;
2254 }
2256 GList *r = NULL;
2257 guint i = 0;
2258 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2259 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2260 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2261 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2262 i++;
2263 if (node->selected) {
2264 r = g_list_append(r, GINT_TO_POINTER(i));
2265 }
2266 }
2267 }
2268 return r;
2269 }
2271 /**
2272 \brief Restores selection by selecting nodes whose positions are in the list
2273 */
2274 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2275 {
2276 sp_nodepath_deselect(nodepath);
2278 guint i = 0;
2279 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2280 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2281 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2282 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2283 i++;
2284 if (g_list_find(r, GINT_TO_POINTER(i))) {
2285 sp_nodepath_node_select(node, TRUE, TRUE);
2286 }
2287 }
2288 }
2290 }
2292 /**
2293 \brief Adjusts handle according to node type and line code.
2294 */
2295 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2296 {
2297 double len, otherlen, linelen;
2299 g_assert(node);
2301 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2302 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2304 /** \todo fixme: */
2305 if (me->other == NULL) return;
2306 if (other->other == NULL) return;
2308 /* I have line */
2310 NRPathcode mecode, ocode;
2311 if (which_adjust == 1) {
2312 mecode = (NRPathcode)me->other->code;
2313 ocode = (NRPathcode)node->code;
2314 } else {
2315 mecode = (NRPathcode)node->code;
2316 ocode = (NRPathcode)other->other->code;
2317 }
2319 if (mecode == NR_LINETO) return;
2321 /* I am curve */
2323 if (other->other == NULL) return;
2325 /* Other has line */
2327 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2329 NR::Point delta;
2330 if (ocode == NR_LINETO) {
2331 /* other is lineto, we are either smooth or symm */
2332 Inkscape::NodePath::Node *othernode = other->other;
2333 len = NR::L2(me->pos - node->pos);
2334 delta = node->pos - othernode->pos;
2335 linelen = NR::L2(delta);
2336 if (linelen < 1e-18)
2337 return;
2338 me->pos = node->pos + (len / linelen)*delta;
2339 return;
2340 }
2342 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2344 me->pos = 2 * node->pos - other->pos;
2345 return;
2346 }
2348 /* We are smooth */
2350 len = NR::L2(me->pos - node->pos);
2351 delta = other->pos - node->pos;
2352 otherlen = NR::L2(delta);
2353 if (otherlen < 1e-18) return;
2355 me->pos = node->pos - (len / otherlen) * delta;
2356 }
2358 /**
2359 \brief Adjusts both handles according to node type and line code
2360 */
2361 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2362 {
2363 g_assert(node);
2365 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2367 /* we are either smooth or symm */
2369 if (node->p.other == NULL) return;
2371 if (node->n.other == NULL) return;
2373 if (node->code == NR_LINETO) {
2374 if (node->n.other->code == NR_LINETO) return;
2375 sp_node_adjust_handle(node, 1);
2376 return;
2377 }
2379 if (node->n.other->code == NR_LINETO) {
2380 if (node->code == NR_LINETO) return;
2381 sp_node_adjust_handle(node, -1);
2382 return;
2383 }
2385 /* both are curves */
2386 NR::Point const delta( node->n.pos - node->p.pos );
2388 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2389 node->p.pos = node->pos - delta / 2;
2390 node->n.pos = node->pos + delta / 2;
2391 return;
2392 }
2394 /* We are smooth */
2395 double plen = NR::L2(node->p.pos - node->pos);
2396 if (plen < 1e-18) return;
2397 double nlen = NR::L2(node->n.pos - node->pos);
2398 if (nlen < 1e-18) return;
2399 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2400 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2401 }
2403 /**
2404 * Node event callback.
2405 */
2406 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2407 {
2408 gboolean ret = FALSE;
2409 switch (event->type) {
2410 case GDK_ENTER_NOTIFY:
2411 active_node = n;
2412 break;
2413 case GDK_LEAVE_NOTIFY:
2414 active_node = NULL;
2415 break;
2416 case GDK_KEY_PRESS:
2417 switch (get_group0_keyval (&event->key)) {
2418 case GDK_space:
2419 if (event->key.state & GDK_BUTTON1_MASK) {
2420 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2421 stamp_repr(nodepath);
2422 ret = TRUE;
2423 }
2424 break;
2425 default:
2426 break;
2427 }
2428 break;
2429 default:
2430 break;
2431 }
2433 return ret;
2434 }
2436 /**
2437 * Handle keypress on node; directly called.
2438 */
2439 gboolean node_key(GdkEvent *event)
2440 {
2441 Inkscape::NodePath::Path *np;
2443 // there is no way to verify nodes so set active_node to nil when deleting!!
2444 if (active_node == NULL) return FALSE;
2446 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2447 gint ret = FALSE;
2448 switch (get_group0_keyval (&event->key)) {
2449 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2450 case GDK_BackSpace:
2451 np = active_node->subpath->nodepath;
2452 sp_nodepath_node_destroy(active_node);
2453 sp_nodepath_update_repr(np);
2454 active_node = NULL;
2455 ret = TRUE;
2456 break;
2457 case GDK_c:
2458 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2459 ret = TRUE;
2460 break;
2461 case GDK_s:
2462 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2463 ret = TRUE;
2464 break;
2465 case GDK_y:
2466 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2467 ret = TRUE;
2468 break;
2469 case GDK_b:
2470 sp_nodepath_node_break(active_node);
2471 ret = TRUE;
2472 break;
2473 }
2474 return ret;
2475 }
2476 return FALSE;
2477 }
2479 /**
2480 * Mouseclick on node callback.
2481 */
2482 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2483 {
2484 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2486 if (state & GDK_CONTROL_MASK) {
2487 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2489 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2490 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2491 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2492 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2493 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2494 } else {
2495 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2496 }
2497 sp_nodepath_update_repr(nodepath);
2498 sp_nodepath_update_statusbar(nodepath);
2500 } else { //ctrl+alt+click: delete node
2501 GList *node_to_delete = NULL;
2502 node_to_delete = g_list_append(node_to_delete, n);
2503 sp_node_delete_preserve(node_to_delete);
2504 }
2506 } else {
2507 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2508 }
2509 }
2511 /**
2512 * Mouse grabbed node callback.
2513 */
2514 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2515 {
2516 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2518 n->origin = knot->pos;
2520 if (!n->selected) {
2521 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2522 }
2523 }
2525 /**
2526 * Mouse ungrabbed node callback.
2527 */
2528 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2529 {
2530 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2532 n->dragging_out = NULL;
2534 sp_nodepath_update_repr(n->subpath->nodepath);
2535 }
2537 /**
2538 * The point on a line, given by its angle, closest to the given point.
2539 * \param p A point.
2540 * \param a Angle of the line; it is assumed to go through coordinate origin.
2541 * \param closest Pointer to the point struct where the result is stored.
2542 * \todo FIXME: use dot product perhaps?
2543 */
2544 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2545 {
2546 if (a == HUGE_VAL) { // vertical
2547 *closest = NR::Point(0, (*p)[NR::Y]);
2548 } else {
2549 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2550 (*closest)[NR::Y] = a * (*closest)[NR::X];
2551 }
2552 }
2554 /**
2555 * Distance from the point to a line given by its angle.
2556 * \param p A point.
2557 * \param a Angle of the line; it is assumed to go through coordinate origin.
2558 */
2559 static double point_line_distance(NR::Point *p, double a)
2560 {
2561 NR::Point c;
2562 point_line_closest(p, a, &c);
2563 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]));
2564 }
2566 /**
2567 * Callback for node "request" signal.
2568 * \todo fixme: This goes to "moved" event? (lauris)
2569 */
2570 static gboolean
2571 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2572 {
2573 double yn, xn, yp, xp;
2574 double an, ap, na, pa;
2575 double d_an, d_ap, d_na, d_pa;
2576 gboolean collinear = FALSE;
2577 NR::Point c;
2578 NR::Point pr;
2580 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2582 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2583 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2585 NR::Point mouse = (*p);
2587 if (!n->dragging_out) {
2588 // This is the first drag-out event; find out which handle to drag out
2589 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2590 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2592 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2593 return FALSE;
2595 Inkscape::NodePath::NodeSide *opposite;
2596 if (appr_p > appr_n) { // closer to p
2597 n->dragging_out = &n->p;
2598 opposite = &n->n;
2599 n->code = NR_CURVETO;
2600 } else if (appr_p < appr_n) { // closer to n
2601 n->dragging_out = &n->n;
2602 opposite = &n->p;
2603 n->n.other->code = NR_CURVETO;
2604 } else { // p and n nodes are the same
2605 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2606 n->dragging_out = &n->p;
2607 opposite = &n->n;
2608 n->code = NR_CURVETO;
2609 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2610 n->dragging_out = &n->n;
2611 opposite = &n->p;
2612 n->n.other->code = NR_CURVETO;
2613 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2614 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);
2615 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);
2616 if (appr_other_p > appr_other_n) { // closer to other's p handle
2617 n->dragging_out = &n->n;
2618 opposite = &n->p;
2619 n->n.other->code = NR_CURVETO;
2620 } else { // closer to other's n handle
2621 n->dragging_out = &n->p;
2622 opposite = &n->n;
2623 n->code = NR_CURVETO;
2624 }
2625 }
2626 }
2628 // if there's another handle, make sure the one we drag out starts parallel to it
2629 if (opposite->pos != n->pos) {
2630 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2631 }
2633 // knots might not be created yet!
2634 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2635 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2636 }
2638 // pass this on to the handle-moved callback
2639 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2640 sp_node_update_handles(n);
2641 return TRUE;
2642 }
2644 if (state & GDK_CONTROL_MASK) { // constrained motion
2646 // calculate relative distances of handles
2647 // n handle:
2648 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2649 xn = n->n.pos[NR::X] - n->pos[NR::X];
2650 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2651 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2652 if (n->n.other) { // if there is the next point
2653 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2654 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2655 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2656 }
2657 }
2658 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2659 if (yn < 0) { xn = -xn; yn = -yn; }
2661 // p handle:
2662 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2663 xp = n->p.pos[NR::X] - n->pos[NR::X];
2664 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2665 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2666 if (n->p.other) {
2667 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2668 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2669 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2670 }
2671 }
2672 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2673 if (yp < 0) { xp = -xp; yp = -yp; }
2675 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2676 // sliding on handles, only if at least one of the handles is non-vertical
2677 // (otherwise it's the same as ctrl+drag anyway)
2679 // calculate angles of the handles
2680 if (xn == 0) {
2681 if (yn == 0) { // no handle, consider it the continuation of the other one
2682 an = 0;
2683 collinear = TRUE;
2684 }
2685 else an = 0; // vertical; set the angle to horizontal
2686 } else an = yn/xn;
2688 if (xp == 0) {
2689 if (yp == 0) { // no handle, consider it the continuation of the other one
2690 ap = an;
2691 }
2692 else ap = 0; // vertical; set the angle to horizontal
2693 } else ap = yp/xp;
2695 if (collinear) an = ap;
2697 // angles of the perpendiculars; HUGE_VAL means vertical
2698 if (an == 0) na = HUGE_VAL; else na = -1/an;
2699 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2701 //g_print("an %g ap %g\n", an, ap);
2703 // mouse point relative to the node's original pos
2704 pr = (*p) - n->origin;
2706 // distances to the four lines (two handles and two perpendiculars)
2707 d_an = point_line_distance(&pr, an);
2708 d_na = point_line_distance(&pr, na);
2709 d_ap = point_line_distance(&pr, ap);
2710 d_pa = point_line_distance(&pr, pa);
2712 // find out which line is the closest, save its closest point in c
2713 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2714 point_line_closest(&pr, an, &c);
2715 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2716 point_line_closest(&pr, ap, &c);
2717 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2718 point_line_closest(&pr, na, &c);
2719 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2720 point_line_closest(&pr, pa, &c);
2721 }
2723 // move the node to the closest point
2724 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2725 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2726 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2728 } else { // constraining to hor/vert
2730 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2731 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2732 } else { // snap to vert
2733 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2734 }
2735 }
2736 } else { // move freely
2737 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2738 (*p)[NR::X] - n->pos[NR::X],
2739 (*p)[NR::Y] - n->pos[NR::Y],
2740 (state & GDK_SHIFT_MASK) == 0);
2741 }
2743 n->subpath->nodepath->desktop->scroll_to_point(p);
2745 return TRUE;
2746 }
2748 /**
2749 * Node handle clicked callback.
2750 */
2751 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2752 {
2753 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2755 if (state & GDK_CONTROL_MASK) { // "delete" handle
2756 if (n->p.knot == knot) {
2757 n->p.pos = n->pos;
2758 } else if (n->n.knot == knot) {
2759 n->n.pos = n->pos;
2760 }
2761 sp_node_update_handles(n);
2762 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2763 sp_nodepath_update_repr(nodepath);
2764 sp_nodepath_update_statusbar(nodepath);
2766 } else { // just select or add to selection, depending in Shift
2767 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2768 }
2769 }
2771 /**
2772 * Node handle grabbed callback.
2773 */
2774 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2775 {
2776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2778 if (!n->selected) {
2779 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2780 }
2782 // remember the origin point of the handle
2783 if (n->p.knot == knot) {
2784 n->p.origin = n->p.pos - n->pos;
2785 } else if (n->n.knot == knot) {
2786 n->n.origin = n->n.pos - n->pos;
2787 } else {
2788 g_assert_not_reached();
2789 }
2791 }
2793 /**
2794 * Node handle ungrabbed callback.
2795 */
2796 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2797 {
2798 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2800 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2801 if (n->p.knot == knot) {
2802 n->p.origin.a = 0;
2803 sp_knot_set_position(knot, &n->p.pos, state);
2804 } else if (n->n.knot == knot) {
2805 n->n.origin.a = 0;
2806 sp_knot_set_position(knot, &n->n.pos, state);
2807 } else {
2808 g_assert_not_reached();
2809 }
2811 sp_nodepath_update_repr(n->subpath->nodepath);
2812 }
2814 /**
2815 * Node handle "request" signal callback.
2816 */
2817 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2818 {
2819 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2821 Inkscape::NodePath::NodeSide *me, *opposite;
2822 gint which;
2823 if (n->p.knot == knot) {
2824 me = &n->p;
2825 opposite = &n->n;
2826 which = -1;
2827 } else if (n->n.knot == knot) {
2828 me = &n->n;
2829 opposite = &n->p;
2830 which = 1;
2831 } else {
2832 me = opposite = NULL;
2833 which = 0;
2834 g_assert_not_reached();
2835 }
2837 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2839 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
2841 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2842 /* We are smooth node adjacent with line */
2843 NR::Point const delta = *p - n->pos;
2844 NR::Coord const len = NR::L2(delta);
2845 Inkscape::NodePath::Node *othernode = opposite->other;
2846 NR::Point const ndelta = n->pos - othernode->pos;
2847 NR::Coord const linelen = NR::L2(ndelta);
2848 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2849 NR::Coord const scal = dot(delta, ndelta) / linelen;
2850 (*p) = n->pos + (scal / linelen) * ndelta;
2851 }
2852 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
2853 } else {
2854 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2855 }
2857 sp_node_adjust_handle(n, -which);
2859 return FALSE;
2860 }
2862 /**
2863 * Node handle moved callback.
2864 */
2865 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2866 {
2867 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2869 Inkscape::NodePath::NodeSide *me;
2870 Inkscape::NodePath::NodeSide *other;
2871 if (n->p.knot == knot) {
2872 me = &n->p;
2873 other = &n->n;
2874 } else if (n->n.knot == knot) {
2875 me = &n->n;
2876 other = &n->p;
2877 } else {
2878 me = NULL;
2879 other = NULL;
2880 g_assert_not_reached();
2881 }
2883 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2884 Radial rme(me->pos - n->pos);
2885 Radial rother(other->pos - n->pos);
2886 Radial rnew(*p - n->pos);
2888 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2889 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2890 /* 0 interpreted as "no snapping". */
2892 // The closest PI/snaps angle, starting from zero.
2893 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2894 if (me->origin.a == HUGE_VAL) {
2895 // ortho doesn't exist: original handle was zero length.
2896 rnew.a = a_snapped;
2897 } else {
2898 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2899 * its opposite and perpendiculars). */
2900 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2902 // Snap to the closest.
2903 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2904 ? a_snapped
2905 : a_ortho );
2906 }
2907 }
2909 if (state & GDK_MOD1_MASK) {
2910 // lock handle length
2911 rnew.r = me->origin.r;
2912 }
2914 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2915 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2916 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2917 rother.a += rnew.a - rme.a;
2918 other->pos = NR::Point(rother) + n->pos;
2919 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2920 sp_knot_set_position(other->knot, &other->pos, 0);
2921 }
2923 me->pos = NR::Point(rnew) + n->pos;
2924 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2926 // this is what sp_knot_set_position does, but without emitting the signal:
2927 // we cannot emit a "moved" signal because we're now processing it
2928 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2930 knot->desktop->set_coordinate_status(me->pos);
2932 update_object(n->subpath->nodepath);
2934 /* status text */
2935 SPDesktop *desktop = n->subpath->nodepath->desktop;
2936 if (!desktop) return;
2937 SPEventContext *ec = desktop->event_context;
2938 if (!ec) return;
2939 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2940 if (!mc) return;
2942 double degrees = 180 / M_PI * rnew.a;
2943 if (degrees > 180) degrees -= 360;
2944 if (degrees < -180) degrees += 360;
2945 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2946 degrees = angle_to_compass (degrees);
2948 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2950 mc->setF(Inkscape::NORMAL_MESSAGE,
2951 _("<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);
2953 g_string_free(length, TRUE);
2954 }
2956 /**
2957 * Node handle event callback.
2958 */
2959 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2960 {
2961 gboolean ret = FALSE;
2962 switch (event->type) {
2963 case GDK_KEY_PRESS:
2964 switch (get_group0_keyval (&event->key)) {
2965 case GDK_space:
2966 if (event->key.state & GDK_BUTTON1_MASK) {
2967 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2968 stamp_repr(nodepath);
2969 ret = TRUE;
2970 }
2971 break;
2972 default:
2973 break;
2974 }
2975 break;
2976 default:
2977 break;
2978 }
2980 return ret;
2981 }
2983 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2984 Radial &rme, Radial &rother, gboolean const both)
2985 {
2986 rme.a += angle;
2987 if ( both
2988 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2989 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2990 {
2991 rother.a += angle;
2992 }
2993 }
2995 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2996 Radial &rme, Radial &rother, gboolean const both)
2997 {
2998 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3000 gdouble r;
3001 if ( both
3002 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3003 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3004 {
3005 r = MAX(rme.r, rother.r);
3006 } else {
3007 r = rme.r;
3008 }
3010 gdouble const weird_angle = atan2(norm_angle, r);
3011 /* Bulia says norm_angle is just the visible distance that the
3012 * object's end must travel on the screen. Left as 'angle' for want of
3013 * a better name.*/
3015 rme.a += weird_angle;
3016 if ( both
3017 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3018 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3019 {
3020 rother.a += weird_angle;
3021 }
3022 }
3024 /**
3025 * Rotate one node.
3026 */
3027 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3028 {
3029 Inkscape::NodePath::NodeSide *me, *other;
3030 bool both = false;
3032 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3033 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3035 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3036 me = &(n->p);
3037 other = &(n->n);
3038 } else if (!n->p.other) {
3039 me = &(n->n);
3040 other = &(n->p);
3041 } else {
3042 if (which > 0) { // right handle
3043 if (xn > xp) {
3044 me = &(n->n);
3045 other = &(n->p);
3046 } else {
3047 me = &(n->p);
3048 other = &(n->n);
3049 }
3050 } else if (which < 0){ // left handle
3051 if (xn <= xp) {
3052 me = &(n->n);
3053 other = &(n->p);
3054 } else {
3055 me = &(n->p);
3056 other = &(n->n);
3057 }
3058 } else { // both handles
3059 me = &(n->n);
3060 other = &(n->p);
3061 both = true;
3062 }
3063 }
3065 Radial rme(me->pos - n->pos);
3066 Radial rother(other->pos - n->pos);
3068 if (screen) {
3069 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3070 } else {
3071 node_rotate_one_internal (*n, angle, rme, rother, both);
3072 }
3074 me->pos = n->pos + NR::Point(rme);
3076 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3077 other->pos = n->pos + NR::Point(rother);
3078 }
3080 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3081 // so here we just move all the knots without emitting move signals, for speed
3082 sp_node_update_handles(n, false);
3083 }
3085 /**
3086 * Rotate selected nodes.
3087 */
3088 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3089 {
3090 if (!nodepath || !nodepath->selected) return;
3092 if (g_list_length(nodepath->selected) == 1) {
3093 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3094 node_rotate_one (n, angle, which, screen);
3095 } else {
3096 // rotate as an object:
3098 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3099 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3100 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3101 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3102 box.expandTo (n->pos); // contain all selected nodes
3103 }
3105 gdouble rot;
3106 if (screen) {
3107 gdouble const zoom = nodepath->desktop->current_zoom();
3108 gdouble const zmove = angle / zoom;
3109 gdouble const r = NR::L2(box.max() - box.midpoint());
3110 rot = atan2(zmove, r);
3111 } else {
3112 rot = angle;
3113 }
3115 NR::Matrix t =
3116 NR::Matrix (NR::translate(-box.midpoint())) *
3117 NR::Matrix (NR::rotate(rot)) *
3118 NR::Matrix (NR::translate(box.midpoint()));
3120 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3121 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3122 n->pos *= t;
3123 n->n.pos *= t;
3124 n->p.pos *= t;
3125 sp_node_update_handles(n, false);
3126 }
3127 }
3129 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3130 }
3132 /**
3133 * Scale one node.
3134 */
3135 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3136 {
3137 bool both = false;
3138 Inkscape::NodePath::NodeSide *me, *other;
3140 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3141 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3143 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3144 me = &(n->p);
3145 other = &(n->n);
3146 n->code = NR_CURVETO;
3147 } else if (!n->p.other) {
3148 me = &(n->n);
3149 other = &(n->p);
3150 if (n->n.other)
3151 n->n.other->code = NR_CURVETO;
3152 } else {
3153 if (which > 0) { // right handle
3154 if (xn > xp) {
3155 me = &(n->n);
3156 other = &(n->p);
3157 if (n->n.other)
3158 n->n.other->code = NR_CURVETO;
3159 } else {
3160 me = &(n->p);
3161 other = &(n->n);
3162 n->code = NR_CURVETO;
3163 }
3164 } else if (which < 0){ // left handle
3165 if (xn <= xp) {
3166 me = &(n->n);
3167 other = &(n->p);
3168 if (n->n.other)
3169 n->n.other->code = NR_CURVETO;
3170 } else {
3171 me = &(n->p);
3172 other = &(n->n);
3173 n->code = NR_CURVETO;
3174 }
3175 } else { // both handles
3176 me = &(n->n);
3177 other = &(n->p);
3178 both = true;
3179 n->code = NR_CURVETO;
3180 if (n->n.other)
3181 n->n.other->code = NR_CURVETO;
3182 }
3183 }
3185 Radial rme(me->pos - n->pos);
3186 Radial rother(other->pos - n->pos);
3188 rme.r += grow;
3189 if (rme.r < 0) rme.r = 0;
3190 if (rme.a == HUGE_VAL) {
3191 if (me->other) { // if direction is unknown, initialize it towards the next node
3192 Radial rme_next(me->other->pos - n->pos);
3193 rme.a = rme_next.a;
3194 } else { // if there's no next, initialize to 0
3195 rme.a = 0;
3196 }
3197 }
3198 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3199 rother.r += grow;
3200 if (rother.r < 0) rother.r = 0;
3201 if (rother.a == HUGE_VAL) {
3202 rother.a = rme.a + M_PI;
3203 }
3204 }
3206 me->pos = n->pos + NR::Point(rme);
3208 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3209 other->pos = n->pos + NR::Point(rother);
3210 }
3212 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3213 // so here we just move all the knots without emitting move signals, for speed
3214 sp_node_update_handles(n, false);
3215 }
3217 /**
3218 * Scale selected nodes.
3219 */
3220 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3221 {
3222 if (!nodepath || !nodepath->selected) return;
3224 if (g_list_length(nodepath->selected) == 1) {
3225 // scale handles of the single selected node
3226 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3227 node_scale_one (n, grow, which);
3228 } else {
3229 // scale nodes as an "object":
3231 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3232 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3233 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3234 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3235 box.expandTo (n->pos); // contain all selected nodes
3236 }
3238 double scale = (box.maxExtent() + grow)/box.maxExtent();
3240 NR::Matrix t =
3241 NR::Matrix (NR::translate(-box.midpoint())) *
3242 NR::Matrix (NR::scale(scale, scale)) *
3243 NR::Matrix (NR::translate(box.midpoint()));
3245 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3246 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3247 n->pos *= t;
3248 n->n.pos *= t;
3249 n->p.pos *= t;
3250 sp_node_update_handles(n, false);
3251 }
3252 }
3254 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3255 }
3257 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3258 {
3259 if (!nodepath) return;
3260 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3261 }
3263 /**
3264 * Flip selected nodes horizontally/vertically.
3265 */
3266 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3267 {
3268 if (!nodepath || !nodepath->selected) return;
3270 if (g_list_length(nodepath->selected) == 1) {
3271 // flip handles of the single selected node
3272 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3273 double temp = n->p.pos[axis];
3274 n->p.pos[axis] = n->n.pos[axis];
3275 n->n.pos[axis] = temp;
3276 sp_node_update_handles(n, false);
3277 } else {
3278 // scale nodes as an "object":
3280 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3281 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3282 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3283 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3284 box.expandTo (n->pos); // contain all selected nodes
3285 }
3287 NR::Matrix t =
3288 NR::Matrix (NR::translate(-box.midpoint())) *
3289 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3290 NR::Matrix (NR::translate(box.midpoint()));
3292 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3293 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3294 n->pos *= t;
3295 n->n.pos *= t;
3296 n->p.pos *= t;
3297 sp_node_update_handles(n, false);
3298 }
3299 }
3301 sp_nodepath_update_repr(nodepath);
3302 }
3304 //-----------------------------------------------
3305 /**
3306 * Return new subpath under given nodepath.
3307 */
3308 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3309 {
3310 g_assert(nodepath);
3311 g_assert(nodepath->desktop);
3313 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3315 s->nodepath = nodepath;
3316 s->closed = FALSE;
3317 s->nodes = NULL;
3318 s->first = NULL;
3319 s->last = NULL;
3321 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3322 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3323 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3325 return s;
3326 }
3328 /**
3329 * Destroy nodes in subpath, then subpath itself.
3330 */
3331 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3332 {
3333 g_assert(subpath);
3334 g_assert(subpath->nodepath);
3335 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3337 while (subpath->nodes) {
3338 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3339 }
3341 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3343 g_free(subpath);
3344 }
3346 /**
3347 * Link head to tail in subpath.
3348 */
3349 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3350 {
3351 g_assert(!sp->closed);
3352 g_assert(sp->last != sp->first);
3353 g_assert(sp->first->code == NR_MOVETO);
3355 sp->closed = TRUE;
3357 //Link the head to the tail
3358 sp->first->p.other = sp->last;
3359 sp->last->n.other = sp->first;
3360 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3361 sp->first = sp->last;
3363 //Remove the extra end node
3364 sp_nodepath_node_destroy(sp->last->n.other);
3365 }
3367 /**
3368 * Open closed (loopy) subpath at node.
3369 */
3370 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3371 {
3372 g_assert(sp->closed);
3373 g_assert(n->subpath == sp);
3374 g_assert(sp->first == sp->last);
3376 /* We create new startpoint, current node will become last one */
3378 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3379 &n->pos, &n->pos, &n->n.pos);
3382 sp->closed = FALSE;
3384 //Unlink to make a head and tail
3385 sp->first = new_path;
3386 sp->last = n;
3387 n->n.other = NULL;
3388 new_path->p.other = NULL;
3389 }
3391 /**
3392 * Returns area in triangle given by points; may be negative.
3393 */
3394 inline double
3395 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3396 {
3397 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]);
3398 }
3400 /**
3401 * Return new node in subpath with given properties.
3402 * \param pos Position of node.
3403 * \param ppos Handle position in previous direction
3404 * \param npos Handle position in previous direction
3405 */
3406 Inkscape::NodePath::Node *
3407 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)
3408 {
3409 g_assert(sp);
3410 g_assert(sp->nodepath);
3411 g_assert(sp->nodepath->desktop);
3413 if (nodechunk == NULL)
3414 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3416 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3418 n->subpath = sp;
3420 if (type != Inkscape::NodePath::NODE_NONE) {
3421 // use the type from sodipodi:nodetypes
3422 n->type = type;
3423 } else {
3424 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3425 // points are (almost) collinear
3426 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3427 // endnode, or a node with a retracted handle
3428 n->type = Inkscape::NodePath::NODE_CUSP;
3429 } else {
3430 n->type = Inkscape::NodePath::NODE_SMOOTH;
3431 }
3432 } else {
3433 n->type = Inkscape::NodePath::NODE_CUSP;
3434 }
3435 }
3437 n->code = code;
3438 n->selected = FALSE;
3439 n->pos = *pos;
3440 n->p.pos = *ppos;
3441 n->n.pos = *npos;
3443 n->dragging_out = NULL;
3445 Inkscape::NodePath::Node *prev;
3446 if (next) {
3447 //g_assert(g_list_find(sp->nodes, next));
3448 prev = next->p.other;
3449 } else {
3450 prev = sp->last;
3451 }
3453 if (prev)
3454 prev->n.other = n;
3455 else
3456 sp->first = n;
3458 if (next)
3459 next->p.other = n;
3460 else
3461 sp->last = n;
3463 n->p.other = prev;
3464 n->n.other = next;
3466 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"));
3467 sp_knot_set_position(n->knot, pos, 0);
3469 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3470 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3471 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3472 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3473 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3474 sp_knot_update_ctrl(n->knot);
3476 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3477 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3478 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3479 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3480 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3481 sp_knot_show(n->knot);
3483 // We only create handle knots and lines on demand
3484 n->p.knot = NULL;
3485 n->p.line = NULL;
3486 n->n.knot = NULL;
3487 n->n.line = NULL;
3489 sp->nodes = g_list_prepend(sp->nodes, n);
3491 return n;
3492 }
3494 /**
3495 * Destroy node and its knots, link neighbors in subpath.
3496 */
3497 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3498 {
3499 g_assert(node);
3500 g_assert(node->subpath);
3501 g_assert(SP_IS_KNOT(node->knot));
3503 Inkscape::NodePath::SubPath *sp = node->subpath;
3505 if (node->selected) { // first, deselect
3506 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3507 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3508 }
3510 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3512 g_object_unref(G_OBJECT(node->knot));
3513 if (node->p.knot)
3514 g_object_unref(G_OBJECT(node->p.knot));
3515 if (node->n.knot)
3516 g_object_unref(G_OBJECT(node->n.knot));
3518 if (node->p.line)
3519 gtk_object_destroy(GTK_OBJECT(node->p.line));
3520 if (node->n.line)
3521 gtk_object_destroy(GTK_OBJECT(node->n.line));
3523 if (sp->nodes) { // there are others nodes on the subpath
3524 if (sp->closed) {
3525 if (sp->first == node) {
3526 g_assert(sp->last == node);
3527 sp->first = node->n.other;
3528 sp->last = sp->first;
3529 }
3530 node->p.other->n.other = node->n.other;
3531 node->n.other->p.other = node->p.other;
3532 } else {
3533 if (sp->first == node) {
3534 sp->first = node->n.other;
3535 sp->first->code = NR_MOVETO;
3536 }
3537 if (sp->last == node) sp->last = node->p.other;
3538 if (node->p.other) node->p.other->n.other = node->n.other;
3539 if (node->n.other) node->n.other->p.other = node->p.other;
3540 }
3541 } else { // this was the last node on subpath
3542 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3543 }
3545 g_mem_chunk_free(nodechunk, node);
3546 }
3548 /**
3549 * Returns one of the node's two sides.
3550 * \param which Indicates which side.
3551 * \return Pointer to previous node side if which==-1, next if which==1.
3552 */
3553 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3554 {
3555 g_assert(node);
3557 switch (which) {
3558 case -1:
3559 return &node->p;
3560 case 1:
3561 return &node->n;
3562 default:
3563 break;
3564 }
3566 g_assert_not_reached();
3568 return NULL;
3569 }
3571 /**
3572 * Return the other side of the node, given one of its sides.
3573 */
3574 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3575 {
3576 g_assert(node);
3578 if (me == &node->p) return &node->n;
3579 if (me == &node->n) return &node->p;
3581 g_assert_not_reached();
3583 return NULL;
3584 }
3586 /**
3587 * Return NRPathcode on the given side of the node.
3588 */
3589 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3590 {
3591 g_assert(node);
3593 if (me == &node->p) {
3594 if (node->p.other) return (NRPathcode)node->code;
3595 return NR_MOVETO;
3596 }
3598 if (me == &node->n) {
3599 if (node->n.other) return (NRPathcode)node->n.other->code;
3600 return NR_MOVETO;
3601 }
3603 g_assert_not_reached();
3605 return NR_END;
3606 }
3608 /**
3609 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3610 */
3611 Inkscape::NodePath::Node *
3612 sp_nodepath_get_node_by_index(int index)
3613 {
3614 Inkscape::NodePath::Node *e = NULL;
3616 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3617 if (!nodepath) {
3618 return e;
3619 }
3621 //find segment
3622 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3624 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3625 int n = g_list_length(sp->nodes);
3626 if (sp->closed) {
3627 n++;
3628 }
3630 //if the piece belongs to this subpath grab it
3631 //otherwise move onto the next subpath
3632 if (index < n) {
3633 e = sp->first;
3634 for (int i = 0; i < index; ++i) {
3635 e = e->n.other;
3636 }
3637 break;
3638 } else {
3639 if (sp->closed) {
3640 index -= (n+1);
3641 } else {
3642 index -= n;
3643 }
3644 }
3645 }
3647 return e;
3648 }
3650 /**
3651 * Returns plain text meaning of node type.
3652 */
3653 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3654 {
3655 unsigned retracted = 0;
3656 bool endnode = false;
3658 for (int which = -1; which <= 1; which += 2) {
3659 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3660 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3661 retracted ++;
3662 if (!side->other)
3663 endnode = true;
3664 }
3666 if (retracted == 0) {
3667 if (endnode) {
3668 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3669 return _("end node");
3670 } else {
3671 switch (node->type) {
3672 case Inkscape::NodePath::NODE_CUSP:
3673 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3674 return _("cusp");
3675 case Inkscape::NodePath::NODE_SMOOTH:
3676 // TRANSLATORS: "smooth" is an adjective here
3677 return _("smooth");
3678 case Inkscape::NodePath::NODE_SYMM:
3679 return _("symmetric");
3680 }
3681 }
3682 } else if (retracted == 1) {
3683 if (endnode) {
3684 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3685 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3686 } else {
3687 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3688 }
3689 } else {
3690 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3691 }
3693 return NULL;
3694 }
3696 /**
3697 * Handles content of statusbar as long as node tool is active.
3698 */
3699 void
3700 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3701 {
3702 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3703 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3705 gint total = 0;
3706 gint selected = 0;
3707 SPDesktop *desktop = NULL;
3709 if (nodepath) {
3710 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3711 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3712 total += g_list_length(subpath->nodes);
3713 }
3714 selected = g_list_length(nodepath->selected);
3715 desktop = nodepath->desktop;
3716 } else {
3717 desktop = SP_ACTIVE_DESKTOP;
3718 }
3720 SPEventContext *ec = desktop->event_context;
3721 if (!ec) return;
3722 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3723 if (!mc) return;
3725 if (selected == 0) {
3726 Inkscape::Selection *sel = desktop->selection;
3727 if (!sel || sel->isEmpty()) {
3728 mc->setF(Inkscape::NORMAL_MESSAGE,
3729 _("Select a single object to edit its nodes or handles."));
3730 } else {
3731 if (nodepath) {
3732 mc->setF(Inkscape::NORMAL_MESSAGE,
3733 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.",
3734 "<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.",
3735 total),
3736 total);
3737 } else {
3738 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3739 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3740 } else {
3741 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3742 }
3743 }
3744 }
3745 } else if (nodepath && selected == 1) {
3746 mc->setF(Inkscape::NORMAL_MESSAGE,
3747 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3748 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3749 total),
3750 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3751 } else {
3752 mc->setF(Inkscape::NORMAL_MESSAGE,
3753 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3754 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3755 total),
3756 selected, total, when_selected);
3757 }
3758 }
3761 /*
3762 Local Variables:
3763 mode:c++
3764 c-file-style:"stroustrup"
3765 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3766 indent-tabs-mode:nil
3767 fill-column:99
3768 End:
3769 */
3770 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :