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 || e->p.other->selected))) {
1229 force = TRUE;
1230 }
1231 sp_nodepath_node_select(e, (gboolean) toggle, force);
1232 if (e->p.other)
1233 sp_nodepath_node_select(e->p.other, TRUE, force);
1235 sp_nodepath_ensure_ctrls(nodepath);
1237 sp_nodepath_update_statusbar(nodepath);
1238 }
1240 /**
1241 * Add a node nearest to point
1242 */
1243 void
1244 sp_nodepath_add_node_near_point(SPItem * item, NR::Point p)
1245 {
1246 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1247 if (!nodepath) {
1248 return;
1249 }
1251 Path::cut_position position = get_nearest_position_on_Path(item, p);
1253 //find segment to split
1254 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1256 //don't know why but t seems to flip for lines
1257 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1258 position.t = 1.0 - position.t;
1259 }
1260 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1261 sp_nodepath_node_select(n, FALSE, TRUE);
1263 /* fixme: adjust ? */
1264 sp_nodepath_ensure_ctrls(nodepath);
1266 update_repr(nodepath);
1268 sp_nodepath_update_statusbar(nodepath);
1269 }
1271 /*
1272 * Adjusts a segment so that t moves by a certain delta for dragging
1273 * converts lines to curves
1274 *
1275 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1276 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1277 */
1278 void
1279 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta, char * key)
1280 {
1281 /* feel good is an arbitrary parameter that distributes the delta between handles
1282 * if t of the drag point is less than 1/6 distance form the endpoint only
1283 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1284 */
1285 double feel_good;
1286 if (t <= 1.0 / 6.0)
1287 feel_good = 0;
1288 else if (t <= 0.5)
1289 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1290 else if (t <= 5.0 / 6.0)
1291 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1292 else
1293 feel_good = 1;
1295 //if we're dragging a line convert it to a curve
1296 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1297 sp_nodepath_set_line_type(e, NR_CURVETO);
1298 }
1300 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1301 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1302 e->p.other->n.pos += offsetcoord0;
1303 e->p.pos += offsetcoord1;
1305 // adjust controls of adjacent segments where necessary
1306 sp_node_adjust_knot(e,1);
1307 sp_node_adjust_knot(e->p.other,-1);
1309 sp_nodepath_ensure_ctrls(e->subpath->nodepath);
1311 update_repr_keyed(e->subpath->nodepath, key);
1313 sp_nodepath_update_statusbar(e->subpath->nodepath);
1314 }
1317 /**
1318 * Call sp_nodepath_break() for all selected segments.
1319 */
1320 void sp_node_selected_break()
1321 {
1322 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1323 if (!nodepath) return;
1325 GList *temp = NULL;
1326 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1327 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1328 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1329 if (nn == NULL) continue; // no break, no new node
1330 temp = g_list_prepend(temp, nn);
1331 }
1333 if (temp) {
1334 sp_nodepath_deselect(nodepath);
1335 }
1336 for (GList *l = temp; l != NULL; l = l->next) {
1337 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1338 }
1340 sp_nodepath_ensure_ctrls(nodepath);
1342 update_repr(nodepath);
1343 }
1345 /**
1346 * Duplicate the selected node(s).
1347 */
1348 void sp_node_selected_duplicate()
1349 {
1350 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1351 if (!nodepath) {
1352 return;
1353 }
1355 GList *temp = NULL;
1356 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1357 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1358 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1359 if (nn == NULL) continue; // could not duplicate
1360 temp = g_list_prepend(temp, nn);
1361 }
1363 if (temp) {
1364 sp_nodepath_deselect(nodepath);
1365 }
1366 for (GList *l = temp; l != NULL; l = l->next) {
1367 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1368 }
1370 sp_nodepath_ensure_ctrls(nodepath);
1372 update_repr(nodepath);
1373 }
1375 /**
1376 * Join two nodes by merging them into one.
1377 */
1378 void sp_node_selected_join()
1379 {
1380 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1381 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1383 if (g_list_length(nodepath->selected) != 2) {
1384 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1385 return;
1386 }
1388 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1389 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1391 g_assert(a != b);
1392 g_assert(a->p.other || a->n.other);
1393 g_assert(b->p.other || b->n.other);
1395 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1396 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1397 return;
1398 }
1400 /* a and b are endpoints */
1402 NR::Point c = (a->pos + b->pos) / 2;
1404 if (a->subpath == b->subpath) {
1405 Inkscape::NodePath::SubPath *sp = a->subpath;
1406 sp_nodepath_subpath_close(sp);
1408 sp_nodepath_ensure_ctrls(sp->nodepath);
1410 update_repr(nodepath);
1412 return;
1413 }
1415 /* a and b are separate subpaths */
1416 Inkscape::NodePath::SubPath *sa = a->subpath;
1417 Inkscape::NodePath::SubPath *sb = b->subpath;
1418 NR::Point p;
1419 Inkscape::NodePath::Node *n;
1420 NRPathcode code;
1421 if (a == sa->first) {
1422 p = sa->first->n.pos;
1423 code = (NRPathcode)sa->first->n.other->code;
1424 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1425 n = sa->last;
1426 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1427 n = n->p.other;
1428 while (n) {
1429 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1430 n = n->p.other;
1431 if (n == sa->first) n = NULL;
1432 }
1433 sp_nodepath_subpath_destroy(sa);
1434 sa = t;
1435 } else if (a == sa->last) {
1436 p = sa->last->p.pos;
1437 code = (NRPathcode)sa->last->code;
1438 sp_nodepath_node_destroy(sa->last);
1439 } else {
1440 code = NR_END;
1441 g_assert_not_reached();
1442 }
1444 if (b == sb->first) {
1445 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1446 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1447 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1448 }
1449 } else if (b == sb->last) {
1450 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1451 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1452 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1453 }
1454 } else {
1455 g_assert_not_reached();
1456 }
1457 /* and now destroy sb */
1459 sp_nodepath_subpath_destroy(sb);
1461 sp_nodepath_ensure_ctrls(sa->nodepath);
1463 update_repr(nodepath);
1465 sp_nodepath_update_statusbar(nodepath);
1466 }
1468 /**
1469 * Join two nodes by adding a segment between them.
1470 */
1471 void sp_node_selected_join_segment()
1472 {
1473 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1474 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1476 if (g_list_length(nodepath->selected) != 2) {
1477 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1478 return;
1479 }
1481 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1482 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1484 g_assert(a != b);
1485 g_assert(a->p.other || a->n.other);
1486 g_assert(b->p.other || b->n.other);
1488 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1489 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1490 return;
1491 }
1493 if (a->subpath == b->subpath) {
1494 Inkscape::NodePath::SubPath *sp = a->subpath;
1496 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1497 sp->closed = TRUE;
1499 sp->first->p.other = sp->last;
1500 sp->last->n.other = sp->first;
1502 sp_node_control_mirror_p_to_n(sp->last);
1503 sp_node_control_mirror_n_to_p(sp->first);
1505 sp->first->code = sp->last->code;
1506 sp->first = sp->last;
1508 sp_nodepath_ensure_ctrls(sp->nodepath);
1510 update_repr(nodepath);
1512 return;
1513 }
1515 /* a and b are separate subpaths */
1516 Inkscape::NodePath::SubPath *sa = a->subpath;
1517 Inkscape::NodePath::SubPath *sb = b->subpath;
1519 Inkscape::NodePath::Node *n;
1520 NR::Point p;
1521 NRPathcode code;
1522 if (a == sa->first) {
1523 code = (NRPathcode) sa->first->n.other->code;
1524 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1525 n = sa->last;
1526 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1527 for (n = n->p.other; n != NULL; n = n->p.other) {
1528 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1529 }
1530 sp_nodepath_subpath_destroy(sa);
1531 sa = t;
1532 } else if (a == sa->last) {
1533 code = (NRPathcode)sa->last->code;
1534 } else {
1535 code = NR_END;
1536 g_assert_not_reached();
1537 }
1539 if (b == sb->first) {
1540 n = sb->first;
1541 sp_node_control_mirror_p_to_n(sa->last);
1542 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1543 sp_node_control_mirror_n_to_p(sa->last);
1544 for (n = n->n.other; n != NULL; n = n->n.other) {
1545 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1546 }
1547 } else if (b == sb->last) {
1548 n = sb->last;
1549 sp_node_control_mirror_p_to_n(sa->last);
1550 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1551 sp_node_control_mirror_n_to_p(sa->last);
1552 for (n = n->p.other; n != NULL; n = n->p.other) {
1553 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1554 }
1555 } else {
1556 g_assert_not_reached();
1557 }
1558 /* and now destroy sb */
1560 sp_nodepath_subpath_destroy(sb);
1562 sp_nodepath_ensure_ctrls(sa->nodepath);
1564 update_repr(nodepath);
1565 }
1567 /**
1568 * Delete one or more selected nodes.
1569 */
1570 void sp_node_selected_delete()
1571 {
1572 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1573 if (!nodepath) return;
1574 if (!nodepath->selected) return;
1576 /** \todo fixme: do it the right way */
1577 while (nodepath->selected) {
1578 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1579 sp_nodepath_node_destroy(node);
1580 }
1583 //clean up the nodepath (such as for trivial subpaths)
1584 sp_nodepath_cleanup(nodepath);
1586 sp_nodepath_ensure_ctrls(nodepath);
1588 // if the entire nodepath is removed, delete the selected object.
1589 if (nodepath->subpaths == NULL ||
1590 sp_nodepath_get_node_count(nodepath) < 2) {
1591 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1592 sp_nodepath_destroy(nodepath);
1593 sp_selection_delete();
1594 sp_document_done (document);
1595 return;
1596 }
1598 update_repr(nodepath);
1600 sp_nodepath_update_statusbar(nodepath);
1601 }
1603 /**
1604 * Delete one or more segments between two selected nodes.
1605 * This is the code for 'split'.
1606 */
1607 void
1608 sp_node_selected_delete_segment(void)
1609 {
1610 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1611 Inkscape::NodePath::Node *curr, *next; //Iterators
1613 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1614 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1616 if (g_list_length(nodepath->selected) != 2) {
1617 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1618 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1619 return;
1620 }
1622 //Selected nodes, not inclusive
1623 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1624 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1626 if ( ( a==b) || //same node
1627 (a->subpath != b->subpath ) || //not the same path
1628 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1629 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1630 {
1631 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1632 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1633 return;
1634 }
1636 //###########################################
1637 //# BEGIN EDITS
1638 //###########################################
1639 //##################################
1640 //# CLOSED PATH
1641 //##################################
1642 if (a->subpath->closed) {
1645 gboolean reversed = FALSE;
1647 //Since we can go in a circle, we need to find the shorter distance.
1648 // a->b or b->a
1649 start = end = NULL;
1650 int distance = 0;
1651 int minDistance = 0;
1652 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1653 if (curr==b) {
1654 //printf("a to b:%d\n", distance);
1655 start = a;//go from a to b
1656 end = b;
1657 minDistance = distance;
1658 //printf("A to B :\n");
1659 break;
1660 }
1661 distance++;
1662 }
1664 //try again, the other direction
1665 distance = 0;
1666 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1667 if (curr==a) {
1668 //printf("b to a:%d\n", distance);
1669 if (distance < minDistance) {
1670 start = b; //we go from b to a
1671 end = a;
1672 reversed = TRUE;
1673 //printf("B to A\n");
1674 }
1675 break;
1676 }
1677 distance++;
1678 }
1681 //Copy everything from 'end' to 'start' to a new subpath
1682 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1683 for (curr=end ; curr ; curr=curr->n.other) {
1684 NRPathcode code = (NRPathcode) curr->code;
1685 if (curr == end)
1686 code = NR_MOVETO;
1687 sp_nodepath_node_new(t, NULL,
1688 (Inkscape::NodePath::NodeType)curr->type, code,
1689 &curr->p.pos, &curr->pos, &curr->n.pos);
1690 if (curr == start)
1691 break;
1692 }
1693 sp_nodepath_subpath_destroy(a->subpath);
1696 }
1700 //##################################
1701 //# OPEN PATH
1702 //##################################
1703 else {
1705 //We need to get the direction of the list between A and B
1706 //Can we walk from a to b?
1707 start = end = NULL;
1708 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1709 if (curr==b) {
1710 start = a; //did it! we go from a to b
1711 end = b;
1712 //printf("A to B\n");
1713 break;
1714 }
1715 }
1716 if (!start) {//didn't work? let's try the other direction
1717 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1718 if (curr==a) {
1719 start = b; //did it! we go from b to a
1720 end = a;
1721 //printf("B to A\n");
1722 break;
1723 }
1724 }
1725 }
1726 if (!start) {
1727 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1728 _("Cannot find path between nodes."));
1729 return;
1730 }
1734 //Copy everything after 'end' to a new subpath
1735 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1736 for (curr=end ; curr ; curr=curr->n.other) {
1737 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1738 &curr->p.pos, &curr->pos, &curr->n.pos);
1739 }
1741 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1742 for (curr = start->n.other ; curr ; curr=next) {
1743 next = curr->n.other;
1744 sp_nodepath_node_destroy(curr);
1745 }
1747 }
1748 //###########################################
1749 //# END EDITS
1750 //###########################################
1752 //clean up the nodepath (such as for trivial subpaths)
1753 sp_nodepath_cleanup(nodepath);
1755 sp_nodepath_ensure_ctrls(nodepath);
1757 update_repr(nodepath);
1759 // if the entire nodepath is removed, delete the selected object.
1760 if (nodepath->subpaths == NULL ||
1761 sp_nodepath_get_node_count(nodepath) < 2) {
1762 sp_nodepath_destroy(nodepath);
1763 sp_selection_delete();
1764 return;
1765 }
1767 sp_nodepath_update_statusbar(nodepath);
1768 }
1770 /**
1771 * Call sp_nodepath_set_line() for all selected segments.
1772 */
1773 void
1774 sp_node_selected_set_line_type(NRPathcode code)
1775 {
1776 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1777 if (nodepath == NULL) return;
1779 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1780 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1781 g_assert(n->selected);
1782 if (n->p.other && n->p.other->selected) {
1783 sp_nodepath_set_line_type(n, code);
1784 }
1785 }
1787 update_repr(nodepath);
1788 }
1790 /**
1791 * Call sp_nodepath_convert_node_type() for all selected nodes.
1792 */
1793 void
1794 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1795 {
1796 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1797 if (nodepath == NULL) return;
1799 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1800 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1801 }
1803 update_repr(nodepath);
1804 }
1806 /**
1807 * Change select status of node, update its own and neighbour handles.
1808 */
1809 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1810 {
1811 node->selected = selected;
1813 if (selected) {
1814 g_object_set(G_OBJECT(node->knot),
1815 "fill", NODE_FILL_SEL,
1816 "fill_mouseover", NODE_FILL_SEL_HI,
1817 "stroke", NODE_STROKE_SEL,
1818 "stroke_mouseover", NODE_STROKE_SEL_HI,
1819 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9,
1820 NULL);
1821 } else {
1822 g_object_set(G_OBJECT(node->knot),
1823 "fill", NODE_FILL,
1824 "fill_mouseover", NODE_FILL_HI,
1825 "stroke", NODE_STROKE,
1826 "stroke_mouseover", NODE_STROKE_HI,
1827 "size", (node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7,
1828 NULL);
1829 }
1831 sp_node_ensure_ctrls(node);
1832 if (node->n.other) sp_node_ensure_ctrls(node->n.other);
1833 if (node->p.other) sp_node_ensure_ctrls(node->p.other);
1834 }
1836 /**
1837 \brief Select a node
1838 \param node The node to select
1839 \param incremental If true, add to selection, otherwise deselect others
1840 \param override If true, always select this node, otherwise toggle selected status
1841 */
1842 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1843 {
1844 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1846 if (incremental) {
1847 if (override) {
1848 if (!g_list_find(nodepath->selected, node)) {
1849 nodepath->selected = g_list_append(nodepath->selected, node);
1850 }
1851 sp_node_set_selected(node, TRUE);
1852 } else { // toggle
1853 if (node->selected) {
1854 g_assert(g_list_find(nodepath->selected, node));
1855 nodepath->selected = g_list_remove(nodepath->selected, node);
1856 } else {
1857 g_assert(!g_list_find(nodepath->selected, node));
1858 nodepath->selected = g_list_append(nodepath->selected, node);
1859 }
1860 sp_node_set_selected(node, !node->selected);
1861 }
1862 } else {
1863 sp_nodepath_deselect(nodepath);
1864 nodepath->selected = g_list_append(nodepath->selected, node);
1865 sp_node_set_selected(node, TRUE);
1866 }
1868 sp_nodepath_update_statusbar(nodepath);
1869 }
1872 /**
1873 \brief Deselect all nodes in the nodepath
1874 */
1875 void
1876 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1877 {
1878 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1880 while (nodepath->selected) {
1881 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1882 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1883 }
1884 sp_nodepath_update_statusbar(nodepath);
1885 }
1887 /**
1888 \brief Select or invert selection of all nodes in the nodepath
1889 */
1890 void
1891 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1892 {
1893 if (!nodepath) return;
1895 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1896 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1897 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1898 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1899 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1900 }
1901 }
1902 }
1904 /**
1905 * If nothing selected, does the same as sp_nodepath_select_all();
1906 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1907 * (i.e., similar to "select all in layer", with the "selected" subpaths
1908 * being treated as "layers" in the path).
1909 */
1910 void
1911 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1912 {
1913 if (!nodepath) return;
1915 if (g_list_length (nodepath->selected) == 0) {
1916 sp_nodepath_select_all (nodepath, invert);
1917 return;
1918 }
1920 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1921 GSList *subpaths = NULL;
1923 for (GList *l = copy; l != NULL; l = l->next) {
1924 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1925 Inkscape::NodePath::SubPath *subpath = n->subpath;
1926 if (!g_slist_find (subpaths, subpath))
1927 subpaths = g_slist_prepend (subpaths, subpath);
1928 }
1930 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
1931 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
1932 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1933 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1934 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
1935 }
1936 }
1938 g_slist_free (subpaths);
1939 g_list_free (copy);
1940 }
1942 /**
1943 * \brief Select the node after the last selected; if none is selected,
1944 * select the first within path.
1945 */
1946 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
1947 {
1948 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1950 Inkscape::NodePath::Node *last = NULL;
1951 if (nodepath->selected) {
1952 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1953 Inkscape::NodePath::SubPath *subpath, *subpath_next;
1954 subpath = (Inkscape::NodePath::SubPath *) spl->data;
1955 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1956 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1957 if (node->selected) {
1958 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
1959 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
1960 if (spl->next) { // there's a next subpath
1961 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
1962 last = subpath_next->first;
1963 } else if (spl->prev) { // there's a previous subpath
1964 last = NULL; // to be set later to the first node of first subpath
1965 } else {
1966 last = node->n.other;
1967 }
1968 } else {
1969 last = node->n.other;
1970 }
1971 } else {
1972 if (node->n.other) {
1973 last = node->n.other;
1974 } else {
1975 if (spl->next) { // there's a next subpath
1976 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
1977 last = subpath_next->first;
1978 } else if (spl->prev) { // there's a previous subpath
1979 last = NULL; // to be set later to the first node of first subpath
1980 } else {
1981 last = (Inkscape::NodePath::Node *) subpath->first;
1982 }
1983 }
1984 }
1985 }
1986 }
1987 }
1988 sp_nodepath_deselect(nodepath);
1989 }
1991 if (last) { // there's at least one more node after selected
1992 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
1993 } else { // no more nodes, select the first one in first subpath
1994 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
1995 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
1996 }
1997 }
1999 /**
2000 * \brief Select the node before the first selected; if none is selected,
2001 * select the last within path
2002 */
2003 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2004 {
2005 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2007 Inkscape::NodePath::Node *last = NULL;
2008 if (nodepath->selected) {
2009 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2010 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2011 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2012 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2013 if (node->selected) {
2014 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2015 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2016 if (spl->prev) { // there's a prev subpath
2017 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2018 last = subpath_prev->last;
2019 } else if (spl->next) { // there's a next subpath
2020 last = NULL; // to be set later to the last node of last subpath
2021 } else {
2022 last = node->p.other;
2023 }
2024 } else {
2025 last = node->p.other;
2026 }
2027 } else {
2028 if (node->p.other) {
2029 last = node->p.other;
2030 } else {
2031 if (spl->prev) { // there's a prev subpath
2032 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2033 last = subpath_prev->last;
2034 } else if (spl->next) { // there's a next subpath
2035 last = NULL; // to be set later to the last node of last subpath
2036 } else {
2037 last = (Inkscape::NodePath::Node *) subpath->last;
2038 }
2039 }
2040 }
2041 }
2042 }
2043 }
2044 sp_nodepath_deselect(nodepath);
2045 }
2047 if (last) { // there's at least one more node before selected
2048 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2049 } else { // no more nodes, select the last one in last subpath
2050 GList *spl = g_list_last(nodepath->subpaths);
2051 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2052 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2053 }
2054 }
2056 /**
2057 * \brief Select all nodes that are within the rectangle.
2058 */
2059 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2060 {
2061 if (!incremental) {
2062 sp_nodepath_deselect(nodepath);
2063 }
2065 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2066 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2067 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2068 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2070 if (b.contains(node->pos)) {
2071 sp_nodepath_node_select(node, TRUE, TRUE);
2072 }
2073 }
2074 }
2075 }
2077 /**
2078 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2079 */
2080 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2081 {
2082 if (!nodepath->selected) {
2083 return NULL;
2084 }
2086 GList *r = NULL;
2087 guint i = 0;
2088 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2089 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2090 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2091 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2092 i++;
2093 if (node->selected) {
2094 r = g_list_append(r, GINT_TO_POINTER(i));
2095 }
2096 }
2097 }
2098 return r;
2099 }
2101 /**
2102 \brief Restores selection by selecting nodes whose positions are in the list
2103 */
2104 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2105 {
2106 sp_nodepath_deselect(nodepath);
2108 guint i = 0;
2109 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2110 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2111 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2112 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2113 i++;
2114 if (g_list_find(r, GINT_TO_POINTER(i))) {
2115 sp_nodepath_node_select(node, TRUE, TRUE);
2116 }
2117 }
2118 }
2120 }
2122 /**
2123 \brief Adjusts control point according to node type and line code.
2124 */
2125 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust)
2126 {
2127 double len, otherlen, linelen;
2129 g_assert(node);
2131 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2132 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2134 /** \todo fixme: */
2135 if (me->other == NULL) return;
2136 if (other->other == NULL) return;
2138 /* I have line */
2140 NRPathcode mecode, ocode;
2141 if (which_adjust == 1) {
2142 mecode = (NRPathcode)me->other->code;
2143 ocode = (NRPathcode)node->code;
2144 } else {
2145 mecode = (NRPathcode)node->code;
2146 ocode = (NRPathcode)other->other->code;
2147 }
2149 if (mecode == NR_LINETO) return;
2151 /* I am curve */
2153 if (other->other == NULL) return;
2155 /* Other has line */
2157 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2159 NR::Point delta;
2160 if (ocode == NR_LINETO) {
2161 /* other is lineto, we are either smooth or symm */
2162 Inkscape::NodePath::Node *othernode = other->other;
2163 len = NR::L2(me->pos - node->pos);
2164 delta = node->pos - othernode->pos;
2165 linelen = NR::L2(delta);
2166 if (linelen < 1e-18) return;
2168 me->pos = node->pos + (len / linelen)*delta;
2169 sp_knot_set_position(me->knot, &me->pos, 0);
2171 sp_node_ensure_ctrls(node);
2172 return;
2173 }
2175 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2177 me->pos = 2 * node->pos - other->pos;
2178 sp_knot_set_position(me->knot, &me->pos, 0);
2180 sp_node_ensure_ctrls(node);
2181 return;
2182 }
2184 /* We are smooth */
2186 len = NR::L2(me->pos - node->pos);
2187 delta = other->pos - node->pos;
2188 otherlen = NR::L2(delta);
2189 if (otherlen < 1e-18) return;
2191 me->pos = node->pos - (len / otherlen) * delta;
2192 sp_knot_set_position(me->knot, &me->pos, 0);
2194 sp_node_ensure_ctrls(node);
2195 }
2197 /**
2198 \brief Adjusts control point according to node type and line code
2199 */
2200 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node)
2201 {
2202 g_assert(node);
2204 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2206 /* we are either smooth or symm */
2208 if (node->p.other == NULL) return;
2210 if (node->n.other == NULL) return;
2212 if (node->code == NR_LINETO) {
2213 if (node->n.other->code == NR_LINETO) return;
2214 sp_node_adjust_knot(node, 1);
2215 sp_node_ensure_ctrls(node);
2216 return;
2217 }
2219 if (node->n.other->code == NR_LINETO) {
2220 if (node->code == NR_LINETO) return;
2221 sp_node_adjust_knot(node, -1);
2222 sp_node_ensure_ctrls(node);
2223 return;
2224 }
2226 /* both are curves */
2228 NR::Point const delta( node->n.pos - node->p.pos );
2230 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2231 node->p.pos = node->pos - delta / 2;
2232 node->n.pos = node->pos + delta / 2;
2233 sp_node_ensure_ctrls(node);
2234 return;
2235 }
2237 /* We are smooth */
2239 double plen = NR::L2(node->p.pos - node->pos);
2240 if (plen < 1e-18) return;
2241 double nlen = NR::L2(node->n.pos - node->pos);
2242 if (nlen < 1e-18) return;
2243 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2244 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2245 sp_node_ensure_ctrls(node);
2246 }
2248 /**
2249 * Knot events handler callback.
2250 */
2251 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2252 {
2253 gboolean ret = FALSE;
2254 switch (event->type) {
2255 case GDK_ENTER_NOTIFY:
2256 active_node = n;
2257 break;
2258 case GDK_LEAVE_NOTIFY:
2259 active_node = NULL;
2260 break;
2261 case GDK_KEY_PRESS:
2262 switch (get_group0_keyval (&event->key)) {
2263 case GDK_space:
2264 if (event->key.state & GDK_BUTTON1_MASK) {
2265 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2266 stamp_repr(nodepath);
2267 ret = TRUE;
2268 }
2269 break;
2270 default:
2271 break;
2272 }
2273 break;
2274 default:
2275 break;
2276 }
2278 return ret;
2279 }
2281 /**
2282 * Handle keypress on node; directly called.
2283 */
2284 gboolean node_key(GdkEvent *event)
2285 {
2286 Inkscape::NodePath::Path *np;
2288 // there is no way to verify nodes so set active_node to nil when deleting!!
2289 if (active_node == NULL) return FALSE;
2291 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2292 gint ret = FALSE;
2293 switch (get_group0_keyval (&event->key)) {
2294 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2295 case GDK_BackSpace:
2296 np = active_node->subpath->nodepath;
2297 sp_nodepath_node_destroy(active_node);
2298 update_repr(np);
2299 active_node = NULL;
2300 ret = TRUE;
2301 break;
2302 case GDK_c:
2303 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2304 ret = TRUE;
2305 break;
2306 case GDK_s:
2307 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2308 ret = TRUE;
2309 break;
2310 case GDK_y:
2311 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2312 ret = TRUE;
2313 break;
2314 case GDK_b:
2315 sp_nodepath_node_break(active_node);
2316 ret = TRUE;
2317 break;
2318 }
2319 return ret;
2320 }
2321 return FALSE;
2322 }
2324 /**
2325 * Mouseclick on node callback.
2326 */
2327 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2328 {
2329 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2331 if (state & GDK_CONTROL_MASK) {
2332 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2334 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2335 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2336 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2337 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2338 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2339 } else {
2340 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2341 }
2342 update_repr(nodepath);
2343 sp_nodepath_update_statusbar(nodepath);
2345 } else { //ctrl+alt+click: delete node
2346 sp_nodepath_node_destroy(n);
2347 //clean up the nodepath (such as for trivial subpaths)
2348 sp_nodepath_cleanup(nodepath);
2350 // if the entire nodepath is removed, delete the selected object.
2351 if (nodepath->subpaths == NULL ||
2352 sp_nodepath_get_node_count(nodepath) < 2) {
2353 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2354 sp_nodepath_destroy(nodepath);
2355 sp_selection_delete();
2356 sp_document_done (document);
2358 } else {
2359 sp_nodepath_ensure_ctrls(nodepath);
2360 update_repr(nodepath);
2361 sp_nodepath_update_statusbar(nodepath);
2362 }
2363 }
2365 } else {
2366 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2367 }
2368 }
2370 /**
2371 * Mouse grabbed node callback.
2372 */
2373 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2374 {
2375 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2377 n->origin = knot->pos;
2379 if (!n->selected) {
2380 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2381 }
2382 }
2384 /**
2385 * Mouse ungrabbed node callback.
2386 */
2387 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2388 {
2389 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2391 n->dragging_out = NULL;
2393 update_repr(n->subpath->nodepath);
2394 }
2396 /**
2397 * The point on a line, given by its angle, closest to the given point.
2398 * \param p A point.
2399 * \param a Angle of the line; it is assumed to go through coordinate origin.
2400 * \param closest Pointer to the point struct where the result is stored.
2401 * \todo FIXME: use dot product perhaps?
2402 */
2403 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2404 {
2405 if (a == HUGE_VAL) { // vertical
2406 *closest = NR::Point(0, (*p)[NR::Y]);
2407 } else {
2408 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2409 (*closest)[NR::Y] = a * (*closest)[NR::X];
2410 }
2411 }
2413 /**
2414 * Distance from the point to a line given by its angle.
2415 * \param p A point.
2416 * \param a Angle of the line; it is assumed to go through coordinate origin.
2417 */
2418 static double point_line_distance(NR::Point *p, double a)
2419 {
2420 NR::Point c;
2421 point_line_closest(p, a, &c);
2422 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]));
2423 }
2425 /**
2426 * Callback for node "request" signal.
2427 * \todo fixme: This goes to "moved" event? (lauris)
2428 */
2429 static gboolean
2430 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2431 {
2432 double yn, xn, yp, xp;
2433 double an, ap, na, pa;
2434 double d_an, d_ap, d_na, d_pa;
2435 gboolean collinear = FALSE;
2436 NR::Point c;
2437 NR::Point pr;
2439 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2441 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2442 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2444 NR::Point mouse = (*p);
2446 if (!n->dragging_out) {
2447 // This is the first drag-out event; find out which handle to drag out
2448 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2449 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2451 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2452 return FALSE;
2454 Inkscape::NodePath::NodeSide *opposite;
2455 if (appr_p > appr_n) { // closer to p
2456 n->dragging_out = &n->p;
2457 opposite = &n->n;
2458 n->code = NR_CURVETO;
2459 } else if (appr_p < appr_n) { // closer to n
2460 n->dragging_out = &n->n;
2461 opposite = &n->p;
2462 n->n.other->code = NR_CURVETO;
2463 } else { // p and n nodes are the same
2464 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2465 n->dragging_out = &n->p;
2466 opposite = &n->n;
2467 n->code = NR_CURVETO;
2468 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2469 n->dragging_out = &n->n;
2470 opposite = &n->p;
2471 n->n.other->code = NR_CURVETO;
2472 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2473 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);
2474 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);
2475 if (appr_other_p > appr_other_n) { // closer to other's p handle
2476 n->dragging_out = &n->n;
2477 opposite = &n->p;
2478 n->n.other->code = NR_CURVETO;
2479 } else { // closer to other's n handle
2480 n->dragging_out = &n->p;
2481 opposite = &n->n;
2482 n->code = NR_CURVETO;
2483 }
2484 }
2485 }
2487 // if there's another handle, make sure the one we drag out starts parallel to it
2488 if (opposite->pos != n->pos) {
2489 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2490 }
2491 }
2493 // pass this on to the handle-moved callback
2494 node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2495 sp_node_ensure_ctrls(n);
2496 return TRUE;
2497 }
2499 if (state & GDK_CONTROL_MASK) { // constrained motion
2501 // calculate relative distances of handles
2502 // n handle:
2503 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2504 xn = n->n.pos[NR::X] - n->pos[NR::X];
2505 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2506 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2507 if (n->n.other) { // if there is the next point
2508 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2509 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2510 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2511 }
2512 }
2513 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2514 if (yn < 0) { xn = -xn; yn = -yn; }
2516 // p handle:
2517 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2518 xp = n->p.pos[NR::X] - n->pos[NR::X];
2519 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2520 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2521 if (n->p.other) {
2522 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2523 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2524 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2525 }
2526 }
2527 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2528 if (yp < 0) { xp = -xp; yp = -yp; }
2530 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2531 // sliding on handles, only if at least one of the handles is non-vertical
2532 // (otherwise it's the same as ctrl+drag anyway)
2534 // calculate angles of the control handles
2535 if (xn == 0) {
2536 if (yn == 0) { // no handle, consider it the continuation of the other one
2537 an = 0;
2538 collinear = TRUE;
2539 }
2540 else an = 0; // vertical; set the angle to horizontal
2541 } else an = yn/xn;
2543 if (xp == 0) {
2544 if (yp == 0) { // no handle, consider it the continuation of the other one
2545 ap = an;
2546 }
2547 else ap = 0; // vertical; set the angle to horizontal
2548 } else ap = yp/xp;
2550 if (collinear) an = ap;
2552 // angles of the perpendiculars; HUGE_VAL means vertical
2553 if (an == 0) na = HUGE_VAL; else na = -1/an;
2554 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2556 //g_print("an %g ap %g\n", an, ap);
2558 // mouse point relative to the node's original pos
2559 pr = (*p) - n->origin;
2561 // distances to the four lines (two handles and two perpendiculars)
2562 d_an = point_line_distance(&pr, an);
2563 d_na = point_line_distance(&pr, na);
2564 d_ap = point_line_distance(&pr, ap);
2565 d_pa = point_line_distance(&pr, pa);
2567 // find out which line is the closest, save its closest point in c
2568 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2569 point_line_closest(&pr, an, &c);
2570 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2571 point_line_closest(&pr, ap, &c);
2572 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2573 point_line_closest(&pr, na, &c);
2574 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2575 point_line_closest(&pr, pa, &c);
2576 }
2578 // move the node to the closest point
2579 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2580 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2581 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2583 } else { // constraining to hor/vert
2585 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2586 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2587 } else { // snap to vert
2588 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2589 }
2590 }
2591 } else { // move freely
2592 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2593 (*p)[NR::X] - n->pos[NR::X],
2594 (*p)[NR::Y] - n->pos[NR::Y],
2595 (state & GDK_SHIFT_MASK) == 0);
2596 }
2598 n->subpath->nodepath->desktop->scroll_to_point(p);
2600 return TRUE;
2601 }
2603 /**
2604 * Node handle clicked callback.
2605 */
2606 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data)
2607 {
2608 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2610 if (state & GDK_CONTROL_MASK) { // "delete" handle
2611 if (n->p.knot == knot) {
2612 n->p.pos = n->pos;
2613 } else if (n->n.knot == knot) {
2614 n->n.pos = n->pos;
2615 }
2616 sp_node_ensure_ctrls(n);
2617 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2618 update_repr(nodepath);
2619 sp_nodepath_update_statusbar(nodepath);
2621 } else { // just select or add to selection, depending in Shift
2622 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2623 }
2624 }
2626 /**
2627 * Node handle grabbed callback.
2628 */
2629 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data)
2630 {
2631 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2633 if (!n->selected) {
2634 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2635 }
2637 // remember the origin of the control
2638 if (n->p.knot == knot) {
2639 n->p.origin = n->p.pos - n->pos;
2640 } else if (n->n.knot == knot) {
2641 n->n.origin = n->n.pos - n->pos;
2642 } else {
2643 g_assert_not_reached();
2644 }
2646 }
2648 /**
2649 * Node handle ungrabbed callback.
2650 */
2651 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data)
2652 {
2653 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2655 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2656 if (n->p.knot == knot) {
2657 n->p.origin.a = 0;
2658 sp_knot_set_position(knot, &n->p.pos, state);
2659 } else if (n->n.knot == knot) {
2660 n->n.origin.a = 0;
2661 sp_knot_set_position(knot, &n->n.pos, state);
2662 } else {
2663 g_assert_not_reached();
2664 }
2666 update_repr(n->subpath->nodepath);
2667 }
2669 /**
2670 * Node handle "request" signal callback.
2671 */
2672 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2673 {
2674 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2676 Inkscape::NodePath::NodeSide *me, *opposite;
2677 gint which;
2678 if (n->p.knot == knot) {
2679 me = &n->p;
2680 opposite = &n->n;
2681 which = -1;
2682 } else if (n->n.knot == knot) {
2683 me = &n->n;
2684 opposite = &n->p;
2685 which = 1;
2686 } else {
2687 me = opposite = NULL;
2688 which = 0;
2689 g_assert_not_reached();
2690 }
2692 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2694 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2696 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2697 /* We are smooth node adjacent with line */
2698 NR::Point const delta = *p - n->pos;
2699 NR::Coord const len = NR::L2(delta);
2700 Inkscape::NodePath::Node *othernode = opposite->other;
2701 NR::Point const ndelta = n->pos - othernode->pos;
2702 NR::Coord const linelen = NR::L2(ndelta);
2703 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2704 NR::Coord const scal = dot(delta, ndelta) / linelen;
2705 (*p) = n->pos + (scal / linelen) * ndelta;
2706 }
2707 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2708 } else {
2709 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2710 }
2712 sp_node_adjust_knot(n, -which);
2714 return FALSE;
2715 }
2717 /**
2718 * Node handle moved callback.
2719 */
2720 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2721 {
2722 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2724 Inkscape::NodePath::NodeSide *me;
2725 Inkscape::NodePath::NodeSide *other;
2726 if (n->p.knot == knot) {
2727 me = &n->p;
2728 other = &n->n;
2729 } else if (n->n.knot == knot) {
2730 me = &n->n;
2731 other = &n->p;
2732 } else {
2733 me = NULL;
2734 other = NULL;
2735 g_assert_not_reached();
2736 }
2738 // calculate radial coordinates of the grabbed control, other control, and the mouse point
2739 Radial rme(me->pos - n->pos);
2740 Radial rother(other->pos - n->pos);
2741 Radial rnew(*p - n->pos);
2743 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2744 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2745 /* 0 interpreted as "no snapping". */
2747 // The closest PI/snaps angle, starting from zero.
2748 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2749 if (me->origin.a == HUGE_VAL) {
2750 // ortho doesn't exist: original control was zero length.
2751 rnew.a = a_snapped;
2752 } else {
2753 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2754 * its opposite and perpendiculars). */
2755 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2757 // Snap to the closest.
2758 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2759 ? a_snapped
2760 : a_ortho );
2761 }
2762 }
2764 if (state & GDK_MOD1_MASK) {
2765 // lock handle length
2766 rnew.r = me->origin.r;
2767 }
2769 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2770 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2771 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2772 rother.a += rnew.a - rme.a;
2773 other->pos = NR::Point(rother) + n->pos;
2774 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2775 sp_knot_set_position(other->knot, &other->pos, 0);
2776 }
2778 me->pos = NR::Point(rnew) + n->pos;
2779 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2781 // this is what sp_knot_set_position does, but without emitting the signal:
2782 // we cannot emit a "moved" signal because we're now processing it
2783 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2785 knot->desktop->set_coordinate_status(me->pos);
2787 update_object(n->subpath->nodepath);
2789 /* status text */
2790 SPDesktop *desktop = n->subpath->nodepath->desktop;
2791 if (!desktop) return;
2792 SPEventContext *ec = desktop->event_context;
2793 if (!ec) return;
2794 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2795 if (!mc) return;
2797 double degrees = 180 / M_PI * rnew.a;
2798 if (degrees > 180) degrees -= 360;
2799 if (degrees < -180) degrees += 360;
2800 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2801 degrees = angle_to_compass (degrees);
2803 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2805 mc->setF(Inkscape::NORMAL_MESSAGE,
2806 _("<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);
2808 g_string_free(length, TRUE);
2809 }
2811 /**
2812 * Node handle event callback.
2813 */
2814 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2815 {
2816 gboolean ret = FALSE;
2817 switch (event->type) {
2818 case GDK_KEY_PRESS:
2819 switch (get_group0_keyval (&event->key)) {
2820 case GDK_space:
2821 if (event->key.state & GDK_BUTTON1_MASK) {
2822 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2823 stamp_repr(nodepath);
2824 ret = TRUE;
2825 }
2826 break;
2827 default:
2828 break;
2829 }
2830 break;
2831 default:
2832 break;
2833 }
2835 return ret;
2836 }
2838 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2839 Radial &rme, Radial &rother, gboolean const both)
2840 {
2841 rme.a += angle;
2842 if ( both
2843 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2844 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2845 {
2846 rother.a += angle;
2847 }
2848 }
2850 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2851 Radial &rme, Radial &rother, gboolean const both)
2852 {
2853 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2855 gdouble r;
2856 if ( both
2857 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2858 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2859 {
2860 r = MAX(rme.r, rother.r);
2861 } else {
2862 r = rme.r;
2863 }
2865 gdouble const weird_angle = atan2(norm_angle, r);
2866 /* Bulia says norm_angle is just the visible distance that the
2867 * object's end must travel on the screen. Left as 'angle' for want of
2868 * a better name.*/
2870 rme.a += weird_angle;
2871 if ( both
2872 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2873 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2874 {
2875 rother.a += weird_angle;
2876 }
2877 }
2879 /**
2880 * Rotate one node.
2881 */
2882 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2883 {
2884 Inkscape::NodePath::NodeSide *me, *other;
2885 bool both = false;
2887 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2888 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2890 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2891 me = &(n->p);
2892 other = &(n->n);
2893 } else if (!n->p.other) {
2894 me = &(n->n);
2895 other = &(n->p);
2896 } else {
2897 if (which > 0) { // right handle
2898 if (xn > xp) {
2899 me = &(n->n);
2900 other = &(n->p);
2901 } else {
2902 me = &(n->p);
2903 other = &(n->n);
2904 }
2905 } else if (which < 0){ // left handle
2906 if (xn <= xp) {
2907 me = &(n->n);
2908 other = &(n->p);
2909 } else {
2910 me = &(n->p);
2911 other = &(n->n);
2912 }
2913 } else { // both handles
2914 me = &(n->n);
2915 other = &(n->p);
2916 both = true;
2917 }
2918 }
2920 Radial rme(me->pos - n->pos);
2921 Radial rother(other->pos - n->pos);
2923 if (screen) {
2924 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2925 } else {
2926 node_rotate_one_internal (*n, angle, rme, rother, both);
2927 }
2929 me->pos = n->pos + NR::Point(rme);
2931 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
2932 other->pos = n->pos + NR::Point(rother);
2933 }
2935 sp_node_ensure_ctrls(n);
2936 }
2938 /**
2939 * Rotate selected nodes.
2940 */
2941 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
2942 {
2943 if (!nodepath || !nodepath->selected) return;
2945 if (g_list_length(nodepath->selected) == 1) {
2946 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
2947 node_rotate_one (n, angle, which, screen);
2948 } else {
2949 // rotate as an object:
2951 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
2952 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
2953 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2954 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2955 box.expandTo (n->pos); // contain all selected nodes
2956 }
2958 gdouble rot;
2959 if (screen) {
2960 gdouble const zoom = nodepath->desktop->current_zoom();
2961 gdouble const zmove = angle / zoom;
2962 gdouble const r = NR::L2(box.max() - box.midpoint());
2963 rot = atan2(zmove, r);
2964 } else {
2965 rot = angle;
2966 }
2968 NR::Matrix t =
2969 NR::Matrix (NR::translate(-box.midpoint())) *
2970 NR::Matrix (NR::rotate(rot)) *
2971 NR::Matrix (NR::translate(box.midpoint()));
2973 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2974 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2975 n->pos *= t;
2976 n->n.pos *= t;
2977 n->p.pos *= t;
2978 sp_node_ensure_ctrls(n);
2979 }
2980 }
2982 update_object(nodepath);
2983 /// \todo fixme: use _keyed
2984 update_repr(nodepath);
2985 }
2987 /**
2988 * Scale one node.
2989 */
2990 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
2991 {
2992 bool both = false;
2993 Inkscape::NodePath::NodeSide *me, *other;
2995 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2996 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2998 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2999 me = &(n->p);
3000 other = &(n->n);
3001 n->code = NR_CURVETO;
3002 } else if (!n->p.other) {
3003 me = &(n->n);
3004 other = &(n->p);
3005 if (n->n.other)
3006 n->n.other->code = NR_CURVETO;
3007 } else {
3008 if (which > 0) { // right handle
3009 if (xn > xp) {
3010 me = &(n->n);
3011 other = &(n->p);
3012 if (n->n.other)
3013 n->n.other->code = NR_CURVETO;
3014 } else {
3015 me = &(n->p);
3016 other = &(n->n);
3017 n->code = NR_CURVETO;
3018 }
3019 } else if (which < 0){ // left handle
3020 if (xn <= xp) {
3021 me = &(n->n);
3022 other = &(n->p);
3023 if (n->n.other)
3024 n->n.other->code = NR_CURVETO;
3025 } else {
3026 me = &(n->p);
3027 other = &(n->n);
3028 n->code = NR_CURVETO;
3029 }
3030 } else { // both handles
3031 me = &(n->n);
3032 other = &(n->p);
3033 both = true;
3034 n->code = NR_CURVETO;
3035 if (n->n.other)
3036 n->n.other->code = NR_CURVETO;
3037 }
3038 }
3040 Radial rme(me->pos - n->pos);
3041 Radial rother(other->pos - n->pos);
3043 rme.r += grow;
3044 if (rme.r < 0) rme.r = 0;
3045 if (rme.a == HUGE_VAL) {
3046 if (me->other) { // if direction is unknown, initialize it towards the next node
3047 Radial rme_next(me->other->pos - n->pos);
3048 rme.a = rme_next.a;
3049 } else { // if there's no next, initialize to 0
3050 rme.a = 0;
3051 }
3052 }
3053 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3054 rother.r += grow;
3055 if (rother.r < 0) rother.r = 0;
3056 if (rother.a == HUGE_VAL) {
3057 rother.a = rme.a + M_PI;
3058 }
3059 }
3061 me->pos = n->pos + NR::Point(rme);
3063 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3064 other->pos = n->pos + NR::Point(rother);
3065 }
3067 sp_node_ensure_ctrls(n);
3068 }
3070 /**
3071 * Scale selected nodes.
3072 */
3073 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3074 {
3075 if (!nodepath || !nodepath->selected) return;
3077 if (g_list_length(nodepath->selected) == 1) {
3078 // scale handles of the single selected node
3079 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3080 node_scale_one (n, grow, which);
3081 } else {
3082 // scale nodes as an "object":
3084 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3085 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3086 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3087 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3088 box.expandTo (n->pos); // contain all selected nodes
3089 }
3091 double scale = (box.maxExtent() + grow)/box.maxExtent();
3093 NR::Matrix t =
3094 NR::Matrix (NR::translate(-box.midpoint())) *
3095 NR::Matrix (NR::scale(scale, scale)) *
3096 NR::Matrix (NR::translate(box.midpoint()));
3098 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3099 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3100 n->pos *= t;
3101 n->n.pos *= t;
3102 n->p.pos *= t;
3103 sp_node_ensure_ctrls(n);
3104 }
3105 }
3107 update_object(nodepath);
3108 /// \todo fixme: use _keyed
3109 update_repr(nodepath);
3110 }
3112 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3113 {
3114 if (!nodepath) return;
3115 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3116 }
3118 /**
3119 * Flip selected nodes horizontally/vertically.
3120 */
3121 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3122 {
3123 if (!nodepath || !nodepath->selected) return;
3125 if (g_list_length(nodepath->selected) == 1) {
3126 // flip handles of the single selected node
3127 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3128 double temp = n->p.pos[axis];
3129 n->p.pos[axis] = n->n.pos[axis];
3130 n->n.pos[axis] = temp;
3131 sp_node_ensure_ctrls(n);
3132 } else {
3133 // scale nodes as an "object":
3135 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3136 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3137 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3138 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3139 box.expandTo (n->pos); // contain all selected nodes
3140 }
3142 NR::Matrix t =
3143 NR::Matrix (NR::translate(-box.midpoint())) *
3144 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3145 NR::Matrix (NR::translate(box.midpoint()));
3147 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3148 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3149 n->pos *= t;
3150 n->n.pos *= t;
3151 n->p.pos *= t;
3152 sp_node_ensure_ctrls(n);
3153 }
3154 }
3156 update_object(nodepath);
3157 /// \todo fixme: use _keyed
3158 update_repr(nodepath);
3159 }
3161 //-----------------------------------------------
3162 /**
3163 * Return new subpath under given nodepath.
3164 */
3165 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3166 {
3167 g_assert(nodepath);
3168 g_assert(nodepath->desktop);
3170 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3172 s->nodepath = nodepath;
3173 s->closed = FALSE;
3174 s->nodes = NULL;
3175 s->first = NULL;
3176 s->last = NULL;
3178 // do not use prepend here because:
3179 // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with
3180 // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not
3181 // the i-th node in the nodepath (only if there are multiple subpaths)
3182 // note that the problem only arise when called from subpath_from_bpath(), since for all the other
3183 // cases, the repr is updated after the call to sp_nodepath_subpath_new()
3184 nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s);
3186 return s;
3187 }
3189 /**
3190 * Destroy nodes in subpath, then subpath itself.
3191 */
3192 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3193 {
3194 g_assert(subpath);
3195 g_assert(subpath->nodepath);
3196 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3198 while (subpath->nodes) {
3199 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3200 }
3202 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3204 g_free(subpath);
3205 }
3207 /**
3208 * Link head to tail in subpath.
3209 */
3210 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3211 {
3212 g_assert(!sp->closed);
3213 g_assert(sp->last != sp->first);
3214 g_assert(sp->first->code == NR_MOVETO);
3216 sp->closed = TRUE;
3218 //Link the head to the tail
3219 sp->first->p.other = sp->last;
3220 sp->last->n.other = sp->first;
3221 sp->last->n.pos = sp->first->n.pos;
3222 sp->first = sp->last;
3224 //Remove the extra end node
3225 sp_nodepath_node_destroy(sp->last->n.other);
3226 }
3228 /**
3229 * Open closed (loopy) subpath at node.
3230 */
3231 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3232 {
3233 g_assert(sp->closed);
3234 g_assert(n->subpath == sp);
3235 g_assert(sp->first == sp->last);
3237 /* We create new startpoint, current node will become last one */
3239 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3240 &n->pos, &n->pos, &n->n.pos);
3243 sp->closed = FALSE;
3245 //Unlink to make a head and tail
3246 sp->first = new_path;
3247 sp->last = n;
3248 n->n.other = NULL;
3249 new_path->p.other = NULL;
3250 }
3252 /**
3253 * Returns area in triangle given by points; may be negative.
3254 */
3255 inline double
3256 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3257 {
3258 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]);
3259 }
3261 /**
3262 * Return new node in subpath with given properties.
3263 * \param pos Position of node.
3264 * \param ppos Handle position in previous direction
3265 * \param npos Handle position in previous direction
3266 */
3267 Inkscape::NodePath::Node *
3268 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)
3269 {
3270 g_assert(sp);
3271 g_assert(sp->nodepath);
3272 g_assert(sp->nodepath->desktop);
3274 if (nodechunk == NULL)
3275 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3277 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3279 n->subpath = sp;
3281 if (type != Inkscape::NodePath::NODE_NONE) {
3282 // use the type from sodipodi:nodetypes
3283 n->type = type;
3284 } else {
3285 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3286 // points are (almost) collinear
3287 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3288 // endnode, or a node with a retracted handle
3289 n->type = Inkscape::NodePath::NODE_CUSP;
3290 } else {
3291 n->type = Inkscape::NodePath::NODE_SMOOTH;
3292 }
3293 } else {
3294 n->type = Inkscape::NodePath::NODE_CUSP;
3295 }
3296 }
3298 n->code = code;
3299 n->selected = FALSE;
3300 n->pos = *pos;
3301 n->p.pos = *ppos;
3302 n->n.pos = *npos;
3304 n->dragging_out = NULL;
3306 Inkscape::NodePath::Node *prev;
3307 if (next) {
3308 g_assert(g_list_find(sp->nodes, next));
3309 prev = next->p.other;
3310 } else {
3311 prev = sp->last;
3312 }
3314 if (prev)
3315 prev->n.other = n;
3316 else
3317 sp->first = n;
3319 if (next)
3320 next->p.other = n;
3321 else
3322 sp->last = n;
3324 n->p.other = prev;
3325 n->n.other = next;
3327 n->knot = sp_knot_new(sp->nodepath->desktop);
3328 sp_knot_set_position(n->knot, pos, 0);
3329 g_object_set(G_OBJECT(n->knot),
3330 "anchor", GTK_ANCHOR_CENTER,
3331 "fill", NODE_FILL,
3332 "fill_mouseover", NODE_FILL_HI,
3333 "stroke", NODE_STROKE,
3334 "stroke_mouseover", NODE_STROKE_HI,
3335 "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"),
3336 NULL);
3337 if (n->type == Inkscape::NodePath::NODE_CUSP)
3338 g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_DIAMOND, "size", 9, NULL);
3339 else
3340 g_object_set(G_OBJECT(n->knot), "shape", SP_KNOT_SHAPE_SQUARE, "size", 7, NULL);
3342 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3343 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3344 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3345 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3346 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3347 sp_knot_show(n->knot);
3349 n->p.knot = sp_knot_new(sp->nodepath->desktop);
3350 sp_knot_set_position(n->p.knot, ppos, 0);
3351 g_object_set(G_OBJECT(n->p.knot),
3352 "shape", SP_KNOT_SHAPE_CIRCLE,
3353 "size", 7,
3354 "anchor", GTK_ANCHOR_CENTER,
3355 "fill", KNOT_FILL,
3356 "fill_mouseover", KNOT_FILL_HI,
3357 "stroke", KNOT_STROKE,
3358 "stroke_mouseover", KNOT_STROKE_HI,
3359 "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"),
3360 NULL);
3361 g_signal_connect(G_OBJECT(n->p.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n);
3362 g_signal_connect(G_OBJECT(n->p.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n);
3363 g_signal_connect(G_OBJECT(n->p.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n);
3364 g_signal_connect(G_OBJECT(n->p.knot), "request", G_CALLBACK(node_ctrl_request), n);
3365 g_signal_connect(G_OBJECT(n->p.knot), "moved", G_CALLBACK(node_ctrl_moved), n);
3366 g_signal_connect(G_OBJECT(n->p.knot), "event", G_CALLBACK(node_ctrl_event), n);
3368 sp_knot_hide(n->p.knot);
3369 n->p.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop),
3370 SP_TYPE_CTRLLINE, NULL);
3371 sp_canvas_item_hide(n->p.line);
3373 n->n.knot = sp_knot_new(sp->nodepath->desktop);
3374 sp_knot_set_position(n->n.knot, npos, 0);
3375 g_object_set(G_OBJECT(n->n.knot),
3376 "shape", SP_KNOT_SHAPE_CIRCLE,
3377 "size", 7,
3378 "anchor", GTK_ANCHOR_CENTER,
3379 "fill", KNOT_FILL,
3380 "fill_mouseover", KNOT_FILL_HI,
3381 "stroke", KNOT_STROKE,
3382 "stroke_mouseover", KNOT_STROKE_HI,
3383 "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"),
3384 NULL);
3385 g_signal_connect(G_OBJECT(n->n.knot), "clicked", G_CALLBACK(node_ctrl_clicked), n);
3386 g_signal_connect(G_OBJECT(n->n.knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), n);
3387 g_signal_connect(G_OBJECT(n->n.knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), n);
3388 g_signal_connect(G_OBJECT(n->n.knot), "request", G_CALLBACK(node_ctrl_request), n);
3389 g_signal_connect(G_OBJECT(n->n.knot), "moved", G_CALLBACK(node_ctrl_moved), n);
3390 g_signal_connect(G_OBJECT(n->n.knot), "event", G_CALLBACK(node_ctrl_event), n);
3391 sp_knot_hide(n->n.knot);
3392 n->n.line = sp_canvas_item_new(SP_DT_CONTROLS(n->subpath->nodepath->desktop),
3393 SP_TYPE_CTRLLINE, NULL);
3394 sp_canvas_item_hide(n->n.line);
3396 sp->nodes = g_list_prepend(sp->nodes, n);
3398 return n;
3399 }
3401 /**
3402 * Destroy node and its knots, link neighbors in subpath.
3403 */
3404 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3405 {
3406 g_assert(node);
3407 g_assert(node->subpath);
3408 g_assert(SP_IS_KNOT(node->knot));
3409 g_assert(SP_IS_KNOT(node->p.knot));
3410 g_assert(SP_IS_KNOT(node->n.knot));
3411 g_assert(g_list_find(node->subpath->nodes, node));
3413 Inkscape::NodePath::SubPath *sp = node->subpath;
3415 if (node->selected) { // first, deselect
3416 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3417 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3418 }
3420 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3422 g_object_unref(G_OBJECT(node->knot));
3423 g_object_unref(G_OBJECT(node->p.knot));
3424 g_object_unref(G_OBJECT(node->n.knot));
3426 gtk_object_destroy(GTK_OBJECT(node->p.line));
3427 gtk_object_destroy(GTK_OBJECT(node->n.line));
3429 if (sp->nodes) { // there are others nodes on the subpath
3430 if (sp->closed) {
3431 if (sp->first == node) {
3432 g_assert(sp->last == node);
3433 sp->first = node->n.other;
3434 sp->last = sp->first;
3435 }
3436 node->p.other->n.other = node->n.other;
3437 node->n.other->p.other = node->p.other;
3438 } else {
3439 if (sp->first == node) {
3440 sp->first = node->n.other;
3441 sp->first->code = NR_MOVETO;
3442 }
3443 if (sp->last == node) sp->last = node->p.other;
3444 if (node->p.other) node->p.other->n.other = node->n.other;
3445 if (node->n.other) node->n.other->p.other = node->p.other;
3446 }
3447 } else { // this was the last node on subpath
3448 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3449 }
3451 g_mem_chunk_free(nodechunk, node);
3452 }
3454 /**
3455 * Returns one of the node's two knots (node sides).
3456 * \param which Indicates which side.
3457 * \return Pointer to previous node side if which==-1, next if which==1.
3458 */
3459 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3460 {
3461 g_assert(node);
3463 switch (which) {
3464 case -1:
3465 return &node->p;
3466 case 1:
3467 return &node->n;
3468 default:
3469 break;
3470 }
3472 g_assert_not_reached();
3474 return NULL;
3475 }
3477 /**
3478 * Return knot on other side of node.
3479 */
3480 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3481 {
3482 g_assert(node);
3484 if (me == &node->p) return &node->n;
3485 if (me == &node->n) return &node->p;
3487 g_assert_not_reached();
3489 return NULL;
3490 }
3492 /**
3493 * Return NRPathcode on this knot's side of the node.
3494 */
3495 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3496 {
3497 g_assert(node);
3499 if (me == &node->p) {
3500 if (node->p.other) return (NRPathcode)node->code;
3501 return NR_MOVETO;
3502 }
3504 if (me == &node->n) {
3505 if (node->n.other) return (NRPathcode)node->n.other->code;
3506 return NR_MOVETO;
3507 }
3509 g_assert_not_reached();
3511 return NR_END;
3512 }
3514 /**
3515 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3516 */
3517 Inkscape::NodePath::Node *
3518 sp_nodepath_get_node_by_index(int index)
3519 {
3520 Inkscape::NodePath::Node *e = NULL;
3522 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3523 if (!nodepath) {
3524 return e;
3525 }
3527 //find segment
3528 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3530 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3531 int n = g_list_length(sp->nodes);
3532 if (sp->closed) {
3533 n++;
3534 }
3536 //if the piece belongs to this subpath grab it
3537 //otherwise move onto the next subpath
3538 if (index < n) {
3539 e = sp->first;
3540 for (int i = 0; i < index; ++i) {
3541 e = e->n.other;
3542 }
3543 break;
3544 } else {
3545 if (sp->closed) {
3546 index -= (n+1);
3547 } else {
3548 index -= n;
3549 }
3550 }
3551 }
3553 return e;
3554 }
3556 /**
3557 * Returns plain text meaning of node type.
3558 */
3559 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3560 {
3561 unsigned retracted = 0;
3562 bool endnode = false;
3564 for (int which = -1; which <= 1; which += 2) {
3565 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3566 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3567 retracted ++;
3568 if (!side->other)
3569 endnode = true;
3570 }
3572 if (retracted == 0) {
3573 if (endnode) {
3574 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3575 return _("end node");
3576 } else {
3577 switch (node->type) {
3578 case Inkscape::NodePath::NODE_CUSP:
3579 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3580 return _("cusp");
3581 case Inkscape::NodePath::NODE_SMOOTH:
3582 // TRANSLATORS: "smooth" is an adjective here
3583 return _("smooth");
3584 case Inkscape::NodePath::NODE_SYMM:
3585 return _("symmetric");
3586 }
3587 }
3588 } else if (retracted == 1) {
3589 if (endnode) {
3590 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3591 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3592 } else {
3593 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3594 }
3595 } else {
3596 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3597 }
3599 return NULL;
3600 }
3602 /**
3603 * Handles content of statusbar as long as node tool is active.
3604 */
3605 void
3606 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3607 {
3608 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3609 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3611 gint total = 0;
3612 gint selected = 0;
3613 SPDesktop *desktop = NULL;
3615 if (nodepath) {
3616 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3617 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3618 total += g_list_length(subpath->nodes);
3619 }
3620 selected = g_list_length(nodepath->selected);
3621 desktop = nodepath->desktop;
3622 } else {
3623 desktop = SP_ACTIVE_DESKTOP;
3624 }
3626 SPEventContext *ec = desktop->event_context;
3627 if (!ec) return;
3628 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3629 if (!mc) return;
3631 if (selected == 0) {
3632 Inkscape::Selection *sel = desktop->selection;
3633 if (!sel || sel->isEmpty()) {
3634 mc->setF(Inkscape::NORMAL_MESSAGE,
3635 _("Select a single object to edit its nodes or handles."));
3636 } else {
3637 if (nodepath) {
3638 mc->setF(Inkscape::NORMAL_MESSAGE,
3639 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.",
3640 "<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.",
3641 total),
3642 total);
3643 } else {
3644 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3645 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3646 } else {
3647 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3648 }
3649 }
3650 }
3651 } else if (nodepath && selected == 1) {
3652 mc->setF(Inkscape::NORMAL_MESSAGE,
3653 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3654 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3655 total),
3656 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3657 } else {
3658 mc->setF(Inkscape::NORMAL_MESSAGE,
3659 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3660 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3661 total),
3662 selected, total, when_selected);
3663 }
3664 }
3667 /*
3668 Local Variables:
3669 mode:c++
3670 c-file-style:"stroustrup"
3671 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3672 indent-tabs-mode:nil
3673 fill-column:99
3674 End:
3675 */
3676 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :