1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * This code is in public domain
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include "libnr/nr-matrix-ops.h"
41 #include "splivarot.h"
42 #include "svg/svg.h"
43 #include "display/bezier-utils.h"
44 #include <vector>
45 #include <algorithm>
47 class NR::Matrix;
49 /// \todo
50 /// evil evil evil. FIXME: conflict of two different Path classes!
51 /// There is a conflict in the namespace between two classes named Path.
52 /// #include "sp-flowtext.h"
53 /// #include "sp-flowregion.h"
55 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
56 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
57 GType sp_flowregion_get_type (void);
58 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
59 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
60 GType sp_flowtext_get_type (void);
61 // end evil workaround
63 #include "helper/stlport.h"
66 /// \todo fixme: Implement these via preferences */
68 #define NODE_FILL 0xbfbfbf00
69 #define NODE_STROKE 0x000000ff
70 #define NODE_FILL_HI 0xff000000
71 #define NODE_STROKE_HI 0x000000ff
72 #define NODE_FILL_SEL 0x0000ffff
73 #define NODE_STROKE_SEL 0x000000ff
74 #define NODE_FILL_SEL_HI 0xff000000
75 #define NODE_STROKE_SEL_HI 0x000000ff
76 #define KNOT_FILL 0xffffffff
77 #define KNOT_STROKE 0x000000ff
78 #define KNOT_FILL_HI 0xff000000
79 #define KNOT_STROKE_HI 0x000000ff
81 static GMemChunk *nodechunk = NULL;
83 /* Creation from object */
85 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
86 static gchar *parse_nodetypes(gchar const *types, gint length);
88 /* Object updating */
90 static void stamp_repr(Inkscape::NodePath::Path *np);
91 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
92 static gchar *create_typestr(Inkscape::NodePath::Path *np);
94 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
96 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
98 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
100 /* Adjust handle placement, if the node or the other handle is moved */
101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
102 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
104 /* Node event callbacks */
105 static void node_clicked(SPKnot *knot, guint state, gpointer data);
106 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
107 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
108 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
110 /* Handle event callbacks */
111 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
112 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
114 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
115 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
118 /* Constructors and destructors */
120 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
121 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
122 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
123 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
124 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
125 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
126 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
128 /* Helpers */
130 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
131 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 // active_node indicates mouseover node
135 static Inkscape::NodePath::Node *active_node = NULL;
137 /**
138 * \brief Creates new nodepath from item
139 */
140 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
141 {
142 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
144 /** \todo
145 * FIXME: remove this. We don't want to edit paths inside flowtext.
146 * Instead we will build our flowtext with cloned paths, so that the
147 * real paths are outside the flowtext and thus editable as usual.
148 */
149 if (SP_IS_FLOWTEXT(item)) {
150 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
151 if SP_IS_FLOWREGION(child) {
152 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
153 if (grandchild && SP_IS_PATH(grandchild)) {
154 item = SP_ITEM(grandchild);
155 break;
156 }
157 }
158 }
159 }
161 if (!SP_IS_PATH(item))
162 return NULL;
163 SPPath *path = SP_PATH(item);
164 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
165 if (curve == NULL)
166 return NULL;
168 NArtBpath *bpath = sp_curve_first_bpath(curve);
169 gint length = curve->end;
170 if (length == 0)
171 return NULL; // prevent crash for one-node paths
173 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
174 gchar *typestr = parse_nodetypes(nodetypes, length);
176 //Create new nodepath
177 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
178 if (!np)
179 return NULL;
181 // Set defaults
182 np->desktop = desktop;
183 np->path = path;
184 np->subpaths = NULL;
185 np->selected = NULL;
186 np->nodeContext = NULL; //Let the context that makes this set it
187 np->livarot_path = NULL;
188 np->local_change = 0;
190 // we need to update item's transform from the repr here,
191 // because they may be out of sync when we respond
192 // to a change in repr by regenerating nodepath --bb
193 sp_object_read_attr(SP_OBJECT(item), "transform");
195 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
196 np->d2i = np->i2d.inverse();
197 np->repr = repr;
199 // create the subpath(s) from the bpath
200 NArtBpath *b = bpath;
201 while (b->code != NR_END) {
202 b = subpath_from_bpath(np, b, typestr + (b - bpath));
203 }
205 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
206 np->subpaths = g_list_reverse(np->subpaths);
208 g_free(typestr);
209 sp_curve_unref(curve);
211 // create the livarot representation from the same item
212 np->livarot_path = Path_for_item(item, true, true);
213 if (np->livarot_path)
214 np->livarot_path->ConvertWithBackData(0.01);
216 return np;
217 }
219 /**
220 * Destroys nodepath's subpaths, then itself, also tell context about it.
221 */
222 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
224 if (!np) //soft fail, like delete
225 return;
227 while (np->subpaths) {
228 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
229 }
231 //Inform the context that made me, if any, that I am gone.
232 if (np->nodeContext)
233 np->nodeContext->nodepath = NULL;
235 g_assert(!np->selected);
237 if (np->livarot_path) {
238 delete np->livarot_path;
239 np->livarot_path = NULL;
240 }
242 np->desktop = NULL;
244 g_free(np);
245 }
248 /**
249 * Return the node count of a given NodeSubPath.
250 */
251 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
252 {
253 if (!subpath)
254 return 0;
255 gint nodeCount = g_list_length(subpath->nodes);
256 return nodeCount;
257 }
259 /**
260 * Return the node count of a given NodePath.
261 */
262 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
263 {
264 if (!np)
265 return 0;
266 gint nodeCount = 0;
267 for (GList *item = np->subpaths ; item ; item=item->next) {
268 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
269 nodeCount += g_list_length(subpath->nodes);
270 }
271 return nodeCount;
272 }
275 /**
276 * Clean up a nodepath after editing.
277 *
278 * Currently we are deleting trivial subpaths.
279 */
280 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
281 {
282 GList *badSubPaths = NULL;
284 //Check all subpaths to be >=2 nodes
285 for (GList *l = nodepath->subpaths; l ; l=l->next) {
286 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
287 if (sp_nodepath_subpath_get_node_count(sp)<2)
288 badSubPaths = g_list_append(badSubPaths, sp);
289 }
291 //Delete them. This second step is because sp_nodepath_subpath_destroy()
292 //also removes the subpath from nodepath->subpaths
293 for (GList *l = badSubPaths; l ; l=l->next) {
294 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
295 sp_nodepath_subpath_destroy(sp);
296 }
298 g_list_free(badSubPaths);
299 }
301 /**
302 * Create new nodepath from b, make it subpath of np.
303 * \param t The node type.
304 * \todo Fixme: t should be a proper type, rather than gchar
305 */
306 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
307 {
308 NR::Point ppos, pos, npos;
310 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
312 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
313 bool const closed = (b->code == NR_MOVETO);
315 pos = NR::Point(b->x3, b->y3) * np->i2d;
316 if (b[1].code == NR_CURVETO) {
317 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
318 } else {
319 npos = pos;
320 }
321 Inkscape::NodePath::Node *n;
322 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
323 g_assert(sp->first == n);
324 g_assert(sp->last == n);
326 b++;
327 t++;
328 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
329 pos = NR::Point(b->x3, b->y3) * np->i2d;
330 if (b->code == NR_CURVETO) {
331 ppos = NR::Point(b->x2, b->y2) * np->i2d;
332 } else {
333 ppos = pos;
334 }
335 if (b[1].code == NR_CURVETO) {
336 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
337 } else {
338 npos = pos;
339 }
340 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
341 b++;
342 t++;
343 }
345 if (closed) sp_nodepath_subpath_close(sp);
347 return b;
348 }
350 /**
351 * Convert from sodipodi:nodetypes to new style type string.
352 */
353 static gchar *parse_nodetypes(gchar const *types, gint length)
354 {
355 g_assert(length > 0);
357 gchar *typestr = g_new(gchar, length + 1);
359 gint pos = 0;
361 if (types) {
362 for (gint i = 0; types[i] && ( i < length ); i++) {
363 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
364 if (types[i] != '\0') {
365 switch (types[i]) {
366 case 's':
367 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
368 break;
369 case 'z':
370 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
371 break;
372 case 'c':
373 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
374 break;
375 default:
376 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
377 break;
378 }
379 }
380 }
381 }
383 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
385 return typestr;
386 }
388 /**
389 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
390 * updated but repr is not (for speed). Used during curve and node drag.
391 */
392 static void update_object(Inkscape::NodePath::Path *np)
393 {
394 g_assert(np);
396 SPCurve *curve = create_curve(np);
398 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
400 sp_curve_unref(curve);
401 }
403 /**
404 * Update XML path node with data from path object.
405 */
406 static void update_repr_internal(Inkscape::NodePath::Path *np)
407 {
408 g_assert(np);
410 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
412 SPCurve *curve = create_curve(np);
413 gchar *typestr = create_typestr(np);
414 gchar *svgpath = sp_svg_write_path(curve->bpath);
416 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
417 np->local_change++;
418 repr->setAttribute("d", svgpath);
419 }
421 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
422 np->local_change++;
423 repr->setAttribute("sodipodi:nodetypes", typestr);
424 }
426 g_free(svgpath);
427 g_free(typestr);
428 sp_curve_unref(curve);
429 }
431 /**
432 * Update XML path node with data from path object, commit changes forever.
433 */
434 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
435 {
436 update_repr_internal(np);
437 sp_document_done(sp_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(curve->bpath);
491 new_repr->setAttribute("d", svgpath);
492 new_repr->setAttribute("sodipodi:nodetypes", typestr);
494 // add the new repr to the parent
495 parent->appendChild(new_repr);
496 // move to the saved position
497 new_repr->setPosition(pos > 0 ? pos : 0);
499 sp_document_done(sp_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[2] = { NR_HUGE, NR_HUGE };
925 NR::Point delta(dx, dy);
926 NR::Point best_pt = delta;
928 if (snap) {
929 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
930 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
931 NR::Point p = n->pos + delta;
932 for (int dim = 0; dim < 2; dim++) {
933 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
934 Inkscape::Snapper::SNAP_POINT, p,
935 NR::Dim2(dim), nodepath->path);
936 if (dist < best[dim]) {
937 best[dim] = dist;
938 best_pt[dim] = p[dim] - n->pos[dim];
939 }
940 }
941 }
942 }
944 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
945 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
946 sp_node_moveto(n, n->pos + best_pt);
947 }
949 // do not update repr here so that node dragging is acceptably fast
950 update_object(nodepath);
951 }
953 /**
954 * Move node selection to point, adjust its and neighbouring handles,
955 * handle possible snapping, and commit the change with possible undo.
956 */
957 void
958 sp_node_selected_move(gdouble dx, gdouble dy)
959 {
960 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
961 if (!nodepath) return;
963 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
965 if (dx == 0) {
966 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
967 } else if (dy == 0) {
968 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
969 } else {
970 sp_nodepath_update_repr(nodepath);
971 }
972 }
974 /**
975 * Move node selection off screen and commit the change.
976 */
977 void
978 sp_node_selected_move_screen(gdouble dx, gdouble dy)
979 {
980 // borrowed from sp_selection_move_screen in selection-chemistry.c
981 // we find out the current zoom factor and divide deltas by it
982 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
984 gdouble zoom = desktop->current_zoom();
985 gdouble zdx = dx / zoom;
986 gdouble zdy = dy / zoom;
988 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
989 if (!nodepath) return;
991 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
993 if (dx == 0) {
994 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
995 } else if (dy == 0) {
996 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
997 } else {
998 sp_nodepath_update_repr(nodepath);
999 }
1000 }
1002 /** If they don't yet exist, creates knot and line for the given side of the node */
1003 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1004 {
1005 if (!side->knot) {
1006 side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
1008 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1009 side->knot->setSize (7);
1010 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1011 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1012 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1013 sp_knot_update_ctrl(side->knot);
1015 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1016 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1017 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1018 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1019 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1020 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1021 }
1023 if (!side->line) {
1024 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1025 SP_TYPE_CTRLLINE, NULL);
1026 }
1027 }
1029 /**
1030 * Ensure the given handle of the node is visible/invisible, update its screen position
1031 */
1032 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1033 {
1034 g_assert(node != NULL);
1036 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1037 NRPathcode code = sp_node_path_code_from_side(node, side);
1039 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1041 if (show_handle) {
1042 if (!side->knot) { // No handle knot at all
1043 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1044 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1045 side->knot->pos = side->pos;
1046 if (side->knot->item)
1047 SP_CTRL(side->knot->item)->moveto(side->pos);
1048 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1049 sp_knot_show(side->knot);
1050 } else {
1051 if (side->knot->pos != side->pos) { // only if it's really moved
1052 if (fire_move_signals) {
1053 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1054 } else {
1055 sp_knot_moveto(side->knot, &side->pos);
1056 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1057 }
1058 }
1059 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1060 sp_knot_show(side->knot);
1061 }
1062 }
1063 sp_canvas_item_show(side->line);
1064 } else {
1065 if (side->knot) {
1066 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1067 sp_knot_hide(side->knot);
1068 }
1069 }
1070 if (side->line) {
1071 sp_canvas_item_hide(side->line);
1072 }
1073 }
1074 }
1076 /**
1077 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1078 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1079 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1080 * updated; otherwise, just move the knots silently (used in batch moves).
1081 */
1082 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1083 {
1084 g_assert(node != NULL);
1086 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1087 sp_knot_show(node->knot);
1088 }
1090 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1091 if (fire_move_signals)
1092 sp_knot_set_position(node->knot, &node->pos, 0);
1093 else
1094 sp_knot_moveto(node->knot, &node->pos);
1095 }
1097 gboolean show_handles = node->selected;
1098 if (node->p.other != NULL) {
1099 if (node->p.other->selected) show_handles = TRUE;
1100 }
1101 if (node->n.other != NULL) {
1102 if (node->n.other->selected) show_handles = TRUE;
1103 }
1105 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1106 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1107 }
1109 /**
1110 * Call sp_node_update_handles() for all nodes on subpath.
1111 */
1112 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1113 {
1114 g_assert(subpath != NULL);
1116 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1117 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1118 }
1119 }
1121 /**
1122 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1123 */
1124 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1125 {
1126 g_assert(nodepath != NULL);
1128 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1129 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1130 }
1131 }
1133 /**
1134 * Adds all selected nodes in nodepath to list.
1135 */
1136 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1137 {
1138 StlConv<Node *>::list(l, selected);
1139 /// \todo this adds a copying, rework when the selection becomes a stl list
1140 }
1142 /**
1143 * Align selected nodes on the specified axis.
1144 */
1145 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1146 {
1147 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1148 return;
1149 }
1151 if ( !nodepath->selected->next ) { // only one node selected
1152 return;
1153 }
1154 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1155 NR::Point dest(pNode->pos);
1156 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1157 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1158 if (pNode) {
1159 dest[axis] = pNode->pos[axis];
1160 sp_node_moveto(pNode, dest);
1161 }
1162 }
1164 sp_nodepath_update_repr(nodepath);
1165 }
1167 /// Helper struct.
1168 struct NodeSort
1169 {
1170 Inkscape::NodePath::Node *_node;
1171 NR::Coord _coord;
1172 /// \todo use vectorof pointers instead of calling copy ctor
1173 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1174 _node(node), _coord(node->pos[axis])
1175 {}
1177 };
1179 static bool operator<(NodeSort const &a, NodeSort const &b)
1180 {
1181 return (a._coord < b._coord);
1182 }
1184 /**
1185 * Distribute selected nodes on the specified axis.
1186 */
1187 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1188 {
1189 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1190 return;
1191 }
1193 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1194 return;
1195 }
1197 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1198 std::vector<NodeSort> sorted;
1199 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1200 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1201 if (pNode) {
1202 NodeSort n(pNode, axis);
1203 sorted.push_back(n);
1204 //dest[axis] = pNode->pos[axis];
1205 //sp_node_moveto(pNode, dest);
1206 }
1207 }
1208 std::sort(sorted.begin(), sorted.end());
1209 unsigned int len = sorted.size();
1210 //overall bboxes span
1211 float dist = (sorted.back()._coord -
1212 sorted.front()._coord);
1213 //new distance between each bbox
1214 float step = (dist) / (len - 1);
1215 float pos = sorted.front()._coord;
1216 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1217 it < sorted.end();
1218 it ++ )
1219 {
1220 NR::Point dest((*it)._node->pos);
1221 dest[axis] = pos;
1222 sp_node_moveto((*it)._node, dest);
1223 pos += step;
1224 }
1226 sp_nodepath_update_repr(nodepath);
1227 }
1230 /**
1231 * Call sp_nodepath_line_add_node() for all selected segments.
1232 */
1233 void
1234 sp_node_selected_add_node(void)
1235 {
1236 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1237 if (!nodepath) {
1238 return;
1239 }
1241 GList *nl = NULL;
1243 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1244 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1245 g_assert(t->selected);
1246 if (t->p.other && t->p.other->selected) {
1247 nl = g_list_prepend(nl, t);
1248 }
1249 }
1251 while (nl) {
1252 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1253 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1254 sp_nodepath_node_select(n, TRUE, FALSE);
1255 nl = g_list_remove(nl, t);
1256 }
1258 /** \todo fixme: adjust ? */
1259 sp_nodepath_update_handles(nodepath);
1261 sp_nodepath_update_repr(nodepath);
1263 sp_nodepath_update_statusbar(nodepath);
1264 }
1266 /**
1267 * Select segment nearest to point
1268 */
1269 void
1270 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1271 {
1272 if (!nodepath) {
1273 return;
1274 }
1276 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1278 //find segment to segment
1279 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1281 gboolean force = FALSE;
1282 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1283 force = TRUE;
1284 }
1285 sp_nodepath_node_select(e, (gboolean) toggle, force);
1286 if (e->p.other)
1287 sp_nodepath_node_select(e->p.other, TRUE, force);
1289 sp_nodepath_update_handles(nodepath);
1291 sp_nodepath_update_statusbar(nodepath);
1292 }
1294 /**
1295 * Add a node nearest to point
1296 */
1297 void
1298 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1299 {
1300 if (!nodepath) {
1301 return;
1302 }
1304 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1306 //find segment to split
1307 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1309 //don't know why but t seems to flip for lines
1310 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1311 position.t = 1.0 - position.t;
1312 }
1313 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1314 sp_nodepath_node_select(n, FALSE, TRUE);
1316 /* fixme: adjust ? */
1317 sp_nodepath_update_handles(nodepath);
1319 sp_nodepath_update_repr(nodepath);
1321 sp_nodepath_update_statusbar(nodepath);
1322 }
1324 /*
1325 * Adjusts a segment so that t moves by a certain delta for dragging
1326 * converts lines to curves
1327 *
1328 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1329 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1330 */
1331 void
1332 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1333 {
1334 /* feel good is an arbitrary parameter that distributes the delta between handles
1335 * if t of the drag point is less than 1/6 distance form the endpoint only
1336 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1337 */
1338 double feel_good;
1339 if (t <= 1.0 / 6.0)
1340 feel_good = 0;
1341 else if (t <= 0.5)
1342 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1343 else if (t <= 5.0 / 6.0)
1344 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1345 else
1346 feel_good = 1;
1348 //if we're dragging a line convert it to a curve
1349 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1350 sp_nodepath_set_line_type(e, NR_CURVETO);
1351 }
1353 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1354 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1355 e->p.other->n.pos += offsetcoord0;
1356 e->p.pos += offsetcoord1;
1358 // adjust handles of adjacent nodes where necessary
1359 sp_node_adjust_handle(e,1);
1360 sp_node_adjust_handle(e->p.other,-1);
1362 sp_nodepath_update_handles(e->subpath->nodepath);
1364 update_object(e->subpath->nodepath);
1366 sp_nodepath_update_statusbar(e->subpath->nodepath);
1367 }
1370 /**
1371 * Call sp_nodepath_break() for all selected segments.
1372 */
1373 void sp_node_selected_break()
1374 {
1375 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1376 if (!nodepath) return;
1378 GList *temp = NULL;
1379 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1380 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1381 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1382 if (nn == NULL) continue; // no break, no new node
1383 temp = g_list_prepend(temp, nn);
1384 }
1386 if (temp) {
1387 sp_nodepath_deselect(nodepath);
1388 }
1389 for (GList *l = temp; l != NULL; l = l->next) {
1390 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1391 }
1393 sp_nodepath_update_handles(nodepath);
1395 sp_nodepath_update_repr(nodepath);
1396 }
1398 /**
1399 * Duplicate the selected node(s).
1400 */
1401 void sp_node_selected_duplicate()
1402 {
1403 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1404 if (!nodepath) {
1405 return;
1406 }
1408 GList *temp = NULL;
1409 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1410 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1411 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1412 if (nn == NULL) continue; // could not duplicate
1413 temp = g_list_prepend(temp, nn);
1414 }
1416 if (temp) {
1417 sp_nodepath_deselect(nodepath);
1418 }
1419 for (GList *l = temp; l != NULL; l = l->next) {
1420 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1421 }
1423 sp_nodepath_update_handles(nodepath);
1425 sp_nodepath_update_repr(nodepath);
1426 }
1428 /**
1429 * Join two nodes by merging them into one.
1430 */
1431 void sp_node_selected_join()
1432 {
1433 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1434 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1436 if (g_list_length(nodepath->selected) != 2) {
1437 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1438 return;
1439 }
1441 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1442 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1444 g_assert(a != b);
1445 g_assert(a->p.other || a->n.other);
1446 g_assert(b->p.other || b->n.other);
1448 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1449 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1450 return;
1451 }
1453 /* a and b are endpoints */
1455 NR::Point c;
1456 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1457 c = a->pos;
1458 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1459 c = b->pos;
1460 } else {
1461 c = (a->pos + b->pos) / 2;
1462 }
1464 if (a->subpath == b->subpath) {
1465 Inkscape::NodePath::SubPath *sp = a->subpath;
1466 sp_nodepath_subpath_close(sp);
1467 sp_node_moveto (sp->first, c);
1469 sp_nodepath_update_handles(sp->nodepath);
1470 sp_nodepath_update_repr(nodepath);
1471 return;
1472 }
1474 /* a and b are separate subpaths */
1475 Inkscape::NodePath::SubPath *sa = a->subpath;
1476 Inkscape::NodePath::SubPath *sb = b->subpath;
1477 NR::Point p;
1478 Inkscape::NodePath::Node *n;
1479 NRPathcode code;
1480 if (a == sa->first) {
1481 p = sa->first->n.pos;
1482 code = (NRPathcode)sa->first->n.other->code;
1483 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1484 n = sa->last;
1485 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1486 n = n->p.other;
1487 while (n) {
1488 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1489 n = n->p.other;
1490 if (n == sa->first) n = NULL;
1491 }
1492 sp_nodepath_subpath_destroy(sa);
1493 sa = t;
1494 } else if (a == sa->last) {
1495 p = sa->last->p.pos;
1496 code = (NRPathcode)sa->last->code;
1497 sp_nodepath_node_destroy(sa->last);
1498 } else {
1499 code = NR_END;
1500 g_assert_not_reached();
1501 }
1503 if (b == sb->first) {
1504 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1505 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1506 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1507 }
1508 } else if (b == sb->last) {
1509 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1510 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1511 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1512 }
1513 } else {
1514 g_assert_not_reached();
1515 }
1516 /* and now destroy sb */
1518 sp_nodepath_subpath_destroy(sb);
1520 sp_nodepath_update_handles(sa->nodepath);
1522 sp_nodepath_update_repr(nodepath);
1524 sp_nodepath_update_statusbar(nodepath);
1525 }
1527 /**
1528 * Join two nodes by adding a segment between them.
1529 */
1530 void sp_node_selected_join_segment()
1531 {
1532 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1533 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1535 if (g_list_length(nodepath->selected) != 2) {
1536 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1537 return;
1538 }
1540 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1541 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1543 g_assert(a != b);
1544 g_assert(a->p.other || a->n.other);
1545 g_assert(b->p.other || b->n.other);
1547 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1548 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1549 return;
1550 }
1552 if (a->subpath == b->subpath) {
1553 Inkscape::NodePath::SubPath *sp = a->subpath;
1555 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1556 sp->closed = TRUE;
1558 sp->first->p.other = sp->last;
1559 sp->last->n.other = sp->first;
1561 sp_node_handle_mirror_p_to_n(sp->last);
1562 sp_node_handle_mirror_n_to_p(sp->first);
1564 sp->first->code = sp->last->code;
1565 sp->first = sp->last;
1567 sp_nodepath_update_handles(sp->nodepath);
1569 sp_nodepath_update_repr(nodepath);
1571 return;
1572 }
1574 /* a and b are separate subpaths */
1575 Inkscape::NodePath::SubPath *sa = a->subpath;
1576 Inkscape::NodePath::SubPath *sb = b->subpath;
1578 Inkscape::NodePath::Node *n;
1579 NR::Point p;
1580 NRPathcode code;
1581 if (a == sa->first) {
1582 code = (NRPathcode) sa->first->n.other->code;
1583 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1584 n = sa->last;
1585 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1586 for (n = n->p.other; n != NULL; n = n->p.other) {
1587 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1588 }
1589 sp_nodepath_subpath_destroy(sa);
1590 sa = t;
1591 } else if (a == sa->last) {
1592 code = (NRPathcode)sa->last->code;
1593 } else {
1594 code = NR_END;
1595 g_assert_not_reached();
1596 }
1598 if (b == sb->first) {
1599 n = sb->first;
1600 sp_node_handle_mirror_p_to_n(sa->last);
1601 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1602 sp_node_handle_mirror_n_to_p(sa->last);
1603 for (n = n->n.other; n != NULL; n = n->n.other) {
1604 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1605 }
1606 } else if (b == sb->last) {
1607 n = sb->last;
1608 sp_node_handle_mirror_p_to_n(sa->last);
1609 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1610 sp_node_handle_mirror_n_to_p(sa->last);
1611 for (n = n->p.other; n != NULL; n = n->p.other) {
1612 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1613 }
1614 } else {
1615 g_assert_not_reached();
1616 }
1617 /* and now destroy sb */
1619 sp_nodepath_subpath_destroy(sb);
1621 sp_nodepath_update_handles(sa->nodepath);
1623 sp_nodepath_update_repr(nodepath);
1624 }
1626 /**
1627 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1628 */
1629 void sp_node_delete_preserve(GList *nodes_to_delete)
1630 {
1632 while (nodes_to_delete) {
1633 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1634 Inkscape::NodePath::SubPath *sp = node->subpath;
1635 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1636 Inkscape::NodePath::Node *sample_cursor = NULL;
1637 Inkscape::NodePath::Node *sample_end = NULL;
1638 Inkscape::NodePath::Node *delete_cursor = node;
1639 bool just_delete = false;
1641 //find the start of this contiguous selection
1642 //move left to the first node that is not selected
1643 //or the start of the non-closed path
1644 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1645 delete_cursor = curr;
1646 }
1648 //just delete at the beginning of an open path
1649 if (!delete_cursor->p.other) {
1650 sample_cursor = delete_cursor;
1651 just_delete = true;
1652 } else {
1653 sample_cursor = delete_cursor->p.other;
1654 }
1656 //calculate points for each segment
1657 int rate = 5;
1658 float period = 1.0 / rate;
1659 std::vector<NR::Point> data;
1660 if (!just_delete) {
1661 data.push_back(sample_cursor->pos);
1662 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1663 //just delete at the end of an open path
1664 if (!sp->closed && curr->n.other == sp->last) {
1665 just_delete = true;
1666 break;
1667 }
1669 //sample points on the contiguous selected segment
1670 NR::Point *bez;
1671 bez = new NR::Point [4];
1672 bez[0] = curr->pos;
1673 bez[1] = curr->n.pos;
1674 bez[2] = curr->n.other->p.pos;
1675 bez[3] = curr->n.other->pos;
1676 for (int i=1; i<rate; i++) {
1677 gdouble t = i * period;
1678 NR::Point p = bezier_pt(3, bez, t);
1679 data.push_back(p);
1680 }
1681 data.push_back(curr->n.other->pos);
1683 sample_end = curr->n.other;
1684 //break if we've come full circle or hit the end of the selection
1685 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1686 break;
1687 }
1688 }
1689 }
1691 if (!just_delete) {
1692 //calculate the best fitting single segment and adjust the endpoints
1693 NR::Point *adata;
1694 adata = new NR::Point [data.size()];
1695 copy(data.begin(), data.end(), adata);
1697 NR::Point *bez;
1698 bez = new NR::Point [4];
1699 //would decreasing error create a better fitting approximation?
1700 gdouble error = 1.0;
1701 gint ret;
1702 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1704 //adjust endpoints
1705 sample_cursor->n.pos = bez[1];
1706 sample_end->p.pos = bez[2];
1707 }
1709 //destroy this contiguous selection
1710 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1711 Inkscape::NodePath::Node *temp = delete_cursor;
1712 if (delete_cursor->n.other == delete_cursor) {
1713 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1714 delete_cursor = NULL;
1715 } else {
1716 delete_cursor = delete_cursor->n.other;
1717 }
1718 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1719 sp_nodepath_node_destroy(temp);
1720 }
1722 //clean up the nodepath (such as for trivial subpaths)
1723 sp_nodepath_cleanup(nodepath);
1725 sp_nodepath_update_handles(nodepath);
1727 // if the entire nodepath is removed, delete the selected object.
1728 if (nodepath->subpaths == NULL ||
1729 sp_nodepath_get_node_count(nodepath) < 2) {
1730 SPDocument *document = sp_desktop_document (nodepath->desktop);
1731 sp_nodepath_destroy(nodepath);
1732 g_list_free(nodes_to_delete);
1733 nodes_to_delete = NULL;
1734 //is the next line necessary?
1735 sp_selection_delete();
1736 sp_document_done (document);
1737 return;
1738 }
1740 sp_nodepath_update_repr(nodepath);
1742 sp_nodepath_update_statusbar(nodepath);
1743 }
1744 }
1746 /**
1747 * Delete one or more selected nodes.
1748 */
1749 void sp_node_selected_delete()
1750 {
1751 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1752 if (!nodepath) return;
1753 if (!nodepath->selected) return;
1755 /** \todo fixme: do it the right way */
1756 while (nodepath->selected) {
1757 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1758 sp_nodepath_node_destroy(node);
1759 }
1762 //clean up the nodepath (such as for trivial subpaths)
1763 sp_nodepath_cleanup(nodepath);
1765 sp_nodepath_update_handles(nodepath);
1767 // if the entire nodepath is removed, delete the selected object.
1768 if (nodepath->subpaths == NULL ||
1769 sp_nodepath_get_node_count(nodepath) < 2) {
1770 SPDocument *document = sp_desktop_document (nodepath->desktop);
1771 sp_nodepath_destroy(nodepath);
1772 sp_selection_delete();
1773 sp_document_done (document);
1774 return;
1775 }
1777 sp_nodepath_update_repr(nodepath);
1779 sp_nodepath_update_statusbar(nodepath);
1780 }
1782 /**
1783 * Delete one or more segments between two selected nodes.
1784 * This is the code for 'split'.
1785 */
1786 void
1787 sp_node_selected_delete_segment(void)
1788 {
1789 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1790 Inkscape::NodePath::Node *curr, *next; //Iterators
1792 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1793 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1795 if (g_list_length(nodepath->selected) != 2) {
1796 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1797 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1798 return;
1799 }
1801 //Selected nodes, not inclusive
1802 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1803 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1805 if ( ( a==b) || //same node
1806 (a->subpath != b->subpath ) || //not the same path
1807 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1808 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1809 {
1810 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1811 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1812 return;
1813 }
1815 //###########################################
1816 //# BEGIN EDITS
1817 //###########################################
1818 //##################################
1819 //# CLOSED PATH
1820 //##################################
1821 if (a->subpath->closed) {
1824 gboolean reversed = FALSE;
1826 //Since we can go in a circle, we need to find the shorter distance.
1827 // a->b or b->a
1828 start = end = NULL;
1829 int distance = 0;
1830 int minDistance = 0;
1831 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1832 if (curr==b) {
1833 //printf("a to b:%d\n", distance);
1834 start = a;//go from a to b
1835 end = b;
1836 minDistance = distance;
1837 //printf("A to B :\n");
1838 break;
1839 }
1840 distance++;
1841 }
1843 //try again, the other direction
1844 distance = 0;
1845 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1846 if (curr==a) {
1847 //printf("b to a:%d\n", distance);
1848 if (distance < minDistance) {
1849 start = b; //we go from b to a
1850 end = a;
1851 reversed = TRUE;
1852 //printf("B to A\n");
1853 }
1854 break;
1855 }
1856 distance++;
1857 }
1860 //Copy everything from 'end' to 'start' to a new subpath
1861 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1862 for (curr=end ; curr ; curr=curr->n.other) {
1863 NRPathcode code = (NRPathcode) curr->code;
1864 if (curr == end)
1865 code = NR_MOVETO;
1866 sp_nodepath_node_new(t, NULL,
1867 (Inkscape::NodePath::NodeType)curr->type, code,
1868 &curr->p.pos, &curr->pos, &curr->n.pos);
1869 if (curr == start)
1870 break;
1871 }
1872 sp_nodepath_subpath_destroy(a->subpath);
1875 }
1879 //##################################
1880 //# OPEN PATH
1881 //##################################
1882 else {
1884 //We need to get the direction of the list between A and B
1885 //Can we walk from a to b?
1886 start = end = NULL;
1887 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1888 if (curr==b) {
1889 start = a; //did it! we go from a to b
1890 end = b;
1891 //printf("A to B\n");
1892 break;
1893 }
1894 }
1895 if (!start) {//didn't work? let's try the other direction
1896 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1897 if (curr==a) {
1898 start = b; //did it! we go from b to a
1899 end = a;
1900 //printf("B to A\n");
1901 break;
1902 }
1903 }
1904 }
1905 if (!start) {
1906 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1907 _("Cannot find path between nodes."));
1908 return;
1909 }
1913 //Copy everything after 'end' to a new subpath
1914 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1915 for (curr=end ; curr ; curr=curr->n.other) {
1916 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1917 &curr->p.pos, &curr->pos, &curr->n.pos);
1918 }
1920 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1921 for (curr = start->n.other ; curr ; curr=next) {
1922 next = curr->n.other;
1923 sp_nodepath_node_destroy(curr);
1924 }
1926 }
1927 //###########################################
1928 //# END EDITS
1929 //###########################################
1931 //clean up the nodepath (such as for trivial subpaths)
1932 sp_nodepath_cleanup(nodepath);
1934 sp_nodepath_update_handles(nodepath);
1936 sp_nodepath_update_repr(nodepath);
1938 // if the entire nodepath is removed, delete the selected object.
1939 if (nodepath->subpaths == NULL ||
1940 sp_nodepath_get_node_count(nodepath) < 2) {
1941 sp_nodepath_destroy(nodepath);
1942 sp_selection_delete();
1943 return;
1944 }
1946 sp_nodepath_update_statusbar(nodepath);
1947 }
1949 /**
1950 * Call sp_nodepath_set_line() for all selected segments.
1951 */
1952 void
1953 sp_node_selected_set_line_type(NRPathcode code)
1954 {
1955 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1956 if (nodepath == NULL) return;
1958 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1959 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1960 g_assert(n->selected);
1961 if (n->p.other && n->p.other->selected) {
1962 sp_nodepath_set_line_type(n, code);
1963 }
1964 }
1966 sp_nodepath_update_repr(nodepath);
1967 }
1969 /**
1970 * Call sp_nodepath_convert_node_type() for all selected nodes.
1971 */
1972 void
1973 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1974 {
1975 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1976 if (nodepath == NULL) return;
1978 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1979 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1980 }
1982 sp_nodepath_update_repr(nodepath);
1983 }
1985 /**
1986 * Change select status of node, update its own and neighbour handles.
1987 */
1988 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1989 {
1990 node->selected = selected;
1992 if (selected) {
1993 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1994 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1995 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1996 sp_knot_update_ctrl(node->knot);
1997 } else {
1998 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1999 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2000 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2001 sp_knot_update_ctrl(node->knot);
2002 }
2004 sp_node_update_handles(node);
2005 if (node->n.other) sp_node_update_handles(node->n.other);
2006 if (node->p.other) sp_node_update_handles(node->p.other);
2007 }
2009 /**
2010 \brief Select a node
2011 \param node The node to select
2012 \param incremental If true, add to selection, otherwise deselect others
2013 \param override If true, always select this node, otherwise toggle selected status
2014 */
2015 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2016 {
2017 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2019 if (incremental) {
2020 if (override) {
2021 if (!g_list_find(nodepath->selected, node)) {
2022 nodepath->selected = g_list_prepend(nodepath->selected, node);
2023 }
2024 sp_node_set_selected(node, TRUE);
2025 } else { // toggle
2026 if (node->selected) {
2027 g_assert(g_list_find(nodepath->selected, node));
2028 nodepath->selected = g_list_remove(nodepath->selected, node);
2029 } else {
2030 g_assert(!g_list_find(nodepath->selected, node));
2031 nodepath->selected = g_list_prepend(nodepath->selected, node);
2032 }
2033 sp_node_set_selected(node, !node->selected);
2034 }
2035 } else {
2036 sp_nodepath_deselect(nodepath);
2037 nodepath->selected = g_list_prepend(nodepath->selected, node);
2038 sp_node_set_selected(node, TRUE);
2039 }
2041 sp_nodepath_update_statusbar(nodepath);
2042 }
2045 /**
2046 \brief Deselect all nodes in the nodepath
2047 */
2048 void
2049 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2050 {
2051 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2053 while (nodepath->selected) {
2054 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2055 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2056 }
2057 sp_nodepath_update_statusbar(nodepath);
2058 }
2060 /**
2061 \brief Select or invert selection of all nodes in the nodepath
2062 */
2063 void
2064 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2065 {
2066 if (!nodepath) return;
2068 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2069 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2070 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2071 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2072 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2073 }
2074 }
2075 }
2077 /**
2078 * If nothing selected, does the same as sp_nodepath_select_all();
2079 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2080 * (i.e., similar to "select all in layer", with the "selected" subpaths
2081 * being treated as "layers" in the path).
2082 */
2083 void
2084 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2085 {
2086 if (!nodepath) return;
2088 if (g_list_length (nodepath->selected) == 0) {
2089 sp_nodepath_select_all (nodepath, invert);
2090 return;
2091 }
2093 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2094 GSList *subpaths = NULL;
2096 for (GList *l = copy; l != NULL; l = l->next) {
2097 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2098 Inkscape::NodePath::SubPath *subpath = n->subpath;
2099 if (!g_slist_find (subpaths, subpath))
2100 subpaths = g_slist_prepend (subpaths, subpath);
2101 }
2103 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2104 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2105 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2106 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2107 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2108 }
2109 }
2111 g_slist_free (subpaths);
2112 g_list_free (copy);
2113 }
2115 /**
2116 * \brief Select the node after the last selected; if none is selected,
2117 * select the first within path.
2118 */
2119 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2120 {
2121 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2123 Inkscape::NodePath::Node *last = NULL;
2124 if (nodepath->selected) {
2125 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2126 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2127 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2128 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2129 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2130 if (node->selected) {
2131 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2132 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2133 if (spl->next) { // there's a next subpath
2134 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2135 last = subpath_next->first;
2136 } else if (spl->prev) { // there's a previous subpath
2137 last = NULL; // to be set later to the first node of first subpath
2138 } else {
2139 last = node->n.other;
2140 }
2141 } else {
2142 last = node->n.other;
2143 }
2144 } else {
2145 if (node->n.other) {
2146 last = node->n.other;
2147 } else {
2148 if (spl->next) { // there's a next subpath
2149 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2150 last = subpath_next->first;
2151 } else if (spl->prev) { // there's a previous subpath
2152 last = NULL; // to be set later to the first node of first subpath
2153 } else {
2154 last = (Inkscape::NodePath::Node *) subpath->first;
2155 }
2156 }
2157 }
2158 }
2159 }
2160 }
2161 sp_nodepath_deselect(nodepath);
2162 }
2164 if (last) { // there's at least one more node after selected
2165 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2166 } else { // no more nodes, select the first one in first subpath
2167 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2168 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2169 }
2170 }
2172 /**
2173 * \brief Select the node before the first selected; if none is selected,
2174 * select the last within path
2175 */
2176 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2177 {
2178 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2180 Inkscape::NodePath::Node *last = NULL;
2181 if (nodepath->selected) {
2182 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2183 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2184 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2185 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2186 if (node->selected) {
2187 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2188 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2189 if (spl->prev) { // there's a prev subpath
2190 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2191 last = subpath_prev->last;
2192 } else if (spl->next) { // there's a next subpath
2193 last = NULL; // to be set later to the last node of last subpath
2194 } else {
2195 last = node->p.other;
2196 }
2197 } else {
2198 last = node->p.other;
2199 }
2200 } else {
2201 if (node->p.other) {
2202 last = node->p.other;
2203 } else {
2204 if (spl->prev) { // there's a prev subpath
2205 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2206 last = subpath_prev->last;
2207 } else if (spl->next) { // there's a next subpath
2208 last = NULL; // to be set later to the last node of last subpath
2209 } else {
2210 last = (Inkscape::NodePath::Node *) subpath->last;
2211 }
2212 }
2213 }
2214 }
2215 }
2216 }
2217 sp_nodepath_deselect(nodepath);
2218 }
2220 if (last) { // there's at least one more node before selected
2221 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2222 } else { // no more nodes, select the last one in last subpath
2223 GList *spl = g_list_last(nodepath->subpaths);
2224 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2225 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2226 }
2227 }
2229 /**
2230 * \brief Select all nodes that are within the rectangle.
2231 */
2232 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2233 {
2234 if (!incremental) {
2235 sp_nodepath_deselect(nodepath);
2236 }
2238 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2239 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2240 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2241 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2243 if (b.contains(node->pos)) {
2244 sp_nodepath_node_select(node, TRUE, TRUE);
2245 }
2246 }
2247 }
2248 }
2250 /**
2251 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2252 */
2253 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2254 {
2255 if (!nodepath->selected) {
2256 return NULL;
2257 }
2259 GList *r = NULL;
2260 guint i = 0;
2261 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2262 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2263 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2264 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2265 i++;
2266 if (node->selected) {
2267 r = g_list_append(r, GINT_TO_POINTER(i));
2268 }
2269 }
2270 }
2271 return r;
2272 }
2274 /**
2275 \brief Restores selection by selecting nodes whose positions are in the list
2276 */
2277 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2278 {
2279 sp_nodepath_deselect(nodepath);
2281 guint i = 0;
2282 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2283 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2284 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2285 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2286 i++;
2287 if (g_list_find(r, GINT_TO_POINTER(i))) {
2288 sp_nodepath_node_select(node, TRUE, TRUE);
2289 }
2290 }
2291 }
2293 }
2295 /**
2296 \brief Adjusts handle according to node type and line code.
2297 */
2298 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2299 {
2300 double len, otherlen, linelen;
2302 g_assert(node);
2304 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2305 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2307 /** \todo fixme: */
2308 if (me->other == NULL) return;
2309 if (other->other == NULL) return;
2311 /* I have line */
2313 NRPathcode mecode, ocode;
2314 if (which_adjust == 1) {
2315 mecode = (NRPathcode)me->other->code;
2316 ocode = (NRPathcode)node->code;
2317 } else {
2318 mecode = (NRPathcode)node->code;
2319 ocode = (NRPathcode)other->other->code;
2320 }
2322 if (mecode == NR_LINETO) return;
2324 /* I am curve */
2326 if (other->other == NULL) return;
2328 /* Other has line */
2330 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2332 NR::Point delta;
2333 if (ocode == NR_LINETO) {
2334 /* other is lineto, we are either smooth or symm */
2335 Inkscape::NodePath::Node *othernode = other->other;
2336 len = NR::L2(me->pos - node->pos);
2337 delta = node->pos - othernode->pos;
2338 linelen = NR::L2(delta);
2339 if (linelen < 1e-18)
2340 return;
2341 me->pos = node->pos + (len / linelen)*delta;
2342 return;
2343 }
2345 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2347 me->pos = 2 * node->pos - other->pos;
2348 return;
2349 }
2351 /* We are smooth */
2353 len = NR::L2(me->pos - node->pos);
2354 delta = other->pos - node->pos;
2355 otherlen = NR::L2(delta);
2356 if (otherlen < 1e-18) return;
2358 me->pos = node->pos - (len / otherlen) * delta;
2359 }
2361 /**
2362 \brief Adjusts both handles according to node type and line code
2363 */
2364 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2365 {
2366 g_assert(node);
2368 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2370 /* we are either smooth or symm */
2372 if (node->p.other == NULL) return;
2374 if (node->n.other == NULL) return;
2376 if (node->code == NR_LINETO) {
2377 if (node->n.other->code == NR_LINETO) return;
2378 sp_node_adjust_handle(node, 1);
2379 return;
2380 }
2382 if (node->n.other->code == NR_LINETO) {
2383 if (node->code == NR_LINETO) return;
2384 sp_node_adjust_handle(node, -1);
2385 return;
2386 }
2388 /* both are curves */
2389 NR::Point const delta( node->n.pos - node->p.pos );
2391 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2392 node->p.pos = node->pos - delta / 2;
2393 node->n.pos = node->pos + delta / 2;
2394 return;
2395 }
2397 /* We are smooth */
2398 double plen = NR::L2(node->p.pos - node->pos);
2399 if (plen < 1e-18) return;
2400 double nlen = NR::L2(node->n.pos - node->pos);
2401 if (nlen < 1e-18) return;
2402 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2403 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2404 }
2406 /**
2407 * Node event callback.
2408 */
2409 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2410 {
2411 gboolean ret = FALSE;
2412 switch (event->type) {
2413 case GDK_ENTER_NOTIFY:
2414 active_node = n;
2415 break;
2416 case GDK_LEAVE_NOTIFY:
2417 active_node = NULL;
2418 break;
2419 case GDK_KEY_PRESS:
2420 switch (get_group0_keyval (&event->key)) {
2421 case GDK_space:
2422 if (event->key.state & GDK_BUTTON1_MASK) {
2423 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2424 stamp_repr(nodepath);
2425 ret = TRUE;
2426 }
2427 break;
2428 default:
2429 break;
2430 }
2431 break;
2432 default:
2433 break;
2434 }
2436 return ret;
2437 }
2439 /**
2440 * Handle keypress on node; directly called.
2441 */
2442 gboolean node_key(GdkEvent *event)
2443 {
2444 Inkscape::NodePath::Path *np;
2446 // there is no way to verify nodes so set active_node to nil when deleting!!
2447 if (active_node == NULL) return FALSE;
2449 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2450 gint ret = FALSE;
2451 switch (get_group0_keyval (&event->key)) {
2452 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2453 case GDK_BackSpace:
2454 np = active_node->subpath->nodepath;
2455 sp_nodepath_node_destroy(active_node);
2456 sp_nodepath_update_repr(np);
2457 active_node = NULL;
2458 ret = TRUE;
2459 break;
2460 case GDK_c:
2461 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2462 ret = TRUE;
2463 break;
2464 case GDK_s:
2465 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2466 ret = TRUE;
2467 break;
2468 case GDK_y:
2469 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2470 ret = TRUE;
2471 break;
2472 case GDK_b:
2473 sp_nodepath_node_break(active_node);
2474 ret = TRUE;
2475 break;
2476 }
2477 return ret;
2478 }
2479 return FALSE;
2480 }
2482 /**
2483 * Mouseclick on node callback.
2484 */
2485 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2486 {
2487 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2489 if (state & GDK_CONTROL_MASK) {
2490 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2492 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2493 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2494 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2495 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2496 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2497 } else {
2498 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2499 }
2500 sp_nodepath_update_repr(nodepath);
2501 sp_nodepath_update_statusbar(nodepath);
2503 } else { //ctrl+alt+click: delete node
2504 GList *node_to_delete = NULL;
2505 node_to_delete = g_list_append(node_to_delete, n);
2506 sp_node_delete_preserve(node_to_delete);
2507 }
2509 } else {
2510 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2511 }
2512 }
2514 /**
2515 * Mouse grabbed node callback.
2516 */
2517 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2518 {
2519 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2521 n->origin = knot->pos;
2523 if (!n->selected) {
2524 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2525 }
2526 }
2528 /**
2529 * Mouse ungrabbed node callback.
2530 */
2531 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2532 {
2533 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2535 n->dragging_out = NULL;
2537 sp_nodepath_update_repr(n->subpath->nodepath);
2538 }
2540 /**
2541 * The point on a line, given by its angle, closest to the given point.
2542 * \param p A point.
2543 * \param a Angle of the line; it is assumed to go through coordinate origin.
2544 * \param closest Pointer to the point struct where the result is stored.
2545 * \todo FIXME: use dot product perhaps?
2546 */
2547 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2548 {
2549 if (a == HUGE_VAL) { // vertical
2550 *closest = NR::Point(0, (*p)[NR::Y]);
2551 } else {
2552 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2553 (*closest)[NR::Y] = a * (*closest)[NR::X];
2554 }
2555 }
2557 /**
2558 * Distance from the point to a line given by its angle.
2559 * \param p A point.
2560 * \param a Angle of the line; it is assumed to go through coordinate origin.
2561 */
2562 static double point_line_distance(NR::Point *p, double a)
2563 {
2564 NR::Point c;
2565 point_line_closest(p, a, &c);
2566 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]));
2567 }
2569 /**
2570 * Callback for node "request" signal.
2571 * \todo fixme: This goes to "moved" event? (lauris)
2572 */
2573 static gboolean
2574 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2575 {
2576 double yn, xn, yp, xp;
2577 double an, ap, na, pa;
2578 double d_an, d_ap, d_na, d_pa;
2579 gboolean collinear = FALSE;
2580 NR::Point c;
2581 NR::Point pr;
2583 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2585 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2586 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2588 NR::Point mouse = (*p);
2590 if (!n->dragging_out) {
2591 // This is the first drag-out event; find out which handle to drag out
2592 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2593 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2595 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2596 return FALSE;
2598 Inkscape::NodePath::NodeSide *opposite;
2599 if (appr_p > appr_n) { // closer to p
2600 n->dragging_out = &n->p;
2601 opposite = &n->n;
2602 n->code = NR_CURVETO;
2603 } else if (appr_p < appr_n) { // closer to n
2604 n->dragging_out = &n->n;
2605 opposite = &n->p;
2606 n->n.other->code = NR_CURVETO;
2607 } else { // p and n nodes are the same
2608 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2609 n->dragging_out = &n->p;
2610 opposite = &n->n;
2611 n->code = NR_CURVETO;
2612 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2613 n->dragging_out = &n->n;
2614 opposite = &n->p;
2615 n->n.other->code = NR_CURVETO;
2616 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2617 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);
2618 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);
2619 if (appr_other_p > appr_other_n) { // closer to other's p handle
2620 n->dragging_out = &n->n;
2621 opposite = &n->p;
2622 n->n.other->code = NR_CURVETO;
2623 } else { // closer to other's n handle
2624 n->dragging_out = &n->p;
2625 opposite = &n->n;
2626 n->code = NR_CURVETO;
2627 }
2628 }
2629 }
2631 // if there's another handle, make sure the one we drag out starts parallel to it
2632 if (opposite->pos != n->pos) {
2633 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2634 }
2636 // knots might not be created yet!
2637 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2638 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2639 }
2641 // pass this on to the handle-moved callback
2642 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2643 sp_node_update_handles(n);
2644 return TRUE;
2645 }
2647 if (state & GDK_CONTROL_MASK) { // constrained motion
2649 // calculate relative distances of handles
2650 // n handle:
2651 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2652 xn = n->n.pos[NR::X] - n->pos[NR::X];
2653 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2654 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2655 if (n->n.other) { // if there is the next point
2656 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2657 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2658 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2659 }
2660 }
2661 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2662 if (yn < 0) { xn = -xn; yn = -yn; }
2664 // p handle:
2665 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2666 xp = n->p.pos[NR::X] - n->pos[NR::X];
2667 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2668 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2669 if (n->p.other) {
2670 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2671 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2672 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2673 }
2674 }
2675 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2676 if (yp < 0) { xp = -xp; yp = -yp; }
2678 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2679 // sliding on handles, only if at least one of the handles is non-vertical
2680 // (otherwise it's the same as ctrl+drag anyway)
2682 // calculate angles of the handles
2683 if (xn == 0) {
2684 if (yn == 0) { // no handle, consider it the continuation of the other one
2685 an = 0;
2686 collinear = TRUE;
2687 }
2688 else an = 0; // vertical; set the angle to horizontal
2689 } else an = yn/xn;
2691 if (xp == 0) {
2692 if (yp == 0) { // no handle, consider it the continuation of the other one
2693 ap = an;
2694 }
2695 else ap = 0; // vertical; set the angle to horizontal
2696 } else ap = yp/xp;
2698 if (collinear) an = ap;
2700 // angles of the perpendiculars; HUGE_VAL means vertical
2701 if (an == 0) na = HUGE_VAL; else na = -1/an;
2702 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2704 //g_print("an %g ap %g\n", an, ap);
2706 // mouse point relative to the node's original pos
2707 pr = (*p) - n->origin;
2709 // distances to the four lines (two handles and two perpendiculars)
2710 d_an = point_line_distance(&pr, an);
2711 d_na = point_line_distance(&pr, na);
2712 d_ap = point_line_distance(&pr, ap);
2713 d_pa = point_line_distance(&pr, pa);
2715 // find out which line is the closest, save its closest point in c
2716 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2717 point_line_closest(&pr, an, &c);
2718 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2719 point_line_closest(&pr, ap, &c);
2720 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2721 point_line_closest(&pr, na, &c);
2722 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2723 point_line_closest(&pr, pa, &c);
2724 }
2726 // move the node to the closest point
2727 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2728 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2729 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2731 } else { // constraining to hor/vert
2733 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2734 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2735 } else { // snap to vert
2736 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2737 }
2738 }
2739 } else { // move freely
2740 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2741 (*p)[NR::X] - n->pos[NR::X],
2742 (*p)[NR::Y] - n->pos[NR::Y],
2743 (state & GDK_SHIFT_MASK) == 0);
2744 }
2746 n->subpath->nodepath->desktop->scroll_to_point(p);
2748 return TRUE;
2749 }
2751 /**
2752 * Node handle clicked callback.
2753 */
2754 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2755 {
2756 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2758 if (state & GDK_CONTROL_MASK) { // "delete" handle
2759 if (n->p.knot == knot) {
2760 n->p.pos = n->pos;
2761 } else if (n->n.knot == knot) {
2762 n->n.pos = n->pos;
2763 }
2764 sp_node_update_handles(n);
2765 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2766 sp_nodepath_update_repr(nodepath);
2767 sp_nodepath_update_statusbar(nodepath);
2769 } else { // just select or add to selection, depending in Shift
2770 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2771 }
2772 }
2774 /**
2775 * Node handle grabbed callback.
2776 */
2777 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2778 {
2779 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2781 if (!n->selected) {
2782 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2783 }
2785 // remember the origin point of the handle
2786 if (n->p.knot == knot) {
2787 n->p.origin = n->p.pos - n->pos;
2788 } else if (n->n.knot == knot) {
2789 n->n.origin = n->n.pos - n->pos;
2790 } else {
2791 g_assert_not_reached();
2792 }
2794 }
2796 /**
2797 * Node handle ungrabbed callback.
2798 */
2799 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2800 {
2801 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2803 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2804 if (n->p.knot == knot) {
2805 n->p.origin.a = 0;
2806 sp_knot_set_position(knot, &n->p.pos, state);
2807 } else if (n->n.knot == knot) {
2808 n->n.origin.a = 0;
2809 sp_knot_set_position(knot, &n->n.pos, state);
2810 } else {
2811 g_assert_not_reached();
2812 }
2814 sp_nodepath_update_repr(n->subpath->nodepath);
2815 }
2817 /**
2818 * Node handle "request" signal callback.
2819 */
2820 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2821 {
2822 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2824 Inkscape::NodePath::NodeSide *me, *opposite;
2825 gint which;
2826 if (n->p.knot == knot) {
2827 me = &n->p;
2828 opposite = &n->n;
2829 which = -1;
2830 } else if (n->n.knot == knot) {
2831 me = &n->n;
2832 opposite = &n->p;
2833 which = 1;
2834 } else {
2835 me = opposite = NULL;
2836 which = 0;
2837 g_assert_not_reached();
2838 }
2840 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2842 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2844 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2845 /* We are smooth node adjacent with line */
2846 NR::Point const delta = *p - n->pos;
2847 NR::Coord const len = NR::L2(delta);
2848 Inkscape::NodePath::Node *othernode = opposite->other;
2849 NR::Point const ndelta = n->pos - othernode->pos;
2850 NR::Coord const linelen = NR::L2(ndelta);
2851 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2852 NR::Coord const scal = dot(delta, ndelta) / linelen;
2853 (*p) = n->pos + (scal / linelen) * ndelta;
2854 }
2855 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2856 } else {
2857 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2858 }
2860 sp_node_adjust_handle(n, -which);
2862 return FALSE;
2863 }
2865 /**
2866 * Node handle moved callback.
2867 */
2868 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2869 {
2870 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2872 Inkscape::NodePath::NodeSide *me;
2873 Inkscape::NodePath::NodeSide *other;
2874 if (n->p.knot == knot) {
2875 me = &n->p;
2876 other = &n->n;
2877 } else if (n->n.knot == knot) {
2878 me = &n->n;
2879 other = &n->p;
2880 } else {
2881 me = NULL;
2882 other = NULL;
2883 g_assert_not_reached();
2884 }
2886 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2887 Radial rme(me->pos - n->pos);
2888 Radial rother(other->pos - n->pos);
2889 Radial rnew(*p - n->pos);
2891 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2892 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2893 /* 0 interpreted as "no snapping". */
2895 // The closest PI/snaps angle, starting from zero.
2896 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2897 if (me->origin.a == HUGE_VAL) {
2898 // ortho doesn't exist: original handle was zero length.
2899 rnew.a = a_snapped;
2900 } else {
2901 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2902 * its opposite and perpendiculars). */
2903 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2905 // Snap to the closest.
2906 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2907 ? a_snapped
2908 : a_ortho );
2909 }
2910 }
2912 if (state & GDK_MOD1_MASK) {
2913 // lock handle length
2914 rnew.r = me->origin.r;
2915 }
2917 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2918 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2919 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2920 rother.a += rnew.a - rme.a;
2921 other->pos = NR::Point(rother) + n->pos;
2922 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2923 sp_knot_set_position(other->knot, &other->pos, 0);
2924 }
2926 me->pos = NR::Point(rnew) + n->pos;
2927 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2929 // this is what sp_knot_set_position does, but without emitting the signal:
2930 // we cannot emit a "moved" signal because we're now processing it
2931 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2933 knot->desktop->set_coordinate_status(me->pos);
2935 update_object(n->subpath->nodepath);
2937 /* status text */
2938 SPDesktop *desktop = n->subpath->nodepath->desktop;
2939 if (!desktop) return;
2940 SPEventContext *ec = desktop->event_context;
2941 if (!ec) return;
2942 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2943 if (!mc) return;
2945 double degrees = 180 / M_PI * rnew.a;
2946 if (degrees > 180) degrees -= 360;
2947 if (degrees < -180) degrees += 360;
2948 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2949 degrees = angle_to_compass (degrees);
2951 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2953 mc->setF(Inkscape::NORMAL_MESSAGE,
2954 _("<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);
2956 g_string_free(length, TRUE);
2957 }
2959 /**
2960 * Node handle event callback.
2961 */
2962 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2963 {
2964 gboolean ret = FALSE;
2965 switch (event->type) {
2966 case GDK_KEY_PRESS:
2967 switch (get_group0_keyval (&event->key)) {
2968 case GDK_space:
2969 if (event->key.state & GDK_BUTTON1_MASK) {
2970 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2971 stamp_repr(nodepath);
2972 ret = TRUE;
2973 }
2974 break;
2975 default:
2976 break;
2977 }
2978 break;
2979 default:
2980 break;
2981 }
2983 return ret;
2984 }
2986 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2987 Radial &rme, Radial &rother, gboolean const both)
2988 {
2989 rme.a += angle;
2990 if ( both
2991 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2992 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2993 {
2994 rother.a += angle;
2995 }
2996 }
2998 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2999 Radial &rme, Radial &rother, gboolean const both)
3000 {
3001 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3003 gdouble r;
3004 if ( both
3005 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3006 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3007 {
3008 r = MAX(rme.r, rother.r);
3009 } else {
3010 r = rme.r;
3011 }
3013 gdouble const weird_angle = atan2(norm_angle, r);
3014 /* Bulia says norm_angle is just the visible distance that the
3015 * object's end must travel on the screen. Left as 'angle' for want of
3016 * a better name.*/
3018 rme.a += weird_angle;
3019 if ( both
3020 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3021 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3022 {
3023 rother.a += weird_angle;
3024 }
3025 }
3027 /**
3028 * Rotate one node.
3029 */
3030 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3031 {
3032 Inkscape::NodePath::NodeSide *me, *other;
3033 bool both = false;
3035 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3036 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3038 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3039 me = &(n->p);
3040 other = &(n->n);
3041 } else if (!n->p.other) {
3042 me = &(n->n);
3043 other = &(n->p);
3044 } else {
3045 if (which > 0) { // right handle
3046 if (xn > xp) {
3047 me = &(n->n);
3048 other = &(n->p);
3049 } else {
3050 me = &(n->p);
3051 other = &(n->n);
3052 }
3053 } else if (which < 0){ // left handle
3054 if (xn <= xp) {
3055 me = &(n->n);
3056 other = &(n->p);
3057 } else {
3058 me = &(n->p);
3059 other = &(n->n);
3060 }
3061 } else { // both handles
3062 me = &(n->n);
3063 other = &(n->p);
3064 both = true;
3065 }
3066 }
3068 Radial rme(me->pos - n->pos);
3069 Radial rother(other->pos - n->pos);
3071 if (screen) {
3072 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3073 } else {
3074 node_rotate_one_internal (*n, angle, rme, rother, both);
3075 }
3077 me->pos = n->pos + NR::Point(rme);
3079 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3080 other->pos = n->pos + NR::Point(rother);
3081 }
3083 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3084 // so here we just move all the knots without emitting move signals, for speed
3085 sp_node_update_handles(n, false);
3086 }
3088 /**
3089 * Rotate selected nodes.
3090 */
3091 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3092 {
3093 if (!nodepath || !nodepath->selected) return;
3095 if (g_list_length(nodepath->selected) == 1) {
3096 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3097 node_rotate_one (n, angle, which, screen);
3098 } else {
3099 // rotate as an object:
3101 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3102 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3103 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3104 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3105 box.expandTo (n->pos); // contain all selected nodes
3106 }
3108 gdouble rot;
3109 if (screen) {
3110 gdouble const zoom = nodepath->desktop->current_zoom();
3111 gdouble const zmove = angle / zoom;
3112 gdouble const r = NR::L2(box.max() - box.midpoint());
3113 rot = atan2(zmove, r);
3114 } else {
3115 rot = angle;
3116 }
3118 NR::Matrix t =
3119 NR::Matrix (NR::translate(-box.midpoint())) *
3120 NR::Matrix (NR::rotate(rot)) *
3121 NR::Matrix (NR::translate(box.midpoint()));
3123 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3124 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3125 n->pos *= t;
3126 n->n.pos *= t;
3127 n->p.pos *= t;
3128 sp_node_update_handles(n, false);
3129 }
3130 }
3132 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3133 }
3135 /**
3136 * Scale one node.
3137 */
3138 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3139 {
3140 bool both = false;
3141 Inkscape::NodePath::NodeSide *me, *other;
3143 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3144 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3146 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3147 me = &(n->p);
3148 other = &(n->n);
3149 n->code = NR_CURVETO;
3150 } else if (!n->p.other) {
3151 me = &(n->n);
3152 other = &(n->p);
3153 if (n->n.other)
3154 n->n.other->code = NR_CURVETO;
3155 } else {
3156 if (which > 0) { // right handle
3157 if (xn > xp) {
3158 me = &(n->n);
3159 other = &(n->p);
3160 if (n->n.other)
3161 n->n.other->code = NR_CURVETO;
3162 } else {
3163 me = &(n->p);
3164 other = &(n->n);
3165 n->code = NR_CURVETO;
3166 }
3167 } else if (which < 0){ // left handle
3168 if (xn <= xp) {
3169 me = &(n->n);
3170 other = &(n->p);
3171 if (n->n.other)
3172 n->n.other->code = NR_CURVETO;
3173 } else {
3174 me = &(n->p);
3175 other = &(n->n);
3176 n->code = NR_CURVETO;
3177 }
3178 } else { // both handles
3179 me = &(n->n);
3180 other = &(n->p);
3181 both = true;
3182 n->code = NR_CURVETO;
3183 if (n->n.other)
3184 n->n.other->code = NR_CURVETO;
3185 }
3186 }
3188 Radial rme(me->pos - n->pos);
3189 Radial rother(other->pos - n->pos);
3191 rme.r += grow;
3192 if (rme.r < 0) rme.r = 0;
3193 if (rme.a == HUGE_VAL) {
3194 if (me->other) { // if direction is unknown, initialize it towards the next node
3195 Radial rme_next(me->other->pos - n->pos);
3196 rme.a = rme_next.a;
3197 } else { // if there's no next, initialize to 0
3198 rme.a = 0;
3199 }
3200 }
3201 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3202 rother.r += grow;
3203 if (rother.r < 0) rother.r = 0;
3204 if (rother.a == HUGE_VAL) {
3205 rother.a = rme.a + M_PI;
3206 }
3207 }
3209 me->pos = n->pos + NR::Point(rme);
3211 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3212 other->pos = n->pos + NR::Point(rother);
3213 }
3215 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3216 // so here we just move all the knots without emitting move signals, for speed
3217 sp_node_update_handles(n, false);
3218 }
3220 /**
3221 * Scale selected nodes.
3222 */
3223 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3224 {
3225 if (!nodepath || !nodepath->selected) return;
3227 if (g_list_length(nodepath->selected) == 1) {
3228 // scale handles of the single selected node
3229 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3230 node_scale_one (n, grow, which);
3231 } else {
3232 // scale nodes as an "object":
3234 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3235 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3236 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3237 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3238 box.expandTo (n->pos); // contain all selected nodes
3239 }
3241 double scale = (box.maxExtent() + grow)/box.maxExtent();
3243 NR::Matrix t =
3244 NR::Matrix (NR::translate(-box.midpoint())) *
3245 NR::Matrix (NR::scale(scale, scale)) *
3246 NR::Matrix (NR::translate(box.midpoint()));
3248 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3250 n->pos *= t;
3251 n->n.pos *= t;
3252 n->p.pos *= t;
3253 sp_node_update_handles(n, false);
3254 }
3255 }
3257 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3258 }
3260 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3261 {
3262 if (!nodepath) return;
3263 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3264 }
3266 /**
3267 * Flip selected nodes horizontally/vertically.
3268 */
3269 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3270 {
3271 if (!nodepath || !nodepath->selected) return;
3273 if (g_list_length(nodepath->selected) == 1) {
3274 // flip handles of the single selected node
3275 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3276 double temp = n->p.pos[axis];
3277 n->p.pos[axis] = n->n.pos[axis];
3278 n->n.pos[axis] = temp;
3279 sp_node_update_handles(n, false);
3280 } else {
3281 // scale nodes as an "object":
3283 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3284 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3285 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3286 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3287 box.expandTo (n->pos); // contain all selected nodes
3288 }
3290 NR::Matrix t =
3291 NR::Matrix (NR::translate(-box.midpoint())) *
3292 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3293 NR::Matrix (NR::translate(box.midpoint()));
3295 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3296 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3297 n->pos *= t;
3298 n->n.pos *= t;
3299 n->p.pos *= t;
3300 sp_node_update_handles(n, false);
3301 }
3302 }
3304 sp_nodepath_update_repr(nodepath);
3305 }
3307 //-----------------------------------------------
3308 /**
3309 * Return new subpath under given nodepath.
3310 */
3311 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3312 {
3313 g_assert(nodepath);
3314 g_assert(nodepath->desktop);
3316 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3318 s->nodepath = nodepath;
3319 s->closed = FALSE;
3320 s->nodes = NULL;
3321 s->first = NULL;
3322 s->last = NULL;
3324 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3325 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3326 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3328 return s;
3329 }
3331 /**
3332 * Destroy nodes in subpath, then subpath itself.
3333 */
3334 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3335 {
3336 g_assert(subpath);
3337 g_assert(subpath->nodepath);
3338 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3340 while (subpath->nodes) {
3341 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3342 }
3344 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3346 g_free(subpath);
3347 }
3349 /**
3350 * Link head to tail in subpath.
3351 */
3352 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3353 {
3354 g_assert(!sp->closed);
3355 g_assert(sp->last != sp->first);
3356 g_assert(sp->first->code == NR_MOVETO);
3358 sp->closed = TRUE;
3360 //Link the head to the tail
3361 sp->first->p.other = sp->last;
3362 sp->last->n.other = sp->first;
3363 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3364 sp->first = sp->last;
3366 //Remove the extra end node
3367 sp_nodepath_node_destroy(sp->last->n.other);
3368 }
3370 /**
3371 * Open closed (loopy) subpath at node.
3372 */
3373 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3374 {
3375 g_assert(sp->closed);
3376 g_assert(n->subpath == sp);
3377 g_assert(sp->first == sp->last);
3379 /* We create new startpoint, current node will become last one */
3381 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3382 &n->pos, &n->pos, &n->n.pos);
3385 sp->closed = FALSE;
3387 //Unlink to make a head and tail
3388 sp->first = new_path;
3389 sp->last = n;
3390 n->n.other = NULL;
3391 new_path->p.other = NULL;
3392 }
3394 /**
3395 * Returns area in triangle given by points; may be negative.
3396 */
3397 inline double
3398 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3399 {
3400 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]);
3401 }
3403 /**
3404 * Return new node in subpath with given properties.
3405 * \param pos Position of node.
3406 * \param ppos Handle position in previous direction
3407 * \param npos Handle position in previous direction
3408 */
3409 Inkscape::NodePath::Node *
3410 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)
3411 {
3412 g_assert(sp);
3413 g_assert(sp->nodepath);
3414 g_assert(sp->nodepath->desktop);
3416 if (nodechunk == NULL)
3417 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3419 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3421 n->subpath = sp;
3423 if (type != Inkscape::NodePath::NODE_NONE) {
3424 // use the type from sodipodi:nodetypes
3425 n->type = type;
3426 } else {
3427 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3428 // points are (almost) collinear
3429 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3430 // endnode, or a node with a retracted handle
3431 n->type = Inkscape::NodePath::NODE_CUSP;
3432 } else {
3433 n->type = Inkscape::NodePath::NODE_SMOOTH;
3434 }
3435 } else {
3436 n->type = Inkscape::NodePath::NODE_CUSP;
3437 }
3438 }
3440 n->code = code;
3441 n->selected = FALSE;
3442 n->pos = *pos;
3443 n->p.pos = *ppos;
3444 n->n.pos = *npos;
3446 n->dragging_out = NULL;
3448 Inkscape::NodePath::Node *prev;
3449 if (next) {
3450 //g_assert(g_list_find(sp->nodes, next));
3451 prev = next->p.other;
3452 } else {
3453 prev = sp->last;
3454 }
3456 if (prev)
3457 prev->n.other = n;
3458 else
3459 sp->first = n;
3461 if (next)
3462 next->p.other = n;
3463 else
3464 sp->last = n;
3466 n->p.other = prev;
3467 n->n.other = next;
3469 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"));
3470 sp_knot_set_position(n->knot, pos, 0);
3472 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3473 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3474 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3475 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3476 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3477 sp_knot_update_ctrl(n->knot);
3479 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3480 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3481 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3482 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3483 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3484 sp_knot_show(n->knot);
3486 // We only create handle knots and lines on demand
3487 n->p.knot = NULL;
3488 n->p.line = NULL;
3489 n->n.knot = NULL;
3490 n->n.line = NULL;
3492 sp->nodes = g_list_prepend(sp->nodes, n);
3494 return n;
3495 }
3497 /**
3498 * Destroy node and its knots, link neighbors in subpath.
3499 */
3500 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3501 {
3502 g_assert(node);
3503 g_assert(node->subpath);
3504 g_assert(SP_IS_KNOT(node->knot));
3506 Inkscape::NodePath::SubPath *sp = node->subpath;
3508 if (node->selected) { // first, deselect
3509 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3510 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3511 }
3513 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3515 g_object_unref(G_OBJECT(node->knot));
3516 if (node->p.knot)
3517 g_object_unref(G_OBJECT(node->p.knot));
3518 if (node->n.knot)
3519 g_object_unref(G_OBJECT(node->n.knot));
3521 if (node->p.line)
3522 gtk_object_destroy(GTK_OBJECT(node->p.line));
3523 if (node->n.line)
3524 gtk_object_destroy(GTK_OBJECT(node->n.line));
3526 if (sp->nodes) { // there are others nodes on the subpath
3527 if (sp->closed) {
3528 if (sp->first == node) {
3529 g_assert(sp->last == node);
3530 sp->first = node->n.other;
3531 sp->last = sp->first;
3532 }
3533 node->p.other->n.other = node->n.other;
3534 node->n.other->p.other = node->p.other;
3535 } else {
3536 if (sp->first == node) {
3537 sp->first = node->n.other;
3538 sp->first->code = NR_MOVETO;
3539 }
3540 if (sp->last == node) sp->last = node->p.other;
3541 if (node->p.other) node->p.other->n.other = node->n.other;
3542 if (node->n.other) node->n.other->p.other = node->p.other;
3543 }
3544 } else { // this was the last node on subpath
3545 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3546 }
3548 g_mem_chunk_free(nodechunk, node);
3549 }
3551 /**
3552 * Returns one of the node's two sides.
3553 * \param which Indicates which side.
3554 * \return Pointer to previous node side if which==-1, next if which==1.
3555 */
3556 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3557 {
3558 g_assert(node);
3560 switch (which) {
3561 case -1:
3562 return &node->p;
3563 case 1:
3564 return &node->n;
3565 default:
3566 break;
3567 }
3569 g_assert_not_reached();
3571 return NULL;
3572 }
3574 /**
3575 * Return the other side of the node, given one of its sides.
3576 */
3577 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3578 {
3579 g_assert(node);
3581 if (me == &node->p) return &node->n;
3582 if (me == &node->n) return &node->p;
3584 g_assert_not_reached();
3586 return NULL;
3587 }
3589 /**
3590 * Return NRPathcode on the given side of the node.
3591 */
3592 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3593 {
3594 g_assert(node);
3596 if (me == &node->p) {
3597 if (node->p.other) return (NRPathcode)node->code;
3598 return NR_MOVETO;
3599 }
3601 if (me == &node->n) {
3602 if (node->n.other) return (NRPathcode)node->n.other->code;
3603 return NR_MOVETO;
3604 }
3606 g_assert_not_reached();
3608 return NR_END;
3609 }
3611 /**
3612 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3613 */
3614 Inkscape::NodePath::Node *
3615 sp_nodepath_get_node_by_index(int index)
3616 {
3617 Inkscape::NodePath::Node *e = NULL;
3619 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3620 if (!nodepath) {
3621 return e;
3622 }
3624 //find segment
3625 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3627 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3628 int n = g_list_length(sp->nodes);
3629 if (sp->closed) {
3630 n++;
3631 }
3633 //if the piece belongs to this subpath grab it
3634 //otherwise move onto the next subpath
3635 if (index < n) {
3636 e = sp->first;
3637 for (int i = 0; i < index; ++i) {
3638 e = e->n.other;
3639 }
3640 break;
3641 } else {
3642 if (sp->closed) {
3643 index -= (n+1);
3644 } else {
3645 index -= n;
3646 }
3647 }
3648 }
3650 return e;
3651 }
3653 /**
3654 * Returns plain text meaning of node type.
3655 */
3656 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3657 {
3658 unsigned retracted = 0;
3659 bool endnode = false;
3661 for (int which = -1; which <= 1; which += 2) {
3662 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3663 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3664 retracted ++;
3665 if (!side->other)
3666 endnode = true;
3667 }
3669 if (retracted == 0) {
3670 if (endnode) {
3671 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3672 return _("end node");
3673 } else {
3674 switch (node->type) {
3675 case Inkscape::NodePath::NODE_CUSP:
3676 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3677 return _("cusp");
3678 case Inkscape::NodePath::NODE_SMOOTH:
3679 // TRANSLATORS: "smooth" is an adjective here
3680 return _("smooth");
3681 case Inkscape::NodePath::NODE_SYMM:
3682 return _("symmetric");
3683 }
3684 }
3685 } else if (retracted == 1) {
3686 if (endnode) {
3687 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3688 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3689 } else {
3690 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3691 }
3692 } else {
3693 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3694 }
3696 return NULL;
3697 }
3699 /**
3700 * Handles content of statusbar as long as node tool is active.
3701 */
3702 void
3703 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3704 {
3705 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3706 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3708 gint total = 0;
3709 gint selected = 0;
3710 SPDesktop *desktop = NULL;
3712 if (nodepath) {
3713 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3714 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3715 total += g_list_length(subpath->nodes);
3716 }
3717 selected = g_list_length(nodepath->selected);
3718 desktop = nodepath->desktop;
3719 } else {
3720 desktop = SP_ACTIVE_DESKTOP;
3721 }
3723 SPEventContext *ec = desktop->event_context;
3724 if (!ec) return;
3725 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3726 if (!mc) return;
3728 if (selected == 0) {
3729 Inkscape::Selection *sel = desktop->selection;
3730 if (!sel || sel->isEmpty()) {
3731 mc->setF(Inkscape::NORMAL_MESSAGE,
3732 _("Select a single object to edit its nodes or handles."));
3733 } else {
3734 if (nodepath) {
3735 mc->setF(Inkscape::NORMAL_MESSAGE,
3736 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.",
3737 "<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.",
3738 total),
3739 total);
3740 } else {
3741 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3742 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3743 } else {
3744 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3745 }
3746 }
3747 }
3748 } else if (nodepath && selected == 1) {
3749 mc->setF(Inkscape::NORMAL_MESSAGE,
3750 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3751 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3752 total),
3753 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3754 } else {
3755 mc->setF(Inkscape::NORMAL_MESSAGE,
3756 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3757 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3758 total),
3759 selected, total, when_selected);
3760 }
3761 }
3764 /*
3765 Local Variables:
3766 mode:c++
3767 c-file-style:"stroustrup"
3768 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3769 indent-tabs-mode:nil
3770 fill-column:99
3771 End:
3772 */
3773 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :