af73a37c2a1fe84130faf7f4028ad8ed7f32ceb5
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_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
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 /* Adjust handle placement, if the node or the other handle is moved */
98 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
99 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
101 /* Node event callbacks */
102 static void node_clicked(SPKnot *knot, guint state, gpointer data);
103 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
104 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
105 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
107 /* Handle event callbacks */
108 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
109 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
110 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
111 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
113 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
115 /* Constructors and destructors */
117 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
118 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
119 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
120 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
121 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
122 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
123 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
125 /* Helpers */
127 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
128 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
129 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
131 // active_node indicates mouseover node
132 static Inkscape::NodePath::Node *active_node = NULL;
134 /**
135 * \brief Creates new nodepath from item
136 */
137 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
138 {
139 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
141 /** \todo
142 * FIXME: remove this. We don't want to edit paths inside flowtext.
143 * Instead we will build our flowtext with cloned paths, so that the
144 * real paths are outside the flowtext and thus editable as usual.
145 */
146 if (SP_IS_FLOWTEXT(item)) {
147 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
148 if SP_IS_FLOWREGION(child) {
149 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
150 if (grandchild && SP_IS_PATH(grandchild)) {
151 item = SP_ITEM(grandchild);
152 break;
153 }
154 }
155 }
156 }
158 if (!SP_IS_PATH(item))
159 return NULL;
160 SPPath *path = SP_PATH(item);
161 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
162 if (curve == NULL)
163 return NULL;
165 NArtBpath *bpath = sp_curve_first_bpath(curve);
166 gint length = curve->end;
167 if (length == 0)
168 return NULL; // prevent crash for one-node paths
170 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
171 gchar *typestr = parse_nodetypes(nodetypes, length);
173 //Create new nodepath
174 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
175 if (!np)
176 return NULL;
178 // Set defaults
179 np->desktop = desktop;
180 np->path = path;
181 np->subpaths = NULL;
182 np->selected = NULL;
183 np->nodeContext = NULL; //Let the context that makes this set it
184 np->livarot_path = NULL;
186 // we need to update item's transform from the repr here,
187 // because they may be out of sync when we respond
188 // to a change in repr by regenerating nodepath --bb
189 sp_object_read_attr(SP_OBJECT(item), "transform");
191 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
192 np->d2i = np->i2d.inverse();
193 np->repr = repr;
195 // create the subpath(s) from the bpath
196 NArtBpath *b = bpath;
197 while (b->code != NR_END) {
198 b = subpath_from_bpath(np, b, typestr + (b - bpath));
199 }
201 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
202 np->subpaths = g_list_reverse(np->subpaths);
204 g_free(typestr);
205 sp_curve_unref(curve);
207 // create the livarot representation from the same item
208 np->livarot_path = Path_for_item(item, true, true);
209 if (np->livarot_path)
210 np->livarot_path->ConvertWithBackData(0.01);
212 return np;
213 }
215 /**
216 * Destroys nodepath's subpaths, then itself, also tell context about it.
217 */
218 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
220 if (!np) //soft fail, like delete
221 return;
223 while (np->subpaths) {
224 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
225 }
227 //Inform the context that made me, if any, that I am gone.
228 if (np->nodeContext)
229 np->nodeContext->nodepath = NULL;
231 g_assert(!np->selected);
233 if (np->livarot_path) {
234 delete np->livarot_path;
235 np->livarot_path = NULL;
236 }
238 np->desktop = NULL;
240 g_free(np);
241 }
244 /**
245 * Return the node count of a given NodeSubPath.
246 */
247 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
248 {
249 if (!subpath)
250 return 0;
251 gint nodeCount = g_list_length(subpath->nodes);
252 return nodeCount;
253 }
255 /**
256 * Return the node count of a given NodePath.
257 */
258 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
259 {
260 if (!np)
261 return 0;
262 gint nodeCount = 0;
263 for (GList *item = np->subpaths ; item ; item=item->next) {
264 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
265 nodeCount += g_list_length(subpath->nodes);
266 }
267 return nodeCount;
268 }
271 /**
272 * Clean up a nodepath after editing.
273 *
274 * Currently we are deleting trivial subpaths.
275 */
276 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
277 {
278 GList *badSubPaths = NULL;
280 //Check all subpaths to be >=2 nodes
281 for (GList *l = nodepath->subpaths; l ; l=l->next) {
282 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
283 if (sp_nodepath_subpath_get_node_count(sp)<2)
284 badSubPaths = g_list_append(badSubPaths, sp);
285 }
287 //Delete them. This second step is because sp_nodepath_subpath_destroy()
288 //also removes the subpath from nodepath->subpaths
289 for (GList *l = badSubPaths; l ; l=l->next) {
290 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
291 sp_nodepath_subpath_destroy(sp);
292 }
294 g_list_free(badSubPaths);
295 }
299 /**
300 * \brief Returns true if the argument nodepath and the d attribute in
301 * its repr do not match.
302 *
303 * This may happen if repr was changed in, e.g., XML editor or by undo.
304 *
305 * \todo
306 * UGLY HACK, think how we can eliminate it. IDEA: try instead a local_change flag in node context?
307 */
308 gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd)
309 {
310 g_assert(np);
312 SPCurve *curve = create_curve(np);
314 gchar *svgpath = sp_svg_write_path(curve->bpath);
316 char const *attr_d = ( newd
317 ? newd
318 : SP_OBJECT(np->path)->repr->attribute("d") );
320 gboolean ret;
321 if (attr_d && svgpath)
322 ret = strcmp(attr_d, svgpath);
323 else
324 ret = TRUE;
326 g_free(svgpath);
327 sp_curve_unref(curve);
329 return ret;
330 }
332 /**
333 * \brief Returns true if the argument nodepath and the sodipodi:nodetypes
334 * attribute in its repr do not match.
335 *
336 * This may happen if repr was changed in, e.g., the XML editor or by undo.
337 * IDEA: try instead a local_change flag in node context?
338 */
339 gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr)
340 {
341 g_assert(np);
342 gchar *typestr = create_typestr(np);
343 char const *attr_typestr = ( newtypestr
344 ? newtypestr
345 : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") );
346 gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr));
348 g_free(typestr);
350 return ret;
351 }
353 /**
354 * Create new nodepath from b, make it subpath of np.
355 * \param t The node type.
356 * \todo Fixme: t should be a proper type, rather than gchar
357 */
358 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
359 {
360 NR::Point ppos, pos, npos;
362 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
364 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
365 bool const closed = (b->code == NR_MOVETO);
367 pos = NR::Point(b->x3, b->y3) * np->i2d;
368 if (b[1].code == NR_CURVETO) {
369 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
370 } else {
371 npos = pos;
372 }
373 Inkscape::NodePath::Node *n;
374 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
375 g_assert(sp->first == n);
376 g_assert(sp->last == n);
378 b++;
379 t++;
380 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
381 pos = NR::Point(b->x3, b->y3) * np->i2d;
382 if (b->code == NR_CURVETO) {
383 ppos = NR::Point(b->x2, b->y2) * np->i2d;
384 } else {
385 ppos = pos;
386 }
387 if (b[1].code == NR_CURVETO) {
388 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
389 } else {
390 npos = pos;
391 }
392 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
393 b++;
394 t++;
395 }
397 if (closed) sp_nodepath_subpath_close(sp);
399 return b;
400 }
402 /**
403 * Convert from sodipodi:nodetypes to new style type string.
404 */
405 static gchar *parse_nodetypes(gchar const *types, gint length)
406 {
407 g_assert(length > 0);
409 gchar *typestr = g_new(gchar, length + 1);
411 gint pos = 0;
413 if (types) {
414 for (gint i = 0; types[i] && ( i < length ); i++) {
415 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
416 if (types[i] != '\0') {
417 switch (types[i]) {
418 case 's':
419 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
420 break;
421 case 'z':
422 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
423 break;
424 case 'c':
425 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
426 break;
427 default:
428 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
429 break;
430 }
431 }
432 }
433 }
435 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
437 return typestr;
438 }
440 /**
441 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
442 * updated but repr is not (for speed). Used during curve and node drag.
443 */
444 static void update_object(Inkscape::NodePath::Path *np)
445 {
446 g_assert(np);
448 SPCurve *curve = create_curve(np);
450 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
452 sp_curve_unref(curve);
453 }
455 /**
456 * Update XML path node with data from path object.
457 */
458 static void update_repr_internal(Inkscape::NodePath::Path *np)
459 {
460 g_assert(np);
462 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
464 SPCurve *curve = create_curve(np);
465 gchar *typestr = create_typestr(np);
466 gchar *svgpath = sp_svg_write_path(curve->bpath);
468 repr->setAttribute("d", svgpath);
469 repr->setAttribute("sodipodi:nodetypes", typestr);
471 g_free(svgpath);
472 g_free(typestr);
473 sp_curve_unref(curve);
474 }
476 /**
477 * Update XML path node with data from path object, commit changes forever.
478 */
479 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
480 {
481 update_repr_internal(np);
482 sp_document_done(SP_DT_DOCUMENT(np->desktop));
484 if (np->livarot_path) {
485 delete np->livarot_path;
486 np->livarot_path = NULL;
487 }
489 if (np->path && SP_IS_ITEM(np->path)) {
490 np->livarot_path = Path_for_item (np->path, true, true);
491 if (np->livarot_path)
492 np->livarot_path->ConvertWithBackData(0.01);
493 }
494 }
496 /**
497 * Update XML path node with data from path object, commit changes with undo.
498 */
499 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
500 {
501 update_repr_internal(np);
502 sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
504 if (np->livarot_path) {
505 delete np->livarot_path;
506 np->livarot_path = NULL;
507 }
509 if (np->path && SP_IS_ITEM(np->path)) {
510 np->livarot_path = Path_for_item (np->path, true, true);
511 if (np->livarot_path)
512 np->livarot_path->ConvertWithBackData(0.01);
513 }
514 }
516 /**
517 * Make duplicate of path, replace corresponding XML node in tree, commit.
518 */
519 static void stamp_repr(Inkscape::NodePath::Path *np)
520 {
521 g_assert(np);
523 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
524 Inkscape::XML::Node *new_repr = old_repr->duplicate();
526 // remember the position of the item
527 gint pos = old_repr->position();
528 // remember parent
529 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
531 SPCurve *curve = create_curve(np);
532 gchar *typestr = create_typestr(np);
534 gchar *svgpath = sp_svg_write_path(curve->bpath);
536 new_repr->setAttribute("d", svgpath);
537 new_repr->setAttribute("sodipodi:nodetypes", typestr);
539 // add the new repr to the parent
540 parent->appendChild(new_repr);
541 // move to the saved position
542 new_repr->setPosition(pos > 0 ? pos : 0);
544 sp_document_done(SP_DT_DOCUMENT(np->desktop));
546 Inkscape::GC::release(new_repr);
547 g_free(svgpath);
548 g_free(typestr);
549 sp_curve_unref(curve);
550 }
552 /**
553 * Create curve from path.
554 */
555 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
556 {
557 SPCurve *curve = sp_curve_new();
559 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
560 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
561 sp_curve_moveto(curve,
562 sp->first->pos * np->d2i);
563 Inkscape::NodePath::Node *n = sp->first->n.other;
564 while (n) {
565 NR::Point const end_pt = n->pos * np->d2i;
566 switch (n->code) {
567 case NR_LINETO:
568 sp_curve_lineto(curve, end_pt);
569 break;
570 case NR_CURVETO:
571 sp_curve_curveto(curve,
572 n->p.other->n.pos * np->d2i,
573 n->p.pos * np->d2i,
574 end_pt);
575 break;
576 default:
577 g_assert_not_reached();
578 break;
579 }
580 if (n != sp->last) {
581 n = n->n.other;
582 } else {
583 n = NULL;
584 }
585 }
586 if (sp->closed) {
587 sp_curve_closepath(curve);
588 }
589 }
591 return curve;
592 }
594 /**
595 * Convert path type string to sodipodi:nodetypes style.
596 */
597 static gchar *create_typestr(Inkscape::NodePath::Path *np)
598 {
599 gchar *typestr = g_new(gchar, 32);
600 gint len = 32;
601 gint pos = 0;
603 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
604 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
606 if (pos >= len) {
607 typestr = g_renew(gchar, typestr, len + 32);
608 len += 32;
609 }
611 typestr[pos++] = 'c';
613 Inkscape::NodePath::Node *n;
614 n = sp->first->n.other;
615 while (n) {
616 gchar code;
618 switch (n->type) {
619 case Inkscape::NodePath::NODE_CUSP:
620 code = 'c';
621 break;
622 case Inkscape::NodePath::NODE_SMOOTH:
623 code = 's';
624 break;
625 case Inkscape::NodePath::NODE_SYMM:
626 code = 'z';
627 break;
628 default:
629 g_assert_not_reached();
630 code = '\0';
631 break;
632 }
634 if (pos >= len) {
635 typestr = g_renew(gchar, typestr, len + 32);
636 len += 32;
637 }
639 typestr[pos++] = code;
641 if (n != sp->last) {
642 n = n->n.other;
643 } else {
644 n = NULL;
645 }
646 }
647 }
649 if (pos >= len) {
650 typestr = g_renew(gchar, typestr, len + 1);
651 len += 1;
652 }
654 typestr[pos++] = '\0';
656 return typestr;
657 }
659 /**
660 * Returns current path in context.
661 */
662 static Inkscape::NodePath::Path *sp_nodepath_current()
663 {
664 if (!SP_ACTIVE_DESKTOP) {
665 return NULL;
666 }
668 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
670 if (!SP_IS_NODE_CONTEXT(event_context)) {
671 return NULL;
672 }
674 return SP_NODE_CONTEXT(event_context)->nodepath;
675 }
679 /**
680 \brief Fills node and handle positions for three nodes, splitting line
681 marked by end at distance t.
682 */
683 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
684 {
685 g_assert(new_path != NULL);
686 g_assert(end != NULL);
688 g_assert(end->p.other == new_path);
689 Inkscape::NodePath::Node *start = new_path->p.other;
690 g_assert(start);
692 if (end->code == NR_LINETO) {
693 new_path->type =Inkscape::NodePath::NODE_CUSP;
694 new_path->code = NR_LINETO;
695 new_path->pos = (t * start->pos + (1 - t) * end->pos);
696 } else {
697 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
698 new_path->code = NR_CURVETO;
699 gdouble s = 1 - t;
700 for (int dim = 0; dim < 2; dim++) {
701 NR::Coord const f000 = start->pos[dim];
702 NR::Coord const f001 = start->n.pos[dim];
703 NR::Coord const f011 = end->p.pos[dim];
704 NR::Coord const f111 = end->pos[dim];
705 NR::Coord const f00t = s * f000 + t * f001;
706 NR::Coord const f01t = s * f001 + t * f011;
707 NR::Coord const f11t = s * f011 + t * f111;
708 NR::Coord const f0tt = s * f00t + t * f01t;
709 NR::Coord const f1tt = s * f01t + t * f11t;
710 NR::Coord const fttt = s * f0tt + t * f1tt;
711 start->n.pos[dim] = f00t;
712 new_path->p.pos[dim] = f0tt;
713 new_path->pos[dim] = fttt;
714 new_path->n.pos[dim] = f1tt;
715 end->p.pos[dim] = f11t;
716 }
717 }
718 }
720 /**
721 * Adds new node on direct line between two nodes, activates handles of all
722 * three nodes.
723 */
724 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
725 {
726 g_assert(end);
727 g_assert(end->subpath);
728 g_assert(g_list_find(end->subpath->nodes, end));
730 Inkscape::NodePath::Node *start = end->p.other;
731 g_assert( start->n.other == end );
732 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
733 end,
734 Inkscape::NodePath::NODE_SMOOTH,
735 (NRPathcode)end->code,
736 &start->pos, &start->pos, &start->n.pos);
737 sp_nodepath_line_midpoint(newnode, end, t);
739 sp_node_update_handles(start);
740 sp_node_update_handles(newnode);
741 sp_node_update_handles(end);
743 return newnode;
744 }
746 /**
747 \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
748 */
749 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
750 {
751 g_assert(node);
752 g_assert(node->subpath);
753 g_assert(g_list_find(node->subpath->nodes, node));
755 Inkscape::NodePath::SubPath *sp = node->subpath;
756 Inkscape::NodePath::Path *np = sp->nodepath;
758 if (sp->closed) {
759 sp_nodepath_subpath_open(sp, node);
760 return sp->first;
761 } else {
762 // no break for end nodes
763 if (node == sp->first) return NULL;
764 if (node == sp->last ) return NULL;
766 // create a new subpath
767 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
769 // duplicate the break node as start of the new subpath
770 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
772 while (node->n.other) { // copy the remaining nodes into the new subpath
773 Inkscape::NodePath::Node *n = node->n.other;
774 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);
775 if (n->selected) {
776 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
777 }
778 sp_nodepath_node_destroy(n); // remove the point on the original subpath
779 }
781 return newnode;
782 }
783 }
785 /**
786 * Duplicate node and connect to neighbours.
787 */
788 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
789 {
790 g_assert(node);
791 g_assert(node->subpath);
792 g_assert(g_list_find(node->subpath->nodes, node));
794 Inkscape::NodePath::SubPath *sp = node->subpath;
796 NRPathcode code = (NRPathcode) node->code;
797 if (code == NR_MOVETO) { // if node is the endnode,
798 node->code = NR_LINETO; // new one is inserted before it, so change that to line
799 }
801 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
803 if (!node->n.other || !node->p.other) // if node is an endnode, select it
804 return node;
805 else
806 return newnode; // otherwise select the newly created node
807 }
809 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
810 {
811 node->p.pos = (node->pos + (node->pos - node->n.pos));
812 }
814 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
815 {
816 node->n.pos = (node->pos + (node->pos - node->p.pos));
817 }
819 /**
820 * Change line type at node, with side effects on neighbours.
821 */
822 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
823 {
824 g_assert(end);
825 g_assert(end->subpath);
826 g_assert(end->p.other);
828 if (end->code == static_cast< guint > ( code ) )
829 return;
831 Inkscape::NodePath::Node *start = end->p.other;
833 end->code = code;
835 if (code == NR_LINETO) {
836 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
837 if (end->n.other) {
838 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
839 }
840 sp_node_adjust_handle(start, -1);
841 sp_node_adjust_handle(end, 1);
842 } else {
843 NR::Point delta = end->pos - start->pos;
844 start->n.pos = start->pos + delta / 3;
845 end->p.pos = end->pos - delta / 3;
846 sp_node_adjust_handle(start, 1);
847 sp_node_adjust_handle(end, -1);
848 }
850 sp_node_update_handles(start);
851 sp_node_update_handles(end);
852 }
854 /**
855 * Change node type, and its handles accordingly.
856 */
857 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
858 {
859 g_assert(node);
860 g_assert(node->subpath);
862 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
863 return node;
865 if ((node->p.other != NULL) && (node->n.other != NULL)) {
866 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
867 type =Inkscape::NodePath::NODE_CUSP;
868 }
869 }
871 node->type = type;
873 if (node->type == Inkscape::NodePath::NODE_CUSP) {
874 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
875 node->knot->setSize (node->selected? 11 : 9);
876 sp_knot_update_ctrl(node->knot);
877 } else {
878 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
879 node->knot->setSize (node->selected? 9 : 7);
880 sp_knot_update_ctrl(node->knot);
881 }
883 // if one of handles is mouseovered, preserve its position
884 if (SP_KNOT_IS_MOSEOVER(node->p.knot)) {
885 sp_node_adjust_handle(node, 1);
886 } else if (SP_KNOT_IS_MOSEOVER(node->n.knot)) {
887 sp_node_adjust_handle(node, -1);
888 } else {
889 sp_node_adjust_handles(node);
890 }
892 sp_node_update_handles(node);
894 sp_nodepath_update_statusbar(node->subpath->nodepath);
896 return node;
897 }
899 /**
900 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
901 * adjacent segments from lines to curves.
902 */
903 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
904 {
905 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
906 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
907 // convert adjacent segment BEFORE to curve
908 node->code = NR_CURVETO;
909 NR::Point delta;
910 if (node->n.other != NULL)
911 delta = node->n.other->pos - node->p.other->pos;
912 else
913 delta = node->pos - node->p.other->pos;
914 node->p.pos = node->pos - delta / 4;
915 sp_node_update_handles(node);
916 }
918 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
919 // convert adjacent segment AFTER to curve
920 node->n.other->code = NR_CURVETO;
921 NR::Point delta;
922 if (node->p.other != NULL)
923 delta = node->p.other->pos - node->n.other->pos;
924 else
925 delta = node->pos - node->n.other->pos;
926 node->n.pos = node->pos - delta / 4;
927 sp_node_update_handles(node);
928 }
929 }
931 sp_nodepath_set_node_type (node, type);
932 }
934 /**
935 * Move node to point, and adjust its and neighbouring handles.
936 */
937 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
938 {
939 NR::Point delta = p - node->pos;
940 node->pos = p;
942 node->p.pos += delta;
943 node->n.pos += delta;
945 if (node->p.other) {
946 if (node->code == NR_LINETO) {
947 sp_node_adjust_handle(node, 1);
948 sp_node_adjust_handle(node->p.other, -1);
949 }
950 }
951 if (node->n.other) {
952 if (node->n.other->code == NR_LINETO) {
953 sp_node_adjust_handle(node, -1);
954 sp_node_adjust_handle(node->n.other, 1);
955 }
956 }
958 // this function is only called from batch movers that will update display at the end
959 // themselves, so here we just move all the knots without emitting move signals, for speed
960 sp_node_update_handles(node, false);
961 }
963 /**
964 * Call sp_node_moveto() for node selection and handle possible snapping.
965 */
966 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
967 bool const snap = true)
968 {
969 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
970 NR::Point delta(dx, dy);
971 NR::Point best_pt = delta;
973 if (snap) {
974 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
975 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
976 NR::Point p = n->pos + delta;
977 for (int dim = 0; dim < 2; dim++) {
978 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
979 Inkscape::Snapper::SNAP_POINT, p,
980 NR::Dim2(dim), nodepath->path);
981 if (dist < best[dim]) {
982 best[dim] = dist;
983 best_pt[dim] = p[dim] - n->pos[dim];
984 }
985 }
986 }
987 }
989 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
990 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
991 sp_node_moveto(n, n->pos + best_pt);
992 }
994 // do not update repr here so that node dragging is acceptably fast
995 update_object(nodepath);
996 }
998 /**
999 * Move node selection to point, adjust its and neighbouring handles,
1000 * handle possible snapping, and commit the change with possible undo.
1001 */
1002 void
1003 sp_node_selected_move(gdouble dx, gdouble dy)
1004 {
1005 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1006 if (!nodepath) return;
1008 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1010 if (dx == 0) {
1011 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1012 } else if (dy == 0) {
1013 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1014 } else {
1015 sp_nodepath_update_repr(nodepath);
1016 }
1017 }
1019 /**
1020 * Move node selection off screen and commit the change.
1021 */
1022 void
1023 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1024 {
1025 // borrowed from sp_selection_move_screen in selection-chemistry.c
1026 // we find out the current zoom factor and divide deltas by it
1027 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1029 gdouble zoom = desktop->current_zoom();
1030 gdouble zdx = dx / zoom;
1031 gdouble zdy = dy / zoom;
1033 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1034 if (!nodepath) return;
1036 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1038 if (dx == 0) {
1039 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1040 } else if (dy == 0) {
1041 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1042 } else {
1043 sp_nodepath_update_repr(nodepath);
1044 }
1045 }
1047 /** If they don't yet exist, creates knot and line for the given side of the node */
1048 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1049 {
1050 if (!side->knot) {
1051 side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"));
1053 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1054 side->knot->setSize (7);
1055 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1056 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1057 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1058 sp_knot_update_ctrl(side->knot);
1060 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1061 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1062 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1063 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1064 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1065 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1066 }
1068 if (!side->line) {
1069 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1070 SP_TYPE_CTRLLINE, NULL);
1071 }
1072 }
1074 /**
1075 * Ensure the given handle of the node is visible/invisible, update its screen position
1076 */
1077 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1078 {
1079 g_assert(node != NULL);
1081 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1082 NRPathcode code = sp_node_path_code_from_side(node, side);
1084 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1086 if (show_handle) {
1087 if (!side->knot) { // No handle knot at all
1088 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1089 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1090 side->knot->pos = side->pos;
1091 if (side->knot->item)
1092 SP_CTRL(side->knot->item)->moveto(side->pos);
1093 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1094 sp_knot_show(side->knot);
1095 } else {
1096 if (side->knot->pos != side->pos) { // only if it's really moved
1097 if (fire_move_signals) {
1098 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1099 } else {
1100 sp_knot_moveto(side->knot, &side->pos);
1101 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1102 }
1103 }
1104 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1105 sp_knot_show(side->knot);
1106 }
1107 }
1108 sp_canvas_item_show(side->line);
1109 } else {
1110 if (side->knot) {
1111 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1112 sp_knot_hide(side->knot);
1113 }
1114 }
1115 if (side->line) {
1116 sp_canvas_item_hide(side->line);
1117 }
1118 }
1119 }
1121 /**
1122 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1123 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1124 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1125 * updated; otherwise, just move the knots silently (used in batch moves).
1126 */
1127 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1128 {
1129 g_assert(node != NULL);
1131 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1132 sp_knot_show(node->knot);
1133 }
1135 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1136 if (fire_move_signals)
1137 sp_knot_set_position(node->knot, &node->pos, 0);
1138 else
1139 sp_knot_moveto(node->knot, &node->pos);
1140 }
1142 gboolean show_handles = node->selected;
1143 if (node->p.other != NULL) {
1144 if (node->p.other->selected) show_handles = TRUE;
1145 }
1146 if (node->n.other != NULL) {
1147 if (node->n.other->selected) show_handles = TRUE;
1148 }
1150 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1151 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1152 }
1154 /**
1155 * Call sp_node_update_handles() for all nodes on subpath.
1156 */
1157 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1158 {
1159 g_assert(subpath != NULL);
1161 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1162 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1163 }
1164 }
1166 /**
1167 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1168 */
1169 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1170 {
1171 g_assert(nodepath != NULL);
1173 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1174 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1175 }
1176 }
1178 /**
1179 * Adds all selected nodes in nodepath to list.
1180 */
1181 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1182 {
1183 StlConv<Node *>::list(l, selected);
1184 /// \todo this adds a copying, rework when the selection becomes a stl list
1185 }
1187 /**
1188 * Align selected nodes on the specified axis.
1189 */
1190 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1191 {
1192 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1193 return;
1194 }
1196 if ( !nodepath->selected->next ) { // only one node selected
1197 return;
1198 }
1199 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1200 NR::Point dest(pNode->pos);
1201 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1202 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1203 if (pNode) {
1204 dest[axis] = pNode->pos[axis];
1205 sp_node_moveto(pNode, dest);
1206 }
1207 }
1209 sp_nodepath_update_repr(nodepath);
1210 }
1212 /// Helper struct.
1213 struct NodeSort
1214 {
1215 Inkscape::NodePath::Node *_node;
1216 NR::Coord _coord;
1217 /// \todo use vectorof pointers instead of calling copy ctor
1218 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1219 _node(node), _coord(node->pos[axis])
1220 {}
1222 };
1224 static bool operator<(NodeSort const &a, NodeSort const &b)
1225 {
1226 return (a._coord < b._coord);
1227 }
1229 /**
1230 * Distribute selected nodes on the specified axis.
1231 */
1232 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1233 {
1234 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1235 return;
1236 }
1238 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1239 return;
1240 }
1242 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1243 std::vector<NodeSort> sorted;
1244 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1245 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1246 if (pNode) {
1247 NodeSort n(pNode, axis);
1248 sorted.push_back(n);
1249 //dest[axis] = pNode->pos[axis];
1250 //sp_node_moveto(pNode, dest);
1251 }
1252 }
1253 std::sort(sorted.begin(), sorted.end());
1254 unsigned int len = sorted.size();
1255 //overall bboxes span
1256 float dist = (sorted.back()._coord -
1257 sorted.front()._coord);
1258 //new distance between each bbox
1259 float step = (dist) / (len - 1);
1260 float pos = sorted.front()._coord;
1261 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1262 it < sorted.end();
1263 it ++ )
1264 {
1265 NR::Point dest((*it)._node->pos);
1266 dest[axis] = pos;
1267 sp_node_moveto((*it)._node, dest);
1268 pos += step;
1269 }
1271 sp_nodepath_update_repr(nodepath);
1272 }
1275 /**
1276 * Call sp_nodepath_line_add_node() for all selected segments.
1277 */
1278 void
1279 sp_node_selected_add_node(void)
1280 {
1281 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1282 if (!nodepath) {
1283 return;
1284 }
1286 GList *nl = NULL;
1288 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1289 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1290 g_assert(t->selected);
1291 if (t->p.other && t->p.other->selected) {
1292 nl = g_list_prepend(nl, t);
1293 }
1294 }
1296 while (nl) {
1297 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1298 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1299 sp_nodepath_node_select(n, TRUE, FALSE);
1300 nl = g_list_remove(nl, t);
1301 }
1303 /** \todo fixme: adjust ? */
1304 sp_nodepath_update_handles(nodepath);
1306 sp_nodepath_update_repr(nodepath);
1308 sp_nodepath_update_statusbar(nodepath);
1309 }
1311 /**
1312 * Select segment nearest to point
1313 */
1314 void
1315 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1316 {
1317 if (!nodepath) {
1318 return;
1319 }
1321 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1323 //find segment to segment
1324 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1326 gboolean force = FALSE;
1327 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1328 force = TRUE;
1329 }
1330 sp_nodepath_node_select(e, (gboolean) toggle, force);
1331 if (e->p.other)
1332 sp_nodepath_node_select(e->p.other, TRUE, force);
1334 sp_nodepath_update_handles(nodepath);
1336 sp_nodepath_update_statusbar(nodepath);
1337 }
1339 /**
1340 * Add a node nearest to point
1341 */
1342 void
1343 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1344 {
1345 if (!nodepath) {
1346 return;
1347 }
1349 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1351 //find segment to split
1352 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1354 //don't know why but t seems to flip for lines
1355 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1356 position.t = 1.0 - position.t;
1357 }
1358 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1359 sp_nodepath_node_select(n, FALSE, TRUE);
1361 /* fixme: adjust ? */
1362 sp_nodepath_update_handles(nodepath);
1364 sp_nodepath_update_repr(nodepath);
1366 sp_nodepath_update_statusbar(nodepath);
1367 }
1369 /*
1370 * Adjusts a segment so that t moves by a certain delta for dragging
1371 * converts lines to curves
1372 *
1373 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1374 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1375 */
1376 void
1377 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1378 {
1379 /* feel good is an arbitrary parameter that distributes the delta between handles
1380 * if t of the drag point is less than 1/6 distance form the endpoint only
1381 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1382 */
1383 double feel_good;
1384 if (t <= 1.0 / 6.0)
1385 feel_good = 0;
1386 else if (t <= 0.5)
1387 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1388 else if (t <= 5.0 / 6.0)
1389 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1390 else
1391 feel_good = 1;
1393 //if we're dragging a line convert it to a curve
1394 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1395 sp_nodepath_set_line_type(e, NR_CURVETO);
1396 }
1398 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1399 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1400 e->p.other->n.pos += offsetcoord0;
1401 e->p.pos += offsetcoord1;
1403 // adjust handles of adjacent nodes where necessary
1404 sp_node_adjust_handle(e,1);
1405 sp_node_adjust_handle(e->p.other,-1);
1407 sp_nodepath_update_handles(e->subpath->nodepath);
1409 update_object(e->subpath->nodepath);
1411 sp_nodepath_update_statusbar(e->subpath->nodepath);
1412 }
1415 /**
1416 * Call sp_nodepath_break() for all selected segments.
1417 */
1418 void sp_node_selected_break()
1419 {
1420 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1421 if (!nodepath) return;
1423 GList *temp = NULL;
1424 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1426 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1427 if (nn == NULL) continue; // no break, no new node
1428 temp = g_list_prepend(temp, nn);
1429 }
1431 if (temp) {
1432 sp_nodepath_deselect(nodepath);
1433 }
1434 for (GList *l = temp; l != NULL; l = l->next) {
1435 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1436 }
1438 sp_nodepath_update_handles(nodepath);
1440 sp_nodepath_update_repr(nodepath);
1441 }
1443 /**
1444 * Duplicate the selected node(s).
1445 */
1446 void sp_node_selected_duplicate()
1447 {
1448 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1449 if (!nodepath) {
1450 return;
1451 }
1453 GList *temp = NULL;
1454 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1455 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1456 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1457 if (nn == NULL) continue; // could not duplicate
1458 temp = g_list_prepend(temp, nn);
1459 }
1461 if (temp) {
1462 sp_nodepath_deselect(nodepath);
1463 }
1464 for (GList *l = temp; l != NULL; l = l->next) {
1465 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1466 }
1468 sp_nodepath_update_handles(nodepath);
1470 sp_nodepath_update_repr(nodepath);
1471 }
1473 /**
1474 * Join two nodes by merging them into one.
1475 */
1476 void sp_node_selected_join()
1477 {
1478 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1479 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1481 if (g_list_length(nodepath->selected) != 2) {
1482 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1483 return;
1484 }
1486 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1487 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1489 g_assert(a != b);
1490 g_assert(a->p.other || a->n.other);
1491 g_assert(b->p.other || b->n.other);
1493 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1494 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1495 return;
1496 }
1498 /* a and b are endpoints */
1500 NR::Point c = (a->pos + b->pos) / 2;
1502 if (a->subpath == b->subpath) {
1503 Inkscape::NodePath::SubPath *sp = a->subpath;
1504 sp_nodepath_subpath_close(sp);
1506 sp_nodepath_update_handles(sp->nodepath);
1508 sp_nodepath_update_repr(nodepath);
1510 return;
1511 }
1513 /* a and b are separate subpaths */
1514 Inkscape::NodePath::SubPath *sa = a->subpath;
1515 Inkscape::NodePath::SubPath *sb = b->subpath;
1516 NR::Point p;
1517 Inkscape::NodePath::Node *n;
1518 NRPathcode code;
1519 if (a == sa->first) {
1520 p = sa->first->n.pos;
1521 code = (NRPathcode)sa->first->n.other->code;
1522 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1523 n = sa->last;
1524 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1525 n = n->p.other;
1526 while (n) {
1527 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1528 n = n->p.other;
1529 if (n == sa->first) n = NULL;
1530 }
1531 sp_nodepath_subpath_destroy(sa);
1532 sa = t;
1533 } else if (a == sa->last) {
1534 p = sa->last->p.pos;
1535 code = (NRPathcode)sa->last->code;
1536 sp_nodepath_node_destroy(sa->last);
1537 } else {
1538 code = NR_END;
1539 g_assert_not_reached();
1540 }
1542 if (b == sb->first) {
1543 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1544 for (n = sb->first->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 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1549 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1550 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1551 }
1552 } else {
1553 g_assert_not_reached();
1554 }
1555 /* and now destroy sb */
1557 sp_nodepath_subpath_destroy(sb);
1559 sp_nodepath_update_handles(sa->nodepath);
1561 sp_nodepath_update_repr(nodepath);
1563 sp_nodepath_update_statusbar(nodepath);
1564 }
1566 /**
1567 * Join two nodes by adding a segment between them.
1568 */
1569 void sp_node_selected_join_segment()
1570 {
1571 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1572 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1574 if (g_list_length(nodepath->selected) != 2) {
1575 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1576 return;
1577 }
1579 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1580 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1582 g_assert(a != b);
1583 g_assert(a->p.other || a->n.other);
1584 g_assert(b->p.other || b->n.other);
1586 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1587 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1588 return;
1589 }
1591 if (a->subpath == b->subpath) {
1592 Inkscape::NodePath::SubPath *sp = a->subpath;
1594 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1595 sp->closed = TRUE;
1597 sp->first->p.other = sp->last;
1598 sp->last->n.other = sp->first;
1600 sp_node_handle_mirror_p_to_n(sp->last);
1601 sp_node_handle_mirror_n_to_p(sp->first);
1603 sp->first->code = sp->last->code;
1604 sp->first = sp->last;
1606 sp_nodepath_update_handles(sp->nodepath);
1608 sp_nodepath_update_repr(nodepath);
1610 return;
1611 }
1613 /* a and b are separate subpaths */
1614 Inkscape::NodePath::SubPath *sa = a->subpath;
1615 Inkscape::NodePath::SubPath *sb = b->subpath;
1617 Inkscape::NodePath::Node *n;
1618 NR::Point p;
1619 NRPathcode code;
1620 if (a == sa->first) {
1621 code = (NRPathcode) sa->first->n.other->code;
1622 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1623 n = sa->last;
1624 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1625 for (n = n->p.other; n != NULL; n = n->p.other) {
1626 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1627 }
1628 sp_nodepath_subpath_destroy(sa);
1629 sa = t;
1630 } else if (a == sa->last) {
1631 code = (NRPathcode)sa->last->code;
1632 } else {
1633 code = NR_END;
1634 g_assert_not_reached();
1635 }
1637 if (b == sb->first) {
1638 n = sb->first;
1639 sp_node_handle_mirror_p_to_n(sa->last);
1640 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1641 sp_node_handle_mirror_n_to_p(sa->last);
1642 for (n = n->n.other; n != NULL; n = n->n.other) {
1643 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1644 }
1645 } else if (b == sb->last) {
1646 n = sb->last;
1647 sp_node_handle_mirror_p_to_n(sa->last);
1648 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1649 sp_node_handle_mirror_n_to_p(sa->last);
1650 for (n = n->p.other; n != NULL; n = n->p.other) {
1651 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1652 }
1653 } else {
1654 g_assert_not_reached();
1655 }
1656 /* and now destroy sb */
1658 sp_nodepath_subpath_destroy(sb);
1660 sp_nodepath_update_handles(sa->nodepath);
1662 sp_nodepath_update_repr(nodepath);
1663 }
1665 /**
1666 * Delete one or more selected nodes.
1667 */
1668 void sp_node_selected_delete()
1669 {
1670 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1671 if (!nodepath) return;
1672 if (!nodepath->selected) return;
1674 /** \todo fixme: do it the right way */
1675 while (nodepath->selected) {
1676 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1677 sp_nodepath_node_destroy(node);
1678 }
1681 //clean up the nodepath (such as for trivial subpaths)
1682 sp_nodepath_cleanup(nodepath);
1684 sp_nodepath_update_handles(nodepath);
1686 // if the entire nodepath is removed, delete the selected object.
1687 if (nodepath->subpaths == NULL ||
1688 sp_nodepath_get_node_count(nodepath) < 2) {
1689 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1690 sp_nodepath_destroy(nodepath);
1691 sp_selection_delete();
1692 sp_document_done (document);
1693 return;
1694 }
1696 sp_nodepath_update_repr(nodepath);
1698 sp_nodepath_update_statusbar(nodepath);
1699 }
1701 /**
1702 * Delete one or more segments between two selected nodes.
1703 * This is the code for 'split'.
1704 */
1705 void
1706 sp_node_selected_delete_segment(void)
1707 {
1708 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1709 Inkscape::NodePath::Node *curr, *next; //Iterators
1711 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1712 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1714 if (g_list_length(nodepath->selected) != 2) {
1715 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1716 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1717 return;
1718 }
1720 //Selected nodes, not inclusive
1721 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1722 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1724 if ( ( a==b) || //same node
1725 (a->subpath != b->subpath ) || //not the same path
1726 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1727 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1728 {
1729 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1730 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1731 return;
1732 }
1734 //###########################################
1735 //# BEGIN EDITS
1736 //###########################################
1737 //##################################
1738 //# CLOSED PATH
1739 //##################################
1740 if (a->subpath->closed) {
1743 gboolean reversed = FALSE;
1745 //Since we can go in a circle, we need to find the shorter distance.
1746 // a->b or b->a
1747 start = end = NULL;
1748 int distance = 0;
1749 int minDistance = 0;
1750 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1751 if (curr==b) {
1752 //printf("a to b:%d\n", distance);
1753 start = a;//go from a to b
1754 end = b;
1755 minDistance = distance;
1756 //printf("A to B :\n");
1757 break;
1758 }
1759 distance++;
1760 }
1762 //try again, the other direction
1763 distance = 0;
1764 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1765 if (curr==a) {
1766 //printf("b to a:%d\n", distance);
1767 if (distance < minDistance) {
1768 start = b; //we go from b to a
1769 end = a;
1770 reversed = TRUE;
1771 //printf("B to A\n");
1772 }
1773 break;
1774 }
1775 distance++;
1776 }
1779 //Copy everything from 'end' to 'start' to a new subpath
1780 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1781 for (curr=end ; curr ; curr=curr->n.other) {
1782 NRPathcode code = (NRPathcode) curr->code;
1783 if (curr == end)
1784 code = NR_MOVETO;
1785 sp_nodepath_node_new(t, NULL,
1786 (Inkscape::NodePath::NodeType)curr->type, code,
1787 &curr->p.pos, &curr->pos, &curr->n.pos);
1788 if (curr == start)
1789 break;
1790 }
1791 sp_nodepath_subpath_destroy(a->subpath);
1794 }
1798 //##################################
1799 //# OPEN PATH
1800 //##################################
1801 else {
1803 //We need to get the direction of the list between A and B
1804 //Can we walk from a to b?
1805 start = end = NULL;
1806 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1807 if (curr==b) {
1808 start = a; //did it! we go from a to b
1809 end = b;
1810 //printf("A to B\n");
1811 break;
1812 }
1813 }
1814 if (!start) {//didn't work? let's try the other direction
1815 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1816 if (curr==a) {
1817 start = b; //did it! we go from b to a
1818 end = a;
1819 //printf("B to A\n");
1820 break;
1821 }
1822 }
1823 }
1824 if (!start) {
1825 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1826 _("Cannot find path between nodes."));
1827 return;
1828 }
1832 //Copy everything after 'end' to a new subpath
1833 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1834 for (curr=end ; curr ; curr=curr->n.other) {
1835 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1836 &curr->p.pos, &curr->pos, &curr->n.pos);
1837 }
1839 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1840 for (curr = start->n.other ; curr ; curr=next) {
1841 next = curr->n.other;
1842 sp_nodepath_node_destroy(curr);
1843 }
1845 }
1846 //###########################################
1847 //# END EDITS
1848 //###########################################
1850 //clean up the nodepath (such as for trivial subpaths)
1851 sp_nodepath_cleanup(nodepath);
1853 sp_nodepath_update_handles(nodepath);
1855 sp_nodepath_update_repr(nodepath);
1857 // if the entire nodepath is removed, delete the selected object.
1858 if (nodepath->subpaths == NULL ||
1859 sp_nodepath_get_node_count(nodepath) < 2) {
1860 sp_nodepath_destroy(nodepath);
1861 sp_selection_delete();
1862 return;
1863 }
1865 sp_nodepath_update_statusbar(nodepath);
1866 }
1868 /**
1869 * Call sp_nodepath_set_line() for all selected segments.
1870 */
1871 void
1872 sp_node_selected_set_line_type(NRPathcode code)
1873 {
1874 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1875 if (nodepath == NULL) return;
1877 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1878 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1879 g_assert(n->selected);
1880 if (n->p.other && n->p.other->selected) {
1881 sp_nodepath_set_line_type(n, code);
1882 }
1883 }
1885 sp_nodepath_update_repr(nodepath);
1886 }
1888 /**
1889 * Call sp_nodepath_convert_node_type() for all selected nodes.
1890 */
1891 void
1892 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1893 {
1894 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1895 if (nodepath == NULL) return;
1897 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1898 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1899 }
1901 sp_nodepath_update_repr(nodepath);
1902 }
1904 /**
1905 * Change select status of node, update its own and neighbour handles.
1906 */
1907 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1908 {
1909 node->selected = selected;
1911 if (selected) {
1912 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1913 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1914 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1915 sp_knot_update_ctrl(node->knot);
1916 } else {
1917 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1918 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1919 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1920 sp_knot_update_ctrl(node->knot);
1921 }
1923 sp_node_update_handles(node);
1924 if (node->n.other) sp_node_update_handles(node->n.other);
1925 if (node->p.other) sp_node_update_handles(node->p.other);
1926 }
1928 /**
1929 \brief Select a node
1930 \param node The node to select
1931 \param incremental If true, add to selection, otherwise deselect others
1932 \param override If true, always select this node, otherwise toggle selected status
1933 */
1934 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1935 {
1936 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1938 if (incremental) {
1939 if (override) {
1940 if (!g_list_find(nodepath->selected, node)) {
1941 nodepath->selected = g_list_prepend(nodepath->selected, node);
1942 }
1943 sp_node_set_selected(node, TRUE);
1944 } else { // toggle
1945 if (node->selected) {
1946 g_assert(g_list_find(nodepath->selected, node));
1947 nodepath->selected = g_list_remove(nodepath->selected, node);
1948 } else {
1949 g_assert(!g_list_find(nodepath->selected, node));
1950 nodepath->selected = g_list_prepend(nodepath->selected, node);
1951 }
1952 sp_node_set_selected(node, !node->selected);
1953 }
1954 } else {
1955 sp_nodepath_deselect(nodepath);
1956 nodepath->selected = g_list_prepend(nodepath->selected, node);
1957 sp_node_set_selected(node, TRUE);
1958 }
1960 sp_nodepath_update_statusbar(nodepath);
1961 }
1964 /**
1965 \brief Deselect all nodes in the nodepath
1966 */
1967 void
1968 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1969 {
1970 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1972 while (nodepath->selected) {
1973 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1974 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1975 }
1976 sp_nodepath_update_statusbar(nodepath);
1977 }
1979 /**
1980 \brief Select or invert selection of all nodes in the nodepath
1981 */
1982 void
1983 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1984 {
1985 if (!nodepath) return;
1987 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1988 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1989 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1990 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1991 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1992 }
1993 }
1994 }
1996 /**
1997 * If nothing selected, does the same as sp_nodepath_select_all();
1998 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1999 * (i.e., similar to "select all in layer", with the "selected" subpaths
2000 * being treated as "layers" in the path).
2001 */
2002 void
2003 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2004 {
2005 if (!nodepath) return;
2007 if (g_list_length (nodepath->selected) == 0) {
2008 sp_nodepath_select_all (nodepath, invert);
2009 return;
2010 }
2012 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2013 GSList *subpaths = NULL;
2015 for (GList *l = copy; l != NULL; l = l->next) {
2016 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2017 Inkscape::NodePath::SubPath *subpath = n->subpath;
2018 if (!g_slist_find (subpaths, subpath))
2019 subpaths = g_slist_prepend (subpaths, subpath);
2020 }
2022 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2023 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2024 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2025 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2026 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2027 }
2028 }
2030 g_slist_free (subpaths);
2031 g_list_free (copy);
2032 }
2034 /**
2035 * \brief Select the node after the last selected; if none is selected,
2036 * select the first within path.
2037 */
2038 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2039 {
2040 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2042 Inkscape::NodePath::Node *last = NULL;
2043 if (nodepath->selected) {
2044 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2045 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2046 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2047 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2048 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2049 if (node->selected) {
2050 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2051 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2052 if (spl->next) { // there's a next subpath
2053 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2054 last = subpath_next->first;
2055 } else if (spl->prev) { // there's a previous subpath
2056 last = NULL; // to be set later to the first node of first subpath
2057 } else {
2058 last = node->n.other;
2059 }
2060 } else {
2061 last = node->n.other;
2062 }
2063 } else {
2064 if (node->n.other) {
2065 last = node->n.other;
2066 } else {
2067 if (spl->next) { // there's a next subpath
2068 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2069 last = subpath_next->first;
2070 } else if (spl->prev) { // there's a previous subpath
2071 last = NULL; // to be set later to the first node of first subpath
2072 } else {
2073 last = (Inkscape::NodePath::Node *) subpath->first;
2074 }
2075 }
2076 }
2077 }
2078 }
2079 }
2080 sp_nodepath_deselect(nodepath);
2081 }
2083 if (last) { // there's at least one more node after selected
2084 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2085 } else { // no more nodes, select the first one in first subpath
2086 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2087 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2088 }
2089 }
2091 /**
2092 * \brief Select the node before the first selected; if none is selected,
2093 * select the last within path
2094 */
2095 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2096 {
2097 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2099 Inkscape::NodePath::Node *last = NULL;
2100 if (nodepath->selected) {
2101 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2102 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2103 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2104 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2105 if (node->selected) {
2106 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2107 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2108 if (spl->prev) { // there's a prev subpath
2109 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2110 last = subpath_prev->last;
2111 } else if (spl->next) { // there's a next subpath
2112 last = NULL; // to be set later to the last node of last subpath
2113 } else {
2114 last = node->p.other;
2115 }
2116 } else {
2117 last = node->p.other;
2118 }
2119 } else {
2120 if (node->p.other) {
2121 last = node->p.other;
2122 } else {
2123 if (spl->prev) { // there's a prev subpath
2124 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2125 last = subpath_prev->last;
2126 } else if (spl->next) { // there's a next subpath
2127 last = NULL; // to be set later to the last node of last subpath
2128 } else {
2129 last = (Inkscape::NodePath::Node *) subpath->last;
2130 }
2131 }
2132 }
2133 }
2134 }
2135 }
2136 sp_nodepath_deselect(nodepath);
2137 }
2139 if (last) { // there's at least one more node before selected
2140 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2141 } else { // no more nodes, select the last one in last subpath
2142 GList *spl = g_list_last(nodepath->subpaths);
2143 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2144 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2145 }
2146 }
2148 /**
2149 * \brief Select all nodes that are within the rectangle.
2150 */
2151 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2152 {
2153 if (!incremental) {
2154 sp_nodepath_deselect(nodepath);
2155 }
2157 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2158 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2159 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2160 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2162 if (b.contains(node->pos)) {
2163 sp_nodepath_node_select(node, TRUE, TRUE);
2164 }
2165 }
2166 }
2167 }
2169 /**
2170 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2171 */
2172 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2173 {
2174 if (!nodepath->selected) {
2175 return NULL;
2176 }
2178 GList *r = NULL;
2179 guint i = 0;
2180 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2181 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2182 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2183 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2184 i++;
2185 if (node->selected) {
2186 r = g_list_append(r, GINT_TO_POINTER(i));
2187 }
2188 }
2189 }
2190 return r;
2191 }
2193 /**
2194 \brief Restores selection by selecting nodes whose positions are in the list
2195 */
2196 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2197 {
2198 sp_nodepath_deselect(nodepath);
2200 guint i = 0;
2201 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2202 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2203 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2204 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2205 i++;
2206 if (g_list_find(r, GINT_TO_POINTER(i))) {
2207 sp_nodepath_node_select(node, TRUE, TRUE);
2208 }
2209 }
2210 }
2212 }
2214 /**
2215 \brief Adjusts handle according to node type and line code.
2216 */
2217 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2218 {
2219 double len, otherlen, linelen;
2221 g_assert(node);
2223 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2224 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2226 /** \todo fixme: */
2227 if (me->other == NULL) return;
2228 if (other->other == NULL) return;
2230 /* I have line */
2232 NRPathcode mecode, ocode;
2233 if (which_adjust == 1) {
2234 mecode = (NRPathcode)me->other->code;
2235 ocode = (NRPathcode)node->code;
2236 } else {
2237 mecode = (NRPathcode)node->code;
2238 ocode = (NRPathcode)other->other->code;
2239 }
2241 if (mecode == NR_LINETO) return;
2243 /* I am curve */
2245 if (other->other == NULL) return;
2247 /* Other has line */
2249 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2251 NR::Point delta;
2252 if (ocode == NR_LINETO) {
2253 /* other is lineto, we are either smooth or symm */
2254 Inkscape::NodePath::Node *othernode = other->other;
2255 len = NR::L2(me->pos - node->pos);
2256 delta = node->pos - othernode->pos;
2257 linelen = NR::L2(delta);
2258 if (linelen < 1e-18)
2259 return;
2260 me->pos = node->pos + (len / linelen)*delta;
2261 return;
2262 }
2264 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2266 me->pos = 2 * node->pos - other->pos;
2267 return;
2268 }
2270 /* We are smooth */
2272 len = NR::L2(me->pos - node->pos);
2273 delta = other->pos - node->pos;
2274 otherlen = NR::L2(delta);
2275 if (otherlen < 1e-18) return;
2277 me->pos = node->pos - (len / otherlen) * delta;
2278 }
2280 /**
2281 \brief Adjusts both handles according to node type and line code
2282 */
2283 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2284 {
2285 g_assert(node);
2287 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2289 /* we are either smooth or symm */
2291 if (node->p.other == NULL) return;
2293 if (node->n.other == NULL) return;
2295 if (node->code == NR_LINETO) {
2296 if (node->n.other->code == NR_LINETO) return;
2297 sp_node_adjust_handle(node, 1);
2298 return;
2299 }
2301 if (node->n.other->code == NR_LINETO) {
2302 if (node->code == NR_LINETO) return;
2303 sp_node_adjust_handle(node, -1);
2304 return;
2305 }
2307 /* both are curves */
2308 NR::Point const delta( node->n.pos - node->p.pos );
2310 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2311 node->p.pos = node->pos - delta / 2;
2312 node->n.pos = node->pos + delta / 2;
2313 return;
2314 }
2316 /* We are smooth */
2317 double plen = NR::L2(node->p.pos - node->pos);
2318 if (plen < 1e-18) return;
2319 double nlen = NR::L2(node->n.pos - node->pos);
2320 if (nlen < 1e-18) return;
2321 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2322 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2323 }
2325 /**
2326 * Node event callback.
2327 */
2328 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2329 {
2330 gboolean ret = FALSE;
2331 switch (event->type) {
2332 case GDK_ENTER_NOTIFY:
2333 active_node = n;
2334 break;
2335 case GDK_LEAVE_NOTIFY:
2336 active_node = NULL;
2337 break;
2338 case GDK_KEY_PRESS:
2339 switch (get_group0_keyval (&event->key)) {
2340 case GDK_space:
2341 if (event->key.state & GDK_BUTTON1_MASK) {
2342 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2343 stamp_repr(nodepath);
2344 ret = TRUE;
2345 }
2346 break;
2347 default:
2348 break;
2349 }
2350 break;
2351 default:
2352 break;
2353 }
2355 return ret;
2356 }
2358 /**
2359 * Handle keypress on node; directly called.
2360 */
2361 gboolean node_key(GdkEvent *event)
2362 {
2363 Inkscape::NodePath::Path *np;
2365 // there is no way to verify nodes so set active_node to nil when deleting!!
2366 if (active_node == NULL) return FALSE;
2368 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2369 gint ret = FALSE;
2370 switch (get_group0_keyval (&event->key)) {
2371 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2372 case GDK_BackSpace:
2373 np = active_node->subpath->nodepath;
2374 sp_nodepath_node_destroy(active_node);
2375 sp_nodepath_update_repr(np);
2376 active_node = NULL;
2377 ret = TRUE;
2378 break;
2379 case GDK_c:
2380 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2381 ret = TRUE;
2382 break;
2383 case GDK_s:
2384 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2385 ret = TRUE;
2386 break;
2387 case GDK_y:
2388 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2389 ret = TRUE;
2390 break;
2391 case GDK_b:
2392 sp_nodepath_node_break(active_node);
2393 ret = TRUE;
2394 break;
2395 }
2396 return ret;
2397 }
2398 return FALSE;
2399 }
2401 /**
2402 * Mouseclick on node callback.
2403 */
2404 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2405 {
2406 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2408 if (state & GDK_CONTROL_MASK) {
2409 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2411 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2412 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2413 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2414 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2415 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2416 } else {
2417 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2418 }
2419 sp_nodepath_update_repr(nodepath);
2420 sp_nodepath_update_statusbar(nodepath);
2422 } else { //ctrl+alt+click: delete node
2423 sp_nodepath_node_destroy(n);
2424 //clean up the nodepath (such as for trivial subpaths)
2425 sp_nodepath_cleanup(nodepath);
2427 // if the entire nodepath is removed, delete the selected object.
2428 if (nodepath->subpaths == NULL ||
2429 sp_nodepath_get_node_count(nodepath) < 2) {
2430 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2431 sp_nodepath_destroy(nodepath);
2432 sp_selection_delete();
2433 sp_document_done (document);
2435 } else {
2436 sp_nodepath_update_handles(nodepath);
2437 sp_nodepath_update_repr(nodepath);
2438 sp_nodepath_update_statusbar(nodepath);
2439 }
2440 }
2442 } else {
2443 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2444 }
2445 }
2447 /**
2448 * Mouse grabbed node callback.
2449 */
2450 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2451 {
2452 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2454 n->origin = knot->pos;
2456 if (!n->selected) {
2457 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2458 }
2459 }
2461 /**
2462 * Mouse ungrabbed node callback.
2463 */
2464 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2465 {
2466 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2468 n->dragging_out = NULL;
2470 sp_nodepath_update_repr(n->subpath->nodepath);
2471 }
2473 /**
2474 * The point on a line, given by its angle, closest to the given point.
2475 * \param p A point.
2476 * \param a Angle of the line; it is assumed to go through coordinate origin.
2477 * \param closest Pointer to the point struct where the result is stored.
2478 * \todo FIXME: use dot product perhaps?
2479 */
2480 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2481 {
2482 if (a == HUGE_VAL) { // vertical
2483 *closest = NR::Point(0, (*p)[NR::Y]);
2484 } else {
2485 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2486 (*closest)[NR::Y] = a * (*closest)[NR::X];
2487 }
2488 }
2490 /**
2491 * Distance from the point to a line given by its angle.
2492 * \param p A point.
2493 * \param a Angle of the line; it is assumed to go through coordinate origin.
2494 */
2495 static double point_line_distance(NR::Point *p, double a)
2496 {
2497 NR::Point c;
2498 point_line_closest(p, a, &c);
2499 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]));
2500 }
2502 /**
2503 * Callback for node "request" signal.
2504 * \todo fixme: This goes to "moved" event? (lauris)
2505 */
2506 static gboolean
2507 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2508 {
2509 double yn, xn, yp, xp;
2510 double an, ap, na, pa;
2511 double d_an, d_ap, d_na, d_pa;
2512 gboolean collinear = FALSE;
2513 NR::Point c;
2514 NR::Point pr;
2516 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2518 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2519 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2521 NR::Point mouse = (*p);
2523 if (!n->dragging_out) {
2524 // This is the first drag-out event; find out which handle to drag out
2525 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2526 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2528 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2529 return FALSE;
2531 Inkscape::NodePath::NodeSide *opposite;
2532 if (appr_p > appr_n) { // closer to p
2533 n->dragging_out = &n->p;
2534 opposite = &n->n;
2535 n->code = NR_CURVETO;
2536 } else if (appr_p < appr_n) { // closer to n
2537 n->dragging_out = &n->n;
2538 opposite = &n->p;
2539 n->n.other->code = NR_CURVETO;
2540 } else { // p and n nodes are the same
2541 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2542 n->dragging_out = &n->p;
2543 opposite = &n->n;
2544 n->code = NR_CURVETO;
2545 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2546 n->dragging_out = &n->n;
2547 opposite = &n->p;
2548 n->n.other->code = NR_CURVETO;
2549 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2550 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);
2551 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);
2552 if (appr_other_p > appr_other_n) { // closer to other's p handle
2553 n->dragging_out = &n->n;
2554 opposite = &n->p;
2555 n->n.other->code = NR_CURVETO;
2556 } else { // closer to other's n handle
2557 n->dragging_out = &n->p;
2558 opposite = &n->n;
2559 n->code = NR_CURVETO;
2560 }
2561 }
2562 }
2564 // if there's another handle, make sure the one we drag out starts parallel to it
2565 if (opposite->pos != n->pos) {
2566 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2567 }
2569 // knots might not be created yet!
2570 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2571 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2572 }
2574 // pass this on to the handle-moved callback
2575 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2576 sp_node_update_handles(n);
2577 return TRUE;
2578 }
2580 if (state & GDK_CONTROL_MASK) { // constrained motion
2582 // calculate relative distances of handles
2583 // n handle:
2584 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2585 xn = n->n.pos[NR::X] - n->pos[NR::X];
2586 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2587 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2588 if (n->n.other) { // if there is the next point
2589 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2590 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2591 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2592 }
2593 }
2594 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2595 if (yn < 0) { xn = -xn; yn = -yn; }
2597 // p handle:
2598 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2599 xp = n->p.pos[NR::X] - n->pos[NR::X];
2600 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2601 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2602 if (n->p.other) {
2603 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2604 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2605 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2606 }
2607 }
2608 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2609 if (yp < 0) { xp = -xp; yp = -yp; }
2611 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2612 // sliding on handles, only if at least one of the handles is non-vertical
2613 // (otherwise it's the same as ctrl+drag anyway)
2615 // calculate angles of the handles
2616 if (xn == 0) {
2617 if (yn == 0) { // no handle, consider it the continuation of the other one
2618 an = 0;
2619 collinear = TRUE;
2620 }
2621 else an = 0; // vertical; set the angle to horizontal
2622 } else an = yn/xn;
2624 if (xp == 0) {
2625 if (yp == 0) { // no handle, consider it the continuation of the other one
2626 ap = an;
2627 }
2628 else ap = 0; // vertical; set the angle to horizontal
2629 } else ap = yp/xp;
2631 if (collinear) an = ap;
2633 // angles of the perpendiculars; HUGE_VAL means vertical
2634 if (an == 0) na = HUGE_VAL; else na = -1/an;
2635 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2637 //g_print("an %g ap %g\n", an, ap);
2639 // mouse point relative to the node's original pos
2640 pr = (*p) - n->origin;
2642 // distances to the four lines (two handles and two perpendiculars)
2643 d_an = point_line_distance(&pr, an);
2644 d_na = point_line_distance(&pr, na);
2645 d_ap = point_line_distance(&pr, ap);
2646 d_pa = point_line_distance(&pr, pa);
2648 // find out which line is the closest, save its closest point in c
2649 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2650 point_line_closest(&pr, an, &c);
2651 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2652 point_line_closest(&pr, ap, &c);
2653 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2654 point_line_closest(&pr, na, &c);
2655 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2656 point_line_closest(&pr, pa, &c);
2657 }
2659 // move the node to the closest point
2660 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2661 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2662 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2664 } else { // constraining to hor/vert
2666 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2667 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2668 } else { // snap to vert
2669 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2670 }
2671 }
2672 } else { // move freely
2673 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2674 (*p)[NR::X] - n->pos[NR::X],
2675 (*p)[NR::Y] - n->pos[NR::Y],
2676 (state & GDK_SHIFT_MASK) == 0);
2677 }
2679 n->subpath->nodepath->desktop->scroll_to_point(p);
2681 return TRUE;
2682 }
2684 /**
2685 * Node handle clicked callback.
2686 */
2687 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2688 {
2689 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2691 if (state & GDK_CONTROL_MASK) { // "delete" handle
2692 if (n->p.knot == knot) {
2693 n->p.pos = n->pos;
2694 } else if (n->n.knot == knot) {
2695 n->n.pos = n->pos;
2696 }
2697 sp_node_update_handles(n);
2698 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2699 sp_nodepath_update_repr(nodepath);
2700 sp_nodepath_update_statusbar(nodepath);
2702 } else { // just select or add to selection, depending in Shift
2703 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2704 }
2705 }
2707 /**
2708 * Node handle grabbed callback.
2709 */
2710 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2711 {
2712 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2714 if (!n->selected) {
2715 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2716 }
2718 // remember the origin point of the handle
2719 if (n->p.knot == knot) {
2720 n->p.origin = n->p.pos - n->pos;
2721 } else if (n->n.knot == knot) {
2722 n->n.origin = n->n.pos - n->pos;
2723 } else {
2724 g_assert_not_reached();
2725 }
2727 }
2729 /**
2730 * Node handle ungrabbed callback.
2731 */
2732 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2733 {
2734 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2736 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2737 if (n->p.knot == knot) {
2738 n->p.origin.a = 0;
2739 sp_knot_set_position(knot, &n->p.pos, state);
2740 } else if (n->n.knot == knot) {
2741 n->n.origin.a = 0;
2742 sp_knot_set_position(knot, &n->n.pos, state);
2743 } else {
2744 g_assert_not_reached();
2745 }
2747 sp_nodepath_update_repr(n->subpath->nodepath);
2748 }
2750 /**
2751 * Node handle "request" signal callback.
2752 */
2753 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2754 {
2755 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2757 Inkscape::NodePath::NodeSide *me, *opposite;
2758 gint which;
2759 if (n->p.knot == knot) {
2760 me = &n->p;
2761 opposite = &n->n;
2762 which = -1;
2763 } else if (n->n.knot == knot) {
2764 me = &n->n;
2765 opposite = &n->p;
2766 which = 1;
2767 } else {
2768 me = opposite = NULL;
2769 which = 0;
2770 g_assert_not_reached();
2771 }
2773 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2775 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2777 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2778 /* We are smooth node adjacent with line */
2779 NR::Point const delta = *p - n->pos;
2780 NR::Coord const len = NR::L2(delta);
2781 Inkscape::NodePath::Node *othernode = opposite->other;
2782 NR::Point const ndelta = n->pos - othernode->pos;
2783 NR::Coord const linelen = NR::L2(ndelta);
2784 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2785 NR::Coord const scal = dot(delta, ndelta) / linelen;
2786 (*p) = n->pos + (scal / linelen) * ndelta;
2787 }
2788 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2789 } else {
2790 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2791 }
2793 sp_node_adjust_handle(n, -which);
2795 return FALSE;
2796 }
2798 /**
2799 * Node handle moved callback.
2800 */
2801 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2802 {
2803 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2805 Inkscape::NodePath::NodeSide *me;
2806 Inkscape::NodePath::NodeSide *other;
2807 if (n->p.knot == knot) {
2808 me = &n->p;
2809 other = &n->n;
2810 } else if (n->n.knot == knot) {
2811 me = &n->n;
2812 other = &n->p;
2813 } else {
2814 me = NULL;
2815 other = NULL;
2816 g_assert_not_reached();
2817 }
2819 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2820 Radial rme(me->pos - n->pos);
2821 Radial rother(other->pos - n->pos);
2822 Radial rnew(*p - n->pos);
2824 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2825 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2826 /* 0 interpreted as "no snapping". */
2828 // The closest PI/snaps angle, starting from zero.
2829 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2830 if (me->origin.a == HUGE_VAL) {
2831 // ortho doesn't exist: original handle was zero length.
2832 rnew.a = a_snapped;
2833 } else {
2834 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2835 * its opposite and perpendiculars). */
2836 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2838 // Snap to the closest.
2839 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2840 ? a_snapped
2841 : a_ortho );
2842 }
2843 }
2845 if (state & GDK_MOD1_MASK) {
2846 // lock handle length
2847 rnew.r = me->origin.r;
2848 }
2850 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2851 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2852 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2853 rother.a += rnew.a - rme.a;
2854 other->pos = NR::Point(rother) + n->pos;
2855 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2856 sp_knot_set_position(other->knot, &other->pos, 0);
2857 }
2859 me->pos = NR::Point(rnew) + n->pos;
2860 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2862 // this is what sp_knot_set_position does, but without emitting the signal:
2863 // we cannot emit a "moved" signal because we're now processing it
2864 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2866 knot->desktop->set_coordinate_status(me->pos);
2868 update_object(n->subpath->nodepath);
2870 /* status text */
2871 SPDesktop *desktop = n->subpath->nodepath->desktop;
2872 if (!desktop) return;
2873 SPEventContext *ec = desktop->event_context;
2874 if (!ec) return;
2875 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2876 if (!mc) return;
2878 double degrees = 180 / M_PI * rnew.a;
2879 if (degrees > 180) degrees -= 360;
2880 if (degrees < -180) degrees += 360;
2881 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2882 degrees = angle_to_compass (degrees);
2884 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2886 mc->setF(Inkscape::NORMAL_MESSAGE,
2887 _("<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);
2889 g_string_free(length, TRUE);
2890 }
2892 /**
2893 * Node handle event callback.
2894 */
2895 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2896 {
2897 gboolean ret = FALSE;
2898 switch (event->type) {
2899 case GDK_KEY_PRESS:
2900 switch (get_group0_keyval (&event->key)) {
2901 case GDK_space:
2902 if (event->key.state & GDK_BUTTON1_MASK) {
2903 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2904 stamp_repr(nodepath);
2905 ret = TRUE;
2906 }
2907 break;
2908 default:
2909 break;
2910 }
2911 break;
2912 default:
2913 break;
2914 }
2916 return ret;
2917 }
2919 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2920 Radial &rme, Radial &rother, gboolean const both)
2921 {
2922 rme.a += angle;
2923 if ( both
2924 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2925 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2926 {
2927 rother.a += angle;
2928 }
2929 }
2931 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2932 Radial &rme, Radial &rother, gboolean const both)
2933 {
2934 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2936 gdouble r;
2937 if ( both
2938 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2939 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2940 {
2941 r = MAX(rme.r, rother.r);
2942 } else {
2943 r = rme.r;
2944 }
2946 gdouble const weird_angle = atan2(norm_angle, r);
2947 /* Bulia says norm_angle is just the visible distance that the
2948 * object's end must travel on the screen. Left as 'angle' for want of
2949 * a better name.*/
2951 rme.a += weird_angle;
2952 if ( both
2953 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2954 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2955 {
2956 rother.a += weird_angle;
2957 }
2958 }
2960 /**
2961 * Rotate one node.
2962 */
2963 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2964 {
2965 Inkscape::NodePath::NodeSide *me, *other;
2966 bool both = false;
2968 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2969 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2971 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2972 me = &(n->p);
2973 other = &(n->n);
2974 } else if (!n->p.other) {
2975 me = &(n->n);
2976 other = &(n->p);
2977 } else {
2978 if (which > 0) { // right handle
2979 if (xn > xp) {
2980 me = &(n->n);
2981 other = &(n->p);
2982 } else {
2983 me = &(n->p);
2984 other = &(n->n);
2985 }
2986 } else if (which < 0){ // left handle
2987 if (xn <= xp) {
2988 me = &(n->n);
2989 other = &(n->p);
2990 } else {
2991 me = &(n->p);
2992 other = &(n->n);
2993 }
2994 } else { // both handles
2995 me = &(n->n);
2996 other = &(n->p);
2997 both = true;
2998 }
2999 }
3001 Radial rme(me->pos - n->pos);
3002 Radial rother(other->pos - n->pos);
3004 if (screen) {
3005 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3006 } else {
3007 node_rotate_one_internal (*n, angle, rme, rother, both);
3008 }
3010 me->pos = n->pos + NR::Point(rme);
3012 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3013 other->pos = n->pos + NR::Point(rother);
3014 }
3016 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3017 // so here we just move all the knots without emitting move signals, for speed
3018 sp_node_update_handles(n, false);
3019 }
3021 /**
3022 * Rotate selected nodes.
3023 */
3024 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3025 {
3026 if (!nodepath || !nodepath->selected) return;
3028 if (g_list_length(nodepath->selected) == 1) {
3029 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3030 node_rotate_one (n, angle, which, screen);
3031 } else {
3032 // rotate as an object:
3034 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3035 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3036 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3037 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3038 box.expandTo (n->pos); // contain all selected nodes
3039 }
3041 gdouble rot;
3042 if (screen) {
3043 gdouble const zoom = nodepath->desktop->current_zoom();
3044 gdouble const zmove = angle / zoom;
3045 gdouble const r = NR::L2(box.max() - box.midpoint());
3046 rot = atan2(zmove, r);
3047 } else {
3048 rot = angle;
3049 }
3051 NR::Matrix t =
3052 NR::Matrix (NR::translate(-box.midpoint())) *
3053 NR::Matrix (NR::rotate(rot)) *
3054 NR::Matrix (NR::translate(box.midpoint()));
3056 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3057 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3058 n->pos *= t;
3059 n->n.pos *= t;
3060 n->p.pos *= t;
3061 sp_node_update_handles(n, false);
3062 }
3063 }
3065 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3066 }
3068 /**
3069 * Scale one node.
3070 */
3071 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3072 {
3073 bool both = false;
3074 Inkscape::NodePath::NodeSide *me, *other;
3076 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3077 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3079 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3080 me = &(n->p);
3081 other = &(n->n);
3082 n->code = NR_CURVETO;
3083 } else if (!n->p.other) {
3084 me = &(n->n);
3085 other = &(n->p);
3086 if (n->n.other)
3087 n->n.other->code = NR_CURVETO;
3088 } else {
3089 if (which > 0) { // right handle
3090 if (xn > xp) {
3091 me = &(n->n);
3092 other = &(n->p);
3093 if (n->n.other)
3094 n->n.other->code = NR_CURVETO;
3095 } else {
3096 me = &(n->p);
3097 other = &(n->n);
3098 n->code = NR_CURVETO;
3099 }
3100 } else if (which < 0){ // left handle
3101 if (xn <= xp) {
3102 me = &(n->n);
3103 other = &(n->p);
3104 if (n->n.other)
3105 n->n.other->code = NR_CURVETO;
3106 } else {
3107 me = &(n->p);
3108 other = &(n->n);
3109 n->code = NR_CURVETO;
3110 }
3111 } else { // both handles
3112 me = &(n->n);
3113 other = &(n->p);
3114 both = true;
3115 n->code = NR_CURVETO;
3116 if (n->n.other)
3117 n->n.other->code = NR_CURVETO;
3118 }
3119 }
3121 Radial rme(me->pos - n->pos);
3122 Radial rother(other->pos - n->pos);
3124 rme.r += grow;
3125 if (rme.r < 0) rme.r = 0;
3126 if (rme.a == HUGE_VAL) {
3127 if (me->other) { // if direction is unknown, initialize it towards the next node
3128 Radial rme_next(me->other->pos - n->pos);
3129 rme.a = rme_next.a;
3130 } else { // if there's no next, initialize to 0
3131 rme.a = 0;
3132 }
3133 }
3134 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3135 rother.r += grow;
3136 if (rother.r < 0) rother.r = 0;
3137 if (rother.a == HUGE_VAL) {
3138 rother.a = rme.a + M_PI;
3139 }
3140 }
3142 me->pos = n->pos + NR::Point(rme);
3144 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3145 other->pos = n->pos + NR::Point(rother);
3146 }
3148 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3149 // so here we just move all the knots without emitting move signals, for speed
3150 sp_node_update_handles(n, false);
3151 }
3153 /**
3154 * Scale selected nodes.
3155 */
3156 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3157 {
3158 if (!nodepath || !nodepath->selected) return;
3160 if (g_list_length(nodepath->selected) == 1) {
3161 // scale handles of the single selected node
3162 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3163 node_scale_one (n, grow, which);
3164 } else {
3165 // scale nodes as an "object":
3167 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3168 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3169 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3170 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3171 box.expandTo (n->pos); // contain all selected nodes
3172 }
3174 double scale = (box.maxExtent() + grow)/box.maxExtent();
3176 NR::Matrix t =
3177 NR::Matrix (NR::translate(-box.midpoint())) *
3178 NR::Matrix (NR::scale(scale, scale)) *
3179 NR::Matrix (NR::translate(box.midpoint()));
3181 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3182 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3183 n->pos *= t;
3184 n->n.pos *= t;
3185 n->p.pos *= t;
3186 sp_node_update_handles(n, false);
3187 }
3188 }
3190 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3191 }
3193 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3194 {
3195 if (!nodepath) return;
3196 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3197 }
3199 /**
3200 * Flip selected nodes horizontally/vertically.
3201 */
3202 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3203 {
3204 if (!nodepath || !nodepath->selected) return;
3206 if (g_list_length(nodepath->selected) == 1) {
3207 // flip handles of the single selected node
3208 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3209 double temp = n->p.pos[axis];
3210 n->p.pos[axis] = n->n.pos[axis];
3211 n->n.pos[axis] = temp;
3212 sp_node_update_handles(n, false);
3213 } else {
3214 // scale nodes as an "object":
3216 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3217 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3218 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3219 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3220 box.expandTo (n->pos); // contain all selected nodes
3221 }
3223 NR::Matrix t =
3224 NR::Matrix (NR::translate(-box.midpoint())) *
3225 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3226 NR::Matrix (NR::translate(box.midpoint()));
3228 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3229 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3230 n->pos *= t;
3231 n->n.pos *= t;
3232 n->p.pos *= t;
3233 sp_node_update_handles(n, false);
3234 }
3235 }
3237 sp_nodepath_update_repr(nodepath);
3238 }
3240 //-----------------------------------------------
3241 /**
3242 * Return new subpath under given nodepath.
3243 */
3244 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3245 {
3246 g_assert(nodepath);
3247 g_assert(nodepath->desktop);
3249 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3251 s->nodepath = nodepath;
3252 s->closed = FALSE;
3253 s->nodes = NULL;
3254 s->first = NULL;
3255 s->last = NULL;
3257 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3258 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3259 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3261 return s;
3262 }
3264 /**
3265 * Destroy nodes in subpath, then subpath itself.
3266 */
3267 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3268 {
3269 g_assert(subpath);
3270 g_assert(subpath->nodepath);
3271 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3273 while (subpath->nodes) {
3274 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3275 }
3277 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3279 g_free(subpath);
3280 }
3282 /**
3283 * Link head to tail in subpath.
3284 */
3285 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3286 {
3287 g_assert(!sp->closed);
3288 g_assert(sp->last != sp->first);
3289 g_assert(sp->first->code == NR_MOVETO);
3291 sp->closed = TRUE;
3293 //Link the head to the tail
3294 sp->first->p.other = sp->last;
3295 sp->last->n.other = sp->first;
3296 sp->last->n.pos = sp->first->n.pos;
3297 sp->first = sp->last;
3299 //Remove the extra end node
3300 sp_nodepath_node_destroy(sp->last->n.other);
3301 }
3303 /**
3304 * Open closed (loopy) subpath at node.
3305 */
3306 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3307 {
3308 g_assert(sp->closed);
3309 g_assert(n->subpath == sp);
3310 g_assert(sp->first == sp->last);
3312 /* We create new startpoint, current node will become last one */
3314 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3315 &n->pos, &n->pos, &n->n.pos);
3318 sp->closed = FALSE;
3320 //Unlink to make a head and tail
3321 sp->first = new_path;
3322 sp->last = n;
3323 n->n.other = NULL;
3324 new_path->p.other = NULL;
3325 }
3327 /**
3328 * Returns area in triangle given by points; may be negative.
3329 */
3330 inline double
3331 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3332 {
3333 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]);
3334 }
3336 /**
3337 * Return new node in subpath with given properties.
3338 * \param pos Position of node.
3339 * \param ppos Handle position in previous direction
3340 * \param npos Handle position in previous direction
3341 */
3342 Inkscape::NodePath::Node *
3343 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)
3344 {
3345 g_assert(sp);
3346 g_assert(sp->nodepath);
3347 g_assert(sp->nodepath->desktop);
3349 if (nodechunk == NULL)
3350 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3352 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3354 n->subpath = sp;
3356 if (type != Inkscape::NodePath::NODE_NONE) {
3357 // use the type from sodipodi:nodetypes
3358 n->type = type;
3359 } else {
3360 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3361 // points are (almost) collinear
3362 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3363 // endnode, or a node with a retracted handle
3364 n->type = Inkscape::NodePath::NODE_CUSP;
3365 } else {
3366 n->type = Inkscape::NodePath::NODE_SMOOTH;
3367 }
3368 } else {
3369 n->type = Inkscape::NodePath::NODE_CUSP;
3370 }
3371 }
3373 n->code = code;
3374 n->selected = FALSE;
3375 n->pos = *pos;
3376 n->p.pos = *ppos;
3377 n->n.pos = *npos;
3379 n->dragging_out = NULL;
3381 Inkscape::NodePath::Node *prev;
3382 if (next) {
3383 //g_assert(g_list_find(sp->nodes, next));
3384 prev = next->p.other;
3385 } else {
3386 prev = sp->last;
3387 }
3389 if (prev)
3390 prev->n.other = n;
3391 else
3392 sp->first = n;
3394 if (next)
3395 next->p.other = n;
3396 else
3397 sp->last = n;
3399 n->p.other = prev;
3400 n->n.other = next;
3402 n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions"));
3403 sp_knot_set_position(n->knot, pos, 0);
3405 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3406 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3407 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3408 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3409 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3410 sp_knot_update_ctrl(n->knot);
3412 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3413 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3414 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3415 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3416 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3417 sp_knot_show(n->knot);
3419 // We only create handle knots and lines on demand
3420 n->p.knot = NULL;
3421 n->p.line = NULL;
3422 n->n.knot = NULL;
3423 n->n.line = NULL;
3425 sp->nodes = g_list_prepend(sp->nodes, n);
3427 return n;
3428 }
3430 /**
3431 * Destroy node and its knots, link neighbors in subpath.
3432 */
3433 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3434 {
3435 g_assert(node);
3436 g_assert(node->subpath);
3437 g_assert(SP_IS_KNOT(node->knot));
3439 Inkscape::NodePath::SubPath *sp = node->subpath;
3441 if (node->selected) { // first, deselect
3442 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3443 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3444 }
3446 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3448 g_object_unref(G_OBJECT(node->knot));
3449 if (node->p.knot)
3450 g_object_unref(G_OBJECT(node->p.knot));
3451 if (node->n.knot)
3452 g_object_unref(G_OBJECT(node->n.knot));
3454 if (node->p.line)
3455 gtk_object_destroy(GTK_OBJECT(node->p.line));
3456 if (node->n.line)
3457 gtk_object_destroy(GTK_OBJECT(node->n.line));
3459 if (sp->nodes) { // there are others nodes on the subpath
3460 if (sp->closed) {
3461 if (sp->first == node) {
3462 g_assert(sp->last == node);
3463 sp->first = node->n.other;
3464 sp->last = sp->first;
3465 }
3466 node->p.other->n.other = node->n.other;
3467 node->n.other->p.other = node->p.other;
3468 } else {
3469 if (sp->first == node) {
3470 sp->first = node->n.other;
3471 sp->first->code = NR_MOVETO;
3472 }
3473 if (sp->last == node) sp->last = node->p.other;
3474 if (node->p.other) node->p.other->n.other = node->n.other;
3475 if (node->n.other) node->n.other->p.other = node->p.other;
3476 }
3477 } else { // this was the last node on subpath
3478 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3479 }
3481 g_mem_chunk_free(nodechunk, node);
3482 }
3484 /**
3485 * Returns one of the node's two sides.
3486 * \param which Indicates which side.
3487 * \return Pointer to previous node side if which==-1, next if which==1.
3488 */
3489 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3490 {
3491 g_assert(node);
3493 switch (which) {
3494 case -1:
3495 return &node->p;
3496 case 1:
3497 return &node->n;
3498 default:
3499 break;
3500 }
3502 g_assert_not_reached();
3504 return NULL;
3505 }
3507 /**
3508 * Return the other side of the node, given one of its sides.
3509 */
3510 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3511 {
3512 g_assert(node);
3514 if (me == &node->p) return &node->n;
3515 if (me == &node->n) return &node->p;
3517 g_assert_not_reached();
3519 return NULL;
3520 }
3522 /**
3523 * Return NRPathcode on the given side of the node.
3524 */
3525 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3526 {
3527 g_assert(node);
3529 if (me == &node->p) {
3530 if (node->p.other) return (NRPathcode)node->code;
3531 return NR_MOVETO;
3532 }
3534 if (me == &node->n) {
3535 if (node->n.other) return (NRPathcode)node->n.other->code;
3536 return NR_MOVETO;
3537 }
3539 g_assert_not_reached();
3541 return NR_END;
3542 }
3544 /**
3545 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3546 */
3547 Inkscape::NodePath::Node *
3548 sp_nodepath_get_node_by_index(int index)
3549 {
3550 Inkscape::NodePath::Node *e = NULL;
3552 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3553 if (!nodepath) {
3554 return e;
3555 }
3557 //find segment
3558 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3560 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3561 int n = g_list_length(sp->nodes);
3562 if (sp->closed) {
3563 n++;
3564 }
3566 //if the piece belongs to this subpath grab it
3567 //otherwise move onto the next subpath
3568 if (index < n) {
3569 e = sp->first;
3570 for (int i = 0; i < index; ++i) {
3571 e = e->n.other;
3572 }
3573 break;
3574 } else {
3575 if (sp->closed) {
3576 index -= (n+1);
3577 } else {
3578 index -= n;
3579 }
3580 }
3581 }
3583 return e;
3584 }
3586 /**
3587 * Returns plain text meaning of node type.
3588 */
3589 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3590 {
3591 unsigned retracted = 0;
3592 bool endnode = false;
3594 for (int which = -1; which <= 1; which += 2) {
3595 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3596 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3597 retracted ++;
3598 if (!side->other)
3599 endnode = true;
3600 }
3602 if (retracted == 0) {
3603 if (endnode) {
3604 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3605 return _("end node");
3606 } else {
3607 switch (node->type) {
3608 case Inkscape::NodePath::NODE_CUSP:
3609 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3610 return _("cusp");
3611 case Inkscape::NodePath::NODE_SMOOTH:
3612 // TRANSLATORS: "smooth" is an adjective here
3613 return _("smooth");
3614 case Inkscape::NodePath::NODE_SYMM:
3615 return _("symmetric");
3616 }
3617 }
3618 } else if (retracted == 1) {
3619 if (endnode) {
3620 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3621 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3622 } else {
3623 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3624 }
3625 } else {
3626 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3627 }
3629 return NULL;
3630 }
3632 /**
3633 * Handles content of statusbar as long as node tool is active.
3634 */
3635 void
3636 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3637 {
3638 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3639 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3641 gint total = 0;
3642 gint selected = 0;
3643 SPDesktop *desktop = NULL;
3645 if (nodepath) {
3646 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3647 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3648 total += g_list_length(subpath->nodes);
3649 }
3650 selected = g_list_length(nodepath->selected);
3651 desktop = nodepath->desktop;
3652 } else {
3653 desktop = SP_ACTIVE_DESKTOP;
3654 }
3656 SPEventContext *ec = desktop->event_context;
3657 if (!ec) return;
3658 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3659 if (!mc) return;
3661 if (selected == 0) {
3662 Inkscape::Selection *sel = desktop->selection;
3663 if (!sel || sel->isEmpty()) {
3664 mc->setF(Inkscape::NORMAL_MESSAGE,
3665 _("Select a single object to edit its nodes or handles."));
3666 } else {
3667 if (nodepath) {
3668 mc->setF(Inkscape::NORMAL_MESSAGE,
3669 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.",
3670 "<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.",
3671 total),
3672 total);
3673 } else {
3674 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3675 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3676 } else {
3677 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3678 }
3679 }
3680 }
3681 } else if (nodepath && selected == 1) {
3682 mc->setF(Inkscape::NORMAL_MESSAGE,
3683 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3684 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3685 total),
3686 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3687 } else {
3688 mc->setF(Inkscape::NORMAL_MESSAGE,
3689 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3690 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3691 total),
3692 selected, total, when_selected);
3693 }
3694 }
3697 /*
3698 Local Variables:
3699 mode:c++
3700 c-file-style:"stroustrup"
3701 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3702 indent-tabs-mode:nil
3703 fill-column:99
3704 End:
3705 */
3706 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :