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);
114 /* Constructors and destructors */
116 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
117 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
118 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
119 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
120 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
121 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
122 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
124 /* Helpers */
126 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
127 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
128 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
130 // active_node indicates mouseover node
131 static Inkscape::NodePath::Node *active_node = NULL;
133 /**
134 * \brief Creates new nodepath from item
135 */
136 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
137 {
138 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
140 /** \todo
141 * FIXME: remove this. We don't want to edit paths inside flowtext.
142 * Instead we will build our flowtext with cloned paths, so that the
143 * real paths are outside the flowtext and thus editable as usual.
144 */
145 if (SP_IS_FLOWTEXT(item)) {
146 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
147 if SP_IS_FLOWREGION(child) {
148 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
149 if (grandchild && SP_IS_PATH(grandchild)) {
150 item = SP_ITEM(grandchild);
151 break;
152 }
153 }
154 }
155 }
157 if (!SP_IS_PATH(item))
158 return NULL;
159 SPPath *path = SP_PATH(item);
160 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
161 if (curve == NULL)
162 return NULL;
164 NArtBpath *bpath = sp_curve_first_bpath(curve);
165 gint length = curve->end;
166 if (length == 0)
167 return NULL; // prevent crash for one-node paths
169 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
170 gchar *typestr = parse_nodetypes(nodetypes, length);
172 //Create new nodepath
173 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
174 if (!np)
175 return NULL;
177 // Set defaults
178 np->desktop = desktop;
179 np->path = path;
180 np->subpaths = NULL;
181 np->selected = NULL;
182 np->nodeContext = NULL; //Let the context that makes this set it
184 // we need to update item's transform from the repr here,
185 // because they may be out of sync when we respond
186 // to a change in repr by regenerating nodepath --bb
187 sp_object_read_attr(SP_OBJECT(item), "transform");
189 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
190 np->d2i = np->i2d.inverse();
191 np->repr = repr;
193 /* Now the bitchy part (lauris) */
195 NArtBpath *b = bpath;
197 while (b->code != NR_END) {
198 b = subpath_from_bpath(np, b, typestr + (b - bpath));
199 }
201 g_free(typestr);
202 sp_curve_unref(curve);
204 return np;
205 }
207 /**
208 * Destroys nodepath's subpaths, then itself, also tell context about it.
209 */
210 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
212 if (!np) //soft fail, like delete
213 return;
215 while (np->subpaths) {
216 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
217 }
219 //Inform the context that made me, if any, that I am gone.
220 if (np->nodeContext)
221 np->nodeContext->nodepath = NULL;
223 g_assert(!np->selected);
225 np->desktop = NULL;
227 g_free(np);
228 }
231 /**
232 * Return the node count of a given NodeSubPath.
233 */
234 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
235 {
236 if (!subpath)
237 return 0;
238 gint nodeCount = g_list_length(subpath->nodes);
239 return nodeCount;
240 }
242 /**
243 * Return the node count of a given NodePath.
244 */
245 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
246 {
247 if (!np)
248 return 0;
249 gint nodeCount = 0;
250 for (GList *item = np->subpaths ; item ; item=item->next) {
251 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
252 nodeCount += g_list_length(subpath->nodes);
253 }
254 return nodeCount;
255 }
258 /**
259 * Clean up a nodepath after editing.
260 *
261 * Currently we are deleting trivial subpaths.
262 */
263 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
264 {
265 GList *badSubPaths = NULL;
267 //Check all subpaths to be >=2 nodes
268 for (GList *l = nodepath->subpaths; l ; l=l->next) {
269 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
270 if (sp_nodepath_subpath_get_node_count(sp)<2)
271 badSubPaths = g_list_append(badSubPaths, sp);
272 }
274 //Delete them. This second step is because sp_nodepath_subpath_destroy()
275 //also removes the subpath from nodepath->subpaths
276 for (GList *l = badSubPaths; l ; l=l->next) {
277 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
278 sp_nodepath_subpath_destroy(sp);
279 }
281 g_list_free(badSubPaths);
282 }
286 /**
287 * \brief Returns true if the argument nodepath and the d attribute in
288 * its repr do not match.
289 *
290 * This may happen if repr was changed in, e.g., XML editor or by undo.
291 *
292 * \todo
293 * UGLY HACK, think how we can eliminate it.
294 */
295 gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd)
296 {
297 g_assert(np);
299 SPCurve *curve = create_curve(np);
301 gchar *svgpath = sp_svg_write_path(curve->bpath);
303 char const *attr_d = ( newd
304 ? newd
305 : SP_OBJECT(np->path)->repr->attribute("d") );
307 gboolean ret;
308 if (attr_d && svgpath)
309 ret = strcmp(attr_d, svgpath);
310 else
311 ret = TRUE;
313 g_free(svgpath);
314 sp_curve_unref(curve);
316 return ret;
317 }
319 /**
320 * \brief Returns true if the argument nodepath and the sodipodi:nodetypes
321 * attribute in its repr do not match.
322 *
323 * This may happen if repr was changed in, e.g., the XML editor or by undo.
324 */
325 gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr)
326 {
327 g_assert(np);
328 gchar *typestr = create_typestr(np);
329 char const *attr_typestr = ( newtypestr
330 ? newtypestr
331 : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") );
332 gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr));
334 g_free(typestr);
336 return ret;
337 }
339 /**
340 * Create new nodepath from b, make it subpath of np.
341 * \param t The node type.
342 * \todo Fixme: t should be a proper type, rather than gchar
343 */
344 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
345 {
346 NR::Point ppos, pos, npos;
348 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
350 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
351 bool const closed = (b->code == NR_MOVETO);
353 pos = NR::Point(b->x3, b->y3) * np->i2d;
354 if (b[1].code == NR_CURVETO) {
355 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
356 } else {
357 npos = pos;
358 }
359 Inkscape::NodePath::Node *n;
360 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
361 g_assert(sp->first == n);
362 g_assert(sp->last == n);
364 b++;
365 t++;
366 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
367 pos = NR::Point(b->x3, b->y3) * np->i2d;
368 if (b->code == NR_CURVETO) {
369 ppos = NR::Point(b->x2, b->y2) * np->i2d;
370 } else {
371 ppos = pos;
372 }
373 if (b[1].code == NR_CURVETO) {
374 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
375 } else {
376 npos = pos;
377 }
378 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
379 b++;
380 t++;
381 }
383 if (closed) sp_nodepath_subpath_close(sp);
385 return b;
386 }
388 /**
389 * Convert from sodipodi:nodetypes to new style type string.
390 */
391 static gchar *parse_nodetypes(gchar const *types, gint length)
392 {
393 g_assert(length > 0);
395 gchar *typestr = g_new(gchar, length + 1);
397 gint pos = 0;
399 if (types) {
400 for (gint i = 0; types[i] && ( i < length ); i++) {
401 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
402 if (types[i] != '\0') {
403 switch (types[i]) {
404 case 's':
405 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
406 break;
407 case 'z':
408 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
409 break;
410 case 'c':
411 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
412 break;
413 default:
414 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
415 break;
416 }
417 }
418 }
419 }
421 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
423 return typestr;
424 }
426 /**
427 * Make curve out of path and associate it with it.
428 */
429 static void update_object(Inkscape::NodePath::Path *np)
430 {
431 g_assert(np);
433 SPCurve *curve = create_curve(np);
435 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
437 sp_curve_unref(curve);
438 }
440 /**
441 * Update XML path node with data from path object.
442 */
443 static void update_repr_internal(Inkscape::NodePath::Path *np)
444 {
445 g_assert(np);
447 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
449 SPCurve *curve = create_curve(np);
450 gchar *typestr = create_typestr(np);
451 gchar *svgpath = sp_svg_write_path(curve->bpath);
453 repr->setAttribute("d", svgpath);
454 repr->setAttribute("sodipodi:nodetypes", typestr);
456 g_free(svgpath);
457 g_free(typestr);
458 sp_curve_unref(curve);
459 }
461 /**
462 * Update XML path node with data from path object, commit changes forever.
463 */
464 static void update_repr(Inkscape::NodePath::Path *np)
465 {
466 update_repr_internal(np);
467 sp_document_done(SP_DT_DOCUMENT(np->desktop));
468 }
470 /**
471 * Update XML path node with data from path object, commit changes with undo.
472 */
473 static void update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
474 {
475 update_repr_internal(np);
476 sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
477 }
479 /**
480 * Make duplicate of path, replace corresponding XML node in tree, commit.
481 */
482 static void stamp_repr(Inkscape::NodePath::Path *np)
483 {
484 g_assert(np);
486 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
487 Inkscape::XML::Node *new_repr = old_repr->duplicate();
489 // remember the position of the item
490 gint pos = old_repr->position();
491 // remember parent
492 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
494 SPCurve *curve = create_curve(np);
495 gchar *typestr = create_typestr(np);
497 gchar *svgpath = sp_svg_write_path(curve->bpath);
499 new_repr->setAttribute("d", svgpath);
500 new_repr->setAttribute("sodipodi:nodetypes", typestr);
502 // add the new repr to the parent
503 parent->appendChild(new_repr);
504 // move to the saved position
505 new_repr->setPosition(pos > 0 ? pos : 0);
507 sp_document_done(SP_DT_DOCUMENT(np->desktop));
509 Inkscape::GC::release(new_repr);
510 g_free(svgpath);
511 g_free(typestr);
512 sp_curve_unref(curve);
513 }
515 /**
516 * Create curve from path.
517 */
518 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
519 {
520 SPCurve *curve = sp_curve_new();
522 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
523 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
524 sp_curve_moveto(curve,
525 sp->first->pos * np->d2i);
526 Inkscape::NodePath::Node *n = sp->first->n.other;
527 while (n) {
528 NR::Point const end_pt = n->pos * np->d2i;
529 switch (n->code) {
530 case NR_LINETO:
531 sp_curve_lineto(curve, end_pt);
532 break;
533 case NR_CURVETO:
534 sp_curve_curveto(curve,
535 n->p.other->n.pos * np->d2i,
536 n->p.pos * np->d2i,
537 end_pt);
538 break;
539 default:
540 g_assert_not_reached();
541 break;
542 }
543 if (n != sp->last) {
544 n = n->n.other;
545 } else {
546 n = NULL;
547 }
548 }
549 if (sp->closed) {
550 sp_curve_closepath(curve);
551 }
552 }
554 return curve;
555 }
557 /**
558 * Convert path type string to sodipodi:nodetypes style.
559 */
560 static gchar *create_typestr(Inkscape::NodePath::Path *np)
561 {
562 gchar *typestr = g_new(gchar, 32);
563 gint len = 32;
564 gint pos = 0;
566 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
567 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
569 if (pos >= len) {
570 typestr = g_renew(gchar, typestr, len + 32);
571 len += 32;
572 }
574 typestr[pos++] = 'c';
576 Inkscape::NodePath::Node *n;
577 n = sp->first->n.other;
578 while (n) {
579 gchar code;
581 switch (n->type) {
582 case Inkscape::NodePath::NODE_CUSP:
583 code = 'c';
584 break;
585 case Inkscape::NodePath::NODE_SMOOTH:
586 code = 's';
587 break;
588 case Inkscape::NodePath::NODE_SYMM:
589 code = 'z';
590 break;
591 default:
592 g_assert_not_reached();
593 code = '\0';
594 break;
595 }
597 if (pos >= len) {
598 typestr = g_renew(gchar, typestr, len + 32);
599 len += 32;
600 }
602 typestr[pos++] = code;
604 if (n != sp->last) {
605 n = n->n.other;
606 } else {
607 n = NULL;
608 }
609 }
610 }
612 if (pos >= len) {
613 typestr = g_renew(gchar, typestr, len + 1);
614 len += 1;
615 }
617 typestr[pos++] = '\0';
619 return typestr;
620 }
622 /**
623 * Returns current path in context.
624 */
625 static Inkscape::NodePath::Path *sp_nodepath_current()
626 {
627 if (!SP_ACTIVE_DESKTOP) {
628 return NULL;
629 }
631 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
633 if (!SP_IS_NODE_CONTEXT(event_context)) {
634 return NULL;
635 }
637 return SP_NODE_CONTEXT(event_context)->nodepath;
638 }
642 /**
643 \brief Fills node and control positions for three nodes, splitting line
644 marked by end at distance t.
645 */
646 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
647 {
648 g_assert(new_path != NULL);
649 g_assert(end != NULL);
651 g_assert(end->p.other == new_path);
652 Inkscape::NodePath::Node *start = new_path->p.other;
653 g_assert(start);
655 if (end->code == NR_LINETO) {
656 new_path->type =Inkscape::NodePath::NODE_CUSP;
657 new_path->code = NR_LINETO;
658 new_path->pos = (t * start->pos + (1 - t) * end->pos);
659 } else {
660 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
661 new_path->code = NR_CURVETO;
662 gdouble s = 1 - t;
663 for (int dim = 0; dim < 2; dim++) {
664 NR::Coord const f000 = start->pos[dim];
665 NR::Coord const f001 = start->n.pos[dim];
666 NR::Coord const f011 = end->p.pos[dim];
667 NR::Coord const f111 = end->pos[dim];
668 NR::Coord const f00t = s * f000 + t * f001;
669 NR::Coord const f01t = s * f001 + t * f011;
670 NR::Coord const f11t = s * f011 + t * f111;
671 NR::Coord const f0tt = s * f00t + t * f01t;
672 NR::Coord const f1tt = s * f01t + t * f11t;
673 NR::Coord const fttt = s * f0tt + t * f1tt;
674 start->n.pos[dim] = f00t;
675 new_path->p.pos[dim] = f0tt;
676 new_path->pos[dim] = fttt;
677 new_path->n.pos[dim] = f1tt;
678 end->p.pos[dim] = f11t;
679 }
680 }
681 }
683 /**
684 * Adds new node on direct line between two nodes, activates handles of all
685 * three nodes.
686 */
687 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
688 {
689 g_assert(end);
690 g_assert(end->subpath);
691 g_assert(g_list_find(end->subpath->nodes, end));
693 Inkscape::NodePath::Node *start = end->p.other;
694 g_assert( start->n.other == end );
695 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
696 end,
697 Inkscape::NodePath::NODE_SMOOTH,
698 (NRPathcode)end->code,
699 &start->pos, &start->pos, &start->n.pos);
700 sp_nodepath_line_midpoint(newnode, end, t);
702 sp_node_ensure_ctrls(start);
703 sp_node_ensure_ctrls(newnode);
704 sp_node_ensure_ctrls(end);
706 return newnode;
707 }
709 /**
710 \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
711 */
712 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
713 {
714 g_assert(node);
715 g_assert(node->subpath);
716 g_assert(g_list_find(node->subpath->nodes, node));
718 Inkscape::NodePath::SubPath *sp = node->subpath;
719 Inkscape::NodePath::Path *np = sp->nodepath;
721 if (sp->closed) {
722 sp_nodepath_subpath_open(sp, node);
723 return sp->first;
724 } else {
725 // no break for end nodes
726 if (node == sp->first) return NULL;
727 if (node == sp->last ) return NULL;
729 // create a new subpath
730 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
732 // duplicate the break node as start of the new subpath
733 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
735 while (node->n.other) { // copy the remaining nodes into the new subpath
736 Inkscape::NodePath::Node *n = node->n.other;
737 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);
738 if (n->selected) {
739 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
740 }
741 sp_nodepath_node_destroy(n); // remove the point on the original subpath
742 }
744 return newnode;
745 }
746 }
748 /**
749 * Duplicate node and connect to neighbours.
750 */
751 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
752 {
753 g_assert(node);
754 g_assert(node->subpath);
755 g_assert(g_list_find(node->subpath->nodes, node));
757 Inkscape::NodePath::SubPath *sp = node->subpath;
759 NRPathcode code = (NRPathcode) node->code;
760 if (code == NR_MOVETO) { // if node is the endnode,
761 node->code = NR_LINETO; // new one is inserted before it, so change that to line
762 }
764 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
766 if (!node->n.other || !node->p.other) // if node is an endnode, select it
767 return node;
768 else
769 return newnode; // otherwise select the newly created node
770 }
772 static void sp_node_control_mirror_n_to_p(Inkscape::NodePath::Node *node)
773 {
774 node->p.pos = (node->pos + (node->pos - node->n.pos));
775 }
777 static void sp_node_control_mirror_p_to_n(Inkscape::NodePath::Node *node)
778 {
779 node->n.pos = (node->pos + (node->pos - node->p.pos));
780 }
782 /**
783 * Change line type at node, with side effects on neighbours.
784 */
785 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
786 {
787 g_assert(end);
788 g_assert(end->subpath);
789 g_assert(end->p.other);
791 if (end->code == static_cast< guint > ( code ) )
792 return;
794 Inkscape::NodePath::Node *start = end->p.other;
796 end->code = code;
798 if (code == NR_LINETO) {
799 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
800 if (end->n.other) {
801 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
802 }
803 sp_node_adjust_knot(start, -1);
804 sp_node_adjust_knot(end, 1);
805 } else {
806 NR::Point delta = end->pos - start->pos;
807 start->n.pos = start->pos + delta / 3;
808 end->p.pos = end->pos - delta / 3;
809 sp_node_adjust_knot(start, 1);
810 sp_node_adjust_knot(end, -1);
811 }
813 sp_node_ensure_ctrls(start);
814 sp_node_ensure_ctrls(end);
815 }
817 /**
818 * Change node type, and its handles accordingly.
819 */
820 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeType type)
821 {
822 g_assert(node);
823 g_assert(node->subpath);
825 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
826 return node;
828 if ((node->p.other != NULL) && (node->n.other != NULL)) {
829 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
830 type =Inkscape::NodePath::NODE_CUSP;
831 }
832 }
834 node->type = type;
836 if (node->type == Inkscape::NodePath::NODE_CUSP) {
837 g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL);
838 } else {
839 g_object_set(G_OBJECT(node->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL);
840 }
842 sp_node_adjust_knots(node);
844 sp_nodepath_update_statusbar(node->subpath->nodepath);
846 return node;
847 }
849 /**
850 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
851 * adjacent segments from lines to curves.
852 */
853 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
854 {
855 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
856 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
857 // convert adjacent segment BEFORE to curve
858 node->code = NR_CURVETO;
859 NR::Point delta;
860 if (node->n.other != NULL)
861 delta = node->n.other->pos - node->p.other->pos;
862 else
863 delta = node->pos - node->p.other->pos;
864 node->p.pos = node->pos - delta / 4;
865 sp_node_ensure_ctrls(node);
866 }
868 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
869 // convert adjacent segment AFTER to curve
870 node->n.other->code = NR_CURVETO;
871 NR::Point delta;
872 if (node->p.other != NULL)
873 delta = node->p.other->pos - node->n.other->pos;
874 else
875 delta = node->pos - node->n.other->pos;
876 node->n.pos = node->pos - delta / 4;
877 sp_node_ensure_ctrls(node);
878 }
879 }
881 sp_nodepath_set_node_type (node, type);
882 }
884 /**
885 * Move node to point, and adjust its and neighbouring handles.
886 */
887 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
888 {
889 NR::Point delta = p - node->pos;
890 node->pos = p;
892 node->p.pos += delta;
893 node->n.pos += delta;
895 if (node->p.other) {
896 if (node->code == NR_LINETO) {
897 sp_node_adjust_knot(node, 1);
898 sp_node_adjust_knot(node->p.other, -1);
899 }
900 }
901 if (node->n.other) {
902 if (node->n.other->code == NR_LINETO) {
903 sp_node_adjust_knot(node, -1);
904 sp_node_adjust_knot(node->n.other, 1);
905 }
906 }
908 sp_node_ensure_ctrls(node);
909 }
911 /**
912 * Call sp_node_moveto() for node selection and handle possible snapping.
913 */
914 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
915 bool const snap = true)
916 {
917 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
918 NR::Point delta(dx, dy);
919 NR::Point best_pt = delta;
921 if (snap) {
922 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
923 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
924 NR::Point p = n->pos + delta;
925 for (int dim = 0; dim < 2; dim++) {
926 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
927 Inkscape::Snapper::SNAP_POINT, p,
928 NR::Dim2(dim), nodepath->path);
929 if (dist < best[dim]) {
930 best[dim] = dist;
931 best_pt[dim] = p[dim] - n->pos[dim];
932 }
933 }
934 }
935 }
937 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
938 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
939 sp_node_moveto(n, n->pos + best_pt);
940 }
942 update_object(nodepath);
943 }
945 /**
946 * Move node selection to point, adjust its and neighbouring handles,
947 * handle possible snapping, and commit the change with possible undo.
948 */
949 void
950 sp_node_selected_move(gdouble dx, gdouble dy)
951 {
952 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
953 if (!nodepath) return;
955 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
957 if (dx == 0) {
958 update_repr_keyed(nodepath, "node:move:vertical");
959 } else if (dy == 0) {
960 update_repr_keyed(nodepath, "node:move:horizontal");
961 } else {
962 update_repr(nodepath);
963 }
964 }
966 /**
967 * Move node selection off screen and commit the change.
968 */
969 void
970 sp_node_selected_move_screen(gdouble dx, gdouble dy)
971 {
972 // borrowed from sp_selection_move_screen in selection-chemistry.c
973 // we find out the current zoom factor and divide deltas by it
974 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
976 gdouble zoom = desktop->current_zoom();
977 gdouble zdx = dx / zoom;
978 gdouble zdy = dy / zoom;
980 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
981 if (!nodepath) return;
983 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
985 if (dx == 0) {
986 update_repr_keyed(nodepath, "node:move:vertical");
987 } else if (dy == 0) {
988 update_repr_keyed(nodepath, "node:move:horizontal");
989 } else {
990 update_repr(nodepath);
991 }
992 }
994 /**
995 * Ensure knot on side of node is visible/invisible.
996 */
997 static void sp_node_ensure_knot(Inkscape::NodePath::Node *node, gint which, gboolean show_knot)
998 {
999 g_assert(node != NULL);
1001 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1002 NRPathcode code = sp_node_path_code_from_side(node, side);
1004 show_knot = show_knot && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1006 if (show_knot) {
1007 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1008 sp_knot_show(side->knot);
1009 }
1011 sp_knot_set_position(side->knot, &side->pos, 0);
1012 sp_canvas_item_show(side->line);
1014 } else {
1015 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1016 sp_knot_hide(side->knot);
1017 }
1018 sp_canvas_item_hide(side->line);
1019 }
1020 }
1022 /**
1023 * Ensure handles on node and neighbours of node are visible if selected.
1024 */
1025 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node)
1026 {
1027 g_assert(node != NULL);
1029 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1030 sp_knot_show(node->knot);
1031 }
1033 sp_knot_set_position(node->knot, &node->pos, 0);
1035 gboolean show_knots = node->selected;
1036 if (node->p.other != NULL) {
1037 if (node->p.other->selected) show_knots = TRUE;
1038 }
1039 if (node->n.other != NULL) {
1040 if (node->n.other->selected) show_knots = TRUE;
1041 }
1043 sp_node_ensure_knot(node, -1, show_knots);
1044 sp_node_ensure_knot(node, 1, show_knots);
1045 }
1047 /**
1048 * Call sp_node_ensure_ctrls() for all nodes on subpath.
1049 */
1050 static void sp_nodepath_subpath_ensure_ctrls(Inkscape::NodePath::SubPath *subpath)
1051 {
1052 g_assert(subpath != NULL);
1054 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1055 sp_node_ensure_ctrls((Inkscape::NodePath::Node *) l->data);
1056 }
1057 }
1059 /**
1060 * Call sp_nodepath_subpath_ensure_ctrls() for all subpaths of nodepath.
1061 */
1062 static void sp_nodepath_ensure_ctrls(Inkscape::NodePath::Path *nodepath)
1063 {
1064 g_assert(nodepath != NULL);
1066 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1067 sp_nodepath_subpath_ensure_ctrls((Inkscape::NodePath::SubPath *) l->data);
1068 }
1069 }
1071 /**
1072 * Adds all selected nodes in nodepath to list.
1073 */
1074 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1075 {
1076 StlConv<Node *>::list(l, selected);
1077 /// \todo this adds a copying, rework when the selection becomes a stl list
1078 }
1080 /**
1081 * Align selected nodes on the specified axis.
1082 */
1083 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1084 {
1085 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1086 return;
1087 }
1089 if ( !nodepath->selected->next ) { // only one node selected
1090 return;
1091 }
1092 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1093 NR::Point dest(pNode->pos);
1094 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1095 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1096 if (pNode) {
1097 dest[axis] = pNode->pos[axis];
1098 sp_node_moveto(pNode, dest);
1099 }
1100 }
1101 if (axis == NR::X) {
1102 update_repr_keyed(nodepath, "node:move:vertical");
1103 } else {
1104 update_repr_keyed(nodepath, "node:move:horizontal");
1105 }
1106 }
1108 /// Helper struct.
1109 struct NodeSort
1110 {
1111 Inkscape::NodePath::Node *_node;
1112 NR::Coord _coord;
1113 /// \todo use vectorof pointers instead of calling copy ctor
1114 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1115 _node(node), _coord(node->pos[axis])
1116 {}
1118 };
1120 static bool operator<(NodeSort const &a, NodeSort const &b)
1121 {
1122 return (a._coord < b._coord);
1123 }
1125 /**
1126 * Distribute selected nodes on the specified axis.
1127 */
1128 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1129 {
1130 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1131 return;
1132 }
1134 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1135 return;
1136 }
1138 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1139 std::vector<NodeSort> sorted;
1140 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1141 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1142 if (pNode) {
1143 NodeSort n(pNode, axis);
1144 sorted.push_back(n);
1145 //dest[axis] = pNode->pos[axis];
1146 //sp_node_moveto(pNode, dest);
1147 }
1148 }
1149 std::sort(sorted.begin(), sorted.end());
1150 unsigned int len = sorted.size();
1151 //overall bboxes span
1152 float dist = (sorted.back()._coord -
1153 sorted.front()._coord);
1154 //new distance between each bbox
1155 float step = (dist) / (len - 1);
1156 float pos = sorted.front()._coord;
1157 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1158 it < sorted.end();
1159 it ++ )
1160 {
1161 NR::Point dest((*it)._node->pos);
1162 dest[axis] = pos;
1163 sp_node_moveto((*it)._node, dest);
1164 pos += step;
1165 }
1167 if (axis == NR::X) {
1168 update_repr_keyed(nodepath, "node:move:horizontal");
1169 } else {
1170 update_repr_keyed(nodepath, "node:move:vertical");
1171 }
1172 }
1175 /**
1176 * Call sp_nodepath_line_add_node() for all selected segments.
1177 */
1178 void
1179 sp_node_selected_add_node(void)
1180 {
1181 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1182 if (!nodepath) {
1183 return;
1184 }
1186 GList *nl = NULL;
1188 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1189 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1190 g_assert(t->selected);
1191 if (t->p.other && t->p.other->selected) {
1192 nl = g_list_prepend(nl, t);
1193 }
1194 }
1196 while (nl) {
1197 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1198 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1199 sp_nodepath_node_select(n, TRUE, FALSE);
1200 nl = g_list_remove(nl, t);
1201 }
1203 /** \todo fixme: adjust ? */
1204 sp_nodepath_ensure_ctrls(nodepath);
1206 update_repr(nodepath);
1208 sp_nodepath_update_statusbar(nodepath);
1209 }
1211 /**
1212 * Select segment nearest to point
1213 */
1214 void
1215 sp_nodepath_select_segment_near_point(SPItem * item, NR::Point p, bool toggle)
1216 {
1217 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1218 if (!nodepath) {
1219 return;
1220 }
1222 Path::cut_position position = get_nearest_position_on_Path(item, p);
1224 //find segment to segment
1225 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1227 gboolean force = FALSE;
1228 if (!(e->selected && e->p.other->selected)) {
1229 force = TRUE;
1230 }
1231 sp_nodepath_node_select(e, (gboolean) toggle, force);
1232 sp_nodepath_node_select(e->p.other, TRUE, force);
1234 sp_nodepath_ensure_ctrls(nodepath);
1236 sp_nodepath_update_statusbar(nodepath);
1237 }
1239 /**
1240 * Add a node nearest to point
1241 */
1242 void
1243 sp_nodepath_add_node_near_point(SPItem * item, NR::Point p)
1244 {
1245 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1246 if (!nodepath) {
1247 return;
1248 }
1250 Path::cut_position position = get_nearest_position_on_Path(item, p);
1252 //find segment to split
1253 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1255 //don't know why but t seems to flip for lines
1256 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1257 position.t = 1.0 - position.t;
1258 }
1259 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1260 sp_nodepath_node_select(n, FALSE, TRUE);
1262 /* fixme: adjust ? */
1263 sp_nodepath_ensure_ctrls(nodepath);
1265 update_repr(nodepath);
1267 sp_nodepath_update_statusbar(nodepath);
1268 }
1270 /*
1271 * Adjusts a segment so that t moves by a certain delta for dragging
1272 * converts lines to curves
1273 *
1274 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1275 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1276 */
1277 void
1278 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key)
1279 {
1280 /* feel good is an arbitrary parameter that distributes the delta between handles
1281 * if t of the drag point is less than 1/6 distance form the endpoint only
1282 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1283 */
1284 double feel_good;
1285 if (t <= 1.0 / 6.0)
1286 feel_good = 0;
1287 else if (t <= 0.5)
1288 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1289 else if (t <= 5.0 / 6.0)
1290 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1291 else
1292 feel_good = 1;
1294 //if we're dragging a line convert it to a curve
1295 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1296 sp_nodepath_set_line_type(e, NR_CURVETO);
1297 }
1299 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1300 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1301 e->p.other->n.pos += offsetcoord0;
1302 e->p.pos += offsetcoord1;
1304 // adjust controls of adjacent segments where necessary
1305 sp_node_adjust_knot(e,1);
1306 sp_node_adjust_knot(e->p.other,-1);
1308 sp_nodepath_ensure_ctrls(e->subpath->nodepath);
1310 update_repr_keyed(e->subpath->nodepath, key);
1312 sp_nodepath_update_statusbar(e->subpath->nodepath);
1313 }
1316 /**
1317 * Call sp_nodepath_break() for all selected segments.
1318 */
1319 void sp_node_selected_break()
1320 {
1321 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1322 if (!nodepath) return;
1324 GList *temp = NULL;
1325 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1326 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1327 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1328 if (nn == NULL) continue; // no break, no new node
1329 temp = g_list_prepend(temp, nn);
1330 }
1332 if (temp) {
1333 sp_nodepath_deselect(nodepath);
1334 }
1335 for (GList *l = temp; l != NULL; l = l->next) {
1336 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1337 }
1339 sp_nodepath_ensure_ctrls(nodepath);
1341 update_repr(nodepath);
1342 }
1344 /**
1345 * Duplicate the selected node(s).
1346 */
1347 void sp_node_selected_duplicate()
1348 {
1349 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1350 if (!nodepath) {
1351 return;
1352 }
1354 GList *temp = NULL;
1355 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1356 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1357 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1358 if (nn == NULL) continue; // could not duplicate
1359 temp = g_list_prepend(temp, nn);
1360 }
1362 if (temp) {
1363 sp_nodepath_deselect(nodepath);
1364 }
1365 for (GList *l = temp; l != NULL; l = l->next) {
1366 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1367 }
1369 sp_nodepath_ensure_ctrls(nodepath);
1371 update_repr(nodepath);
1372 }
1374 /**
1375 * Join two nodes by merging them into one.
1376 */
1377 void sp_node_selected_join()
1378 {
1379 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1380 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1382 if (g_list_length(nodepath->selected) != 2) {
1383 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1384 return;
1385 }
1387 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1388 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1390 g_assert(a != b);
1391 g_assert(a->p.other || a->n.other);
1392 g_assert(b->p.other || b->n.other);
1394 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1395 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1396 return;
1397 }
1399 /* a and b are endpoints */
1401 NR::Point c = (a->pos + b->pos) / 2;
1403 if (a->subpath == b->subpath) {
1404 Inkscape::NodePath::SubPath *sp = a->subpath;
1405 sp_nodepath_subpath_close(sp);
1407 sp_nodepath_ensure_ctrls(sp->nodepath);
1409 update_repr(nodepath);
1411 return;
1412 }
1414 /* a and b are separate subpaths */
1415 Inkscape::NodePath::SubPath *sa = a->subpath;
1416 Inkscape::NodePath::SubPath *sb = b->subpath;
1417 NR::Point p;
1418 Inkscape::NodePath::Node *n;
1419 NRPathcode code;
1420 if (a == sa->first) {
1421 p = sa->first->n.pos;
1422 code = (NRPathcode)sa->first->n.other->code;
1423 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1424 n = sa->last;
1425 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1426 n = n->p.other;
1427 while (n) {
1428 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1429 n = n->p.other;
1430 if (n == sa->first) n = NULL;
1431 }
1432 sp_nodepath_subpath_destroy(sa);
1433 sa = t;
1434 } else if (a == sa->last) {
1435 p = sa->last->p.pos;
1436 code = (NRPathcode)sa->last->code;
1437 sp_nodepath_node_destroy(sa->last);
1438 } else {
1439 code = NR_END;
1440 g_assert_not_reached();
1441 }
1443 if (b == sb->first) {
1444 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1445 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1446 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1447 }
1448 } else if (b == sb->last) {
1449 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1450 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1451 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1452 }
1453 } else {
1454 g_assert_not_reached();
1455 }
1456 /* and now destroy sb */
1458 sp_nodepath_subpath_destroy(sb);
1460 sp_nodepath_ensure_ctrls(sa->nodepath);
1462 update_repr(nodepath);
1464 sp_nodepath_update_statusbar(nodepath);
1465 }
1467 /**
1468 * Join two nodes by adding a segment between them.
1469 */
1470 void sp_node_selected_join_segment()
1471 {
1472 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1473 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1475 if (g_list_length(nodepath->selected) != 2) {
1476 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1477 return;
1478 }
1480 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1481 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1483 g_assert(a != b);
1484 g_assert(a->p.other || a->n.other);
1485 g_assert(b->p.other || b->n.other);
1487 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1488 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1489 return;
1490 }
1492 if (a->subpath == b->subpath) {
1493 Inkscape::NodePath::SubPath *sp = a->subpath;
1495 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1496 sp->closed = TRUE;
1498 sp->first->p.other = sp->last;
1499 sp->last->n.other = sp->first;
1501 sp_node_control_mirror_p_to_n(sp->last);
1502 sp_node_control_mirror_n_to_p(sp->first);
1504 sp->first->code = sp->last->code;
1505 sp->first = sp->last;
1507 sp_nodepath_ensure_ctrls(sp->nodepath);
1509 update_repr(nodepath);
1511 return;
1512 }
1514 /* a and b are separate subpaths */
1515 Inkscape::NodePath::SubPath *sa = a->subpath;
1516 Inkscape::NodePath::SubPath *sb = b->subpath;
1518 Inkscape::NodePath::Node *n;
1519 NR::Point p;
1520 NRPathcode code;
1521 if (a == sa->first) {
1522 code = (NRPathcode) sa->first->n.other->code;
1523 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1524 n = sa->last;
1525 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1526 for (n = n->p.other; n != NULL; n = n->p.other) {
1527 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1528 }
1529 sp_nodepath_subpath_destroy(sa);
1530 sa = t;
1531 } else if (a == sa->last) {
1532 code = (NRPathcode)sa->last->code;
1533 } else {
1534 code = NR_END;
1535 g_assert_not_reached();
1536 }
1538 if (b == sb->first) {
1539 n = sb->first;
1540 sp_node_control_mirror_p_to_n(sa->last);
1541 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1542 sp_node_control_mirror_n_to_p(sa->last);
1543 for (n = n->n.other; n != NULL; n = n->n.other) {
1544 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1545 }
1546 } else if (b == sb->last) {
1547 n = sb->last;
1548 sp_node_control_mirror_p_to_n(sa->last);
1549 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1550 sp_node_control_mirror_n_to_p(sa->last);
1551 for (n = n->p.other; n != NULL; n = n->p.other) {
1552 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1553 }
1554 } else {
1555 g_assert_not_reached();
1556 }
1557 /* and now destroy sb */
1559 sp_nodepath_subpath_destroy(sb);
1561 sp_nodepath_ensure_ctrls(sa->nodepath);
1563 update_repr(nodepath);
1564 }
1566 /**
1567 * Delete one or more selected nodes.
1568 */
1569 void sp_node_selected_delete()
1570 {
1571 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1572 if (!nodepath) return;
1573 if (!nodepath->selected) return;
1575 /** \todo fixme: do it the right way */
1576 while (nodepath->selected) {
1577 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1578 sp_nodepath_node_destroy(node);
1579 }
1582 //clean up the nodepath (such as for trivial subpaths)
1583 sp_nodepath_cleanup(nodepath);
1585 sp_nodepath_ensure_ctrls(nodepath);
1587 // if the entire nodepath is removed, delete the selected object.
1588 if (nodepath->subpaths == NULL ||
1589 sp_nodepath_get_node_count(nodepath) < 2) {
1590 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1591 sp_nodepath_destroy(nodepath);
1592 sp_selection_delete();
1593 sp_document_done (document);
1594 return;
1595 }
1597 update_repr(nodepath);
1599 sp_nodepath_update_statusbar(nodepath);
1600 }
1602 /**
1603 * Delete one or more segments between two selected nodes.
1604 * This is the code for 'split'.
1605 */
1606 void
1607 sp_node_selected_delete_segment(void)
1608 {
1609 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1610 Inkscape::NodePath::Node *curr, *next; //Iterators
1612 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1613 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1615 if (g_list_length(nodepath->selected) != 2) {
1616 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1617 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1618 return;
1619 }
1621 //Selected nodes, not inclusive
1622 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1623 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1625 if ( ( a==b) || //same node
1626 (a->subpath != b->subpath ) || //not the same path
1627 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1628 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1629 {
1630 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1631 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1632 return;
1633 }
1635 //###########################################
1636 //# BEGIN EDITS
1637 //###########################################
1638 //##################################
1639 //# CLOSED PATH
1640 //##################################
1641 if (a->subpath->closed) {
1644 gboolean reversed = FALSE;
1646 //Since we can go in a circle, we need to find the shorter distance.
1647 // a->b or b->a
1648 start = end = NULL;
1649 int distance = 0;
1650 int minDistance = 0;
1651 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1652 if (curr==b) {
1653 //printf("a to b:%d\n", distance);
1654 start = a;//go from a to b
1655 end = b;
1656 minDistance = distance;
1657 //printf("A to B :\n");
1658 break;
1659 }
1660 distance++;
1661 }
1663 //try again, the other direction
1664 distance = 0;
1665 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1666 if (curr==a) {
1667 //printf("b to a:%d\n", distance);
1668 if (distance < minDistance) {
1669 start = b; //we go from b to a
1670 end = a;
1671 reversed = TRUE;
1672 //printf("B to A\n");
1673 }
1674 break;
1675 }
1676 distance++;
1677 }
1680 //Copy everything from 'end' to 'start' to a new subpath
1681 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1682 for (curr=end ; curr ; curr=curr->n.other) {
1683 NRPathcode code = (NRPathcode) curr->code;
1684 if (curr == end)
1685 code = NR_MOVETO;
1686 sp_nodepath_node_new(t, NULL,
1687 (Inkscape::NodePath::NodeType)curr->type, code,
1688 &curr->p.pos, &curr->pos, &curr->n.pos);
1689 if (curr == start)
1690 break;
1691 }
1692 sp_nodepath_subpath_destroy(a->subpath);
1695 }
1699 //##################################
1700 //# OPEN PATH
1701 //##################################
1702 else {
1704 //We need to get the direction of the list between A and B
1705 //Can we walk from a to b?
1706 start = end = NULL;
1707 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1708 if (curr==b) {
1709 start = a; //did it! we go from a to b
1710 end = b;
1711 //printf("A to B\n");
1712 break;
1713 }
1714 }
1715 if (!start) {//didn't work? let's try the other direction
1716 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1717 if (curr==a) {
1718 start = b; //did it! we go from b to a
1719 end = a;
1720 //printf("B to A\n");
1721 break;
1722 }
1723 }
1724 }
1725 if (!start) {
1726 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1727 _("Cannot find path between nodes."));
1728 return;
1729 }
1733 //Copy everything after 'end' to a new subpath
1734 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1735 for (curr=end ; curr ; curr=curr->n.other) {
1736 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1737 &curr->p.pos, &curr->pos, &curr->n.pos);
1738 }
1740 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1741 for (curr = start->n.other ; curr ; curr=next) {
1742 next = curr->n.other;
1743 sp_nodepath_node_destroy(curr);
1744 }
1746 }
1747 //###########################################
1748 //# END EDITS
1749 //###########################################
1751 //clean up the nodepath (such as for trivial subpaths)
1752 sp_nodepath_cleanup(nodepath);
1754 sp_nodepath_ensure_ctrls(nodepath);
1756 update_repr(nodepath);
1758 // if the entire nodepath is removed, delete the selected object.
1759 if (nodepath->subpaths == NULL ||
1760 sp_nodepath_get_node_count(nodepath) < 2) {
1761 sp_nodepath_destroy(nodepath);
1762 sp_selection_delete();
1763 return;
1764 }
1766 sp_nodepath_update_statusbar(nodepath);
1767 }
1769 /**
1770 * Call sp_nodepath_set_line() for all selected segments.
1771 */
1772 void
1773 sp_node_selected_set_line_type(NRPathcode code)
1774 {
1775 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1776 if (nodepath == NULL) return;
1778 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1779 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1780 g_assert(n->selected);
1781 if (n->p.other && n->p.other->selected) {
1782 sp_nodepath_set_line_type(n, code);
1783 }
1784 }
1786 update_repr(nodepath);
1787 }
1789 /**
1790 * Call sp_nodepath_convert_node_type() for all selected nodes.
1791 */
1792 void
1793 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1794 {
1795 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1796 if (nodepath == NULL) return;
1798 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1799 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1800 }
1802 update_repr(nodepath);
1803 }
1805 /**
1806 * Change select status of node, update its own and neighbour handles.
1807 */
1808 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1809 {
1810 node->selected = selected;
1812 if (selected) {
1813 g_object_set(G_OBJECT(node->knot),
1814 "fill", NODE_FILL_SEL,
1815 "fill_mouseover", NODE_FILL_SEL_HI,
1816 "stroke", NODE_STROKE_SEL,
1817 "stroke_mouseover", NODE_STROKE_SEL_HI,
1818 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9,
1819 NULL);
1820 } else {
1821 g_object_set(G_OBJECT(node->knot),
1822 "fill", NODE_FILL,
1823 "fill_mouseover", NODE_FILL_HI,
1824 "stroke", NODE_STROKE,
1825 "stroke_mouseover", NODE_STROKE_HI,
1826 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7,
1827 NULL);
1828 }
1830 sp_node_ensure_ctrls(node);
1831 if (node->n.other) sp_node_ensure_ctrls(node->n.other);
1832 if (node->p.other) sp_node_ensure_ctrls(node->p.other);
1833 }
1835 /**
1836 \brief Select a node
1837 \param node The node to select
1838 \param incremental If true, add to selection, otherwise deselect others
1839 \param override If true, always select this node, otherwise toggle selected status
1840 */
1841 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1842 {
1843 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1845 if (incremental) {
1846 if (override) {
1847 if (!g_list_find(nodepath->selected, node)) {
1848 nodepath->selected = g_list_append(nodepath->selected, node);
1849 }
1850 sp_node_set_selected(node, TRUE);
1851 } else { // toggle
1852 if (node->selected) {
1853 g_assert(g_list_find(nodepath->selected, node));
1854 nodepath->selected = g_list_remove(nodepath->selected, node);
1855 } else {
1856 g_assert(!g_list_find(nodepath->selected, node));
1857 nodepath->selected = g_list_append(nodepath->selected, node);
1858 }
1859 sp_node_set_selected(node, !node->selected);
1860 }
1861 } else {
1862 sp_nodepath_deselect(nodepath);
1863 nodepath->selected = g_list_append(nodepath->selected, node);
1864 sp_node_set_selected(node, TRUE);
1865 }
1867 sp_nodepath_update_statusbar(nodepath);
1868 }
1871 /**
1872 \brief Deselect all nodes in the nodepath
1873 */
1874 void
1875 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1876 {
1877 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1879 while (nodepath->selected) {
1880 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1881 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1882 }
1883 sp_nodepath_update_statusbar(nodepath);
1884 }
1886 /**
1887 \brief Select or invert selection of all nodes in the nodepath
1888 */
1889 void
1890 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1891 {
1892 if (!nodepath) return;
1894 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1895 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1896 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1897 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1898 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1899 }
1900 }
1901 }
1903 /**
1904 * If nothing selected, does the same as sp_nodepath_select_all();
1905 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1906 * (i.e., similar to "select all in layer", with the "selected" subpaths
1907 * being treated as "layers" in the path).
1908 */
1909 void
1910 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1911 {
1912 if (!nodepath) return;
1914 if (g_list_length (nodepath->selected) == 0) {
1915 sp_nodepath_select_all (nodepath, invert);
1916 return;
1917 }
1919 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1920 GSList *subpaths = NULL;
1922 for (GList *l = copy; l != NULL; l = l->next) {
1923 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1924 Inkscape::NodePath::SubPath *subpath = n->subpath;
1925 if (!g_slist_find (subpaths, subpath))
1926 subpaths = g_slist_prepend (subpaths, subpath);
1927 }
1929 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
1930 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->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? !g_list_find(copy, node) : TRUE);
1934 }
1935 }
1937 g_slist_free (subpaths);
1938 g_list_free (copy);
1939 }
1941 /**
1942 * \brief Select the node after the last selected; if none is selected,
1943 * select the first within path.
1944 */
1945 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
1946 {
1947 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1949 Inkscape::NodePath::Node *last = NULL;
1950 if (nodepath->selected) {
1951 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1952 Inkscape::NodePath::SubPath *subpath, *subpath_next;
1953 subpath = (Inkscape::NodePath::SubPath *) spl->data;
1954 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1955 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1956 if (node->selected) {
1957 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
1958 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
1959 if (spl->next) { // there's a next subpath
1960 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
1961 last = subpath_next->first;
1962 } else if (spl->prev) { // there's a previous subpath
1963 last = NULL; // to be set later to the first node of first subpath
1964 } else {
1965 last = node->n.other;
1966 }
1967 } else {
1968 last = node->n.other;
1969 }
1970 } else {
1971 if (node->n.other) {
1972 last = node->n.other;
1973 } else {
1974 if (spl->next) { // there's a next subpath
1975 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
1976 last = subpath_next->first;
1977 } else if (spl->prev) { // there's a previous subpath
1978 last = NULL; // to be set later to the first node of first subpath
1979 } else {
1980 last = (Inkscape::NodePath::Node *) subpath->first;
1981 }
1982 }
1983 }
1984 }
1985 }
1986 }
1987 sp_nodepath_deselect(nodepath);
1988 }
1990 if (last) { // there's at least one more node after selected
1991 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
1992 } else { // no more nodes, select the first one in first subpath
1993 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
1994 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
1995 }
1996 }
1998 /**
1999 * \brief Select the node before the first selected; if none is selected,
2000 * select the last within path
2001 */
2002 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2003 {
2004 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2006 Inkscape::NodePath::Node *last = NULL;
2007 if (nodepath->selected) {
2008 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2009 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2010 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2011 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2012 if (node->selected) {
2013 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2014 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2015 if (spl->prev) { // there's a prev subpath
2016 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2017 last = subpath_prev->last;
2018 } else if (spl->next) { // there's a next subpath
2019 last = NULL; // to be set later to the last node of last subpath
2020 } else {
2021 last = node->p.other;
2022 }
2023 } else {
2024 last = node->p.other;
2025 }
2026 } else {
2027 if (node->p.other) {
2028 last = node->p.other;
2029 } else {
2030 if (spl->prev) { // there's a prev subpath
2031 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2032 last = subpath_prev->last;
2033 } else if (spl->next) { // there's a next subpath
2034 last = NULL; // to be set later to the last node of last subpath
2035 } else {
2036 last = (Inkscape::NodePath::Node *) subpath->last;
2037 }
2038 }
2039 }
2040 }
2041 }
2042 }
2043 sp_nodepath_deselect(nodepath);
2044 }
2046 if (last) { // there's at least one more node before selected
2047 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2048 } else { // no more nodes, select the last one in last subpath
2049 GList *spl = g_list_last(nodepath->subpaths);
2050 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2051 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2052 }
2053 }
2055 /**
2056 * \brief Select all nodes that are within the rectangle.
2057 */
2058 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2059 {
2060 if (!incremental) {
2061 sp_nodepath_deselect(nodepath);
2062 }
2064 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2065 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2066 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2067 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2069 if (b.contains(node->pos)) {
2070 sp_nodepath_node_select(node, TRUE, TRUE);
2071 }
2072 }
2073 }
2074 }
2076 /**
2077 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2078 */
2079 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2080 {
2081 if (!nodepath->selected) {
2082 return NULL;
2083 }
2085 GList *r = NULL;
2086 guint i = 0;
2087 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2088 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2089 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2090 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2091 i++;
2092 if (node->selected) {
2093 r = g_list_append(r, GINT_TO_POINTER(i));
2094 }
2095 }
2096 }
2097 return r;
2098 }
2100 /**
2101 \brief Restores selection by selecting nodes whose positions are in the list
2102 */
2103 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2104 {
2105 sp_nodepath_deselect(nodepath);
2107 guint i = 0;
2108 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2109 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2110 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2111 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2112 i++;
2113 if (g_list_find(r, GINT_TO_POINTER(i))) {
2114 sp_nodepath_node_select(node, TRUE, TRUE);
2115 }
2116 }
2117 }
2119 }
2121 /**
2122 \brief Adjusts control point according to node type and line code.
2123 */
2124 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust)
2125 {
2126 double len, otherlen, linelen;
2128 g_assert(node);
2130 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2131 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2133 /** \todo fixme: */
2134 if (me->other == NULL) return;
2135 if (other->other == NULL) return;
2137 /* I have line */
2139 NRPathcode mecode, ocode;
2140 if (which_adjust == 1) {
2141 mecode = (NRPathcode)me->other->code;
2142 ocode = (NRPathcode)node->code;
2143 } else {
2144 mecode = (NRPathcode)node->code;
2145 ocode = (NRPathcode)other->other->code;
2146 }
2148 if (mecode == NR_LINETO) return;
2150 /* I am curve */
2152 if (other->other == NULL) return;
2154 /* Other has line */
2156 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2158 NR::Point delta;
2159 if (ocode == NR_LINETO) {
2160 /* other is lineto, we are either smooth or symm */
2161 Inkscape::NodePath::Node *othernode = other->other;
2162 len = NR::L2(me->pos - node->pos);
2163 delta = node->pos - othernode->pos;
2164 linelen = NR::L2(delta);
2165 if (linelen < 1e-18) return;
2167 me->pos = node->pos + (len / linelen)*delta;
2168 sp_knot_set_position(me->knot, &me->pos, 0);
2170 sp_node_ensure_ctrls(node);
2171 return;
2172 }
2174 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2176 me->pos = 2 * node->pos - other->pos;
2177 sp_knot_set_position(me->knot, &me->pos, 0);
2179 sp_node_ensure_ctrls(node);
2180 return;
2181 }
2183 /* We are smooth */
2185 len = NR::L2(me->pos - node->pos);
2186 delta = other->pos - node->pos;
2187 otherlen = NR::L2(delta);
2188 if (otherlen < 1e-18) return;
2190 me->pos = node->pos - (len / otherlen) * delta;
2191 sp_knot_set_position(me->knot, &me->pos, 0);
2193 sp_node_ensure_ctrls(node);
2194 }
2196 /**
2197 \brief Adjusts control point according to node type and line code
2198 */
2199 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node)
2200 {
2201 g_assert(node);
2203 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2205 /* we are either smooth or symm */
2207 if (node->p.other == NULL) return;
2209 if (node->n.other == NULL) return;
2211 if (node->code == NR_LINETO) {
2212 if (node->n.other->code == NR_LINETO) return;
2213 sp_node_adjust_knot(node, 1);
2214 sp_node_ensure_ctrls(node);
2215 return;
2216 }
2218 if (node->n.other->code == NR_LINETO) {
2219 if (node->code == NR_LINETO) return;
2220 sp_node_adjust_knot(node, -1);
2221 sp_node_ensure_ctrls(node);
2222 return;
2223 }
2225 /* both are curves */
2227 NR::Point const delta( node->n.pos - node->p.pos );
2229 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2230 node->p.pos = node->pos - delta / 2;
2231 node->n.pos = node->pos + delta / 2;
2232 sp_node_ensure_ctrls(node);
2233 return;
2234 }
2236 /* We are smooth */
2238 double plen = NR::L2(node->p.pos - node->pos);
2239 if (plen < 1e-18) return;
2240 double nlen = NR::L2(node->n.pos - node->pos);
2241 if (nlen < 1e-18) return;
2242 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2243 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2244 sp_node_ensure_ctrls(node);
2245 }
2247 /**
2248 * Knot events handler callback.
2249 */
2250 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2251 {
2252 gboolean ret = FALSE;
2253 switch (event->type) {
2254 case GDK_ENTER_NOTIFY:
2255 active_node = n;
2256 break;
2257 case GDK_LEAVE_NOTIFY:
2258 active_node = NULL;
2259 break;
2260 case GDK_KEY_PRESS:
2261 switch (get_group0_keyval (&event->key)) {
2262 case GDK_space:
2263 if (event->key.state & GDK_BUTTON1_MASK) {
2264 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2265 stamp_repr(nodepath);
2266 ret = TRUE;
2267 }
2268 break;
2269 default:
2270 break;
2271 }
2272 break;
2273 default:
2274 break;
2275 }
2277 return ret;
2278 }
2280 /**
2281 * Handle keypress on node; directly called.
2282 */
2283 gboolean node_key(GdkEvent *event)
2284 {
2285 Inkscape::NodePath::Path *np;
2287 // there is no way to verify nodes so set active_node to nil when deleting!!
2288 if (active_node == NULL) return FALSE;
2290 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2291 gint ret = FALSE;
2292 switch (get_group0_keyval (&event->key)) {
2293 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2294 case GDK_BackSpace:
2295 np = active_node->subpath->nodepath;
2296 sp_nodepath_node_destroy(active_node);
2297 update_repr(np);
2298 active_node = NULL;
2299 ret = TRUE;
2300 break;
2301 case GDK_c:
2302 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2303 ret = TRUE;
2304 break;
2305 case GDK_s:
2306 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2307 ret = TRUE;
2308 break;
2309 case GDK_y:
2310 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2311 ret = TRUE;
2312 break;
2313 case GDK_b:
2314 sp_nodepath_node_break(active_node);
2315 ret = TRUE;
2316 break;
2317 }
2318 return ret;
2319 }
2320 return FALSE;
2321 }
2323 /**
2324 * Mouseclick on node callback.
2325 */
2326 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2327 {
2328 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2330 if (state & GDK_CONTROL_MASK) {
2331 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2333 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2334 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2335 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2336 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2337 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2338 } else {
2339 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2340 }
2341 update_repr(nodepath);
2342 sp_nodepath_update_statusbar(nodepath);
2344 } else { //ctrl+alt+click: delete node
2345 sp_nodepath_node_destroy(n);
2346 //clean up the nodepath (such as for trivial subpaths)
2347 sp_nodepath_cleanup(nodepath);
2349 // if the entire nodepath is removed, delete the selected object.
2350 if (nodepath->subpaths == NULL ||
2351 sp_nodepath_get_node_count(nodepath) < 2) {
2352 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2353 sp_nodepath_destroy(nodepath);
2354 sp_selection_delete();
2355 sp_document_done (document);
2357 } else {
2358 sp_nodepath_ensure_ctrls(nodepath);
2359 update_repr(nodepath);
2360 sp_nodepath_update_statusbar(nodepath);
2361 }
2362 }
2364 } else {
2365 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2366 }
2367 }
2369 /**
2370 * Mouse grabbed node callback.
2371 */
2372 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2373 {
2374 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2376 n->origin = knot->pos;
2378 if (!n->selected) {
2379 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2380 }
2381 }
2383 /**
2384 * Mouse ungrabbed node callback.
2385 */
2386 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2387 {
2388 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2390 n->dragging_out = NULL;
2392 update_repr(n->subpath->nodepath);
2393 }
2395 /**
2396 * The point on a line, given by its angle, closest to the given point.
2397 * \param p A point.
2398 * \param a Angle of the line; it is assumed to go through coordinate origin.
2399 * \param closest Pointer to the point struct where the result is stored.
2400 * \todo FIXME: use dot product perhaps?
2401 */
2402 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2403 {
2404 if (a == HUGE_VAL) { // vertical
2405 *closest = NR::Point(0, (*p)[NR::Y]);
2406 } else {
2407 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2408 (*closest)[NR::Y] = a * (*closest)[NR::X];
2409 }
2410 }
2412 /**
2413 * Distance from the point to a line given by its angle.
2414 * \param p A point.
2415 * \param a Angle of the line; it is assumed to go through coordinate origin.
2416 */
2417 static double point_line_distance(NR::Point *p, double a)
2418 {
2419 NR::Point c;
2420 point_line_closest(p, a, &c);
2421 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]));
2422 }
2424 /**
2425 * Callback for node "request" signal.
2426 * \todo fixme: This goes to "moved" event? (lauris)
2427 */
2428 static gboolean
2429 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2430 {
2431 double yn, xn, yp, xp;
2432 double an, ap, na, pa;
2433 double d_an, d_ap, d_na, d_pa;
2434 gboolean collinear = FALSE;
2435 NR::Point c;
2436 NR::Point pr;
2438 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2440 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2441 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2443 NR::Point mouse = (*p);
2445 if (!n->dragging_out) {
2446 // This is the first drag-out event; find out which handle to drag out
2447 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2448 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2450 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2451 return FALSE;
2453 Inkscape::NodePath::NodeSide *opposite;
2454 if (appr_p > appr_n) { // closer to p
2455 n->dragging_out = &n->p;
2456 opposite = &n->n;
2457 n->code = NR_CURVETO;
2458 } else if (appr_p < appr_n) { // closer to n
2459 n->dragging_out = &n->n;
2460 opposite = &n->p;
2461 n->n.other->code = NR_CURVETO;
2462 } else { // p and n nodes are the same
2463 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2464 n->dragging_out = &n->p;
2465 opposite = &n->n;
2466 n->code = NR_CURVETO;
2467 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2468 n->dragging_out = &n->n;
2469 opposite = &n->p;
2470 n->n.other->code = NR_CURVETO;
2471 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2472 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);
2473 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);
2474 if (appr_other_p > appr_other_n) { // closer to other's p handle
2475 n->dragging_out = &n->n;
2476 opposite = &n->p;
2477 n->n.other->code = NR_CURVETO;
2478 } else { // closer to other's n handle
2479 n->dragging_out = &n->p;
2480 opposite = &n->n;
2481 n->code = NR_CURVETO;
2482 }
2483 }
2484 }
2486 // if there's another handle, make sure the one we drag out starts parallel to it
2487 if (opposite->pos != n->pos) {
2488 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2489 }
2490 }
2492 // pass this on to the handle-moved callback
2493 node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2494 sp_node_ensure_ctrls(n);
2495 return TRUE;
2496 }
2498 if (state & GDK_CONTROL_MASK) { // constrained motion
2500 // calculate relative distances of handles
2501 // n handle:
2502 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2503 xn = n->n.pos[NR::X] - n->pos[NR::X];
2504 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2505 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2506 if (n->n.other) { // if there is the next point
2507 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2508 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2509 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2510 }
2511 }
2512 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2513 if (yn < 0) { xn = -xn; yn = -yn; }
2515 // p handle:
2516 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2517 xp = n->p.pos[NR::X] - n->pos[NR::X];
2518 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2519 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2520 if (n->p.other) {
2521 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2522 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2523 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2524 }
2525 }
2526 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2527 if (yp < 0) { xp = -xp; yp = -yp; }
2529 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2530 // sliding on handles, only if at least one of the handles is non-vertical
2531 // (otherwise it's the same as ctrl+drag anyway)
2533 // calculate angles of the control handles
2534 if (xn == 0) {
2535 if (yn == 0) { // no handle, consider it the continuation of the other one
2536 an = 0;
2537 collinear = TRUE;
2538 }
2539 else an = 0; // vertical; set the angle to horizontal
2540 } else an = yn/xn;
2542 if (xp == 0) {
2543 if (yp == 0) { // no handle, consider it the continuation of the other one
2544 ap = an;
2545 }
2546 else ap = 0; // vertical; set the angle to horizontal
2547 } else ap = yp/xp;
2549 if (collinear) an = ap;
2551 // angles of the perpendiculars; HUGE_VAL means vertical
2552 if (an == 0) na = HUGE_VAL; else na = -1/an;
2553 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2555 //g_print("an %g ap %g\n", an, ap);
2557 // mouse point relative to the node's original pos
2558 pr = (*p) - n->origin;
2560 // distances to the four lines (two handles and two perpendiculars)
2561 d_an = point_line_distance(&pr, an);
2562 d_na = point_line_distance(&pr, na);
2563 d_ap = point_line_distance(&pr, ap);
2564 d_pa = point_line_distance(&pr, pa);
2566 // find out which line is the closest, save its closest point in c
2567 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2568 point_line_closest(&pr, an, &c);
2569 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2570 point_line_closest(&pr, ap, &c);
2571 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2572 point_line_closest(&pr, na, &c);
2573 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2574 point_line_closest(&pr, pa, &c);
2575 }
2577 // move the node to the closest point
2578 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2579 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2580 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2582 } else { // constraining to hor/vert
2584 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2585 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2586 } else { // snap to vert
2587 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2588 }
2589 }
2590 } else { // move freely
2591 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2592 (*p)[NR::X] - n->pos[NR::X],
2593 (*p)[NR::Y] - n->pos[NR::Y],
2594 (state & GDK_SHIFT_MASK) == 0);
2595 }
2597 n->subpath->nodepath->desktop->scroll_to_point(p);
2599 return TRUE;
2600 }
2602 /**
2603 * Node handle clicked callback.
2604 */
2605 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data)
2606 {
2607 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2609 if (state & GDK_CONTROL_MASK) { // "delete" handle
2610 if (n->p.knot == knot) {
2611 n->p.pos = n->pos;
2612 } else if (n->n.knot == knot) {
2613 n->n.pos = n->pos;
2614 }
2615 sp_node_ensure_ctrls(n);
2616 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2617 update_repr(nodepath);
2618 sp_nodepath_update_statusbar(nodepath);
2620 } else { // just select or add to selection, depending in Shift
2621 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2622 }
2623 }
2625 /**
2626 * Node handle grabbed callback.
2627 */
2628 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data)
2629 {
2630 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2632 if (!n->selected) {
2633 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2634 }
2636 // remember the origin of the control
2637 if (n->p.knot == knot) {
2638 n->p.origin = n->p.pos - n->pos;
2639 } else if (n->n.knot == knot) {
2640 n->n.origin = n->n.pos - n->pos;
2641 } else {
2642 g_assert_not_reached();
2643 }
2645 }
2647 /**
2648 * Node handle ungrabbed callback.
2649 */
2650 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data)
2651 {
2652 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2654 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2655 if (n->p.knot == knot) {
2656 n->p.origin.a = 0;
2657 sp_knot_set_position(knot, &n->p.pos, state);
2658 } else if (n->n.knot == knot) {
2659 n->n.origin.a = 0;
2660 sp_knot_set_position(knot, &n->n.pos, state);
2661 } else {
2662 g_assert_not_reached();
2663 }
2665 update_repr(n->subpath->nodepath);
2666 }
2668 /**
2669 * Node handle "request" signal callback.
2670 */
2671 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2672 {
2673 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2675 Inkscape::NodePath::NodeSide *me, *opposite;
2676 gint which;
2677 if (n->p.knot == knot) {
2678 me = &n->p;
2679 opposite = &n->n;
2680 which = -1;
2681 } else if (n->n.knot == knot) {
2682 me = &n->n;
2683 opposite = &n->p;
2684 which = 1;
2685 } else {
2686 me = opposite = NULL;
2687 which = 0;
2688 g_assert_not_reached();
2689 }
2691 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2693 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2695 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2696 /* We are smooth node adjacent with line */
2697 NR::Point const delta = *p - n->pos;
2698 NR::Coord const len = NR::L2(delta);
2699 Inkscape::NodePath::Node *othernode = opposite->other;
2700 NR::Point const ndelta = n->pos - othernode->pos;
2701 NR::Coord const linelen = NR::L2(ndelta);
2702 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2703 NR::Coord const scal = dot(delta, ndelta) / linelen;
2704 (*p) = n->pos + (scal / linelen) * ndelta;
2705 }
2706 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2707 } else {
2708 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2709 }
2711 sp_node_adjust_knot(n, -which);
2713 return FALSE;
2714 }
2716 /**
2717 * Node handle moved callback.
2718 */
2719 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2720 {
2721 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2723 Inkscape::NodePath::NodeSide *me;
2724 Inkscape::NodePath::NodeSide *other;
2725 if (n->p.knot == knot) {
2726 me = &n->p;
2727 other = &n->n;
2728 } else if (n->n.knot == knot) {
2729 me = &n->n;
2730 other = &n->p;
2731 } else {
2732 me = NULL;
2733 other = NULL;
2734 g_assert_not_reached();
2735 }
2737 // calculate radial coordinates of the grabbed control, other control, and the mouse point
2738 Radial rme(me->pos - n->pos);
2739 Radial rother(other->pos - n->pos);
2740 Radial rnew(*p - n->pos);
2742 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2743 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2744 /* 0 interpreted as "no snapping". */
2746 // The closest PI/snaps angle, starting from zero.
2747 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2748 if (me->origin.a == HUGE_VAL) {
2749 // ortho doesn't exist: original control was zero length.
2750 rnew.a = a_snapped;
2751 } else {
2752 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2753 * its opposite and perpendiculars). */
2754 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2756 // Snap to the closest.
2757 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2758 ? a_snapped
2759 : a_ortho );
2760 }
2761 }
2763 if (state & GDK_MOD1_MASK) {
2764 // lock handle length
2765 rnew.r = me->origin.r;
2766 }
2768 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2769 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2770 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2771 rother.a += rnew.a - rme.a;
2772 other->pos = NR::Point(rother) + n->pos;
2773 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2774 sp_knot_set_position(other->knot, &other->pos, 0);
2775 }
2777 me->pos = NR::Point(rnew) + n->pos;
2778 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2780 // this is what sp_knot_set_position does, but without emitting the signal:
2781 // we cannot emit a "moved" signal because we're now processing it
2782 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2784 knot->desktop->set_coordinate_status(me->pos);
2786 update_object(n->subpath->nodepath);
2788 /* status text */
2789 SPDesktop *desktop = n->subpath->nodepath->desktop;
2790 if (!desktop) return;
2791 SPEventContext *ec = desktop->event_context;
2792 if (!ec) return;
2793 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2794 if (!mc) return;
2796 double degrees = 180 / M_PI * rnew.a;
2797 if (degrees > 180) degrees -= 360;
2798 if (degrees < -180) degrees += 360;
2799 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2800 degrees = angle_to_compass (degrees);
2802 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2804 mc->setF(Inkscape::NORMAL_MESSAGE,
2805 _("<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);
2807 g_string_free(length, TRUE);
2808 }
2810 /**
2811 * Node handle event callback.
2812 */
2813 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2814 {
2815 gboolean ret = FALSE;
2816 switch (event->type) {
2817 case GDK_KEY_PRESS:
2818 switch (get_group0_keyval (&event->key)) {
2819 case GDK_space:
2820 if (event->key.state & GDK_BUTTON1_MASK) {
2821 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2822 stamp_repr(nodepath);
2823 ret = TRUE;
2824 }
2825 break;
2826 default:
2827 break;
2828 }
2829 break;
2830 default:
2831 break;
2832 }
2834 return ret;
2835 }
2837 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2838 Radial &rme, Radial &rother, gboolean const both)
2839 {
2840 rme.a += angle;
2841 if ( both
2842 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2843 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2844 {
2845 rother.a += angle;
2846 }
2847 }
2849 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2850 Radial &rme, Radial &rother, gboolean const both)
2851 {
2852 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2854 gdouble r;
2855 if ( both
2856 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2857 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2858 {
2859 r = MAX(rme.r, rother.r);
2860 } else {
2861 r = rme.r;
2862 }
2864 gdouble const weird_angle = atan2(norm_angle, r);
2865 /* Bulia says norm_angle is just the visible distance that the
2866 * object's end must travel on the screen. Left as 'angle' for want of
2867 * a better name.*/
2869 rme.a += weird_angle;
2870 if ( both
2871 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2872 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2873 {
2874 rother.a += weird_angle;
2875 }
2876 }
2878 /**
2879 * Rotate one node.
2880 */
2881 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2882 {
2883 Inkscape::NodePath::NodeSide *me, *other;
2884 bool both = false;
2886 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2887 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2889 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2890 me = &(n->p);
2891 other = &(n->n);
2892 } else if (!n->p.other) {
2893 me = &(n->n);
2894 other = &(n->p);
2895 } else {
2896 if (which > 0) { // right handle
2897 if (xn > xp) {
2898 me = &(n->n);
2899 other = &(n->p);
2900 } else {
2901 me = &(n->p);
2902 other = &(n->n);
2903 }
2904 } else if (which < 0){ // left handle
2905 if (xn <= xp) {
2906 me = &(n->n);
2907 other = &(n->p);
2908 } else {
2909 me = &(n->p);
2910 other = &(n->n);
2911 }
2912 } else { // both handles
2913 me = &(n->n);
2914 other = &(n->p);
2915 both = true;
2916 }
2917 }
2919 Radial rme(me->pos - n->pos);
2920 Radial rother(other->pos - n->pos);
2922 if (screen) {
2923 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2924 } else {
2925 node_rotate_one_internal (*n, angle, rme, rother, both);
2926 }
2928 me->pos = n->pos + NR::Point(rme);
2930 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
2931 other->pos = n->pos + NR::Point(rother);
2932 }
2934 sp_node_ensure_ctrls(n);
2935 }
2937 /**
2938 * Rotate selected nodes.
2939 */
2940 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
2941 {
2942 if (!nodepath || !nodepath->selected) return;
2944 if (g_list_length(nodepath->selected) == 1) {
2945 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
2946 node_rotate_one (n, angle, which, screen);
2947 } else {
2948 // rotate as an object:
2950 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
2951 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
2952 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2953 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2954 box.expandTo (n->pos); // contain all selected nodes
2955 }
2957 gdouble rot;
2958 if (screen) {
2959 gdouble const zoom = nodepath->desktop->current_zoom();
2960 gdouble const zmove = angle / zoom;
2961 gdouble const r = NR::L2(box.max() - box.midpoint());
2962 rot = atan2(zmove, r);
2963 } else {
2964 rot = angle;
2965 }
2967 NR::Matrix t =
2968 NR::Matrix (NR::translate(-box.midpoint())) *
2969 NR::Matrix (NR::rotate(rot)) *
2970 NR::Matrix (NR::translate(box.midpoint()));
2972 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2973 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2974 n->pos *= t;
2975 n->n.pos *= t;
2976 n->p.pos *= t;
2977 sp_node_ensure_ctrls(n);
2978 }
2979 }
2981 update_object(nodepath);
2982 /// \todo fixme: use _keyed
2983 update_repr(nodepath);
2984 }
2986 /**
2987 * Scale one node.
2988 */
2989 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
2990 {
2991 bool both = false;
2992 Inkscape::NodePath::NodeSide *me, *other;
2994 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2995 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2997 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2998 me = &(n->p);
2999 other = &(n->n);
3000 n->code = NR_CURVETO;
3001 } else if (!n->p.other) {
3002 me = &(n->n);
3003 other = &(n->p);
3004 if (n->n.other)
3005 n->n.other->code = NR_CURVETO;
3006 } else {
3007 if (which > 0) { // right handle
3008 if (xn > xp) {
3009 me = &(n->n);
3010 other = &(n->p);
3011 if (n->n.other)
3012 n->n.other->code = NR_CURVETO;
3013 } else {
3014 me = &(n->p);
3015 other = &(n->n);
3016 n->code = NR_CURVETO;
3017 }
3018 } else if (which < 0){ // left handle
3019 if (xn <= xp) {
3020 me = &(n->n);
3021 other = &(n->p);
3022 if (n->n.other)
3023 n->n.other->code = NR_CURVETO;
3024 } else {
3025 me = &(n->p);
3026 other = &(n->n);
3027 n->code = NR_CURVETO;
3028 }
3029 } else { // both handles
3030 me = &(n->n);
3031 other = &(n->p);
3032 both = true;
3033 n->code = NR_CURVETO;
3034 if (n->n.other)
3035 n->n.other->code = NR_CURVETO;
3036 }
3037 }
3039 Radial rme(me->pos - n->pos);
3040 Radial rother(other->pos - n->pos);
3042 rme.r += grow;
3043 if (rme.r < 0) rme.r = 0;
3044 if (rme.a == HUGE_VAL) {
3045 if (me->other) { // if direction is unknown, initialize it towards the next node
3046 Radial rme_next(me->other->pos - n->pos);
3047 rme.a = rme_next.a;
3048 } else { // if there's no next, initialize to 0
3049 rme.a = 0;
3050 }
3051 }
3052 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3053 rother.r += grow;
3054 if (rother.r < 0) rother.r = 0;
3055 if (rother.a == HUGE_VAL) {
3056 rother.a = rme.a + M_PI;
3057 }
3058 }
3060 me->pos = n->pos + NR::Point(rme);
3062 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3063 other->pos = n->pos + NR::Point(rother);
3064 }
3066 sp_node_ensure_ctrls(n);
3067 }
3069 /**
3070 * Scale selected nodes.
3071 */
3072 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3073 {
3074 if (!nodepath || !nodepath->selected) return;
3076 if (g_list_length(nodepath->selected) == 1) {
3077 // scale handles of the single selected node
3078 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3079 node_scale_one (n, grow, which);
3080 } else {
3081 // scale nodes as an "object":
3083 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3084 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3085 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3086 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3087 box.expandTo (n->pos); // contain all selected nodes
3088 }
3090 double scale = (box.maxExtent() + grow)/box.maxExtent();
3092 NR::Matrix t =
3093 NR::Matrix (NR::translate(-box.midpoint())) *
3094 NR::Matrix (NR::scale(scale, scale)) *
3095 NR::Matrix (NR::translate(box.midpoint()));
3097 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3098 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3099 n->pos *= t;
3100 n->n.pos *= t;
3101 n->p.pos *= t;
3102 sp_node_ensure_ctrls(n);
3103 }
3104 }
3106 update_object(nodepath);
3107 /// \todo fixme: use _keyed
3108 update_repr(nodepath);
3109 }
3111 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3112 {
3113 if (!nodepath) return;
3114 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3115 }
3117 /**
3118 * Flip selected nodes horizontally/vertically.
3119 */
3120 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3121 {
3122 if (!nodepath || !nodepath->selected) return;
3124 if (g_list_length(nodepath->selected) == 1) {
3125 // flip handles of the single selected node
3126 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3127 double temp = n->p.pos[axis];
3128 n->p.pos[axis] = n->n.pos[axis];
3129 n->n.pos[axis] = temp;
3130 sp_node_ensure_ctrls(n);
3131 } else {
3132 // scale nodes as an "object":
3134 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3135 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3136 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3137 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3138 box.expandTo (n->pos); // contain all selected nodes
3139 }
3141 NR::Matrix t =
3142 NR::Matrix (NR::translate(-box.midpoint())) *
3143 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3144 NR::Matrix (NR::translate(box.midpoint()));
3146 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3147 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3148 n->pos *= t;
3149 n->n.pos *= t;
3150 n->p.pos *= t;
3151 sp_node_ensure_ctrls(n);
3152 }
3153 }
3155 update_object(nodepath);
3156 /// \todo fixme: use _keyed
3157 update_repr(nodepath);
3158 }
3160 //-----------------------------------------------
3161 /**
3162 * Return new subpath under given nodepath.
3163 */
3164 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3165 {
3166 g_assert(nodepath);
3167 g_assert(nodepath->desktop);
3169 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3171 s->nodepath = nodepath;
3172 s->closed = FALSE;
3173 s->nodes = NULL;
3174 s->first = NULL;
3175 s->last = NULL;
3177 // do not use prepend here because:
3178 // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with
3179 // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not
3180 // the i-th node in the nodepath (only if there are multiple subpaths)
3181 // note that the problem only arise when called from subpath_from_bpath(), since for all the other
3182 // cases, the repr is updated after the call to sp_nodepath_subpath_new()
3183 nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s);
3185 return s;
3186 }
3188 /**
3189 * Destroy nodes in subpath, then subpath itself.
3190 */
3191 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3192 {
3193 g_assert(subpath);
3194 g_assert(subpath->nodepath);
3195 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3197 while (subpath->nodes) {
3198 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3199 }
3201 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3203 g_free(subpath);
3204 }
3206 /**
3207 * Link head to tail in subpath.
3208 */
3209 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3210 {
3211 g_assert(!sp->closed);
3212 g_assert(sp->last != sp->first);
3213 g_assert(sp->first->code == NR_MOVETO);
3215 sp->closed = TRUE;
3217 //Link the head to the tail
3218 sp->first->p.other = sp->last;
3219 sp->last->n.other = sp->first;
3220 sp->last->n.pos = sp->first->n.pos;
3221 sp->first = sp->last;
3223 //Remove the extra end node
3224 sp_nodepath_node_destroy(sp->last->n.other);
3225 }
3227 /**
3228 * Open closed (loopy) subpath at node.
3229 */
3230 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3231 {
3232 g_assert(sp->closed);
3233 g_assert(n->subpath == sp);
3234 g_assert(sp->first == sp->last);
3236 /* We create new startpoint, current node will become last one */
3238 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3239 &n->pos, &n->pos, &n->n.pos);
3242 sp->closed = FALSE;
3244 //Unlink to make a head and tail
3245 sp->first = new_path;
3246 sp->last = n;
3247 n->n.other = NULL;
3248 new_path->p.other = NULL;
3249 }
3251 /**
3252 * Returns area in triangle given by points; may be negative.
3253 */
3254 inline double
3255 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3256 {
3257 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]);
3258 }
3260 /**
3261 * Return new node in subpath with given properties.
3262 * \param pos Position of node.
3263 * \param ppos Handle position in previous direction
3264 * \param npos Handle position in previous direction
3265 */
3266 Inkscape::NodePath::Node *
3267 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)
3268 {
3269 g_assert(sp);
3270 g_assert(sp->nodepath);
3271 g_assert(sp->nodepath->desktop);
3273 if (nodechunk == NULL)
3274 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3276 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3278 n->subpath = sp;
3280 if (type != Inkscape::NodePath::NODE_NONE) {
3281 // use the type from sodipodi:nodetypes
3282 n->type = type;
3283 } else {
3284 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3285 // points are (almost) collinear
3286 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3287 // endnode, or a node with a retracted handle
3288 n->type = Inkscape::NodePath::NODE_CUSP;
3289 } else {
3290 n->type = Inkscape::NodePath::NODE_SMOOTH;
3291 }
3292 } else {
3293 n->type = Inkscape::NodePath::NODE_CUSP;
3294 }
3295 }
3297 n->code = code;
3298 n->selected = FALSE;
3299 n->pos = *pos;
3300 n->p.pos = *ppos;
3301 n->n.pos = *npos;
3303 n->dragging_out = NULL;
3305 Inkscape::NodePath::Node *prev;
3306 if (next) {
3307 g_assert(g_list_find(sp->nodes, next));
3308 prev = next->p.other;
3309 } else {
3310 prev = sp->last;
3311 }
3313 if (prev)
3314 prev->n.other = n;
3315 else
3316 sp->first = n;
3318 if (next)
3319 next->p.other = n;
3320 else
3321 sp->last = n;
3323 n->p.other = prev;
3324 n->n.other = next;
3326 n->knot = sp_knot_new(sp->nodepath->desktop);
3327 sp_knot_set_position(n->knot, pos, 0);
3328 g_object_set(G_OBJECT(n->knot),
3329 "anchor", GTK_ANCHOR_CENTER,
3330 "fill", NODE_FILL,
3331 "fill_mouseover", NODE_FILL_HI,
3332 "stroke", NODE_STROKE,
3333 "stroke_mouseover", NODE_STROKE_HI,
3334 "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"),
3335 NULL);
3336 if (n->type == Inkscape::NodePath::NODE_CUSP)
3337 g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL);
3338 else
3339 g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL);
3341 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3342 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3343 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3344 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3345 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3346 sp_knot_show(n->knot);
3348 n->p.knot = sp_knot_new(sp->nodepath->desktop);
3349 sp_knot_set_position(n->p.knot, ppos, 0);
3350 g_object_set(G_OBJECT(n->p.knot),
3351 "shape", SP_KNOT_SHAPE_CIRCLE,
3352 "size", 7,
3353 "anchor", GTK_ANCHOR_CENTER,
3354 "fill", KNOT_FILL,
3355 "fill_mouseover", KNOT_FILL_HI,
3356 "stroke", KNOT_STROKE,
3357 "stroke_mouseover", KNOT_STROKE_HI,
3358 "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"),
3359 NULL);
3360 g_signal_connect(G_OBJECT(n->p.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n);
3361 g_signal_connect(G_OBJECT(n->p.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n);
3362 g_signal_connect(G_OBJECT(n->p.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n);
3363 g_signal_connect(G_OBJECT(n->p.knot), "request", G_CALLBACK(node_ctrl_request), n);
3364 g_signal_connect(G_OBJECT(n->p.knot), "moved", G_CALLBACK(node_ctrl_moved), n);
3365 g_signal_connect(G_OBJECT(n->p.knot), "event", G_CALLBACK(node_ctrl_event), n);
3367 sp_knot_hide(n->p.knot);
3368 n->p.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop),
3369 SP_TYPE_CTRLLINE, NULL);
3370 sp_canvas_item_hide(n->p.line);
3372 n->n.knot = sp_knot_new(sp->nodepath->desktop);
3373 sp_knot_set_position(n->n.knot, npos, 0);
3374 g_object_set(G_OBJECT(n->n.knot),
3375 "shape", SP_KNOT_SHAPE_CIRCLE,
3376 "size", 7,
3377 "anchor", GTK_ANCHOR_CENTER,
3378 "fill", KNOT_FILL,
3379 "fill_mouseover", KNOT_FILL_HI,
3380 "stroke", KNOT_STROKE,
3381 "stroke_mouseover", KNOT_STROKE_HI,
3382 "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 the opposite handle in sync"),
3383 NULL);
3384 g_signal_connect(G_OBJECT(n->n.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n);
3385 g_signal_connect(G_OBJECT(n->n.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n);
3386 g_signal_connect(G_OBJECT(n->n.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n);
3387 g_signal_connect(G_OBJECT(n->n.knot), "request", G_CALLBACK(node_ctrl_request), n);
3388 g_signal_connect(G_OBJECT(n->n.knot), "moved", G_CALLBACK(node_ctrl_moved), n);
3389 g_signal_connect(G_OBJECT(n->n.knot), "event", G_CALLBACK(node_ctrl_event), n);
3390 sp_knot_hide(n->n.knot);
3391 n->n.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop),
3392 SP_TYPE_CTRLLINE, NULL);
3393 sp_canvas_item_hide(n->n.line);
3395 sp->nodes = g_list_prepend(sp->nodes, n);
3397 return n;
3398 }
3400 /**
3401 * Destroy node and its knots, link neighbors in subpath.
3402 */
3403 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3404 {
3405 g_assert(node);
3406 g_assert(node->subpath);
3407 g_assert(SP_IS_KNOT(node->knot));
3408 g_assert(SP_IS_KNOT(node->p.knot));
3409 g_assert(SP_IS_KNOT(node->n.knot));
3410 g_assert(g_list_find(node->subpath->nodes, node));
3412 Inkscape::NodePath::SubPath *sp = node->subpath;
3414 if (node->selected) { // first, deselect
3415 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3416 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3417 }
3419 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3421 g_object_unref(G_OBJECT(node->knot));
3422 g_object_unref(G_OBJECT(node->p.knot));
3423 g_object_unref(G_OBJECT(node->n.knot));
3425 gtk_object_destroy(GTK_OBJECT(node->p.line));
3426 gtk_object_destroy(GTK_OBJECT(node->n.line));
3428 if (sp->nodes) { // there are others nodes on the subpath
3429 if (sp->closed) {
3430 if (sp->first == node) {
3431 g_assert(sp->last == node);
3432 sp->first = node->n.other;
3433 sp->last = sp->first;
3434 }
3435 node->p.other->n.other = node->n.other;
3436 node->n.other->p.other = node->p.other;
3437 } else {
3438 if (sp->first == node) {
3439 sp->first = node->n.other;
3440 sp->first->code = NR_MOVETO;
3441 }
3442 if (sp->last == node) sp->last = node->p.other;
3443 if (node->p.other) node->p.other->n.other = node->n.other;
3444 if (node->n.other) node->n.other->p.other = node->p.other;
3445 }
3446 } else { // this was the last node on subpath
3447 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3448 }
3450 g_mem_chunk_free(nodechunk, node);
3451 }
3453 /**
3454 * Returns one of the node's two knots (node sides).
3455 * \param which Indicates which side.
3456 * \return Pointer to previous node side if which==-1, next if which==1.
3457 */
3458 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3459 {
3460 g_assert(node);
3462 switch (which) {
3463 case -1:
3464 return &node->p;
3465 case 1:
3466 return &node->n;
3467 default:
3468 break;
3469 }
3471 g_assert_not_reached();
3473 return NULL;
3474 }
3476 /**
3477 * Return knot on other side of node.
3478 */
3479 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3480 {
3481 g_assert(node);
3483 if (me == &node->p) return &node->n;
3484 if (me == &node->n) return &node->p;
3486 g_assert_not_reached();
3488 return NULL;
3489 }
3491 /**
3492 * Return NRPathcode on this knot's side of the node.
3493 */
3494 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3495 {
3496 g_assert(node);
3498 if (me == &node->p) {
3499 if (node->p.other) return (NRPathcode)node->code;
3500 return NR_MOVETO;
3501 }
3503 if (me == &node->n) {
3504 if (node->n.other) return (NRPathcode)node->n.other->code;
3505 return NR_MOVETO;
3506 }
3508 g_assert_not_reached();
3510 return NR_END;
3511 }
3513 /**
3514 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3515 */
3516 Inkscape::NodePath::Node *
3517 sp_nodepath_get_node_by_index(int index)
3518 {
3519 Inkscape::NodePath::Node *e = NULL;
3521 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3522 if (!nodepath) {
3523 return e;
3524 }
3526 //find segment
3527 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3529 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3530 int n = g_list_length(sp->nodes);
3531 if (sp->closed) {
3532 n++;
3533 }
3535 //if the piece belongs to this subpath grab it
3536 //otherwise move onto the next subpath
3537 if (index < n) {
3538 e = sp->first;
3539 for (int i = 0; i < index; ++i) {
3540 e = e->n.other;
3541 }
3542 break;
3543 } else {
3544 if (sp->closed) {
3545 index -= (n+1);
3546 } else {
3547 index -= n;
3548 }
3549 }
3550 }
3552 return e;
3553 }
3555 /**
3556 * Returns plain text meaning of node type.
3557 */
3558 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3559 {
3560 unsigned retracted = 0;
3561 bool endnode = false;
3563 for (int which = -1; which <= 1; which += 2) {
3564 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3565 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3566 retracted ++;
3567 if (!side->other)
3568 endnode = true;
3569 }
3571 if (retracted == 0) {
3572 if (endnode) {
3573 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3574 return _("end node");
3575 } else {
3576 switch (node->type) {
3577 case Inkscape::NodePath::NODE_CUSP:
3578 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3579 return _("cusp");
3580 case Inkscape::NodePath::NODE_SMOOTH:
3581 // TRANSLATORS: "smooth" is an adjective here
3582 return _("smooth");
3583 case Inkscape::NodePath::NODE_SYMM:
3584 return _("symmetric");
3585 }
3586 }
3587 } else if (retracted == 1) {
3588 if (endnode) {
3589 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3590 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3591 } else {
3592 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3593 }
3594 } else {
3595 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3596 }
3598 return NULL;
3599 }
3601 /**
3602 * Handles content of statusbar as long as node tool is active.
3603 */
3604 void
3605 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3606 {
3607 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3608 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3610 gint total = 0;
3611 gint selected = 0;
3612 SPDesktop *desktop = NULL;
3614 if (nodepath) {
3615 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3616 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3617 total += g_list_length(subpath->nodes);
3618 }
3619 selected = g_list_length(nodepath->selected);
3620 desktop = nodepath->desktop;
3621 } else {
3622 desktop = SP_ACTIVE_DESKTOP;
3623 }
3625 SPEventContext *ec = desktop->event_context;
3626 if (!ec) return;
3627 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3628 if (!mc) return;
3630 if (selected == 0) {
3631 Inkscape::Selection *sel = desktop->selection;
3632 if (!sel || sel->isEmpty()) {
3633 mc->setF(Inkscape::NORMAL_MESSAGE,
3634 _("Select a single object to edit its nodes or handles."));
3635 } else {
3636 if (nodepath) {
3637 mc->setF(Inkscape::NORMAL_MESSAGE,
3638 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.",
3639 "<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.",
3640 total),
3641 total);
3642 } else {
3643 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3644 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3645 } else {
3646 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3647 }
3648 }
3649 }
3650 } else if (nodepath && selected == 1) {
3651 mc->setF(Inkscape::NORMAL_MESSAGE,
3652 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3653 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3654 total),
3655 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3656 } else {
3657 mc->setF(Inkscape::NORMAL_MESSAGE,
3658 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3659 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3660 total),
3661 selected, total, when_selected);
3662 }
3663 }
3666 /*
3667 Local Variables:
3668 mode:c++
3669 c-file-style:"stroustrup"
3670 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3671 indent-tabs-mode:nil
3672 fill-column:99
3673 End:
3674 */
3675 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :