8c08b17c9c7f37ea786ba82f6e7a82627094da0c
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 g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL);
839 } else {
840 g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL);
841 }
843 sp_node_adjust_knots(node);
845 sp_nodepath_update_statusbar(node->subpath->nodepath);
847 return node;
848 }
850 /**
851 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
852 * adjacent segments from lines to curves.
853 */
854 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
855 {
856 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
857 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
858 // convert adjacent segment BEFORE to curve
859 node->code = NR_CURVETO;
860 NR::Point delta;
861 if (node->n.other != NULL)
862 delta = node->n.other->pos - node->p.other->pos;
863 else
864 delta = node->pos - node->p.other->pos;
865 node->p.pos = node->pos - delta / 4;
866 sp_node_ensure_ctrls(node);
867 }
869 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
870 // convert adjacent segment AFTER to curve
871 node->n.other->code = NR_CURVETO;
872 NR::Point delta;
873 if (node->p.other != NULL)
874 delta = node->p.other->pos - node->n.other->pos;
875 else
876 delta = node->pos - node->n.other->pos;
877 node->n.pos = node->pos - delta / 4;
878 sp_node_ensure_ctrls(node);
879 }
880 }
882 sp_nodepath_set_node_type (node, type);
883 }
885 /**
886 * Move node to point, and adjust its and neighbouring handles.
887 */
888 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
889 {
890 NR::Point delta = p - node->pos;
891 node->pos = p;
893 node->p.pos += delta;
894 node->n.pos += delta;
896 if (node->p.other) {
897 if (node->code == NR_LINETO) {
898 sp_node_adjust_knot(node, 1);
899 sp_node_adjust_knot(node->p.other, -1);
900 }
901 }
902 if (node->n.other) {
903 if (node->n.other->code == NR_LINETO) {
904 sp_node_adjust_knot(node, -1);
905 sp_node_adjust_knot(node->n.other, 1);
906 }
907 }
909 sp_node_ensure_ctrls(node);
910 }
912 /**
913 * Call sp_node_moveto() for node selection and handle possible snapping.
914 */
915 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
916 bool const snap = true)
917 {
918 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
919 NR::Point delta(dx, dy);
920 NR::Point best_pt = delta;
922 if (snap) {
923 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
924 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
925 NR::Point p = n->pos + delta;
926 for (int dim = 0; dim < 2; dim++) {
927 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
928 Inkscape::Snapper::SNAP_POINT, p,
929 NR::Dim2(dim), nodepath->path);
930 if (dist < best[dim]) {
931 best[dim] = dist;
932 best_pt[dim] = p[dim] - n->pos[dim];
933 }
934 }
935 }
936 }
938 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
939 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
940 sp_node_moveto(n, n->pos + best_pt);
941 }
943 update_object(nodepath);
944 }
946 /**
947 * Move node selection to point, adjust its and neighbouring handles,
948 * handle possible snapping, and commit the change with possible undo.
949 */
950 void
951 sp_node_selected_move(gdouble dx, gdouble dy)
952 {
953 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
954 if (!nodepath) return;
956 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
958 if (dx == 0) {
959 update_repr_keyed(nodepath, "node:move:vertical");
960 } else if (dy == 0) {
961 update_repr_keyed(nodepath, "node:move:horizontal");
962 } else {
963 update_repr(nodepath);
964 }
965 }
967 /**
968 * Move node selection off screen and commit the change.
969 */
970 void
971 sp_node_selected_move_screen(gdouble dx, gdouble dy)
972 {
973 // borrowed from sp_selection_move_screen in selection-chemistry.c
974 // we find out the current zoom factor and divide deltas by it
975 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
977 gdouble zoom = desktop->current_zoom();
978 gdouble zdx = dx / zoom;
979 gdouble zdy = dy / zoom;
981 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
982 if (!nodepath) return;
984 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
986 if (dx == 0) {
987 update_repr_keyed(nodepath, "node:move:vertical");
988 } else if (dy == 0) {
989 update_repr_keyed(nodepath, "node:move:horizontal");
990 } else {
991 update_repr(nodepath);
992 }
993 }
995 /** If they don't yet exist, creates knot and line for the given side of the node */
996 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
997 {
998 if (!side->knot) {
999 side->knot = sp_knot_new(desktop);
1000 g_object_set(G_OBJECT(side->knot),
1001 "shape", SP_KNOT_SHAPE_CIRCLE,
1002 "size", 7,
1003 "anchor", GTK_ANCHOR_CENTER,
1004 "fill", KNOT_FILL,
1005 "fill_mouseover", KNOT_FILL_HI,
1006 "stroke", KNOT_STROKE,
1007 "stroke_mouseover", KNOT_STROKE_HI,
1008 "tip", _("<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"),
1009 NULL);
1010 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_ctrl_clicked), node);
1011 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), node);
1012 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), node);
1013 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_ctrl_request), node);
1014 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_ctrl_moved), node);
1015 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_ctrl_event), node);
1016 }
1018 if (!side->line) {
1019 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1020 SP_TYPE_CTRLLINE, NULL);
1021 }
1022 }
1024 /**
1025 * Ensure knot on side of node is visible/invisible.
1026 */
1027 static void sp_node_ensure_knot(Inkscape::NodePath::Node *node, gint which, gboolean show_knot)
1028 {
1029 g_assert(node != NULL);
1031 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1032 NRPathcode code = sp_node_path_code_from_side(node, side);
1034 show_knot = show_knot && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1036 if (show_knot) {
1037 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1039 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1040 sp_knot_show(side->knot);
1041 }
1042 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1043 sp_canvas_item_show(side->line);
1044 } else {
1045 if (side->knot) {
1046 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1047 sp_knot_hide(side->knot);
1048 }
1049 }
1050 if (side->line) {
1051 sp_canvas_item_hide(side->line);
1052 }
1053 }
1054 }
1056 /**
1057 * Ensure handles on node and neighbours of node are visible if selected.
1058 */
1059 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node)
1060 {
1061 g_assert(node != NULL);
1063 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1064 sp_knot_show(node->knot);
1065 }
1067 sp_knot_set_position(node->knot, &node->pos, 0);
1069 gboolean show_knots = node->selected;
1070 if (node->p.other != NULL) {
1071 if (node->p.other->selected) show_knots = TRUE;
1072 }
1073 if (node->n.other != NULL) {
1074 if (node->n.other->selected) show_knots = TRUE;
1075 }
1077 sp_node_ensure_knot(node, -1, show_knots);
1078 sp_node_ensure_knot(node, 1, show_knots);
1079 }
1081 /**
1082 * Call sp_node_ensure_ctrls() for all nodes on subpath.
1083 */
1084 static void sp_nodepath_subpath_ensure_ctrls(Inkscape::NodePath::SubPath *subpath)
1085 {
1086 g_assert(subpath != NULL);
1088 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1089 sp_node_ensure_ctrls((Inkscape::NodePath::Node *) l->data);
1090 }
1091 }
1093 /**
1094 * Call sp_nodepath_subpath_ensure_ctrls() for all subpaths of nodepath.
1095 */
1096 static void sp_nodepath_ensure_ctrls(Inkscape::NodePath::Path *nodepath)
1097 {
1098 g_assert(nodepath != NULL);
1100 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1101 sp_nodepath_subpath_ensure_ctrls((Inkscape::NodePath::SubPath *) l->data);
1102 }
1103 }
1105 /**
1106 * Adds all selected nodes in nodepath to list.
1107 */
1108 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1109 {
1110 StlConv<Node *>::list(l, selected);
1111 /// \todo this adds a copying, rework when the selection becomes a stl list
1112 }
1114 /**
1115 * Align selected nodes on the specified axis.
1116 */
1117 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1118 {
1119 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1120 return;
1121 }
1123 if ( !nodepath->selected->next ) { // only one node selected
1124 return;
1125 }
1126 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1127 NR::Point dest(pNode->pos);
1128 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1129 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1130 if (pNode) {
1131 dest[axis] = pNode->pos[axis];
1132 sp_node_moveto(pNode, dest);
1133 }
1134 }
1135 if (axis == NR::X) {
1136 update_repr_keyed(nodepath, "node:move:vertical");
1137 } else {
1138 update_repr_keyed(nodepath, "node:move:horizontal");
1139 }
1140 }
1142 /// Helper struct.
1143 struct NodeSort
1144 {
1145 Inkscape::NodePath::Node *_node;
1146 NR::Coord _coord;
1147 /// \todo use vectorof pointers instead of calling copy ctor
1148 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1149 _node(node), _coord(node->pos[axis])
1150 {}
1152 };
1154 static bool operator<(NodeSort const &a, NodeSort const &b)
1155 {
1156 return (a._coord < b._coord);
1157 }
1159 /**
1160 * Distribute selected nodes on the specified axis.
1161 */
1162 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1163 {
1164 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1165 return;
1166 }
1168 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1169 return;
1170 }
1172 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1173 std::vector<NodeSort> sorted;
1174 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1175 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1176 if (pNode) {
1177 NodeSort n(pNode, axis);
1178 sorted.push_back(n);
1179 //dest[axis] = pNode->pos[axis];
1180 //sp_node_moveto(pNode, dest);
1181 }
1182 }
1183 std::sort(sorted.begin(), sorted.end());
1184 unsigned int len = sorted.size();
1185 //overall bboxes span
1186 float dist = (sorted.back()._coord -
1187 sorted.front()._coord);
1188 //new distance between each bbox
1189 float step = (dist) / (len - 1);
1190 float pos = sorted.front()._coord;
1191 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1192 it < sorted.end();
1193 it ++ )
1194 {
1195 NR::Point dest((*it)._node->pos);
1196 dest[axis] = pos;
1197 sp_node_moveto((*it)._node, dest);
1198 pos += step;
1199 }
1201 if (axis == NR::X) {
1202 update_repr_keyed(nodepath, "node:move:horizontal");
1203 } else {
1204 update_repr_keyed(nodepath, "node:move:vertical");
1205 }
1206 }
1209 /**
1210 * Call sp_nodepath_line_add_node() for all selected segments.
1211 */
1212 void
1213 sp_node_selected_add_node(void)
1214 {
1215 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1216 if (!nodepath) {
1217 return;
1218 }
1220 GList *nl = NULL;
1222 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1223 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1224 g_assert(t->selected);
1225 if (t->p.other && t->p.other->selected) {
1226 nl = g_list_prepend(nl, t);
1227 }
1228 }
1230 while (nl) {
1231 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1232 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1233 sp_nodepath_node_select(n, TRUE, FALSE);
1234 nl = g_list_remove(nl, t);
1235 }
1237 /** \todo fixme: adjust ? */
1238 sp_nodepath_ensure_ctrls(nodepath);
1240 update_repr(nodepath);
1242 sp_nodepath_update_statusbar(nodepath);
1243 }
1245 /**
1246 * Select segment nearest to point
1247 */
1248 void
1249 sp_nodepath_select_segment_near_point(SPItem * item, NR::Point p, bool toggle)
1250 {
1251 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1252 if (!nodepath) {
1253 return;
1254 }
1256 Path::cut_position position = get_nearest_position_on_Path(item, p);
1258 //find segment to segment
1259 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1261 gboolean force = FALSE;
1262 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1263 force = TRUE;
1264 }
1265 sp_nodepath_node_select(e, (gboolean) toggle, force);
1266 if (e->p.other)
1267 sp_nodepath_node_select(e->p.other, TRUE, force);
1269 sp_nodepath_ensure_ctrls(nodepath);
1271 sp_nodepath_update_statusbar(nodepath);
1272 }
1274 /**
1275 * Add a node nearest to point
1276 */
1277 void
1278 sp_nodepath_add_node_near_point(SPItem * item, NR::Point p)
1279 {
1280 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1281 if (!nodepath) {
1282 return;
1283 }
1285 Path::cut_position position = get_nearest_position_on_Path(item, p);
1287 //find segment to split
1288 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1290 //don't know why but t seems to flip for lines
1291 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1292 position.t = 1.0 - position.t;
1293 }
1294 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1295 sp_nodepath_node_select(n, FALSE, TRUE);
1297 /* fixme: adjust ? */
1298 sp_nodepath_ensure_ctrls(nodepath);
1300 update_repr(nodepath);
1302 sp_nodepath_update_statusbar(nodepath);
1303 }
1305 /*
1306 * Adjusts a segment so that t moves by a certain delta for dragging
1307 * converts lines to curves
1308 *
1309 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1310 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1311 */
1312 void
1313 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key)
1314 {
1315 /* feel good is an arbitrary parameter that distributes the delta between handles
1316 * if t of the drag point is less than 1/6 distance form the endpoint only
1317 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1318 */
1319 double feel_good;
1320 if (t <= 1.0 / 6.0)
1321 feel_good = 0;
1322 else if (t <= 0.5)
1323 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1324 else if (t <= 5.0 / 6.0)
1325 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1326 else
1327 feel_good = 1;
1329 //if we're dragging a line convert it to a curve
1330 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1331 sp_nodepath_set_line_type(e, NR_CURVETO);
1332 }
1334 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1335 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1336 e->p.other->n.pos += offsetcoord0;
1337 e->p.pos += offsetcoord1;
1339 // adjust controls of adjacent segments where necessary
1340 sp_node_adjust_knot(e,1);
1341 sp_node_adjust_knot(e->p.other,-1);
1343 sp_nodepath_ensure_ctrls(e->subpath->nodepath);
1345 update_repr_keyed(e->subpath->nodepath, key);
1347 sp_nodepath_update_statusbar(e->subpath->nodepath);
1348 }
1351 /**
1352 * Call sp_nodepath_break() for all selected segments.
1353 */
1354 void sp_node_selected_break()
1355 {
1356 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1357 if (!nodepath) return;
1359 GList *temp = NULL;
1360 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1361 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1362 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1363 if (nn == NULL) continue; // no break, no new node
1364 temp = g_list_prepend(temp, nn);
1365 }
1367 if (temp) {
1368 sp_nodepath_deselect(nodepath);
1369 }
1370 for (GList *l = temp; l != NULL; l = l->next) {
1371 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1372 }
1374 sp_nodepath_ensure_ctrls(nodepath);
1376 update_repr(nodepath);
1377 }
1379 /**
1380 * Duplicate the selected node(s).
1381 */
1382 void sp_node_selected_duplicate()
1383 {
1384 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1385 if (!nodepath) {
1386 return;
1387 }
1389 GList *temp = NULL;
1390 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1391 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1392 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1393 if (nn == NULL) continue; // could not duplicate
1394 temp = g_list_prepend(temp, nn);
1395 }
1397 if (temp) {
1398 sp_nodepath_deselect(nodepath);
1399 }
1400 for (GList *l = temp; l != NULL; l = l->next) {
1401 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1402 }
1404 sp_nodepath_ensure_ctrls(nodepath);
1406 update_repr(nodepath);
1407 }
1409 /**
1410 * Join two nodes by merging them into one.
1411 */
1412 void sp_node_selected_join()
1413 {
1414 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1415 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1417 if (g_list_length(nodepath->selected) != 2) {
1418 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1419 return;
1420 }
1422 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1423 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1425 g_assert(a != b);
1426 g_assert(a->p.other || a->n.other);
1427 g_assert(b->p.other || b->n.other);
1429 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1430 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1431 return;
1432 }
1434 /* a and b are endpoints */
1436 NR::Point c = (a->pos + b->pos) / 2;
1438 if (a->subpath == b->subpath) {
1439 Inkscape::NodePath::SubPath *sp = a->subpath;
1440 sp_nodepath_subpath_close(sp);
1442 sp_nodepath_ensure_ctrls(sp->nodepath);
1444 update_repr(nodepath);
1446 return;
1447 }
1449 /* a and b are separate subpaths */
1450 Inkscape::NodePath::SubPath *sa = a->subpath;
1451 Inkscape::NodePath::SubPath *sb = b->subpath;
1452 NR::Point p;
1453 Inkscape::NodePath::Node *n;
1454 NRPathcode code;
1455 if (a == sa->first) {
1456 p = sa->first->n.pos;
1457 code = (NRPathcode)sa->first->n.other->code;
1458 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1459 n = sa->last;
1460 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1461 n = n->p.other;
1462 while (n) {
1463 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1464 n = n->p.other;
1465 if (n == sa->first) n = NULL;
1466 }
1467 sp_nodepath_subpath_destroy(sa);
1468 sa = t;
1469 } else if (a == sa->last) {
1470 p = sa->last->p.pos;
1471 code = (NRPathcode)sa->last->code;
1472 sp_nodepath_node_destroy(sa->last);
1473 } else {
1474 code = NR_END;
1475 g_assert_not_reached();
1476 }
1478 if (b == sb->first) {
1479 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1480 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1481 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1482 }
1483 } else if (b == sb->last) {
1484 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1485 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1486 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1487 }
1488 } else {
1489 g_assert_not_reached();
1490 }
1491 /* and now destroy sb */
1493 sp_nodepath_subpath_destroy(sb);
1495 sp_nodepath_ensure_ctrls(sa->nodepath);
1497 update_repr(nodepath);
1499 sp_nodepath_update_statusbar(nodepath);
1500 }
1502 /**
1503 * Join two nodes by adding a segment between them.
1504 */
1505 void sp_node_selected_join_segment()
1506 {
1507 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1508 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1510 if (g_list_length(nodepath->selected) != 2) {
1511 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1512 return;
1513 }
1515 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1516 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1518 g_assert(a != b);
1519 g_assert(a->p.other || a->n.other);
1520 g_assert(b->p.other || b->n.other);
1522 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1523 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1524 return;
1525 }
1527 if (a->subpath == b->subpath) {
1528 Inkscape::NodePath::SubPath *sp = a->subpath;
1530 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1531 sp->closed = TRUE;
1533 sp->first->p.other = sp->last;
1534 sp->last->n.other = sp->first;
1536 sp_node_control_mirror_p_to_n(sp->last);
1537 sp_node_control_mirror_n_to_p(sp->first);
1539 sp->first->code = sp->last->code;
1540 sp->first = sp->last;
1542 sp_nodepath_ensure_ctrls(sp->nodepath);
1544 update_repr(nodepath);
1546 return;
1547 }
1549 /* a and b are separate subpaths */
1550 Inkscape::NodePath::SubPath *sa = a->subpath;
1551 Inkscape::NodePath::SubPath *sb = b->subpath;
1553 Inkscape::NodePath::Node *n;
1554 NR::Point p;
1555 NRPathcode code;
1556 if (a == sa->first) {
1557 code = (NRPathcode) sa->first->n.other->code;
1558 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1559 n = sa->last;
1560 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1561 for (n = n->p.other; n != NULL; n = n->p.other) {
1562 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1563 }
1564 sp_nodepath_subpath_destroy(sa);
1565 sa = t;
1566 } else if (a == sa->last) {
1567 code = (NRPathcode)sa->last->code;
1568 } else {
1569 code = NR_END;
1570 g_assert_not_reached();
1571 }
1573 if (b == sb->first) {
1574 n = sb->first;
1575 sp_node_control_mirror_p_to_n(sa->last);
1576 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1577 sp_node_control_mirror_n_to_p(sa->last);
1578 for (n = n->n.other; n != NULL; n = n->n.other) {
1579 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1580 }
1581 } else if (b == sb->last) {
1582 n = sb->last;
1583 sp_node_control_mirror_p_to_n(sa->last);
1584 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1585 sp_node_control_mirror_n_to_p(sa->last);
1586 for (n = n->p.other; n != NULL; n = n->p.other) {
1587 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1588 }
1589 } else {
1590 g_assert_not_reached();
1591 }
1592 /* and now destroy sb */
1594 sp_nodepath_subpath_destroy(sb);
1596 sp_nodepath_ensure_ctrls(sa->nodepath);
1598 update_repr(nodepath);
1599 }
1601 /**
1602 * Delete one or more selected nodes.
1603 */
1604 void sp_node_selected_delete()
1605 {
1606 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1607 if (!nodepath) return;
1608 if (!nodepath->selected) return;
1610 /** \todo fixme: do it the right way */
1611 while (nodepath->selected) {
1612 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1613 sp_nodepath_node_destroy(node);
1614 }
1617 //clean up the nodepath (such as for trivial subpaths)
1618 sp_nodepath_cleanup(nodepath);
1620 sp_nodepath_ensure_ctrls(nodepath);
1622 // if the entire nodepath is removed, delete the selected object.
1623 if (nodepath->subpaths == NULL ||
1624 sp_nodepath_get_node_count(nodepath) < 2) {
1625 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1626 sp_nodepath_destroy(nodepath);
1627 sp_selection_delete();
1628 sp_document_done (document);
1629 return;
1630 }
1632 update_repr(nodepath);
1634 sp_nodepath_update_statusbar(nodepath);
1635 }
1637 /**
1638 * Delete one or more segments between two selected nodes.
1639 * This is the code for 'split'.
1640 */
1641 void
1642 sp_node_selected_delete_segment(void)
1643 {
1644 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1645 Inkscape::NodePath::Node *curr, *next; //Iterators
1647 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1648 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1650 if (g_list_length(nodepath->selected) != 2) {
1651 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1652 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1653 return;
1654 }
1656 //Selected nodes, not inclusive
1657 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1658 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1660 if ( ( a==b) || //same node
1661 (a->subpath != b->subpath ) || //not the same path
1662 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1663 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1664 {
1665 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1666 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1667 return;
1668 }
1670 //###########################################
1671 //# BEGIN EDITS
1672 //###########################################
1673 //##################################
1674 //# CLOSED PATH
1675 //##################################
1676 if (a->subpath->closed) {
1679 gboolean reversed = FALSE;
1681 //Since we can go in a circle, we need to find the shorter distance.
1682 // a->b or b->a
1683 start = end = NULL;
1684 int distance = 0;
1685 int minDistance = 0;
1686 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1687 if (curr==b) {
1688 //printf("a to b:%d\n", distance);
1689 start = a;//go from a to b
1690 end = b;
1691 minDistance = distance;
1692 //printf("A to B :\n");
1693 break;
1694 }
1695 distance++;
1696 }
1698 //try again, the other direction
1699 distance = 0;
1700 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1701 if (curr==a) {
1702 //printf("b to a:%d\n", distance);
1703 if (distance < minDistance) {
1704 start = b; //we go from b to a
1705 end = a;
1706 reversed = TRUE;
1707 //printf("B to A\n");
1708 }
1709 break;
1710 }
1711 distance++;
1712 }
1715 //Copy everything from 'end' to 'start' to a new subpath
1716 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1717 for (curr=end ; curr ; curr=curr->n.other) {
1718 NRPathcode code = (NRPathcode) curr->code;
1719 if (curr == end)
1720 code = NR_MOVETO;
1721 sp_nodepath_node_new(t, NULL,
1722 (Inkscape::NodePath::NodeType)curr->type, code,
1723 &curr->p.pos, &curr->pos, &curr->n.pos);
1724 if (curr == start)
1725 break;
1726 }
1727 sp_nodepath_subpath_destroy(a->subpath);
1730 }
1734 //##################################
1735 //# OPEN PATH
1736 //##################################
1737 else {
1739 //We need to get the direction of the list between A and B
1740 //Can we walk from a to b?
1741 start = end = NULL;
1742 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1743 if (curr==b) {
1744 start = a; //did it! we go from a to b
1745 end = b;
1746 //printf("A to B\n");
1747 break;
1748 }
1749 }
1750 if (!start) {//didn't work? let's try the other direction
1751 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1752 if (curr==a) {
1753 start = b; //did it! we go from b to a
1754 end = a;
1755 //printf("B to A\n");
1756 break;
1757 }
1758 }
1759 }
1760 if (!start) {
1761 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1762 _("Cannot find path between nodes."));
1763 return;
1764 }
1768 //Copy everything after 'end' to a new subpath
1769 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1770 for (curr=end ; curr ; curr=curr->n.other) {
1771 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1772 &curr->p.pos, &curr->pos, &curr->n.pos);
1773 }
1775 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1776 for (curr = start->n.other ; curr ; curr=next) {
1777 next = curr->n.other;
1778 sp_nodepath_node_destroy(curr);
1779 }
1781 }
1782 //###########################################
1783 //# END EDITS
1784 //###########################################
1786 //clean up the nodepath (such as for trivial subpaths)
1787 sp_nodepath_cleanup(nodepath);
1789 sp_nodepath_ensure_ctrls(nodepath);
1791 update_repr(nodepath);
1793 // if the entire nodepath is removed, delete the selected object.
1794 if (nodepath->subpaths == NULL ||
1795 sp_nodepath_get_node_count(nodepath) < 2) {
1796 sp_nodepath_destroy(nodepath);
1797 sp_selection_delete();
1798 return;
1799 }
1801 sp_nodepath_update_statusbar(nodepath);
1802 }
1804 /**
1805 * Call sp_nodepath_set_line() for all selected segments.
1806 */
1807 void
1808 sp_node_selected_set_line_type(NRPathcode code)
1809 {
1810 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1811 if (nodepath == NULL) return;
1813 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1814 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1815 g_assert(n->selected);
1816 if (n->p.other && n->p.other->selected) {
1817 sp_nodepath_set_line_type(n, code);
1818 }
1819 }
1821 update_repr(nodepath);
1822 }
1824 /**
1825 * Call sp_nodepath_convert_node_type() for all selected nodes.
1826 */
1827 void
1828 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1829 {
1830 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1831 if (nodepath == NULL) return;
1833 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1834 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1835 }
1837 update_repr(nodepath);
1838 }
1840 /**
1841 * Change select status of node, update its own and neighbour handles.
1842 */
1843 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1844 {
1845 node->selected = selected;
1847 if (selected) {
1848 g_object_set(G_OBJECT(node->knot),
1849 "fill", NODE_FILL_SEL,
1850 "fill_mouseover", NODE_FILL_SEL_HI,
1851 "stroke", NODE_STROKE_SEL,
1852 "stroke_mouseover", NODE_STROKE_SEL_HI,
1853 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9,
1854 NULL);
1855 } else {
1856 g_object_set(G_OBJECT(node->knot),
1857 "fill", NODE_FILL,
1858 "fill_mouseover", NODE_FILL_HI,
1859 "stroke", NODE_STROKE,
1860 "stroke_mouseover", NODE_STROKE_HI,
1861 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7,
1862 NULL);
1863 }
1865 sp_node_ensure_ctrls(node);
1866 if (node->n.other) sp_node_ensure_ctrls(node->n.other);
1867 if (node->p.other) sp_node_ensure_ctrls(node->p.other);
1868 }
1870 /**
1871 \brief Select a node
1872 \param node The node to select
1873 \param incremental If true, add to selection, otherwise deselect others
1874 \param override If true, always select this node, otherwise toggle selected status
1875 */
1876 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1877 {
1878 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1880 if (incremental) {
1881 if (override) {
1882 if (!g_list_find(nodepath->selected, node)) {
1883 nodepath->selected = g_list_append(nodepath->selected, node);
1884 }
1885 sp_node_set_selected(node, TRUE);
1886 } else { // toggle
1887 if (node->selected) {
1888 g_assert(g_list_find(nodepath->selected, node));
1889 nodepath->selected = g_list_remove(nodepath->selected, node);
1890 } else {
1891 g_assert(!g_list_find(nodepath->selected, node));
1892 nodepath->selected = g_list_append(nodepath->selected, node);
1893 }
1894 sp_node_set_selected(node, !node->selected);
1895 }
1896 } else {
1897 sp_nodepath_deselect(nodepath);
1898 nodepath->selected = g_list_append(nodepath->selected, node);
1899 sp_node_set_selected(node, TRUE);
1900 }
1902 sp_nodepath_update_statusbar(nodepath);
1903 }
1906 /**
1907 \brief Deselect all nodes in the nodepath
1908 */
1909 void
1910 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1911 {
1912 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1914 while (nodepath->selected) {
1915 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1916 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1917 }
1918 sp_nodepath_update_statusbar(nodepath);
1919 }
1921 /**
1922 \brief Select or invert selection of all nodes in the nodepath
1923 */
1924 void
1925 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1926 {
1927 if (!nodepath) return;
1929 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1930 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1931 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1932 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1933 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1934 }
1935 }
1936 }
1938 /**
1939 * If nothing selected, does the same as sp_nodepath_select_all();
1940 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1941 * (i.e., similar to "select all in layer", with the "selected" subpaths
1942 * being treated as "layers" in the path).
1943 */
1944 void
1945 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1946 {
1947 if (!nodepath) return;
1949 if (g_list_length (nodepath->selected) == 0) {
1950 sp_nodepath_select_all (nodepath, invert);
1951 return;
1952 }
1954 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1955 GSList *subpaths = NULL;
1957 for (GList *l = copy; l != NULL; l = l->next) {
1958 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1959 Inkscape::NodePath::SubPath *subpath = n->subpath;
1960 if (!g_slist_find (subpaths, subpath))
1961 subpaths = g_slist_prepend (subpaths, subpath);
1962 }
1964 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
1965 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
1966 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1967 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1968 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
1969 }
1970 }
1972 g_slist_free (subpaths);
1973 g_list_free (copy);
1974 }
1976 /**
1977 * \brief Select the node after the last selected; if none is selected,
1978 * select the first within path.
1979 */
1980 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
1981 {
1982 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1984 Inkscape::NodePath::Node *last = NULL;
1985 if (nodepath->selected) {
1986 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1987 Inkscape::NodePath::SubPath *subpath, *subpath_next;
1988 subpath = (Inkscape::NodePath::SubPath *) spl->data;
1989 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1990 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1991 if (node->selected) {
1992 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
1993 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
1994 if (spl->next) { // there's a next subpath
1995 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
1996 last = subpath_next->first;
1997 } else if (spl->prev) { // there's a previous subpath
1998 last = NULL; // to be set later to the first node of first subpath
1999 } else {
2000 last = node->n.other;
2001 }
2002 } else {
2003 last = node->n.other;
2004 }
2005 } else {
2006 if (node->n.other) {
2007 last = node->n.other;
2008 } else {
2009 if (spl->next) { // there's a next subpath
2010 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2011 last = subpath_next->first;
2012 } else if (spl->prev) { // there's a previous subpath
2013 last = NULL; // to be set later to the first node of first subpath
2014 } else {
2015 last = (Inkscape::NodePath::Node *) subpath->first;
2016 }
2017 }
2018 }
2019 }
2020 }
2021 }
2022 sp_nodepath_deselect(nodepath);
2023 }
2025 if (last) { // there's at least one more node after selected
2026 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2027 } else { // no more nodes, select the first one in first subpath
2028 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2029 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2030 }
2031 }
2033 /**
2034 * \brief Select the node before the first selected; if none is selected,
2035 * select the last within path
2036 */
2037 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2038 {
2039 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2041 Inkscape::NodePath::Node *last = NULL;
2042 if (nodepath->selected) {
2043 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2044 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2045 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2046 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2047 if (node->selected) {
2048 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2049 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2050 if (spl->prev) { // there's a prev subpath
2051 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2052 last = subpath_prev->last;
2053 } else if (spl->next) { // there's a next subpath
2054 last = NULL; // to be set later to the last node of last subpath
2055 } else {
2056 last = node->p.other;
2057 }
2058 } else {
2059 last = node->p.other;
2060 }
2061 } else {
2062 if (node->p.other) {
2063 last = node->p.other;
2064 } else {
2065 if (spl->prev) { // there's a prev subpath
2066 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2067 last = subpath_prev->last;
2068 } else if (spl->next) { // there's a next subpath
2069 last = NULL; // to be set later to the last node of last subpath
2070 } else {
2071 last = (Inkscape::NodePath::Node *) subpath->last;
2072 }
2073 }
2074 }
2075 }
2076 }
2077 }
2078 sp_nodepath_deselect(nodepath);
2079 }
2081 if (last) { // there's at least one more node before selected
2082 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2083 } else { // no more nodes, select the last one in last subpath
2084 GList *spl = g_list_last(nodepath->subpaths);
2085 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2086 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2087 }
2088 }
2090 /**
2091 * \brief Select all nodes that are within the rectangle.
2092 */
2093 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2094 {
2095 if (!incremental) {
2096 sp_nodepath_deselect(nodepath);
2097 }
2099 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2100 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2101 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2102 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2104 if (b.contains(node->pos)) {
2105 sp_nodepath_node_select(node, TRUE, TRUE);
2106 }
2107 }
2108 }
2109 }
2111 /**
2112 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2113 */
2114 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2115 {
2116 if (!nodepath->selected) {
2117 return NULL;
2118 }
2120 GList *r = NULL;
2121 guint i = 0;
2122 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2123 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2124 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2125 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2126 i++;
2127 if (node->selected) {
2128 r = g_list_append(r, GINT_TO_POINTER(i));
2129 }
2130 }
2131 }
2132 return r;
2133 }
2135 /**
2136 \brief Restores selection by selecting nodes whose positions are in the list
2137 */
2138 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2139 {
2140 sp_nodepath_deselect(nodepath);
2142 guint i = 0;
2143 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2144 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2145 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2146 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2147 i++;
2148 if (g_list_find(r, GINT_TO_POINTER(i))) {
2149 sp_nodepath_node_select(node, TRUE, TRUE);
2150 }
2151 }
2152 }
2154 }
2156 /**
2157 \brief Adjusts control point according to node type and line code.
2158 */
2159 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust)
2160 {
2161 double len, otherlen, linelen;
2163 g_assert(node);
2165 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2166 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2168 /** \todo fixme: */
2169 if (me->other == NULL) return;
2170 if (other->other == NULL) return;
2172 /* I have line */
2174 NRPathcode mecode, ocode;
2175 if (which_adjust == 1) {
2176 mecode = (NRPathcode)me->other->code;
2177 ocode = (NRPathcode)node->code;
2178 } else {
2179 mecode = (NRPathcode)node->code;
2180 ocode = (NRPathcode)other->other->code;
2181 }
2183 if (mecode == NR_LINETO) return;
2185 /* I am curve */
2187 if (other->other == NULL) return;
2189 /* Other has line */
2191 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2193 NR::Point delta;
2194 if (ocode == NR_LINETO) {
2195 /* other is lineto, we are either smooth or symm */
2196 Inkscape::NodePath::Node *othernode = other->other;
2197 len = NR::L2(me->pos - node->pos);
2198 delta = node->pos - othernode->pos;
2199 linelen = NR::L2(delta);
2200 if (linelen < 1e-18) return;
2202 me->pos = node->pos + (len / linelen)*delta;
2204 sp_node_ensure_ctrls(node);
2205 return;
2206 }
2208 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2210 me->pos = 2 * node->pos - other->pos;
2212 sp_node_ensure_ctrls(node);
2213 return;
2214 }
2216 /* We are smooth */
2218 len = NR::L2(me->pos - node->pos);
2219 delta = other->pos - node->pos;
2220 otherlen = NR::L2(delta);
2221 if (otherlen < 1e-18) return;
2223 me->pos = node->pos - (len / otherlen) * delta;
2225 sp_node_ensure_ctrls(node);
2226 }
2228 /**
2229 \brief Adjusts control point according to node type and line code
2230 */
2231 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node)
2232 {
2233 g_assert(node);
2235 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2237 /* we are either smooth or symm */
2239 if (node->p.other == NULL) return;
2241 if (node->n.other == NULL) return;
2243 if (node->code == NR_LINETO) {
2244 if (node->n.other->code == NR_LINETO) return;
2245 sp_node_adjust_knot(node, 1);
2246 sp_node_ensure_ctrls(node);
2247 return;
2248 }
2250 if (node->n.other->code == NR_LINETO) {
2251 if (node->code == NR_LINETO) return;
2252 sp_node_adjust_knot(node, -1);
2253 sp_node_ensure_ctrls(node);
2254 return;
2255 }
2257 /* both are curves */
2259 NR::Point const delta( node->n.pos - node->p.pos );
2261 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2262 node->p.pos = node->pos - delta / 2;
2263 node->n.pos = node->pos + delta / 2;
2264 sp_node_ensure_ctrls(node);
2265 return;
2266 }
2268 /* We are smooth */
2270 double plen = NR::L2(node->p.pos - node->pos);
2271 if (plen < 1e-18) return;
2272 double nlen = NR::L2(node->n.pos - node->pos);
2273 if (nlen < 1e-18) return;
2274 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2275 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2276 sp_node_ensure_ctrls(node);
2277 }
2279 /**
2280 * Knot events handler callback.
2281 */
2282 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2283 {
2284 gboolean ret = FALSE;
2285 switch (event->type) {
2286 case GDK_ENTER_NOTIFY:
2287 active_node = n;
2288 break;
2289 case GDK_LEAVE_NOTIFY:
2290 active_node = NULL;
2291 break;
2292 case GDK_KEY_PRESS:
2293 switch (get_group0_keyval (&event->key)) {
2294 case GDK_space:
2295 if (event->key.state & GDK_BUTTON1_MASK) {
2296 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2297 stamp_repr(nodepath);
2298 ret = TRUE;
2299 }
2300 break;
2301 default:
2302 break;
2303 }
2304 break;
2305 default:
2306 break;
2307 }
2309 return ret;
2310 }
2312 /**
2313 * Handle keypress on node; directly called.
2314 */
2315 gboolean node_key(GdkEvent *event)
2316 {
2317 Inkscape::NodePath::Path *np;
2319 // there is no way to verify nodes so set active_node to nil when deleting!!
2320 if (active_node == NULL) return FALSE;
2322 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2323 gint ret = FALSE;
2324 switch (get_group0_keyval (&event->key)) {
2325 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2326 case GDK_BackSpace:
2327 np = active_node->subpath->nodepath;
2328 sp_nodepath_node_destroy(active_node);
2329 update_repr(np);
2330 active_node = NULL;
2331 ret = TRUE;
2332 break;
2333 case GDK_c:
2334 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2335 ret = TRUE;
2336 break;
2337 case GDK_s:
2338 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2339 ret = TRUE;
2340 break;
2341 case GDK_y:
2342 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2343 ret = TRUE;
2344 break;
2345 case GDK_b:
2346 sp_nodepath_node_break(active_node);
2347 ret = TRUE;
2348 break;
2349 }
2350 return ret;
2351 }
2352 return FALSE;
2353 }
2355 /**
2356 * Mouseclick on node callback.
2357 */
2358 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2359 {
2360 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2362 if (state & GDK_CONTROL_MASK) {
2363 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2365 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2366 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2367 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2368 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2369 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2370 } else {
2371 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2372 }
2373 update_repr(nodepath);
2374 sp_nodepath_update_statusbar(nodepath);
2376 } else { //ctrl+alt+click: delete node
2377 sp_nodepath_node_destroy(n);
2378 //clean up the nodepath (such as for trivial subpaths)
2379 sp_nodepath_cleanup(nodepath);
2381 // if the entire nodepath is removed, delete the selected object.
2382 if (nodepath->subpaths == NULL ||
2383 sp_nodepath_get_node_count(nodepath) < 2) {
2384 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2385 sp_nodepath_destroy(nodepath);
2386 sp_selection_delete();
2387 sp_document_done (document);
2389 } else {
2390 sp_nodepath_ensure_ctrls(nodepath);
2391 update_repr(nodepath);
2392 sp_nodepath_update_statusbar(nodepath);
2393 }
2394 }
2396 } else {
2397 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2398 }
2399 }
2401 /**
2402 * Mouse grabbed node callback.
2403 */
2404 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2405 {
2406 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2408 n->origin = knot->pos;
2410 if (!n->selected) {
2411 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2412 }
2413 }
2415 /**
2416 * Mouse ungrabbed node callback.
2417 */
2418 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2419 {
2420 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2422 n->dragging_out = NULL;
2424 update_repr(n->subpath->nodepath);
2425 }
2427 /**
2428 * The point on a line, given by its angle, closest to the given point.
2429 * \param p A point.
2430 * \param a Angle of the line; it is assumed to go through coordinate origin.
2431 * \param closest Pointer to the point struct where the result is stored.
2432 * \todo FIXME: use dot product perhaps?
2433 */
2434 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2435 {
2436 if (a == HUGE_VAL) { // vertical
2437 *closest = NR::Point(0, (*p)[NR::Y]);
2438 } else {
2439 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2440 (*closest)[NR::Y] = a * (*closest)[NR::X];
2441 }
2442 }
2444 /**
2445 * Distance from the point to a line given by its angle.
2446 * \param p A point.
2447 * \param a Angle of the line; it is assumed to go through coordinate origin.
2448 */
2449 static double point_line_distance(NR::Point *p, double a)
2450 {
2451 NR::Point c;
2452 point_line_closest(p, a, &c);
2453 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]));
2454 }
2456 /**
2457 * Callback for node "request" signal.
2458 * \todo fixme: This goes to "moved" event? (lauris)
2459 */
2460 static gboolean
2461 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2462 {
2463 double yn, xn, yp, xp;
2464 double an, ap, na, pa;
2465 double d_an, d_ap, d_na, d_pa;
2466 gboolean collinear = FALSE;
2467 NR::Point c;
2468 NR::Point pr;
2470 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2472 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2473 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2475 NR::Point mouse = (*p);
2477 if (!n->dragging_out) {
2478 // This is the first drag-out event; find out which handle to drag out
2479 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2480 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2482 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2483 return FALSE;
2485 Inkscape::NodePath::NodeSide *opposite;
2486 if (appr_p > appr_n) { // closer to p
2487 n->dragging_out = &n->p;
2488 opposite = &n->n;
2489 n->code = NR_CURVETO;
2490 } else if (appr_p < appr_n) { // closer to n
2491 n->dragging_out = &n->n;
2492 opposite = &n->p;
2493 n->n.other->code = NR_CURVETO;
2494 } else { // p and n nodes are the same
2495 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2496 n->dragging_out = &n->p;
2497 opposite = &n->n;
2498 n->code = NR_CURVETO;
2499 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2500 n->dragging_out = &n->n;
2501 opposite = &n->p;
2502 n->n.other->code = NR_CURVETO;
2503 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2504 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);
2505 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);
2506 if (appr_other_p > appr_other_n) { // closer to other's p handle
2507 n->dragging_out = &n->n;
2508 opposite = &n->p;
2509 n->n.other->code = NR_CURVETO;
2510 } else { // closer to other's n handle
2511 n->dragging_out = &n->p;
2512 opposite = &n->n;
2513 n->code = NR_CURVETO;
2514 }
2515 }
2516 }
2518 // if there's another handle, make sure the one we drag out starts parallel to it
2519 if (opposite->pos != n->pos) {
2520 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2521 }
2523 // knots might not be created yet!
2524 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2525 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2526 }
2528 // pass this on to the handle-moved callback
2529 node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2530 sp_node_ensure_ctrls(n);
2531 return TRUE;
2532 }
2534 if (state & GDK_CONTROL_MASK) { // constrained motion
2536 // calculate relative distances of handles
2537 // n handle:
2538 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2539 xn = n->n.pos[NR::X] - n->pos[NR::X];
2540 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2541 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2542 if (n->n.other) { // if there is the next point
2543 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2544 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2545 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2546 }
2547 }
2548 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2549 if (yn < 0) { xn = -xn; yn = -yn; }
2551 // p handle:
2552 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2553 xp = n->p.pos[NR::X] - n->pos[NR::X];
2554 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2555 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2556 if (n->p.other) {
2557 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2558 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2559 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2560 }
2561 }
2562 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2563 if (yp < 0) { xp = -xp; yp = -yp; }
2565 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2566 // sliding on handles, only if at least one of the handles is non-vertical
2567 // (otherwise it's the same as ctrl+drag anyway)
2569 // calculate angles of the control handles
2570 if (xn == 0) {
2571 if (yn == 0) { // no handle, consider it the continuation of the other one
2572 an = 0;
2573 collinear = TRUE;
2574 }
2575 else an = 0; // vertical; set the angle to horizontal
2576 } else an = yn/xn;
2578 if (xp == 0) {
2579 if (yp == 0) { // no handle, consider it the continuation of the other one
2580 ap = an;
2581 }
2582 else ap = 0; // vertical; set the angle to horizontal
2583 } else ap = yp/xp;
2585 if (collinear) an = ap;
2587 // angles of the perpendiculars; HUGE_VAL means vertical
2588 if (an == 0) na = HUGE_VAL; else na = -1/an;
2589 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2591 //g_print("an %g ap %g\n", an, ap);
2593 // mouse point relative to the node's original pos
2594 pr = (*p) - n->origin;
2596 // distances to the four lines (two handles and two perpendiculars)
2597 d_an = point_line_distance(&pr, an);
2598 d_na = point_line_distance(&pr, na);
2599 d_ap = point_line_distance(&pr, ap);
2600 d_pa = point_line_distance(&pr, pa);
2602 // find out which line is the closest, save its closest point in c
2603 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2604 point_line_closest(&pr, an, &c);
2605 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2606 point_line_closest(&pr, ap, &c);
2607 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2608 point_line_closest(&pr, na, &c);
2609 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2610 point_line_closest(&pr, pa, &c);
2611 }
2613 // move the node to the closest point
2614 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2615 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2616 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2618 } else { // constraining to hor/vert
2620 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2621 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2622 } else { // snap to vert
2623 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2624 }
2625 }
2626 } else { // move freely
2627 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2628 (*p)[NR::X] - n->pos[NR::X],
2629 (*p)[NR::Y] - n->pos[NR::Y],
2630 (state & GDK_SHIFT_MASK) == 0);
2631 }
2633 n->subpath->nodepath->desktop->scroll_to_point(p);
2635 return TRUE;
2636 }
2638 /**
2639 * Node handle clicked callback.
2640 */
2641 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data)
2642 {
2643 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2645 if (state & GDK_CONTROL_MASK) { // "delete" handle
2646 if (n->p.knot == knot) {
2647 n->p.pos = n->pos;
2648 } else if (n->n.knot == knot) {
2649 n->n.pos = n->pos;
2650 }
2651 sp_node_ensure_ctrls(n);
2652 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2653 update_repr(nodepath);
2654 sp_nodepath_update_statusbar(nodepath);
2656 } else { // just select or add to selection, depending in Shift
2657 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2658 }
2659 }
2661 /**
2662 * Node handle grabbed callback.
2663 */
2664 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data)
2665 {
2666 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2668 if (!n->selected) {
2669 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2670 }
2672 // remember the origin of the control
2673 if (n->p.knot == knot) {
2674 n->p.origin = n->p.pos - n->pos;
2675 } else if (n->n.knot == knot) {
2676 n->n.origin = n->n.pos - n->pos;
2677 } else {
2678 g_assert_not_reached();
2679 }
2681 }
2683 /**
2684 * Node handle ungrabbed callback.
2685 */
2686 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data)
2687 {
2688 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2690 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2691 if (n->p.knot == knot) {
2692 n->p.origin.a = 0;
2693 sp_knot_set_position(knot, &n->p.pos, state);
2694 } else if (n->n.knot == knot) {
2695 n->n.origin.a = 0;
2696 sp_knot_set_position(knot, &n->n.pos, state);
2697 } else {
2698 g_assert_not_reached();
2699 }
2701 update_repr(n->subpath->nodepath);
2702 }
2704 /**
2705 * Node handle "request" signal callback.
2706 */
2707 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2708 {
2709 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2711 Inkscape::NodePath::NodeSide *me, *opposite;
2712 gint which;
2713 if (n->p.knot == knot) {
2714 me = &n->p;
2715 opposite = &n->n;
2716 which = -1;
2717 } else if (n->n.knot == knot) {
2718 me = &n->n;
2719 opposite = &n->p;
2720 which = 1;
2721 } else {
2722 me = opposite = NULL;
2723 which = 0;
2724 g_assert_not_reached();
2725 }
2727 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2729 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2731 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2732 /* We are smooth node adjacent with line */
2733 NR::Point const delta = *p - n->pos;
2734 NR::Coord const len = NR::L2(delta);
2735 Inkscape::NodePath::Node *othernode = opposite->other;
2736 NR::Point const ndelta = n->pos - othernode->pos;
2737 NR::Coord const linelen = NR::L2(ndelta);
2738 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2739 NR::Coord const scal = dot(delta, ndelta) / linelen;
2740 (*p) = n->pos + (scal / linelen) * ndelta;
2741 }
2742 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2743 } else {
2744 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2745 }
2747 sp_node_adjust_knot(n, -which);
2749 return FALSE;
2750 }
2752 /**
2753 * Node handle moved callback.
2754 */
2755 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2756 {
2757 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2759 Inkscape::NodePath::NodeSide *me;
2760 Inkscape::NodePath::NodeSide *other;
2761 if (n->p.knot == knot) {
2762 me = &n->p;
2763 other = &n->n;
2764 } else if (n->n.knot == knot) {
2765 me = &n->n;
2766 other = &n->p;
2767 } else {
2768 me = NULL;
2769 other = NULL;
2770 g_assert_not_reached();
2771 }
2773 // calculate radial coordinates of the grabbed control, other control, and the mouse point
2774 Radial rme(me->pos - n->pos);
2775 Radial rother(other->pos - n->pos);
2776 Radial rnew(*p - n->pos);
2778 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2779 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2780 /* 0 interpreted as "no snapping". */
2782 // The closest PI/snaps angle, starting from zero.
2783 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2784 if (me->origin.a == HUGE_VAL) {
2785 // ortho doesn't exist: original control was zero length.
2786 rnew.a = a_snapped;
2787 } else {
2788 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2789 * its opposite and perpendiculars). */
2790 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2792 // Snap to the closest.
2793 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2794 ? a_snapped
2795 : a_ortho );
2796 }
2797 }
2799 if (state & GDK_MOD1_MASK) {
2800 // lock handle length
2801 rnew.r = me->origin.r;
2802 }
2804 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2805 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2806 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2807 rother.a += rnew.a - rme.a;
2808 other->pos = NR::Point(rother) + n->pos;
2809 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2810 sp_knot_set_position(other->knot, &other->pos, 0);
2811 }
2813 me->pos = NR::Point(rnew) + n->pos;
2814 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2816 // this is what sp_knot_set_position does, but without emitting the signal:
2817 // we cannot emit a "moved" signal because we're now processing it
2818 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2820 knot->desktop->set_coordinate_status(me->pos);
2822 update_object(n->subpath->nodepath);
2824 /* status text */
2825 SPDesktop *desktop = n->subpath->nodepath->desktop;
2826 if (!desktop) return;
2827 SPEventContext *ec = desktop->event_context;
2828 if (!ec) return;
2829 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2830 if (!mc) return;
2832 double degrees = 180 / M_PI * rnew.a;
2833 if (degrees > 180) degrees -= 360;
2834 if (degrees < -180) degrees += 360;
2835 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2836 degrees = angle_to_compass (degrees);
2838 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2840 mc->setF(Inkscape::NORMAL_MESSAGE,
2841 _("<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);
2843 g_string_free(length, TRUE);
2844 }
2846 /**
2847 * Node handle event callback.
2848 */
2849 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2850 {
2851 gboolean ret = FALSE;
2852 switch (event->type) {
2853 case GDK_KEY_PRESS:
2854 switch (get_group0_keyval (&event->key)) {
2855 case GDK_space:
2856 if (event->key.state & GDK_BUTTON1_MASK) {
2857 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2858 stamp_repr(nodepath);
2859 ret = TRUE;
2860 }
2861 break;
2862 default:
2863 break;
2864 }
2865 break;
2866 default:
2867 break;
2868 }
2870 return ret;
2871 }
2873 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2874 Radial &rme, Radial &rother, gboolean const both)
2875 {
2876 rme.a += angle;
2877 if ( both
2878 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2879 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2880 {
2881 rother.a += angle;
2882 }
2883 }
2885 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2886 Radial &rme, Radial &rother, gboolean const both)
2887 {
2888 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2890 gdouble r;
2891 if ( both
2892 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2893 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2894 {
2895 r = MAX(rme.r, rother.r);
2896 } else {
2897 r = rme.r;
2898 }
2900 gdouble const weird_angle = atan2(norm_angle, r);
2901 /* Bulia says norm_angle is just the visible distance that the
2902 * object's end must travel on the screen. Left as 'angle' for want of
2903 * a better name.*/
2905 rme.a += weird_angle;
2906 if ( both
2907 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2908 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2909 {
2910 rother.a += weird_angle;
2911 }
2912 }
2914 /**
2915 * Rotate one node.
2916 */
2917 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2918 {
2919 Inkscape::NodePath::NodeSide *me, *other;
2920 bool both = false;
2922 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2923 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2925 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2926 me = &(n->p);
2927 other = &(n->n);
2928 } else if (!n->p.other) {
2929 me = &(n->n);
2930 other = &(n->p);
2931 } else {
2932 if (which > 0) { // right handle
2933 if (xn > xp) {
2934 me = &(n->n);
2935 other = &(n->p);
2936 } else {
2937 me = &(n->p);
2938 other = &(n->n);
2939 }
2940 } else if (which < 0){ // left handle
2941 if (xn <= xp) {
2942 me = &(n->n);
2943 other = &(n->p);
2944 } else {
2945 me = &(n->p);
2946 other = &(n->n);
2947 }
2948 } else { // both handles
2949 me = &(n->n);
2950 other = &(n->p);
2951 both = true;
2952 }
2953 }
2955 Radial rme(me->pos - n->pos);
2956 Radial rother(other->pos - n->pos);
2958 if (screen) {
2959 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2960 } else {
2961 node_rotate_one_internal (*n, angle, rme, rother, both);
2962 }
2964 me->pos = n->pos + NR::Point(rme);
2966 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
2967 other->pos = n->pos + NR::Point(rother);
2968 }
2970 sp_node_ensure_ctrls(n);
2971 }
2973 /**
2974 * Rotate selected nodes.
2975 */
2976 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
2977 {
2978 if (!nodepath || !nodepath->selected) return;
2980 if (g_list_length(nodepath->selected) == 1) {
2981 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
2982 node_rotate_one (n, angle, which, screen);
2983 } else {
2984 // rotate as an object:
2986 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
2987 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
2988 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2989 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2990 box.expandTo (n->pos); // contain all selected nodes
2991 }
2993 gdouble rot;
2994 if (screen) {
2995 gdouble const zoom = nodepath->desktop->current_zoom();
2996 gdouble const zmove = angle / zoom;
2997 gdouble const r = NR::L2(box.max() - box.midpoint());
2998 rot = atan2(zmove, r);
2999 } else {
3000 rot = angle;
3001 }
3003 NR::Matrix t =
3004 NR::Matrix (NR::translate(-box.midpoint())) *
3005 NR::Matrix (NR::rotate(rot)) *
3006 NR::Matrix (NR::translate(box.midpoint()));
3008 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3009 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3010 n->pos *= t;
3011 n->n.pos *= t;
3012 n->p.pos *= t;
3013 sp_node_ensure_ctrls(n);
3014 }
3015 }
3017 update_object(nodepath);
3018 /// \todo fixme: use _keyed
3019 update_repr(nodepath);
3020 }
3022 /**
3023 * Scale one node.
3024 */
3025 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3026 {
3027 bool both = false;
3028 Inkscape::NodePath::NodeSide *me, *other;
3030 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3031 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3033 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3034 me = &(n->p);
3035 other = &(n->n);
3036 n->code = NR_CURVETO;
3037 } else if (!n->p.other) {
3038 me = &(n->n);
3039 other = &(n->p);
3040 if (n->n.other)
3041 n->n.other->code = NR_CURVETO;
3042 } else {
3043 if (which > 0) { // right handle
3044 if (xn > xp) {
3045 me = &(n->n);
3046 other = &(n->p);
3047 if (n->n.other)
3048 n->n.other->code = NR_CURVETO;
3049 } else {
3050 me = &(n->p);
3051 other = &(n->n);
3052 n->code = NR_CURVETO;
3053 }
3054 } else if (which < 0){ // left handle
3055 if (xn <= xp) {
3056 me = &(n->n);
3057 other = &(n->p);
3058 if (n->n.other)
3059 n->n.other->code = NR_CURVETO;
3060 } else {
3061 me = &(n->p);
3062 other = &(n->n);
3063 n->code = NR_CURVETO;
3064 }
3065 } else { // both handles
3066 me = &(n->n);
3067 other = &(n->p);
3068 both = true;
3069 n->code = NR_CURVETO;
3070 if (n->n.other)
3071 n->n.other->code = NR_CURVETO;
3072 }
3073 }
3075 Radial rme(me->pos - n->pos);
3076 Radial rother(other->pos - n->pos);
3078 rme.r += grow;
3079 if (rme.r < 0) rme.r = 0;
3080 if (rme.a == HUGE_VAL) {
3081 if (me->other) { // if direction is unknown, initialize it towards the next node
3082 Radial rme_next(me->other->pos - n->pos);
3083 rme.a = rme_next.a;
3084 } else { // if there's no next, initialize to 0
3085 rme.a = 0;
3086 }
3087 }
3088 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3089 rother.r += grow;
3090 if (rother.r < 0) rother.r = 0;
3091 if (rother.a == HUGE_VAL) {
3092 rother.a = rme.a + M_PI;
3093 }
3094 }
3096 me->pos = n->pos + NR::Point(rme);
3098 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3099 other->pos = n->pos + NR::Point(rother);
3100 }
3102 sp_node_ensure_ctrls(n);
3103 }
3105 /**
3106 * Scale selected nodes.
3107 */
3108 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3109 {
3110 if (!nodepath || !nodepath->selected) return;
3112 if (g_list_length(nodepath->selected) == 1) {
3113 // scale handles of the single selected node
3114 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3115 node_scale_one (n, grow, which);
3116 } else {
3117 // scale nodes as an "object":
3119 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3120 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3121 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3122 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3123 box.expandTo (n->pos); // contain all selected nodes
3124 }
3126 double scale = (box.maxExtent() + grow)/box.maxExtent();
3128 NR::Matrix t =
3129 NR::Matrix (NR::translate(-box.midpoint())) *
3130 NR::Matrix (NR::scale(scale, scale)) *
3131 NR::Matrix (NR::translate(box.midpoint()));
3133 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3134 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3135 n->pos *= t;
3136 n->n.pos *= t;
3137 n->p.pos *= t;
3138 sp_node_ensure_ctrls(n);
3139 }
3140 }
3142 update_object(nodepath);
3143 /// \todo fixme: use _keyed
3144 update_repr(nodepath);
3145 }
3147 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3148 {
3149 if (!nodepath) return;
3150 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3151 }
3153 /**
3154 * Flip selected nodes horizontally/vertically.
3155 */
3156 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3157 {
3158 if (!nodepath || !nodepath->selected) return;
3160 if (g_list_length(nodepath->selected) == 1) {
3161 // flip handles of the single selected node
3162 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3163 double temp = n->p.pos[axis];
3164 n->p.pos[axis] = n->n.pos[axis];
3165 n->n.pos[axis] = temp;
3166 sp_node_ensure_ctrls(n);
3167 } else {
3168 // scale nodes as an "object":
3170 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3171 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3172 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3173 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3174 box.expandTo (n->pos); // contain all selected nodes
3175 }
3177 NR::Matrix t =
3178 NR::Matrix (NR::translate(-box.midpoint())) *
3179 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3180 NR::Matrix (NR::translate(box.midpoint()));
3182 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3183 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3184 n->pos *= t;
3185 n->n.pos *= t;
3186 n->p.pos *= t;
3187 sp_node_ensure_ctrls(n);
3188 }
3189 }
3191 update_object(nodepath);
3192 /// \todo fixme: use _keyed
3193 update_repr(nodepath);
3194 }
3196 //-----------------------------------------------
3197 /**
3198 * Return new subpath under given nodepath.
3199 */
3200 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3201 {
3202 g_assert(nodepath);
3203 g_assert(nodepath->desktop);
3205 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3207 s->nodepath = nodepath;
3208 s->closed = FALSE;
3209 s->nodes = NULL;
3210 s->first = NULL;
3211 s->last = NULL;
3213 // do not use prepend here because:
3214 // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with
3215 // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not
3216 // the i-th node in the nodepath (only if there are multiple subpaths)
3217 // note that the problem only arise when called from subpath_from_bpath(), since for all the other
3218 // cases, the repr is updated after the call to sp_nodepath_subpath_new()
3219 nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s);
3221 return s;
3222 }
3224 /**
3225 * Destroy nodes in subpath, then subpath itself.
3226 */
3227 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3228 {
3229 g_assert(subpath);
3230 g_assert(subpath->nodepath);
3231 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3233 while (subpath->nodes) {
3234 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3235 }
3237 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3239 g_free(subpath);
3240 }
3242 /**
3243 * Link head to tail in subpath.
3244 */
3245 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3246 {
3247 g_assert(!sp->closed);
3248 g_assert(sp->last != sp->first);
3249 g_assert(sp->first->code == NR_MOVETO);
3251 sp->closed = TRUE;
3253 //Link the head to the tail
3254 sp->first->p.other = sp->last;
3255 sp->last->n.other = sp->first;
3256 sp->last->n.pos = sp->first->n.pos;
3257 sp->first = sp->last;
3259 //Remove the extra end node
3260 sp_nodepath_node_destroy(sp->last->n.other);
3261 }
3263 /**
3264 * Open closed (loopy) subpath at node.
3265 */
3266 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3267 {
3268 g_assert(sp->closed);
3269 g_assert(n->subpath == sp);
3270 g_assert(sp->first == sp->last);
3272 /* We create new startpoint, current node will become last one */
3274 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3275 &n->pos, &n->pos, &n->n.pos);
3278 sp->closed = FALSE;
3280 //Unlink to make a head and tail
3281 sp->first = new_path;
3282 sp->last = n;
3283 n->n.other = NULL;
3284 new_path->p.other = NULL;
3285 }
3287 /**
3288 * Returns area in triangle given by points; may be negative.
3289 */
3290 inline double
3291 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3292 {
3293 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]);
3294 }
3296 /**
3297 * Return new node in subpath with given properties.
3298 * \param pos Position of node.
3299 * \param ppos Handle position in previous direction
3300 * \param npos Handle position in previous direction
3301 */
3302 Inkscape::NodePath::Node *
3303 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)
3304 {
3305 g_assert(sp);
3306 g_assert(sp->nodepath);
3307 g_assert(sp->nodepath->desktop);
3309 if (nodechunk == NULL)
3310 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3312 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3314 n->subpath = sp;
3316 if (type != Inkscape::NodePath::NODE_NONE) {
3317 // use the type from sodipodi:nodetypes
3318 n->type = type;
3319 } else {
3320 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3321 // points are (almost) collinear
3322 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3323 // endnode, or a node with a retracted handle
3324 n->type = Inkscape::NodePath::NODE_CUSP;
3325 } else {
3326 n->type = Inkscape::NodePath::NODE_SMOOTH;
3327 }
3328 } else {
3329 n->type = Inkscape::NodePath::NODE_CUSP;
3330 }
3331 }
3333 n->code = code;
3334 n->selected = FALSE;
3335 n->pos = *pos;
3336 n->p.pos = *ppos;
3337 n->n.pos = *npos;
3339 n->dragging_out = NULL;
3341 Inkscape::NodePath::Node *prev;
3342 if (next) {
3343 //g_assert(g_list_find(sp->nodes, next));
3344 prev = next->p.other;
3345 } else {
3346 prev = sp->last;
3347 }
3349 if (prev)
3350 prev->n.other = n;
3351 else
3352 sp->first = n;
3354 if (next)
3355 next->p.other = n;
3356 else
3357 sp->last = n;
3359 n->p.other = prev;
3360 n->n.other = next;
3362 n->knot = sp_knot_new(sp->nodepath->desktop);
3363 sp_knot_set_position(n->knot, pos, 0);
3364 g_object_set(G_OBJECT(n->knot),
3365 "anchor", GTK_ANCHOR_CENTER,
3366 "fill", NODE_FILL,
3367 "fill_mouseover", NODE_FILL_HI,
3368 "stroke", NODE_STROKE,
3369 "stroke_mouseover", NODE_STROKE_HI,
3370 "tip", _("<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"),
3371 "shape", (n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE,
3372 "size", (n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7,
3373 NULL);
3375 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3376 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3377 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3378 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3379 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3380 sp_knot_show(n->knot);
3382 // We only create side knots and lines on demand
3383 n->p.knot = NULL;
3384 n->p.line = NULL;
3385 n->n.knot = NULL;
3386 n->n.line = NULL;
3388 sp->nodes = g_list_prepend(sp->nodes, n);
3390 return n;
3391 }
3393 /**
3394 * Destroy node and its knots, link neighbors in subpath.
3395 */
3396 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3397 {
3398 g_assert(node);
3399 g_assert(node->subpath);
3400 g_assert(SP_IS_KNOT(node->knot));
3401 // g_assert(g_list_find(node->subpath->nodes, node));
3403 Inkscape::NodePath::SubPath *sp = node->subpath;
3405 if (node->selected) { // first, deselect
3406 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3407 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3408 }
3410 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3412 g_object_unref(G_OBJECT(node->knot));
3413 if (node->p.knot)
3414 g_object_unref(G_OBJECT(node->p.knot));
3415 if (node->n.knot)
3416 g_object_unref(G_OBJECT(node->n.knot));
3418 if (node->p.line)
3419 gtk_object_destroy(GTK_OBJECT(node->p.line));
3420 if (node->n.line)
3421 gtk_object_destroy(GTK_OBJECT(node->n.line));
3423 if (sp->nodes) { // there are others nodes on the subpath
3424 if (sp->closed) {
3425 if (sp->first == node) {
3426 g_assert(sp->last == node);
3427 sp->first = node->n.other;
3428 sp->last = sp->first;
3429 }
3430 node->p.other->n.other = node->n.other;
3431 node->n.other->p.other = node->p.other;
3432 } else {
3433 if (sp->first == node) {
3434 sp->first = node->n.other;
3435 sp->first->code = NR_MOVETO;
3436 }
3437 if (sp->last == node) sp->last = node->p.other;
3438 if (node->p.other) node->p.other->n.other = node->n.other;
3439 if (node->n.other) node->n.other->p.other = node->p.other;
3440 }
3441 } else { // this was the last node on subpath
3442 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3443 }
3445 g_mem_chunk_free(nodechunk, node);
3446 }
3448 /**
3449 * Returns one of the node's two knots (node sides).
3450 * \param which Indicates which side.
3451 * \return Pointer to previous node side if which==-1, next if which==1.
3452 */
3453 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3454 {
3455 g_assert(node);
3457 switch (which) {
3458 case -1:
3459 return &node->p;
3460 case 1:
3461 return &node->n;
3462 default:
3463 break;
3464 }
3466 g_assert_not_reached();
3468 return NULL;
3469 }
3471 /**
3472 * Return knot on other side of node.
3473 */
3474 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3475 {
3476 g_assert(node);
3478 if (me == &node->p) return &node->n;
3479 if (me == &node->n) return &node->p;
3481 g_assert_not_reached();
3483 return NULL;
3484 }
3486 /**
3487 * Return NRPathcode on this knot's side of the node.
3488 */
3489 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3490 {
3491 g_assert(node);
3493 if (me == &node->p) {
3494 if (node->p.other) return (NRPathcode)node->code;
3495 return NR_MOVETO;
3496 }
3498 if (me == &node->n) {
3499 if (node->n.other) return (NRPathcode)node->n.other->code;
3500 return NR_MOVETO;
3501 }
3503 g_assert_not_reached();
3505 return NR_END;
3506 }
3508 /**
3509 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3510 */
3511 Inkscape::NodePath::Node *
3512 sp_nodepath_get_node_by_index(int index)
3513 {
3514 Inkscape::NodePath::Node *e = NULL;
3516 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3517 if (!nodepath) {
3518 return e;
3519 }
3521 //find segment
3522 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3524 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3525 int n = g_list_length(sp->nodes);
3526 if (sp->closed) {
3527 n++;
3528 }
3530 //if the piece belongs to this subpath grab it
3531 //otherwise move onto the next subpath
3532 if (index < n) {
3533 e = sp->first;
3534 for (int i = 0; i < index; ++i) {
3535 e = e->n.other;
3536 }
3537 break;
3538 } else {
3539 if (sp->closed) {
3540 index -= (n+1);
3541 } else {
3542 index -= n;
3543 }
3544 }
3545 }
3547 return e;
3548 }
3550 /**
3551 * Returns plain text meaning of node type.
3552 */
3553 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3554 {
3555 unsigned retracted = 0;
3556 bool endnode = false;
3558 for (int which = -1; which <= 1; which += 2) {
3559 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3560 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3561 retracted ++;
3562 if (!side->other)
3563 endnode = true;
3564 }
3566 if (retracted == 0) {
3567 if (endnode) {
3568 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3569 return _("end node");
3570 } else {
3571 switch (node->type) {
3572 case Inkscape::NodePath::NODE_CUSP:
3573 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3574 return _("cusp");
3575 case Inkscape::NodePath::NODE_SMOOTH:
3576 // TRANSLATORS: "smooth" is an adjective here
3577 return _("smooth");
3578 case Inkscape::NodePath::NODE_SYMM:
3579 return _("symmetric");
3580 }
3581 }
3582 } else if (retracted == 1) {
3583 if (endnode) {
3584 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3585 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3586 } else {
3587 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3588 }
3589 } else {
3590 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3591 }
3593 return NULL;
3594 }
3596 /**
3597 * Handles content of statusbar as long as node tool is active.
3598 */
3599 void
3600 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3601 {
3602 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3603 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3605 gint total = 0;
3606 gint selected = 0;
3607 SPDesktop *desktop = NULL;
3609 if (nodepath) {
3610 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3611 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3612 total += g_list_length(subpath->nodes);
3613 }
3614 selected = g_list_length(nodepath->selected);
3615 desktop = nodepath->desktop;
3616 } else {
3617 desktop = SP_ACTIVE_DESKTOP;
3618 }
3620 SPEventContext *ec = desktop->event_context;
3621 if (!ec) return;
3622 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3623 if (!mc) return;
3625 if (selected == 0) {
3626 Inkscape::Selection *sel = desktop->selection;
3627 if (!sel || sel->isEmpty()) {
3628 mc->setF(Inkscape::NORMAL_MESSAGE,
3629 _("Select a single object to edit its nodes or handles."));
3630 } else {
3631 if (nodepath) {
3632 mc->setF(Inkscape::NORMAL_MESSAGE,
3633 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.",
3634 "<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.",
3635 total),
3636 total);
3637 } else {
3638 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3639 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3640 } else {
3641 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3642 }
3643 }
3644 }
3645 } else if (nodepath && selected == 1) {
3646 mc->setF(Inkscape::NORMAL_MESSAGE,
3647 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3648 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3649 total),
3650 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3651 } else {
3652 mc->setF(Inkscape::NORMAL_MESSAGE,
3653 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3654 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3655 total),
3656 selected, total, when_selected);
3657 }
3658 }
3661 /*
3662 Local Variables:
3663 mode:c++
3664 c-file-style:"stroustrup"
3665 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3666 indent-tabs-mode:nil
3667 fill-column:99
3668 End:
3669 */
3670 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :