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"
44 class NR::Matrix;
46 /// \todo
47 /// evil evil evil. FIXME: conflict of two different Path classes!
48 /// There is a conflict in the namespace between two classes named Path.
49 /// #include "sp-flowtext.h"
50 /// #include "sp-flowregion.h"
52 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
53 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
54 GType sp_flowregion_get_type (void);
55 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
56 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
57 GType sp_flowtext_get_type (void);
58 // end evil workaround
60 #include "helper/stlport.h"
63 /// \todo fixme: Implement these via preferences */
65 #define NODE_FILL 0xbfbfbf00
66 #define NODE_STROKE 0x000000ff
67 #define NODE_FILL_HI 0xff000000
68 #define NODE_STROKE_HI 0x000000ff
69 #define NODE_FILL_SEL 0x0000ffff
70 #define NODE_STROKE_SEL 0x000000ff
71 #define NODE_FILL_SEL_HI 0xff000000
72 #define NODE_STROKE_SEL_HI 0x000000ff
73 #define KNOT_FILL 0xffffffff
74 #define KNOT_STROKE 0x000000ff
75 #define KNOT_FILL_HI 0xff000000
76 #define KNOT_STROKE_HI 0x000000ff
78 static GMemChunk *nodechunk = NULL;
80 /* Creation from object */
82 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
83 static gchar *parse_nodetypes(gchar const *types, gint length);
85 /* Object updating */
87 static void stamp_repr(Inkscape::NodePath::Path *np);
88 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
89 static gchar *create_typestr(Inkscape::NodePath::Path *np);
91 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node);
93 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
95 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
97 /* Control knot placement, if node or other knot is moved */
99 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust);
100 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node);
102 /* Knot event handlers */
104 static void node_clicked(SPKnot *knot, guint state, gpointer data);
105 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
106 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
107 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
108 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data);
109 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data);
110 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data);
111 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
113 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
115 /* Constructors and destructors */
117 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
118 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
119 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
120 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
121 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
122 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
123 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
125 /* Helpers */
127 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
128 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
129 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
131 // active_node indicates mouseover node
132 static Inkscape::NodePath::Node *active_node = NULL;
134 /**
135 * \brief Creates new nodepath from item
136 */
137 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
138 {
139 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
141 /** \todo
142 * FIXME: remove this. We don't want to edit paths inside flowtext.
143 * Instead we will build our flowtext with cloned paths, so that the
144 * real paths are outside the flowtext and thus editable as usual.
145 */
146 if (SP_IS_FLOWTEXT(item)) {
147 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
148 if SP_IS_FLOWREGION(child) {
149 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
150 if (grandchild && SP_IS_PATH(grandchild)) {
151 item = SP_ITEM(grandchild);
152 break;
153 }
154 }
155 }
156 }
158 if (!SP_IS_PATH(item))
159 return NULL;
160 SPPath *path = SP_PATH(item);
161 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
162 if (curve == NULL)
163 return NULL;
165 NArtBpath *bpath = sp_curve_first_bpath(curve);
166 gint length = curve->end;
167 if (length == 0)
168 return NULL; // prevent crash for one-node paths
170 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
171 gchar *typestr = parse_nodetypes(nodetypes, length);
173 //Create new nodepath
174 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
175 if (!np)
176 return NULL;
178 // Set defaults
179 np->desktop = desktop;
180 np->path = path;
181 np->subpaths = NULL;
182 np->selected = NULL;
183 np->nodeContext = NULL; //Let the context that makes this set it
185 // we need to update item's transform from the repr here,
186 // because they may be out of sync when we respond
187 // to a change in repr by regenerating nodepath --bb
188 sp_object_read_attr(SP_OBJECT(item), "transform");
190 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
191 np->d2i = np->i2d.inverse();
192 np->repr = repr;
194 /* Now the bitchy part (lauris) */
196 NArtBpath *b = bpath;
198 while (b->code != NR_END) {
199 b = subpath_from_bpath(np, b, typestr + (b - bpath));
200 }
202 g_free(typestr);
203 sp_curve_unref(curve);
205 return np;
206 }
208 /**
209 * Destroys nodepath's subpaths, then itself, also tell context about it.
210 */
211 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
213 if (!np) //soft fail, like delete
214 return;
216 while (np->subpaths) {
217 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
218 }
220 //Inform the context that made me, if any, that I am gone.
221 if (np->nodeContext)
222 np->nodeContext->nodepath = NULL;
224 g_assert(!np->selected);
226 np->desktop = NULL;
228 g_free(np);
229 }
232 /**
233 * Return the node count of a given NodeSubPath.
234 */
235 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
236 {
237 if (!subpath)
238 return 0;
239 gint nodeCount = g_list_length(subpath->nodes);
240 return nodeCount;
241 }
243 /**
244 * Return the node count of a given NodePath.
245 */
246 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
247 {
248 if (!np)
249 return 0;
250 gint nodeCount = 0;
251 for (GList *item = np->subpaths ; item ; item=item->next) {
252 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
253 nodeCount += g_list_length(subpath->nodes);
254 }
255 return nodeCount;
256 }
259 /**
260 * Clean up a nodepath after editing.
261 *
262 * Currently we are deleting trivial subpaths.
263 */
264 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
265 {
266 GList *badSubPaths = NULL;
268 //Check all subpaths to be >=2 nodes
269 for (GList *l = nodepath->subpaths; l ; l=l->next) {
270 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
271 if (sp_nodepath_subpath_get_node_count(sp)<2)
272 badSubPaths = g_list_append(badSubPaths, sp);
273 }
275 //Delete them. This second step is because sp_nodepath_subpath_destroy()
276 //also removes the subpath from nodepath->subpaths
277 for (GList *l = badSubPaths; l ; l=l->next) {
278 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
279 sp_nodepath_subpath_destroy(sp);
280 }
282 g_list_free(badSubPaths);
283 }
287 /**
288 * \brief Returns true if the argument nodepath and the d attribute in
289 * its repr do not match.
290 *
291 * This may happen if repr was changed in, e.g., XML editor or by undo.
292 *
293 * \todo
294 * UGLY HACK, think how we can eliminate it.
295 */
296 gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd)
297 {
298 g_assert(np);
300 SPCurve *curve = create_curve(np);
302 gchar *svgpath = sp_svg_write_path(curve->bpath);
304 char const *attr_d = ( newd
305 ? newd
306 : SP_OBJECT(np->path)->repr->attribute("d") );
308 gboolean ret;
309 if (attr_d && svgpath)
310 ret = strcmp(attr_d, svgpath);
311 else
312 ret = TRUE;
314 g_free(svgpath);
315 sp_curve_unref(curve);
317 return ret;
318 }
320 /**
321 * \brief Returns true if the argument nodepath and the sodipodi:nodetypes
322 * attribute in its repr do not match.
323 *
324 * This may happen if repr was changed in, e.g., the XML editor or by undo.
325 */
326 gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr)
327 {
328 g_assert(np);
329 gchar *typestr = create_typestr(np);
330 char const *attr_typestr = ( newtypestr
331 ? newtypestr
332 : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") );
333 gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr));
335 g_free(typestr);
337 return ret;
338 }
340 /**
341 * Create new nodepath from b, make it subpath of np.
342 * \param t The node type.
343 * \todo Fixme: t should be a proper type, rather than gchar
344 */
345 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
346 {
347 NR::Point ppos, pos, npos;
349 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
351 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
352 bool const closed = (b->code == NR_MOVETO);
354 pos = NR::Point(b->x3, b->y3) * np->i2d;
355 if (b[1].code == NR_CURVETO) {
356 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
357 } else {
358 npos = pos;
359 }
360 Inkscape::NodePath::Node *n;
361 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
362 g_assert(sp->first == n);
363 g_assert(sp->last == n);
365 b++;
366 t++;
367 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
368 pos = NR::Point(b->x3, b->y3) * np->i2d;
369 if (b->code == NR_CURVETO) {
370 ppos = NR::Point(b->x2, b->y2) * np->i2d;
371 } else {
372 ppos = pos;
373 }
374 if (b[1].code == NR_CURVETO) {
375 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
376 } else {
377 npos = pos;
378 }
379 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
380 b++;
381 t++;
382 }
384 if (closed) sp_nodepath_subpath_close(sp);
386 return b;
387 }
389 /**
390 * Convert from sodipodi:nodetypes to new style type string.
391 */
392 static gchar *parse_nodetypes(gchar const *types, gint length)
393 {
394 g_assert(length > 0);
396 gchar *typestr = g_new(gchar, length + 1);
398 gint pos = 0;
400 if (types) {
401 for (gint i = 0; types[i] && ( i < length ); i++) {
402 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
403 if (types[i] != '\0') {
404 switch (types[i]) {
405 case 's':
406 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
407 break;
408 case 'z':
409 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
410 break;
411 case 'c':
412 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
413 break;
414 default:
415 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
416 break;
417 }
418 }
419 }
420 }
422 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
424 return typestr;
425 }
427 /**
428 * Make curve out of path and associate it with it.
429 */
430 static void update_object(Inkscape::NodePath::Path *np)
431 {
432 g_assert(np);
434 SPCurve *curve = create_curve(np);
436 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
438 sp_curve_unref(curve);
439 }
441 /**
442 * Update XML path node with data from path object.
443 */
444 static void update_repr_internal(Inkscape::NodePath::Path *np)
445 {
446 g_assert(np);
448 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
450 SPCurve *curve = create_curve(np);
451 gchar *typestr = create_typestr(np);
452 gchar *svgpath = sp_svg_write_path(curve->bpath);
454 repr->setAttribute("d", svgpath);
455 repr->setAttribute("sodipodi:nodetypes", typestr);
457 g_free(svgpath);
458 g_free(typestr);
459 sp_curve_unref(curve);
460 }
462 /**
463 * Update XML path node with data from path object, commit changes forever.
464 */
465 static void update_repr(Inkscape::NodePath::Path *np)
466 {
467 update_repr_internal(np);
468 sp_document_done(SP_DT_DOCUMENT(np->desktop));
469 }
471 /**
472 * Update XML path node with data from path object, commit changes with undo.
473 */
474 static void update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
475 {
476 update_repr_internal(np);
477 sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
478 }
480 /**
481 * Make duplicate of path, replace corresponding XML node in tree, commit.
482 */
483 static void stamp_repr(Inkscape::NodePath::Path *np)
484 {
485 g_assert(np);
487 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
488 Inkscape::XML::Node *new_repr = old_repr->duplicate();
490 // remember the position of the item
491 gint pos = old_repr->position();
492 // remember parent
493 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
495 SPCurve *curve = create_curve(np);
496 gchar *typestr = create_typestr(np);
498 gchar *svgpath = sp_svg_write_path(curve->bpath);
500 new_repr->setAttribute("d", svgpath);
501 new_repr->setAttribute("sodipodi:nodetypes", typestr);
503 // add the new repr to the parent
504 parent->appendChild(new_repr);
505 // move to the saved position
506 new_repr->setPosition(pos > 0 ? pos : 0);
508 sp_document_done(SP_DT_DOCUMENT(np->desktop));
510 Inkscape::GC::release(new_repr);
511 g_free(svgpath);
512 g_free(typestr);
513 sp_curve_unref(curve);
514 }
516 /**
517 * Create curve from path.
518 */
519 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
520 {
521 SPCurve *curve = sp_curve_new();
523 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
524 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
525 sp_curve_moveto(curve,
526 sp->first->pos * np->d2i);
527 Inkscape::NodePath::Node *n = sp->first->n.other;
528 while (n) {
529 NR::Point const end_pt = n->pos * np->d2i;
530 switch (n->code) {
531 case NR_LINETO:
532 sp_curve_lineto(curve, end_pt);
533 break;
534 case NR_CURVETO:
535 sp_curve_curveto(curve,
536 n->p.other->n.pos * np->d2i,
537 n->p.pos * np->d2i,
538 end_pt);
539 break;
540 default:
541 g_assert_not_reached();
542 break;
543 }
544 if (n != sp->last) {
545 n = n->n.other;
546 } else {
547 n = NULL;
548 }
549 }
550 if (sp->closed) {
551 sp_curve_closepath(curve);
552 }
553 }
555 return curve;
556 }
558 /**
559 * Convert path type string to sodipodi:nodetypes style.
560 */
561 static gchar *create_typestr(Inkscape::NodePath::Path *np)
562 {
563 gchar *typestr = g_new(gchar, 32);
564 gint len = 32;
565 gint pos = 0;
567 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
568 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
570 if (pos >= len) {
571 typestr = g_renew(gchar, typestr, len + 32);
572 len += 32;
573 }
575 typestr[pos++] = 'c';
577 Inkscape::NodePath::Node *n;
578 n = sp->first->n.other;
579 while (n) {
580 gchar code;
582 switch (n->type) {
583 case Inkscape::NodePath::NODE_CUSP:
584 code = 'c';
585 break;
586 case Inkscape::NodePath::NODE_SMOOTH:
587 code = 's';
588 break;
589 case Inkscape::NodePath::NODE_SYMM:
590 code = 'z';
591 break;
592 default:
593 g_assert_not_reached();
594 code = '\0';
595 break;
596 }
598 if (pos >= len) {
599 typestr = g_renew(gchar, typestr, len + 32);
600 len += 32;
601 }
603 typestr[pos++] = code;
605 if (n != sp->last) {
606 n = n->n.other;
607 } else {
608 n = NULL;
609 }
610 }
611 }
613 if (pos >= len) {
614 typestr = g_renew(gchar, typestr, len + 1);
615 len += 1;
616 }
618 typestr[pos++] = '\0';
620 return typestr;
621 }
623 /**
624 * Returns current path in context.
625 */
626 static Inkscape::NodePath::Path *sp_nodepath_current()
627 {
628 if (!SP_ACTIVE_DESKTOP) {
629 return NULL;
630 }
632 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
634 if (!SP_IS_NODE_CONTEXT(event_context)) {
635 return NULL;
636 }
638 return SP_NODE_CONTEXT(event_context)->nodepath;
639 }
643 /**
644 \brief Fills node and control positions for three nodes, splitting line
645 marked by end at distance t.
646 */
647 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
648 {
649 g_assert(new_path != NULL);
650 g_assert(end != NULL);
652 g_assert(end->p.other == new_path);
653 Inkscape::NodePath::Node *start = new_path->p.other;
654 g_assert(start);
656 if (end->code == NR_LINETO) {
657 new_path->type =Inkscape::NodePath::NODE_CUSP;
658 new_path->code = NR_LINETO;
659 new_path->pos = (t * start->pos + (1 - t) * end->pos);
660 } else {
661 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
662 new_path->code = NR_CURVETO;
663 gdouble s = 1 - t;
664 for (int dim = 0; dim < 2; dim++) {
665 NR::Coord const f000 = start->pos[dim];
666 NR::Coord const f001 = start->n.pos[dim];
667 NR::Coord const f011 = end->p.pos[dim];
668 NR::Coord const f111 = end->pos[dim];
669 NR::Coord const f00t = s * f000 + t * f001;
670 NR::Coord const f01t = s * f001 + t * f011;
671 NR::Coord const f11t = s * f011 + t * f111;
672 NR::Coord const f0tt = s * f00t + t * f01t;
673 NR::Coord const f1tt = s * f01t + t * f11t;
674 NR::Coord const fttt = s * f0tt + t * f1tt;
675 start->n.pos[dim] = f00t;
676 new_path->p.pos[dim] = f0tt;
677 new_path->pos[dim] = fttt;
678 new_path->n.pos[dim] = f1tt;
679 end->p.pos[dim] = f11t;
680 }
681 }
682 }
684 /**
685 * Adds new node on direct line between two nodes, activates handles of all
686 * three nodes.
687 */
688 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
689 {
690 g_assert(end);
691 g_assert(end->subpath);
692 g_assert(g_list_find(end->subpath->nodes, end));
694 Inkscape::NodePath::Node *start = end->p.other;
695 g_assert( start->n.other == end );
696 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
697 end,
698 Inkscape::NodePath::NODE_SMOOTH,
699 (NRPathcode)end->code,
700 &start->pos, &start->pos, &start->n.pos);
701 sp_nodepath_line_midpoint(newnode, end, t);
703 sp_node_ensure_ctrls(start);
704 sp_node_ensure_ctrls(newnode);
705 sp_node_ensure_ctrls(end);
707 return newnode;
708 }
710 /**
711 \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
712 */
713 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
714 {
715 g_assert(node);
716 g_assert(node->subpath);
717 g_assert(g_list_find(node->subpath->nodes, node));
719 Inkscape::NodePath::SubPath *sp = node->subpath;
720 Inkscape::NodePath::Path *np = sp->nodepath;
722 if (sp->closed) {
723 sp_nodepath_subpath_open(sp, node);
724 return sp->first;
725 } else {
726 // no break for end nodes
727 if (node == sp->first) return NULL;
728 if (node == sp->last ) return NULL;
730 // create a new subpath
731 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
733 // duplicate the break node as start of the new subpath
734 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
736 while (node->n.other) { // copy the remaining nodes into the new subpath
737 Inkscape::NodePath::Node *n = node->n.other;
738 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);
739 if (n->selected) {
740 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
741 }
742 sp_nodepath_node_destroy(n); // remove the point on the original subpath
743 }
745 return newnode;
746 }
747 }
749 /**
750 * Duplicate node and connect to neighbours.
751 */
752 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
753 {
754 g_assert(node);
755 g_assert(node->subpath);
756 g_assert(g_list_find(node->subpath->nodes, node));
758 Inkscape::NodePath::SubPath *sp = node->subpath;
760 NRPathcode code = (NRPathcode) node->code;
761 if (code == NR_MOVETO) { // if node is the endnode,
762 node->code = NR_LINETO; // new one is inserted before it, so change that to line
763 }
765 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
767 if (!node->n.other || !node->p.other) // if node is an endnode, select it
768 return node;
769 else
770 return newnode; // otherwise select the newly created node
771 }
773 static void sp_node_control_mirror_n_to_p(Inkscape::NodePath::Node *node)
774 {
775 node->p.pos = (node->pos + (node->pos - node->n.pos));
776 }
778 static void sp_node_control_mirror_p_to_n(Inkscape::NodePath::Node *node)
779 {
780 node->n.pos = (node->pos + (node->pos - node->p.pos));
781 }
783 /**
784 * Change line type at node, with side effects on neighbours.
785 */
786 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
787 {
788 g_assert(end);
789 g_assert(end->subpath);
790 g_assert(end->p.other);
792 if (end->code == static_cast< guint > ( code ) )
793 return;
795 Inkscape::NodePath::Node *start = end->p.other;
797 end->code = code;
799 if (code == NR_LINETO) {
800 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
801 if (end->n.other) {
802 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
803 }
804 sp_node_adjust_knot(start, -1);
805 sp_node_adjust_knot(end, 1);
806 } else {
807 NR::Point delta = end->pos - start->pos;
808 start->n.pos = start->pos + delta / 3;
809 end->p.pos = end->pos - delta / 3;
810 sp_node_adjust_knot(start, 1);
811 sp_node_adjust_knot(end, -1);
812 }
814 sp_node_ensure_ctrls(start);
815 sp_node_ensure_ctrls(end);
816 }
818 /**
819 * Change node type, and its handles accordingly.
820 */
821 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeType type)
822 {
823 g_assert(node);
824 g_assert(node->subpath);
826 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
827 return node;
829 if ((node->p.other != NULL) && (node->n.other != NULL)) {
830 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
831 type =Inkscape::NodePath::NODE_CUSP;
832 }
833 }
835 node->type = type;
837 if (node->type == Inkscape::NodePath::NODE_CUSP) {
838 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
839 node->knot->setSize (node->selected? 11 : 9);
840 sp_knot_update_ctrl(node->knot);
841 } else {
842 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
843 node->knot->setSize (node->selected? 9 : 7);
844 sp_knot_update_ctrl(node->knot);
845 }
847 sp_node_adjust_knots(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_ensure_ctrls(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_ensure_ctrls(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_knot(node, 1);
903 sp_node_adjust_knot(node->p.other, -1);
904 }
905 }
906 if (node->n.other) {
907 if (node->n.other->code == NR_LINETO) {
908 sp_node_adjust_knot(node, -1);
909 sp_node_adjust_knot(node->n.other, 1);
910 }
911 }
913 sp_node_ensure_ctrls(node);
914 }
916 /**
917 * Call sp_node_moveto() for node selection and handle possible snapping.
918 */
919 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
920 bool const snap = true)
921 {
922 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
923 NR::Point delta(dx, dy);
924 NR::Point best_pt = delta;
926 if (snap) {
927 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
928 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
929 NR::Point p = n->pos + delta;
930 for (int dim = 0; dim < 2; dim++) {
931 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
932 Inkscape::Snapper::SNAP_POINT, p,
933 NR::Dim2(dim), nodepath->path);
934 if (dist < best[dim]) {
935 best[dim] = dist;
936 best_pt[dim] = p[dim] - n->pos[dim];
937 }
938 }
939 }
940 }
942 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
943 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
944 sp_node_moveto(n, n->pos + best_pt);
945 }
947 update_object(nodepath);
948 }
950 /**
951 * Move node selection to point, adjust its and neighbouring handles,
952 * handle possible snapping, and commit the change with possible undo.
953 */
954 void
955 sp_node_selected_move(gdouble dx, gdouble dy)
956 {
957 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
958 if (!nodepath) return;
960 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
962 if (dx == 0) {
963 update_repr_keyed(nodepath, "node:move:vertical");
964 } else if (dy == 0) {
965 update_repr_keyed(nodepath, "node:move:horizontal");
966 } else {
967 update_repr(nodepath);
968 }
969 }
971 /**
972 * Move node selection off screen and commit the change.
973 */
974 void
975 sp_node_selected_move_screen(gdouble dx, gdouble dy)
976 {
977 // borrowed from sp_selection_move_screen in selection-chemistry.c
978 // we find out the current zoom factor and divide deltas by it
979 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
981 gdouble zoom = desktop->current_zoom();
982 gdouble zdx = dx / zoom;
983 gdouble zdy = dy / zoom;
985 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
986 if (!nodepath) return;
988 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
990 if (dx == 0) {
991 update_repr_keyed(nodepath, "node:move:vertical");
992 } else if (dy == 0) {
993 update_repr_keyed(nodepath, "node:move:horizontal");
994 } else {
995 update_repr(nodepath);
996 }
997 }
999 /** If they don't yet exist, creates knot and line for the given side of the node */
1000 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1001 {
1002 if (!side->knot) {
1003 side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
1005 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1006 side->knot->setSize (7);
1007 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1008 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1009 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1010 sp_knot_update_ctrl(side->knot);
1012 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_ctrl_clicked), node);
1013 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), node);
1014 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), node);
1015 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_ctrl_request), node);
1016 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_ctrl_moved), node);
1017 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_ctrl_event), node);
1018 }
1020 if (!side->line) {
1021 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1022 SP_TYPE_CTRLLINE, NULL);
1023 }
1024 }
1026 /**
1027 * Ensure knot on side of node is visible/invisible.
1028 */
1029 static void sp_node_ensure_knot(Inkscape::NodePath::Node *node, gint which, gboolean show_knot)
1030 {
1031 g_assert(node != NULL);
1033 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1034 NRPathcode code = sp_node_path_code_from_side(node, side);
1036 show_knot = show_knot && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1038 if (show_knot) {
1039 if (!side->knot) {
1040 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1041 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1042 side->knot->pos = side->pos;
1043 if (side->knot->item) SP_CTRL(side->knot->item)->moveto(side->pos);
1044 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1045 sp_knot_show(side->knot);
1046 } else {
1047 if (side->knot->pos != side->pos) { // only if it's really moved
1048 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1049 }
1050 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1051 sp_knot_show(side->knot);
1052 }
1053 }
1054 sp_canvas_item_show(side->line);
1055 } else {
1056 if (side->knot) {
1057 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1058 sp_knot_hide(side->knot);
1059 }
1060 }
1061 if (side->line) {
1062 sp_canvas_item_hide(side->line);
1063 }
1064 }
1065 }
1067 /**
1068 * Ensure handles on node and neighbours of node are visible if selected.
1069 */
1070 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node)
1071 {
1072 g_assert(node != NULL);
1074 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1075 sp_knot_show(node->knot);
1076 }
1078 sp_knot_set_position(node->knot, &node->pos, 0);
1080 gboolean show_knots = node->selected;
1081 if (node->p.other != NULL) {
1082 if (node->p.other->selected) show_knots = TRUE;
1083 }
1084 if (node->n.other != NULL) {
1085 if (node->n.other->selected) show_knots = TRUE;
1086 }
1088 sp_node_ensure_knot(node, -1, show_knots);
1089 sp_node_ensure_knot(node, 1, show_knots);
1090 }
1092 /**
1093 * Call sp_node_ensure_ctrls() for all nodes on subpath.
1094 */
1095 static void sp_nodepath_subpath_ensure_ctrls(Inkscape::NodePath::SubPath *subpath)
1096 {
1097 g_assert(subpath != NULL);
1099 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1100 sp_node_ensure_ctrls((Inkscape::NodePath::Node *) l->data);
1101 }
1102 }
1104 /**
1105 * Call sp_nodepath_subpath_ensure_ctrls() for all subpaths of nodepath.
1106 */
1107 static void sp_nodepath_ensure_ctrls(Inkscape::NodePath::Path *nodepath)
1108 {
1109 g_assert(nodepath != NULL);
1111 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1112 sp_nodepath_subpath_ensure_ctrls((Inkscape::NodePath::SubPath *) l->data);
1113 }
1114 }
1116 /**
1117 * Adds all selected nodes in nodepath to list.
1118 */
1119 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1120 {
1121 StlConv<Node *>::list(l, selected);
1122 /// \todo this adds a copying, rework when the selection becomes a stl list
1123 }
1125 /**
1126 * Align selected nodes on the specified axis.
1127 */
1128 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1129 {
1130 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1131 return;
1132 }
1134 if ( !nodepath->selected->next ) { // only one node selected
1135 return;
1136 }
1137 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1138 NR::Point dest(pNode->pos);
1139 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1140 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1141 if (pNode) {
1142 dest[axis] = pNode->pos[axis];
1143 sp_node_moveto(pNode, dest);
1144 }
1145 }
1146 if (axis == NR::X) {
1147 update_repr_keyed(nodepath, "node:move:vertical");
1148 } else {
1149 update_repr_keyed(nodepath, "node:move:horizontal");
1150 }
1151 }
1153 /// Helper struct.
1154 struct NodeSort
1155 {
1156 Inkscape::NodePath::Node *_node;
1157 NR::Coord _coord;
1158 /// \todo use vectorof pointers instead of calling copy ctor
1159 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1160 _node(node), _coord(node->pos[axis])
1161 {}
1163 };
1165 static bool operator<(NodeSort const &a, NodeSort const &b)
1166 {
1167 return (a._coord < b._coord);
1168 }
1170 /**
1171 * Distribute selected nodes on the specified axis.
1172 */
1173 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1174 {
1175 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1176 return;
1177 }
1179 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1180 return;
1181 }
1183 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1184 std::vector<NodeSort> sorted;
1185 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1186 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1187 if (pNode) {
1188 NodeSort n(pNode, axis);
1189 sorted.push_back(n);
1190 //dest[axis] = pNode->pos[axis];
1191 //sp_node_moveto(pNode, dest);
1192 }
1193 }
1194 std::sort(sorted.begin(), sorted.end());
1195 unsigned int len = sorted.size();
1196 //overall bboxes span
1197 float dist = (sorted.back()._coord -
1198 sorted.front()._coord);
1199 //new distance between each bbox
1200 float step = (dist) / (len - 1);
1201 float pos = sorted.front()._coord;
1202 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1203 it < sorted.end();
1204 it ++ )
1205 {
1206 NR::Point dest((*it)._node->pos);
1207 dest[axis] = pos;
1208 sp_node_moveto((*it)._node, dest);
1209 pos += step;
1210 }
1212 if (axis == NR::X) {
1213 update_repr_keyed(nodepath, "node:move:horizontal");
1214 } else {
1215 update_repr_keyed(nodepath, "node:move:vertical");
1216 }
1217 }
1220 /**
1221 * Call sp_nodepath_line_add_node() for all selected segments.
1222 */
1223 void
1224 sp_node_selected_add_node(void)
1225 {
1226 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1227 if (!nodepath) {
1228 return;
1229 }
1231 GList *nl = NULL;
1233 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1234 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1235 g_assert(t->selected);
1236 if (t->p.other && t->p.other->selected) {
1237 nl = g_list_prepend(nl, t);
1238 }
1239 }
1241 while (nl) {
1242 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1243 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1244 sp_nodepath_node_select(n, TRUE, FALSE);
1245 nl = g_list_remove(nl, t);
1246 }
1248 /** \todo fixme: adjust ? */
1249 sp_nodepath_ensure_ctrls(nodepath);
1251 update_repr(nodepath);
1253 sp_nodepath_update_statusbar(nodepath);
1254 }
1256 /**
1257 * Select segment nearest to point
1258 */
1259 void
1260 sp_nodepath_select_segment_near_point(SPItem * item, NR::Point p, bool toggle)
1261 {
1262 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1263 if (!nodepath) {
1264 return;
1265 }
1267 Path::cut_position position = get_nearest_position_on_Path(item, p);
1269 //find segment to segment
1270 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1272 gboolean force = FALSE;
1273 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1274 force = TRUE;
1275 }
1276 sp_nodepath_node_select(e, (gboolean) toggle, force);
1277 if (e->p.other)
1278 sp_nodepath_node_select(e->p.other, TRUE, force);
1280 sp_nodepath_ensure_ctrls(nodepath);
1282 sp_nodepath_update_statusbar(nodepath);
1283 }
1285 /**
1286 * Add a node nearest to point
1287 */
1288 void
1289 sp_nodepath_add_node_near_point(SPItem * item, NR::Point p)
1290 {
1291 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1292 if (!nodepath) {
1293 return;
1294 }
1296 Path::cut_position position = get_nearest_position_on_Path(item, p);
1298 //find segment to split
1299 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1301 //don't know why but t seems to flip for lines
1302 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1303 position.t = 1.0 - position.t;
1304 }
1305 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1306 sp_nodepath_node_select(n, FALSE, TRUE);
1308 /* fixme: adjust ? */
1309 sp_nodepath_ensure_ctrls(nodepath);
1311 update_repr(nodepath);
1313 sp_nodepath_update_statusbar(nodepath);
1314 }
1316 /*
1317 * Adjusts a segment so that t moves by a certain delta for dragging
1318 * converts lines to curves
1319 *
1320 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1321 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1322 */
1323 void
1324 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key)
1325 {
1326 /* feel good is an arbitrary parameter that distributes the delta between handles
1327 * if t of the drag point is less than 1/6 distance form the endpoint only
1328 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1329 */
1330 double feel_good;
1331 if (t <= 1.0 / 6.0)
1332 feel_good = 0;
1333 else if (t <= 0.5)
1334 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1335 else if (t <= 5.0 / 6.0)
1336 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1337 else
1338 feel_good = 1;
1340 //if we're dragging a line convert it to a curve
1341 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1342 sp_nodepath_set_line_type(e, NR_CURVETO);
1343 }
1345 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1346 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1347 e->p.other->n.pos += offsetcoord0;
1348 e->p.pos += offsetcoord1;
1350 // adjust controls of adjacent segments where necessary
1351 sp_node_adjust_knot(e,1);
1352 sp_node_adjust_knot(e->p.other,-1);
1354 sp_nodepath_ensure_ctrls(e->subpath->nodepath);
1356 update_repr_keyed(e->subpath->nodepath, key);
1358 sp_nodepath_update_statusbar(e->subpath->nodepath);
1359 }
1362 /**
1363 * Call sp_nodepath_break() for all selected segments.
1364 */
1365 void sp_node_selected_break()
1366 {
1367 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1368 if (!nodepath) return;
1370 GList *temp = NULL;
1371 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1372 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1373 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1374 if (nn == NULL) continue; // no break, no new node
1375 temp = g_list_prepend(temp, nn);
1376 }
1378 if (temp) {
1379 sp_nodepath_deselect(nodepath);
1380 }
1381 for (GList *l = temp; l != NULL; l = l->next) {
1382 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1383 }
1385 sp_nodepath_ensure_ctrls(nodepath);
1387 update_repr(nodepath);
1388 }
1390 /**
1391 * Duplicate the selected node(s).
1392 */
1393 void sp_node_selected_duplicate()
1394 {
1395 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1396 if (!nodepath) {
1397 return;
1398 }
1400 GList *temp = NULL;
1401 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1402 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1403 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1404 if (nn == NULL) continue; // could not duplicate
1405 temp = g_list_prepend(temp, nn);
1406 }
1408 if (temp) {
1409 sp_nodepath_deselect(nodepath);
1410 }
1411 for (GList *l = temp; l != NULL; l = l->next) {
1412 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1413 }
1415 sp_nodepath_ensure_ctrls(nodepath);
1417 update_repr(nodepath);
1418 }
1420 /**
1421 * Join two nodes by merging them into one.
1422 */
1423 void sp_node_selected_join()
1424 {
1425 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1426 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1428 if (g_list_length(nodepath->selected) != 2) {
1429 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1430 return;
1431 }
1433 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1434 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1436 g_assert(a != b);
1437 g_assert(a->p.other || a->n.other);
1438 g_assert(b->p.other || b->n.other);
1440 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1441 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1442 return;
1443 }
1445 /* a and b are endpoints */
1447 NR::Point c = (a->pos + b->pos) / 2;
1449 if (a->subpath == b->subpath) {
1450 Inkscape::NodePath::SubPath *sp = a->subpath;
1451 sp_nodepath_subpath_close(sp);
1453 sp_nodepath_ensure_ctrls(sp->nodepath);
1455 update_repr(nodepath);
1457 return;
1458 }
1460 /* a and b are separate subpaths */
1461 Inkscape::NodePath::SubPath *sa = a->subpath;
1462 Inkscape::NodePath::SubPath *sb = b->subpath;
1463 NR::Point p;
1464 Inkscape::NodePath::Node *n;
1465 NRPathcode code;
1466 if (a == sa->first) {
1467 p = sa->first->n.pos;
1468 code = (NRPathcode)sa->first->n.other->code;
1469 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1470 n = sa->last;
1471 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1472 n = n->p.other;
1473 while (n) {
1474 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1475 n = n->p.other;
1476 if (n == sa->first) n = NULL;
1477 }
1478 sp_nodepath_subpath_destroy(sa);
1479 sa = t;
1480 } else if (a == sa->last) {
1481 p = sa->last->p.pos;
1482 code = (NRPathcode)sa->last->code;
1483 sp_nodepath_node_destroy(sa->last);
1484 } else {
1485 code = NR_END;
1486 g_assert_not_reached();
1487 }
1489 if (b == sb->first) {
1490 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1491 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1492 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1493 }
1494 } else if (b == sb->last) {
1495 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1496 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1497 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1498 }
1499 } else {
1500 g_assert_not_reached();
1501 }
1502 /* and now destroy sb */
1504 sp_nodepath_subpath_destroy(sb);
1506 sp_nodepath_ensure_ctrls(sa->nodepath);
1508 update_repr(nodepath);
1510 sp_nodepath_update_statusbar(nodepath);
1511 }
1513 /**
1514 * Join two nodes by adding a segment between them.
1515 */
1516 void sp_node_selected_join_segment()
1517 {
1518 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1519 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1521 if (g_list_length(nodepath->selected) != 2) {
1522 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1523 return;
1524 }
1526 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1527 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1529 g_assert(a != b);
1530 g_assert(a->p.other || a->n.other);
1531 g_assert(b->p.other || b->n.other);
1533 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1534 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1535 return;
1536 }
1538 if (a->subpath == b->subpath) {
1539 Inkscape::NodePath::SubPath *sp = a->subpath;
1541 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1542 sp->closed = TRUE;
1544 sp->first->p.other = sp->last;
1545 sp->last->n.other = sp->first;
1547 sp_node_control_mirror_p_to_n(sp->last);
1548 sp_node_control_mirror_n_to_p(sp->first);
1550 sp->first->code = sp->last->code;
1551 sp->first = sp->last;
1553 sp_nodepath_ensure_ctrls(sp->nodepath);
1555 update_repr(nodepath);
1557 return;
1558 }
1560 /* a and b are separate subpaths */
1561 Inkscape::NodePath::SubPath *sa = a->subpath;
1562 Inkscape::NodePath::SubPath *sb = b->subpath;
1564 Inkscape::NodePath::Node *n;
1565 NR::Point p;
1566 NRPathcode code;
1567 if (a == sa->first) {
1568 code = (NRPathcode) sa->first->n.other->code;
1569 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1570 n = sa->last;
1571 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1572 for (n = n->p.other; n != NULL; n = n->p.other) {
1573 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1574 }
1575 sp_nodepath_subpath_destroy(sa);
1576 sa = t;
1577 } else if (a == sa->last) {
1578 code = (NRPathcode)sa->last->code;
1579 } else {
1580 code = NR_END;
1581 g_assert_not_reached();
1582 }
1584 if (b == sb->first) {
1585 n = sb->first;
1586 sp_node_control_mirror_p_to_n(sa->last);
1587 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1588 sp_node_control_mirror_n_to_p(sa->last);
1589 for (n = n->n.other; n != NULL; n = n->n.other) {
1590 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1591 }
1592 } else if (b == sb->last) {
1593 n = sb->last;
1594 sp_node_control_mirror_p_to_n(sa->last);
1595 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1596 sp_node_control_mirror_n_to_p(sa->last);
1597 for (n = n->p.other; n != NULL; n = n->p.other) {
1598 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1599 }
1600 } else {
1601 g_assert_not_reached();
1602 }
1603 /* and now destroy sb */
1605 sp_nodepath_subpath_destroy(sb);
1607 sp_nodepath_ensure_ctrls(sa->nodepath);
1609 update_repr(nodepath);
1610 }
1612 /**
1613 * Delete one or more selected nodes.
1614 */
1615 void sp_node_selected_delete()
1616 {
1617 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1618 if (!nodepath) return;
1619 if (!nodepath->selected) return;
1621 /** \todo fixme: do it the right way */
1622 while (nodepath->selected) {
1623 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1624 sp_nodepath_node_destroy(node);
1625 }
1628 //clean up the nodepath (such as for trivial subpaths)
1629 sp_nodepath_cleanup(nodepath);
1631 sp_nodepath_ensure_ctrls(nodepath);
1633 // if the entire nodepath is removed, delete the selected object.
1634 if (nodepath->subpaths == NULL ||
1635 sp_nodepath_get_node_count(nodepath) < 2) {
1636 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1637 sp_nodepath_destroy(nodepath);
1638 sp_selection_delete();
1639 sp_document_done (document);
1640 return;
1641 }
1643 update_repr(nodepath);
1645 sp_nodepath_update_statusbar(nodepath);
1646 }
1648 /**
1649 * Delete one or more segments between two selected nodes.
1650 * This is the code for 'split'.
1651 */
1652 void
1653 sp_node_selected_delete_segment(void)
1654 {
1655 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1656 Inkscape::NodePath::Node *curr, *next; //Iterators
1658 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1659 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1661 if (g_list_length(nodepath->selected) != 2) {
1662 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1663 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1664 return;
1665 }
1667 //Selected nodes, not inclusive
1668 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1669 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1671 if ( ( a==b) || //same node
1672 (a->subpath != b->subpath ) || //not the same path
1673 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1674 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1675 {
1676 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1677 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1678 return;
1679 }
1681 //###########################################
1682 //# BEGIN EDITS
1683 //###########################################
1684 //##################################
1685 //# CLOSED PATH
1686 //##################################
1687 if (a->subpath->closed) {
1690 gboolean reversed = FALSE;
1692 //Since we can go in a circle, we need to find the shorter distance.
1693 // a->b or b->a
1694 start = end = NULL;
1695 int distance = 0;
1696 int minDistance = 0;
1697 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1698 if (curr==b) {
1699 //printf("a to b:%d\n", distance);
1700 start = a;//go from a to b
1701 end = b;
1702 minDistance = distance;
1703 //printf("A to B :\n");
1704 break;
1705 }
1706 distance++;
1707 }
1709 //try again, the other direction
1710 distance = 0;
1711 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1712 if (curr==a) {
1713 //printf("b to a:%d\n", distance);
1714 if (distance < minDistance) {
1715 start = b; //we go from b to a
1716 end = a;
1717 reversed = TRUE;
1718 //printf("B to A\n");
1719 }
1720 break;
1721 }
1722 distance++;
1723 }
1726 //Copy everything from 'end' to 'start' to a new subpath
1727 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1728 for (curr=end ; curr ; curr=curr->n.other) {
1729 NRPathcode code = (NRPathcode) curr->code;
1730 if (curr == end)
1731 code = NR_MOVETO;
1732 sp_nodepath_node_new(t, NULL,
1733 (Inkscape::NodePath::NodeType)curr->type, code,
1734 &curr->p.pos, &curr->pos, &curr->n.pos);
1735 if (curr == start)
1736 break;
1737 }
1738 sp_nodepath_subpath_destroy(a->subpath);
1741 }
1745 //##################################
1746 //# OPEN PATH
1747 //##################################
1748 else {
1750 //We need to get the direction of the list between A and B
1751 //Can we walk from a to b?
1752 start = end = NULL;
1753 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1754 if (curr==b) {
1755 start = a; //did it! we go from a to b
1756 end = b;
1757 //printf("A to B\n");
1758 break;
1759 }
1760 }
1761 if (!start) {//didn't work? let's try the other direction
1762 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1763 if (curr==a) {
1764 start = b; //did it! we go from b to a
1765 end = a;
1766 //printf("B to A\n");
1767 break;
1768 }
1769 }
1770 }
1771 if (!start) {
1772 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1773 _("Cannot find path between nodes."));
1774 return;
1775 }
1779 //Copy everything after 'end' to a new subpath
1780 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1781 for (curr=end ; curr ; curr=curr->n.other) {
1782 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1783 &curr->p.pos, &curr->pos, &curr->n.pos);
1784 }
1786 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1787 for (curr = start->n.other ; curr ; curr=next) {
1788 next = curr->n.other;
1789 sp_nodepath_node_destroy(curr);
1790 }
1792 }
1793 //###########################################
1794 //# END EDITS
1795 //###########################################
1797 //clean up the nodepath (such as for trivial subpaths)
1798 sp_nodepath_cleanup(nodepath);
1800 sp_nodepath_ensure_ctrls(nodepath);
1802 update_repr(nodepath);
1804 // if the entire nodepath is removed, delete the selected object.
1805 if (nodepath->subpaths == NULL ||
1806 sp_nodepath_get_node_count(nodepath) < 2) {
1807 sp_nodepath_destroy(nodepath);
1808 sp_selection_delete();
1809 return;
1810 }
1812 sp_nodepath_update_statusbar(nodepath);
1813 }
1815 /**
1816 * Call sp_nodepath_set_line() for all selected segments.
1817 */
1818 void
1819 sp_node_selected_set_line_type(NRPathcode code)
1820 {
1821 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1822 if (nodepath == NULL) return;
1824 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1825 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1826 g_assert(n->selected);
1827 if (n->p.other && n->p.other->selected) {
1828 sp_nodepath_set_line_type(n, code);
1829 }
1830 }
1832 update_repr(nodepath);
1833 }
1835 /**
1836 * Call sp_nodepath_convert_node_type() for all selected nodes.
1837 */
1838 void
1839 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1840 {
1841 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1842 if (nodepath == NULL) return;
1844 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1845 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1846 }
1848 update_repr(nodepath);
1849 }
1851 /**
1852 * Change select status of node, update its own and neighbour handles.
1853 */
1854 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1855 {
1856 node->selected = selected;
1858 if (selected) {
1859 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1860 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1861 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1862 sp_knot_update_ctrl(node->knot);
1863 } else {
1864 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1865 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1866 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1867 sp_knot_update_ctrl(node->knot);
1868 }
1870 sp_node_ensure_ctrls(node);
1871 if (node->n.other) sp_node_ensure_ctrls(node->n.other);
1872 if (node->p.other) sp_node_ensure_ctrls(node->p.other);
1873 }
1875 /**
1876 \brief Select a node
1877 \param node The node to select
1878 \param incremental If true, add to selection, otherwise deselect others
1879 \param override If true, always select this node, otherwise toggle selected status
1880 */
1881 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1882 {
1883 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1885 if (incremental) {
1886 if (override) {
1887 if (!g_list_find(nodepath->selected, node)) {
1888 nodepath->selected = g_list_prepend(nodepath->selected, node);
1889 }
1890 sp_node_set_selected(node, TRUE);
1891 } else { // toggle
1892 if (node->selected) {
1893 g_assert(g_list_find(nodepath->selected, node));
1894 nodepath->selected = g_list_remove(nodepath->selected, node);
1895 } else {
1896 g_assert(!g_list_find(nodepath->selected, node));
1897 nodepath->selected = g_list_prepend(nodepath->selected, node);
1898 }
1899 sp_node_set_selected(node, !node->selected);
1900 }
1901 } else {
1902 sp_nodepath_deselect(nodepath);
1903 nodepath->selected = g_list_prepend(nodepath->selected, node);
1904 sp_node_set_selected(node, TRUE);
1905 }
1907 sp_nodepath_update_statusbar(nodepath);
1908 }
1911 /**
1912 \brief Deselect all nodes in the nodepath
1913 */
1914 void
1915 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1916 {
1917 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1919 while (nodepath->selected) {
1920 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1921 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1922 }
1923 sp_nodepath_update_statusbar(nodepath);
1924 }
1926 /**
1927 \brief Select or invert selection of all nodes in the nodepath
1928 */
1929 void
1930 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1931 {
1932 if (!nodepath) return;
1934 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1935 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1936 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1937 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1938 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1939 }
1940 }
1941 }
1943 /**
1944 * If nothing selected, does the same as sp_nodepath_select_all();
1945 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1946 * (i.e., similar to "select all in layer", with the "selected" subpaths
1947 * being treated as "layers" in the path).
1948 */
1949 void
1950 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1951 {
1952 if (!nodepath) return;
1954 if (g_list_length (nodepath->selected) == 0) {
1955 sp_nodepath_select_all (nodepath, invert);
1956 return;
1957 }
1959 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1960 GSList *subpaths = NULL;
1962 for (GList *l = copy; l != NULL; l = l->next) {
1963 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1964 Inkscape::NodePath::SubPath *subpath = n->subpath;
1965 if (!g_slist_find (subpaths, subpath))
1966 subpaths = g_slist_prepend (subpaths, subpath);
1967 }
1969 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
1970 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
1971 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1972 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1973 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
1974 }
1975 }
1977 g_slist_free (subpaths);
1978 g_list_free (copy);
1979 }
1981 /**
1982 * \brief Select the node after the last selected; if none is selected,
1983 * select the first within path.
1984 */
1985 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
1986 {
1987 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1989 Inkscape::NodePath::Node *last = NULL;
1990 if (nodepath->selected) {
1991 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1992 Inkscape::NodePath::SubPath *subpath, *subpath_next;
1993 subpath = (Inkscape::NodePath::SubPath *) spl->data;
1994 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1995 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1996 if (node->selected) {
1997 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
1998 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
1999 if (spl->next) { // there's a next subpath
2000 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2001 last = subpath_next->first;
2002 } else if (spl->prev) { // there's a previous subpath
2003 last = NULL; // to be set later to the first node of first subpath
2004 } else {
2005 last = node->n.other;
2006 }
2007 } else {
2008 last = node->n.other;
2009 }
2010 } else {
2011 if (node->n.other) {
2012 last = node->n.other;
2013 } else {
2014 if (spl->next) { // there's a next subpath
2015 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2016 last = subpath_next->first;
2017 } else if (spl->prev) { // there's a previous subpath
2018 last = NULL; // to be set later to the first node of first subpath
2019 } else {
2020 last = (Inkscape::NodePath::Node *) subpath->first;
2021 }
2022 }
2023 }
2024 }
2025 }
2026 }
2027 sp_nodepath_deselect(nodepath);
2028 }
2030 if (last) { // there's at least one more node after selected
2031 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2032 } else { // no more nodes, select the first one in first subpath
2033 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2034 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2035 }
2036 }
2038 /**
2039 * \brief Select the node before the first selected; if none is selected,
2040 * select the last within path
2041 */
2042 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2043 {
2044 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2046 Inkscape::NodePath::Node *last = NULL;
2047 if (nodepath->selected) {
2048 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2049 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2050 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2051 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2052 if (node->selected) {
2053 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2054 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2055 if (spl->prev) { // there's a prev subpath
2056 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2057 last = subpath_prev->last;
2058 } else if (spl->next) { // there's a next subpath
2059 last = NULL; // to be set later to the last node of last subpath
2060 } else {
2061 last = node->p.other;
2062 }
2063 } else {
2064 last = node->p.other;
2065 }
2066 } else {
2067 if (node->p.other) {
2068 last = node->p.other;
2069 } else {
2070 if (spl->prev) { // there's a prev subpath
2071 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2072 last = subpath_prev->last;
2073 } else if (spl->next) { // there's a next subpath
2074 last = NULL; // to be set later to the last node of last subpath
2075 } else {
2076 last = (Inkscape::NodePath::Node *) subpath->last;
2077 }
2078 }
2079 }
2080 }
2081 }
2082 }
2083 sp_nodepath_deselect(nodepath);
2084 }
2086 if (last) { // there's at least one more node before selected
2087 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2088 } else { // no more nodes, select the last one in last subpath
2089 GList *spl = g_list_last(nodepath->subpaths);
2090 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2091 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2092 }
2093 }
2095 /**
2096 * \brief Select all nodes that are within the rectangle.
2097 */
2098 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2099 {
2100 if (!incremental) {
2101 sp_nodepath_deselect(nodepath);
2102 }
2104 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2105 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2106 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2107 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2109 if (b.contains(node->pos)) {
2110 sp_nodepath_node_select(node, TRUE, TRUE);
2111 }
2112 }
2113 }
2114 }
2116 /**
2117 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2118 */
2119 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2120 {
2121 if (!nodepath->selected) {
2122 return NULL;
2123 }
2125 GList *r = NULL;
2126 guint i = 0;
2127 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2128 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2129 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2130 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2131 i++;
2132 if (node->selected) {
2133 r = g_list_append(r, GINT_TO_POINTER(i));
2134 }
2135 }
2136 }
2137 return r;
2138 }
2140 /**
2141 \brief Restores selection by selecting nodes whose positions are in the list
2142 */
2143 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2144 {
2145 sp_nodepath_deselect(nodepath);
2147 guint i = 0;
2148 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2149 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2150 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2151 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2152 i++;
2153 if (g_list_find(r, GINT_TO_POINTER(i))) {
2154 sp_nodepath_node_select(node, TRUE, TRUE);
2155 }
2156 }
2157 }
2159 }
2161 /**
2162 \brief Adjusts control point according to node type and line code.
2163 */
2164 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust)
2165 {
2166 double len, otherlen, linelen;
2168 g_assert(node);
2170 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2171 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2173 /** \todo fixme: */
2174 if (me->other == NULL) return;
2175 if (other->other == NULL) return;
2177 /* I have line */
2179 NRPathcode mecode, ocode;
2180 if (which_adjust == 1) {
2181 mecode = (NRPathcode)me->other->code;
2182 ocode = (NRPathcode)node->code;
2183 } else {
2184 mecode = (NRPathcode)node->code;
2185 ocode = (NRPathcode)other->other->code;
2186 }
2188 if (mecode == NR_LINETO) return;
2190 /* I am curve */
2192 if (other->other == NULL) return;
2194 /* Other has line */
2196 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2198 NR::Point delta;
2199 if (ocode == NR_LINETO) {
2200 /* other is lineto, we are either smooth or symm */
2201 Inkscape::NodePath::Node *othernode = other->other;
2202 len = NR::L2(me->pos - node->pos);
2203 delta = node->pos - othernode->pos;
2204 linelen = NR::L2(delta);
2205 if (linelen < 1e-18) return;
2207 me->pos = node->pos + (len / linelen)*delta;
2209 sp_node_ensure_ctrls(node);
2210 return;
2211 }
2213 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2215 me->pos = 2 * node->pos - other->pos;
2217 sp_node_ensure_ctrls(node);
2218 return;
2219 }
2221 /* We are smooth */
2223 len = NR::L2(me->pos - node->pos);
2224 delta = other->pos - node->pos;
2225 otherlen = NR::L2(delta);
2226 if (otherlen < 1e-18) return;
2228 me->pos = node->pos - (len / otherlen) * delta;
2230 sp_node_ensure_ctrls(node);
2231 }
2233 /**
2234 \brief Adjusts control point according to node type and line code
2235 */
2236 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node)
2237 {
2238 g_assert(node);
2240 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2242 /* we are either smooth or symm */
2244 if (node->p.other == NULL) return;
2246 if (node->n.other == NULL) return;
2248 if (node->code == NR_LINETO) {
2249 if (node->n.other->code == NR_LINETO) return;
2250 sp_node_adjust_knot(node, 1);
2251 sp_node_ensure_ctrls(node);
2252 return;
2253 }
2255 if (node->n.other->code == NR_LINETO) {
2256 if (node->code == NR_LINETO) return;
2257 sp_node_adjust_knot(node, -1);
2258 sp_node_ensure_ctrls(node);
2259 return;
2260 }
2262 /* both are curves */
2264 NR::Point const delta( node->n.pos - node->p.pos );
2266 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2267 node->p.pos = node->pos - delta / 2;
2268 node->n.pos = node->pos + delta / 2;
2269 sp_node_ensure_ctrls(node);
2270 return;
2271 }
2273 /* We are smooth */
2275 double plen = NR::L2(node->p.pos - node->pos);
2276 if (plen < 1e-18) return;
2277 double nlen = NR::L2(node->n.pos - node->pos);
2278 if (nlen < 1e-18) return;
2279 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2280 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2281 sp_node_ensure_ctrls(node);
2282 }
2284 /**
2285 * Knot events handler callback.
2286 */
2287 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2288 {
2289 gboolean ret = FALSE;
2290 switch (event->type) {
2291 case GDK_ENTER_NOTIFY:
2292 active_node = n;
2293 break;
2294 case GDK_LEAVE_NOTIFY:
2295 active_node = NULL;
2296 break;
2297 case GDK_KEY_PRESS:
2298 switch (get_group0_keyval (&event->key)) {
2299 case GDK_space:
2300 if (event->key.state & GDK_BUTTON1_MASK) {
2301 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2302 stamp_repr(nodepath);
2303 ret = TRUE;
2304 }
2305 break;
2306 default:
2307 break;
2308 }
2309 break;
2310 default:
2311 break;
2312 }
2314 return ret;
2315 }
2317 /**
2318 * Handle keypress on node; directly called.
2319 */
2320 gboolean node_key(GdkEvent *event)
2321 {
2322 Inkscape::NodePath::Path *np;
2324 // there is no way to verify nodes so set active_node to nil when deleting!!
2325 if (active_node == NULL) return FALSE;
2327 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2328 gint ret = FALSE;
2329 switch (get_group0_keyval (&event->key)) {
2330 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2331 case GDK_BackSpace:
2332 np = active_node->subpath->nodepath;
2333 sp_nodepath_node_destroy(active_node);
2334 update_repr(np);
2335 active_node = NULL;
2336 ret = TRUE;
2337 break;
2338 case GDK_c:
2339 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2340 ret = TRUE;
2341 break;
2342 case GDK_s:
2343 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2344 ret = TRUE;
2345 break;
2346 case GDK_y:
2347 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2348 ret = TRUE;
2349 break;
2350 case GDK_b:
2351 sp_nodepath_node_break(active_node);
2352 ret = TRUE;
2353 break;
2354 }
2355 return ret;
2356 }
2357 return FALSE;
2358 }
2360 /**
2361 * Mouseclick on node callback.
2362 */
2363 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2364 {
2365 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2367 if (state & GDK_CONTROL_MASK) {
2368 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2370 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2371 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2372 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2373 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2374 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2375 } else {
2376 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2377 }
2378 update_repr(nodepath);
2379 sp_nodepath_update_statusbar(nodepath);
2381 } else { //ctrl+alt+click: delete node
2382 sp_nodepath_node_destroy(n);
2383 //clean up the nodepath (such as for trivial subpaths)
2384 sp_nodepath_cleanup(nodepath);
2386 // if the entire nodepath is removed, delete the selected object.
2387 if (nodepath->subpaths == NULL ||
2388 sp_nodepath_get_node_count(nodepath) < 2) {
2389 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2390 sp_nodepath_destroy(nodepath);
2391 sp_selection_delete();
2392 sp_document_done (document);
2394 } else {
2395 sp_nodepath_ensure_ctrls(nodepath);
2396 update_repr(nodepath);
2397 sp_nodepath_update_statusbar(nodepath);
2398 }
2399 }
2401 } else {
2402 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2403 }
2404 }
2406 /**
2407 * Mouse grabbed node callback.
2408 */
2409 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2410 {
2411 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2413 n->origin = knot->pos;
2415 if (!n->selected) {
2416 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2417 }
2418 }
2420 /**
2421 * Mouse ungrabbed node callback.
2422 */
2423 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2424 {
2425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2427 n->dragging_out = NULL;
2429 update_repr(n->subpath->nodepath);
2430 }
2432 /**
2433 * The point on a line, given by its angle, closest to the given point.
2434 * \param p A point.
2435 * \param a Angle of the line; it is assumed to go through coordinate origin.
2436 * \param closest Pointer to the point struct where the result is stored.
2437 * \todo FIXME: use dot product perhaps?
2438 */
2439 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2440 {
2441 if (a == HUGE_VAL) { // vertical
2442 *closest = NR::Point(0, (*p)[NR::Y]);
2443 } else {
2444 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2445 (*closest)[NR::Y] = a * (*closest)[NR::X];
2446 }
2447 }
2449 /**
2450 * Distance from the point to a line given by its angle.
2451 * \param p A point.
2452 * \param a Angle of the line; it is assumed to go through coordinate origin.
2453 */
2454 static double point_line_distance(NR::Point *p, double a)
2455 {
2456 NR::Point c;
2457 point_line_closest(p, a, &c);
2458 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]));
2459 }
2461 /**
2462 * Callback for node "request" signal.
2463 * \todo fixme: This goes to "moved" event? (lauris)
2464 */
2465 static gboolean
2466 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2467 {
2468 double yn, xn, yp, xp;
2469 double an, ap, na, pa;
2470 double d_an, d_ap, d_na, d_pa;
2471 gboolean collinear = FALSE;
2472 NR::Point c;
2473 NR::Point pr;
2475 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2477 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2478 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2480 NR::Point mouse = (*p);
2482 if (!n->dragging_out) {
2483 // This is the first drag-out event; find out which handle to drag out
2484 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2485 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2487 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2488 return FALSE;
2490 Inkscape::NodePath::NodeSide *opposite;
2491 if (appr_p > appr_n) { // closer to p
2492 n->dragging_out = &n->p;
2493 opposite = &n->n;
2494 n->code = NR_CURVETO;
2495 } else if (appr_p < appr_n) { // closer to n
2496 n->dragging_out = &n->n;
2497 opposite = &n->p;
2498 n->n.other->code = NR_CURVETO;
2499 } else { // p and n nodes are the same
2500 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2501 n->dragging_out = &n->p;
2502 opposite = &n->n;
2503 n->code = NR_CURVETO;
2504 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2505 n->dragging_out = &n->n;
2506 opposite = &n->p;
2507 n->n.other->code = NR_CURVETO;
2508 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2509 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);
2510 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);
2511 if (appr_other_p > appr_other_n) { // closer to other's p handle
2512 n->dragging_out = &n->n;
2513 opposite = &n->p;
2514 n->n.other->code = NR_CURVETO;
2515 } else { // closer to other's n handle
2516 n->dragging_out = &n->p;
2517 opposite = &n->n;
2518 n->code = NR_CURVETO;
2519 }
2520 }
2521 }
2523 // if there's another handle, make sure the one we drag out starts parallel to it
2524 if (opposite->pos != n->pos) {
2525 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2526 }
2528 // knots might not be created yet!
2529 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2530 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2531 }
2533 // pass this on to the handle-moved callback
2534 node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2535 sp_node_ensure_ctrls(n);
2536 return TRUE;
2537 }
2539 if (state & GDK_CONTROL_MASK) { // constrained motion
2541 // calculate relative distances of handles
2542 // n handle:
2543 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2544 xn = n->n.pos[NR::X] - n->pos[NR::X];
2545 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2546 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2547 if (n->n.other) { // if there is the next point
2548 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2549 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2550 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2551 }
2552 }
2553 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2554 if (yn < 0) { xn = -xn; yn = -yn; }
2556 // p handle:
2557 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2558 xp = n->p.pos[NR::X] - n->pos[NR::X];
2559 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2560 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2561 if (n->p.other) {
2562 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2563 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2564 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2565 }
2566 }
2567 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2568 if (yp < 0) { xp = -xp; yp = -yp; }
2570 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2571 // sliding on handles, only if at least one of the handles is non-vertical
2572 // (otherwise it's the same as ctrl+drag anyway)
2574 // calculate angles of the control handles
2575 if (xn == 0) {
2576 if (yn == 0) { // no handle, consider it the continuation of the other one
2577 an = 0;
2578 collinear = TRUE;
2579 }
2580 else an = 0; // vertical; set the angle to horizontal
2581 } else an = yn/xn;
2583 if (xp == 0) {
2584 if (yp == 0) { // no handle, consider it the continuation of the other one
2585 ap = an;
2586 }
2587 else ap = 0; // vertical; set the angle to horizontal
2588 } else ap = yp/xp;
2590 if (collinear) an = ap;
2592 // angles of the perpendiculars; HUGE_VAL means vertical
2593 if (an == 0) na = HUGE_VAL; else na = -1/an;
2594 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2596 //g_print("an %g ap %g\n", an, ap);
2598 // mouse point relative to the node's original pos
2599 pr = (*p) - n->origin;
2601 // distances to the four lines (two handles and two perpendiculars)
2602 d_an = point_line_distance(&pr, an);
2603 d_na = point_line_distance(&pr, na);
2604 d_ap = point_line_distance(&pr, ap);
2605 d_pa = point_line_distance(&pr, pa);
2607 // find out which line is the closest, save its closest point in c
2608 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2609 point_line_closest(&pr, an, &c);
2610 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2611 point_line_closest(&pr, ap, &c);
2612 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2613 point_line_closest(&pr, na, &c);
2614 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2615 point_line_closest(&pr, pa, &c);
2616 }
2618 // move the node to the closest point
2619 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2620 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2621 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2623 } else { // constraining to hor/vert
2625 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2626 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2627 } else { // snap to vert
2628 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2629 }
2630 }
2631 } else { // move freely
2632 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2633 (*p)[NR::X] - n->pos[NR::X],
2634 (*p)[NR::Y] - n->pos[NR::Y],
2635 (state & GDK_SHIFT_MASK) == 0);
2636 }
2638 n->subpath->nodepath->desktop->scroll_to_point(p);
2640 return TRUE;
2641 }
2643 /**
2644 * Node handle clicked callback.
2645 */
2646 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data)
2647 {
2648 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2650 if (state & GDK_CONTROL_MASK) { // "delete" handle
2651 if (n->p.knot == knot) {
2652 n->p.pos = n->pos;
2653 } else if (n->n.knot == knot) {
2654 n->n.pos = n->pos;
2655 }
2656 sp_node_ensure_ctrls(n);
2657 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2658 update_repr(nodepath);
2659 sp_nodepath_update_statusbar(nodepath);
2661 } else { // just select or add to selection, depending in Shift
2662 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2663 }
2664 }
2666 /**
2667 * Node handle grabbed callback.
2668 */
2669 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data)
2670 {
2671 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2673 if (!n->selected) {
2674 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2675 }
2677 // remember the origin of the control
2678 if (n->p.knot == knot) {
2679 n->p.origin = n->p.pos - n->pos;
2680 } else if (n->n.knot == knot) {
2681 n->n.origin = n->n.pos - n->pos;
2682 } else {
2683 g_assert_not_reached();
2684 }
2686 }
2688 /**
2689 * Node handle ungrabbed callback.
2690 */
2691 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data)
2692 {
2693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2695 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2696 if (n->p.knot == knot) {
2697 n->p.origin.a = 0;
2698 sp_knot_set_position(knot, &n->p.pos, state);
2699 } else if (n->n.knot == knot) {
2700 n->n.origin.a = 0;
2701 sp_knot_set_position(knot, &n->n.pos, state);
2702 } else {
2703 g_assert_not_reached();
2704 }
2706 update_repr(n->subpath->nodepath);
2707 }
2709 /**
2710 * Node handle "request" signal callback.
2711 */
2712 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2713 {
2714 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2716 Inkscape::NodePath::NodeSide *me, *opposite;
2717 gint which;
2718 if (n->p.knot == knot) {
2719 me = &n->p;
2720 opposite = &n->n;
2721 which = -1;
2722 } else if (n->n.knot == knot) {
2723 me = &n->n;
2724 opposite = &n->p;
2725 which = 1;
2726 } else {
2727 me = opposite = NULL;
2728 which = 0;
2729 g_assert_not_reached();
2730 }
2732 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2734 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2736 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2737 /* We are smooth node adjacent with line */
2738 NR::Point const delta = *p - n->pos;
2739 NR::Coord const len = NR::L2(delta);
2740 Inkscape::NodePath::Node *othernode = opposite->other;
2741 NR::Point const ndelta = n->pos - othernode->pos;
2742 NR::Coord const linelen = NR::L2(ndelta);
2743 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2744 NR::Coord const scal = dot(delta, ndelta) / linelen;
2745 (*p) = n->pos + (scal / linelen) * ndelta;
2746 }
2747 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2748 } else {
2749 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2750 }
2752 sp_node_adjust_knot(n, -which);
2754 return FALSE;
2755 }
2757 /**
2758 * Node handle moved callback.
2759 */
2760 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2761 {
2762 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2764 Inkscape::NodePath::NodeSide *me;
2765 Inkscape::NodePath::NodeSide *other;
2766 if (n->p.knot == knot) {
2767 me = &n->p;
2768 other = &n->n;
2769 } else if (n->n.knot == knot) {
2770 me = &n->n;
2771 other = &n->p;
2772 } else {
2773 me = NULL;
2774 other = NULL;
2775 g_assert_not_reached();
2776 }
2778 // calculate radial coordinates of the grabbed control, other control, and the mouse point
2779 Radial rme(me->pos - n->pos);
2780 Radial rother(other->pos - n->pos);
2781 Radial rnew(*p - n->pos);
2783 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2784 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2785 /* 0 interpreted as "no snapping". */
2787 // The closest PI/snaps angle, starting from zero.
2788 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2789 if (me->origin.a == HUGE_VAL) {
2790 // ortho doesn't exist: original control was zero length.
2791 rnew.a = a_snapped;
2792 } else {
2793 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2794 * its opposite and perpendiculars). */
2795 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2797 // Snap to the closest.
2798 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2799 ? a_snapped
2800 : a_ortho );
2801 }
2802 }
2804 if (state & GDK_MOD1_MASK) {
2805 // lock handle length
2806 rnew.r = me->origin.r;
2807 }
2809 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2810 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2811 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2812 rother.a += rnew.a - rme.a;
2813 other->pos = NR::Point(rother) + n->pos;
2814 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2815 sp_knot_set_position(other->knot, &other->pos, 0);
2816 }
2818 me->pos = NR::Point(rnew) + n->pos;
2819 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2821 // this is what sp_knot_set_position does, but without emitting the signal:
2822 // we cannot emit a "moved" signal because we're now processing it
2823 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2825 knot->desktop->set_coordinate_status(me->pos);
2827 update_object(n->subpath->nodepath);
2829 /* status text */
2830 SPDesktop *desktop = n->subpath->nodepath->desktop;
2831 if (!desktop) return;
2832 SPEventContext *ec = desktop->event_context;
2833 if (!ec) return;
2834 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2835 if (!mc) return;
2837 double degrees = 180 / M_PI * rnew.a;
2838 if (degrees > 180) degrees -= 360;
2839 if (degrees < -180) degrees += 360;
2840 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2841 degrees = angle_to_compass (degrees);
2843 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2845 mc->setF(Inkscape::NORMAL_MESSAGE,
2846 _("<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);
2848 g_string_free(length, TRUE);
2849 }
2851 /**
2852 * Node handle event callback.
2853 */
2854 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2855 {
2856 gboolean ret = FALSE;
2857 switch (event->type) {
2858 case GDK_KEY_PRESS:
2859 switch (get_group0_keyval (&event->key)) {
2860 case GDK_space:
2861 if (event->key.state & GDK_BUTTON1_MASK) {
2862 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2863 stamp_repr(nodepath);
2864 ret = TRUE;
2865 }
2866 break;
2867 default:
2868 break;
2869 }
2870 break;
2871 default:
2872 break;
2873 }
2875 return ret;
2876 }
2878 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2879 Radial &rme, Radial &rother, gboolean const both)
2880 {
2881 rme.a += angle;
2882 if ( both
2883 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2884 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2885 {
2886 rother.a += angle;
2887 }
2888 }
2890 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2891 Radial &rme, Radial &rother, gboolean const both)
2892 {
2893 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2895 gdouble r;
2896 if ( both
2897 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2898 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2899 {
2900 r = MAX(rme.r, rother.r);
2901 } else {
2902 r = rme.r;
2903 }
2905 gdouble const weird_angle = atan2(norm_angle, r);
2906 /* Bulia says norm_angle is just the visible distance that the
2907 * object's end must travel on the screen. Left as 'angle' for want of
2908 * a better name.*/
2910 rme.a += weird_angle;
2911 if ( both
2912 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2913 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2914 {
2915 rother.a += weird_angle;
2916 }
2917 }
2919 /**
2920 * Rotate one node.
2921 */
2922 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2923 {
2924 Inkscape::NodePath::NodeSide *me, *other;
2925 bool both = false;
2927 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2928 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2930 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2931 me = &(n->p);
2932 other = &(n->n);
2933 } else if (!n->p.other) {
2934 me = &(n->n);
2935 other = &(n->p);
2936 } else {
2937 if (which > 0) { // right handle
2938 if (xn > xp) {
2939 me = &(n->n);
2940 other = &(n->p);
2941 } else {
2942 me = &(n->p);
2943 other = &(n->n);
2944 }
2945 } else if (which < 0){ // left handle
2946 if (xn <= xp) {
2947 me = &(n->n);
2948 other = &(n->p);
2949 } else {
2950 me = &(n->p);
2951 other = &(n->n);
2952 }
2953 } else { // both handles
2954 me = &(n->n);
2955 other = &(n->p);
2956 both = true;
2957 }
2958 }
2960 Radial rme(me->pos - n->pos);
2961 Radial rother(other->pos - n->pos);
2963 if (screen) {
2964 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2965 } else {
2966 node_rotate_one_internal (*n, angle, rme, rother, both);
2967 }
2969 me->pos = n->pos + NR::Point(rme);
2971 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
2972 other->pos = n->pos + NR::Point(rother);
2973 }
2975 sp_node_ensure_ctrls(n);
2976 }
2978 /**
2979 * Rotate selected nodes.
2980 */
2981 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
2982 {
2983 if (!nodepath || !nodepath->selected) return;
2985 if (g_list_length(nodepath->selected) == 1) {
2986 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
2987 node_rotate_one (n, angle, which, screen);
2988 } else {
2989 // rotate as an object:
2991 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
2992 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
2993 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2994 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2995 box.expandTo (n->pos); // contain all selected nodes
2996 }
2998 gdouble rot;
2999 if (screen) {
3000 gdouble const zoom = nodepath->desktop->current_zoom();
3001 gdouble const zmove = angle / zoom;
3002 gdouble const r = NR::L2(box.max() - box.midpoint());
3003 rot = atan2(zmove, r);
3004 } else {
3005 rot = angle;
3006 }
3008 NR::Matrix t =
3009 NR::Matrix (NR::translate(-box.midpoint())) *
3010 NR::Matrix (NR::rotate(rot)) *
3011 NR::Matrix (NR::translate(box.midpoint()));
3013 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3014 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3015 n->pos *= t;
3016 n->n.pos *= t;
3017 n->p.pos *= t;
3018 sp_node_ensure_ctrls(n);
3019 }
3020 }
3022 update_object(nodepath);
3023 /// \todo fixme: use _keyed
3024 update_repr(nodepath);
3025 }
3027 /**
3028 * Scale one node.
3029 */
3030 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3031 {
3032 bool both = false;
3033 Inkscape::NodePath::NodeSide *me, *other;
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 n->code = NR_CURVETO;
3042 } else if (!n->p.other) {
3043 me = &(n->n);
3044 other = &(n->p);
3045 if (n->n.other)
3046 n->n.other->code = NR_CURVETO;
3047 } else {
3048 if (which > 0) { // right handle
3049 if (xn > xp) {
3050 me = &(n->n);
3051 other = &(n->p);
3052 if (n->n.other)
3053 n->n.other->code = NR_CURVETO;
3054 } else {
3055 me = &(n->p);
3056 other = &(n->n);
3057 n->code = NR_CURVETO;
3058 }
3059 } else if (which < 0){ // left handle
3060 if (xn <= xp) {
3061 me = &(n->n);
3062 other = &(n->p);
3063 if (n->n.other)
3064 n->n.other->code = NR_CURVETO;
3065 } else {
3066 me = &(n->p);
3067 other = &(n->n);
3068 n->code = NR_CURVETO;
3069 }
3070 } else { // both handles
3071 me = &(n->n);
3072 other = &(n->p);
3073 both = true;
3074 n->code = NR_CURVETO;
3075 if (n->n.other)
3076 n->n.other->code = NR_CURVETO;
3077 }
3078 }
3080 Radial rme(me->pos - n->pos);
3081 Radial rother(other->pos - n->pos);
3083 rme.r += grow;
3084 if (rme.r < 0) rme.r = 0;
3085 if (rme.a == HUGE_VAL) {
3086 if (me->other) { // if direction is unknown, initialize it towards the next node
3087 Radial rme_next(me->other->pos - n->pos);
3088 rme.a = rme_next.a;
3089 } else { // if there's no next, initialize to 0
3090 rme.a = 0;
3091 }
3092 }
3093 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3094 rother.r += grow;
3095 if (rother.r < 0) rother.r = 0;
3096 if (rother.a == HUGE_VAL) {
3097 rother.a = rme.a + M_PI;
3098 }
3099 }
3101 me->pos = n->pos + NR::Point(rme);
3103 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3104 other->pos = n->pos + NR::Point(rother);
3105 }
3107 sp_node_ensure_ctrls(n);
3108 }
3110 /**
3111 * Scale selected nodes.
3112 */
3113 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3114 {
3115 if (!nodepath || !nodepath->selected) return;
3117 if (g_list_length(nodepath->selected) == 1) {
3118 // scale handles of the single selected node
3119 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3120 node_scale_one (n, grow, which);
3121 } else {
3122 // scale nodes as an "object":
3124 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3125 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3126 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3127 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3128 box.expandTo (n->pos); // contain all selected nodes
3129 }
3131 double scale = (box.maxExtent() + grow)/box.maxExtent();
3133 NR::Matrix t =
3134 NR::Matrix (NR::translate(-box.midpoint())) *
3135 NR::Matrix (NR::scale(scale, scale)) *
3136 NR::Matrix (NR::translate(box.midpoint()));
3138 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3139 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3140 n->pos *= t;
3141 n->n.pos *= t;
3142 n->p.pos *= t;
3143 sp_node_ensure_ctrls(n);
3144 }
3145 }
3147 update_object(nodepath);
3148 /// \todo fixme: use _keyed
3149 update_repr(nodepath);
3150 }
3152 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3153 {
3154 if (!nodepath) return;
3155 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3156 }
3158 /**
3159 * Flip selected nodes horizontally/vertically.
3160 */
3161 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3162 {
3163 if (!nodepath || !nodepath->selected) return;
3165 if (g_list_length(nodepath->selected) == 1) {
3166 // flip handles of the single selected node
3167 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3168 double temp = n->p.pos[axis];
3169 n->p.pos[axis] = n->n.pos[axis];
3170 n->n.pos[axis] = temp;
3171 sp_node_ensure_ctrls(n);
3172 } else {
3173 // scale nodes as an "object":
3175 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3176 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3177 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3178 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3179 box.expandTo (n->pos); // contain all selected nodes
3180 }
3182 NR::Matrix t =
3183 NR::Matrix (NR::translate(-box.midpoint())) *
3184 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3185 NR::Matrix (NR::translate(box.midpoint()));
3187 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3188 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3189 n->pos *= t;
3190 n->n.pos *= t;
3191 n->p.pos *= t;
3192 sp_node_ensure_ctrls(n);
3193 }
3194 }
3196 update_object(nodepath);
3197 /// \todo fixme: use _keyed
3198 update_repr(nodepath);
3199 }
3201 //-----------------------------------------------
3202 /**
3203 * Return new subpath under given nodepath.
3204 */
3205 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3206 {
3207 g_assert(nodepath);
3208 g_assert(nodepath->desktop);
3210 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3212 s->nodepath = nodepath;
3213 s->closed = FALSE;
3214 s->nodes = NULL;
3215 s->first = NULL;
3216 s->last = NULL;
3218 // do not use prepend here because:
3219 // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with
3220 // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not
3221 // the i-th node in the nodepath (only if there are multiple subpaths)
3222 // note that the problem only arise when called from subpath_from_bpath(), since for all the other
3223 // cases, the repr is updated after the call to sp_nodepath_subpath_new()
3224 nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s);
3226 return s;
3227 }
3229 /**
3230 * Destroy nodes in subpath, then subpath itself.
3231 */
3232 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3233 {
3234 g_assert(subpath);
3235 g_assert(subpath->nodepath);
3236 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3238 while (subpath->nodes) {
3239 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3240 }
3242 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3244 g_free(subpath);
3245 }
3247 /**
3248 * Link head to tail in subpath.
3249 */
3250 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3251 {
3252 g_assert(!sp->closed);
3253 g_assert(sp->last != sp->first);
3254 g_assert(sp->first->code == NR_MOVETO);
3256 sp->closed = TRUE;
3258 //Link the head to the tail
3259 sp->first->p.other = sp->last;
3260 sp->last->n.other = sp->first;
3261 sp->last->n.pos = sp->first->n.pos;
3262 sp->first = sp->last;
3264 //Remove the extra end node
3265 sp_nodepath_node_destroy(sp->last->n.other);
3266 }
3268 /**
3269 * Open closed (loopy) subpath at node.
3270 */
3271 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3272 {
3273 g_assert(sp->closed);
3274 g_assert(n->subpath == sp);
3275 g_assert(sp->first == sp->last);
3277 /* We create new startpoint, current node will become last one */
3279 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3280 &n->pos, &n->pos, &n->n.pos);
3283 sp->closed = FALSE;
3285 //Unlink to make a head and tail
3286 sp->first = new_path;
3287 sp->last = n;
3288 n->n.other = NULL;
3289 new_path->p.other = NULL;
3290 }
3292 /**
3293 * Returns area in triangle given by points; may be negative.
3294 */
3295 inline double
3296 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3297 {
3298 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]);
3299 }
3301 /**
3302 * Return new node in subpath with given properties.
3303 * \param pos Position of node.
3304 * \param ppos Handle position in previous direction
3305 * \param npos Handle position in previous direction
3306 */
3307 Inkscape::NodePath::Node *
3308 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)
3309 {
3310 g_assert(sp);
3311 g_assert(sp->nodepath);
3312 g_assert(sp->nodepath->desktop);
3314 if (nodechunk == NULL)
3315 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3317 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3319 n->subpath = sp;
3321 if (type != Inkscape::NodePath::NODE_NONE) {
3322 // use the type from sodipodi:nodetypes
3323 n->type = type;
3324 } else {
3325 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3326 // points are (almost) collinear
3327 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3328 // endnode, or a node with a retracted handle
3329 n->type = Inkscape::NodePath::NODE_CUSP;
3330 } else {
3331 n->type = Inkscape::NodePath::NODE_SMOOTH;
3332 }
3333 } else {
3334 n->type = Inkscape::NodePath::NODE_CUSP;
3335 }
3336 }
3338 n->code = code;
3339 n->selected = FALSE;
3340 n->pos = *pos;
3341 n->p.pos = *ppos;
3342 n->n.pos = *npos;
3344 n->dragging_out = NULL;
3346 Inkscape::NodePath::Node *prev;
3347 if (next) {
3348 //g_assert(g_list_find(sp->nodes, next));
3349 prev = next->p.other;
3350 } else {
3351 prev = sp->last;
3352 }
3354 if (prev)
3355 prev->n.other = n;
3356 else
3357 sp->first = n;
3359 if (next)
3360 next->p.other = n;
3361 else
3362 sp->last = n;
3364 n->p.other = prev;
3365 n->n.other = next;
3367 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"));
3368 sp_knot_set_position(n->knot, pos, 0);
3370 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3371 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3372 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3373 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3374 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3375 sp_knot_update_ctrl(n->knot);
3377 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3378 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3379 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3380 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3381 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3382 sp_knot_show(n->knot);
3384 // We only create side knots and lines on demand
3385 n->p.knot = NULL;
3386 n->p.line = NULL;
3387 n->n.knot = NULL;
3388 n->n.line = NULL;
3390 sp->nodes = g_list_prepend(sp->nodes, n);
3392 return n;
3393 }
3395 /**
3396 * Destroy node and its knots, link neighbors in subpath.
3397 */
3398 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3399 {
3400 g_assert(node);
3401 g_assert(node->subpath);
3402 g_assert(SP_IS_KNOT(node->knot));
3403 // g_assert(g_list_find(node->subpath->nodes, node));
3405 Inkscape::NodePath::SubPath *sp = node->subpath;
3407 if (node->selected) { // first, deselect
3408 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3409 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3410 }
3412 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3414 g_object_unref(G_OBJECT(node->knot));
3415 if (node->p.knot)
3416 g_object_unref(G_OBJECT(node->p.knot));
3417 if (node->n.knot)
3418 g_object_unref(G_OBJECT(node->n.knot));
3420 if (node->p.line)
3421 gtk_object_destroy(GTK_OBJECT(node->p.line));
3422 if (node->n.line)
3423 gtk_object_destroy(GTK_OBJECT(node->n.line));
3425 if (sp->nodes) { // there are others nodes on the subpath
3426 if (sp->closed) {
3427 if (sp->first == node) {
3428 g_assert(sp->last == node);
3429 sp->first = node->n.other;
3430 sp->last = sp->first;
3431 }
3432 node->p.other->n.other = node->n.other;
3433 node->n.other->p.other = node->p.other;
3434 } else {
3435 if (sp->first == node) {
3436 sp->first = node->n.other;
3437 sp->first->code = NR_MOVETO;
3438 }
3439 if (sp->last == node) sp->last = node->p.other;
3440 if (node->p.other) node->p.other->n.other = node->n.other;
3441 if (node->n.other) node->n.other->p.other = node->p.other;
3442 }
3443 } else { // this was the last node on subpath
3444 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3445 }
3447 g_mem_chunk_free(nodechunk, node);
3448 }
3450 /**
3451 * Returns one of the node's two knots (node sides).
3452 * \param which Indicates which side.
3453 * \return Pointer to previous node side if which==-1, next if which==1.
3454 */
3455 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3456 {
3457 g_assert(node);
3459 switch (which) {
3460 case -1:
3461 return &node->p;
3462 case 1:
3463 return &node->n;
3464 default:
3465 break;
3466 }
3468 g_assert_not_reached();
3470 return NULL;
3471 }
3473 /**
3474 * Return knot on other side of node.
3475 */
3476 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3477 {
3478 g_assert(node);
3480 if (me == &node->p) return &node->n;
3481 if (me == &node->n) return &node->p;
3483 g_assert_not_reached();
3485 return NULL;
3486 }
3488 /**
3489 * Return NRPathcode on this knot's side of the node.
3490 */
3491 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3492 {
3493 g_assert(node);
3495 if (me == &node->p) {
3496 if (node->p.other) return (NRPathcode)node->code;
3497 return NR_MOVETO;
3498 }
3500 if (me == &node->n) {
3501 if (node->n.other) return (NRPathcode)node->n.other->code;
3502 return NR_MOVETO;
3503 }
3505 g_assert_not_reached();
3507 return NR_END;
3508 }
3510 /**
3511 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3512 */
3513 Inkscape::NodePath::Node *
3514 sp_nodepath_get_node_by_index(int index)
3515 {
3516 Inkscape::NodePath::Node *e = NULL;
3518 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3519 if (!nodepath) {
3520 return e;
3521 }
3523 //find segment
3524 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3526 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3527 int n = g_list_length(sp->nodes);
3528 if (sp->closed) {
3529 n++;
3530 }
3532 //if the piece belongs to this subpath grab it
3533 //otherwise move onto the next subpath
3534 if (index < n) {
3535 e = sp->first;
3536 for (int i = 0; i < index; ++i) {
3537 e = e->n.other;
3538 }
3539 break;
3540 } else {
3541 if (sp->closed) {
3542 index -= (n+1);
3543 } else {
3544 index -= n;
3545 }
3546 }
3547 }
3549 return e;
3550 }
3552 /**
3553 * Returns plain text meaning of node type.
3554 */
3555 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3556 {
3557 unsigned retracted = 0;
3558 bool endnode = false;
3560 for (int which = -1; which <= 1; which += 2) {
3561 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3562 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3563 retracted ++;
3564 if (!side->other)
3565 endnode = true;
3566 }
3568 if (retracted == 0) {
3569 if (endnode) {
3570 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3571 return _("end node");
3572 } else {
3573 switch (node->type) {
3574 case Inkscape::NodePath::NODE_CUSP:
3575 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3576 return _("cusp");
3577 case Inkscape::NodePath::NODE_SMOOTH:
3578 // TRANSLATORS: "smooth" is an adjective here
3579 return _("smooth");
3580 case Inkscape::NodePath::NODE_SYMM:
3581 return _("symmetric");
3582 }
3583 }
3584 } else if (retracted == 1) {
3585 if (endnode) {
3586 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3587 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3588 } else {
3589 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3590 }
3591 } else {
3592 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3593 }
3595 return NULL;
3596 }
3598 /**
3599 * Handles content of statusbar as long as node tool is active.
3600 */
3601 void
3602 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3603 {
3604 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3605 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3607 gint total = 0;
3608 gint selected = 0;
3609 SPDesktop *desktop = NULL;
3611 if (nodepath) {
3612 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3613 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3614 total += g_list_length(subpath->nodes);
3615 }
3616 selected = g_list_length(nodepath->selected);
3617 desktop = nodepath->desktop;
3618 } else {
3619 desktop = SP_ACTIVE_DESKTOP;
3620 }
3622 SPEventContext *ec = desktop->event_context;
3623 if (!ec) return;
3624 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3625 if (!mc) return;
3627 if (selected == 0) {
3628 Inkscape::Selection *sel = desktop->selection;
3629 if (!sel || sel->isEmpty()) {
3630 mc->setF(Inkscape::NORMAL_MESSAGE,
3631 _("Select a single object to edit its nodes or handles."));
3632 } else {
3633 if (nodepath) {
3634 mc->setF(Inkscape::NORMAL_MESSAGE,
3635 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.",
3636 "<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.",
3637 total),
3638 total);
3639 } else {
3640 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3641 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3642 } else {
3643 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3644 }
3645 }
3646 }
3647 } else if (nodepath && selected == 1) {
3648 mc->setF(Inkscape::NORMAL_MESSAGE,
3649 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3650 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3651 total),
3652 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3653 } else {
3654 mc->setF(Inkscape::NORMAL_MESSAGE,
3655 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3656 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3657 total),
3658 selected, total, when_selected);
3659 }
3660 }
3663 /*
3664 Local Variables:
3665 mode:c++
3666 c-file-style:"stroustrup"
3667 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3668 indent-tabs-mode:nil
3669 fill-column:99
3670 End:
3671 */
3672 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :