5cb4ed7f8ca543e621fa9989bfb9ca3c41c0964b
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 sp_node_adjust_handles(node);
884 sp_node_update_handles(node);
886 sp_nodepath_update_statusbar(node->subpath->nodepath);
888 return node;
889 }
891 /**
892 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
893 * adjacent segments from lines to curves.
894 */
895 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
896 {
897 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
898 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
899 // convert adjacent segment BEFORE to curve
900 node->code = NR_CURVETO;
901 NR::Point delta;
902 if (node->n.other != NULL)
903 delta = node->n.other->pos - node->p.other->pos;
904 else
905 delta = node->pos - node->p.other->pos;
906 node->p.pos = node->pos - delta / 4;
907 sp_node_update_handles(node);
908 }
910 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
911 // convert adjacent segment AFTER to curve
912 node->n.other->code = NR_CURVETO;
913 NR::Point delta;
914 if (node->p.other != NULL)
915 delta = node->p.other->pos - node->n.other->pos;
916 else
917 delta = node->pos - node->n.other->pos;
918 node->n.pos = node->pos - delta / 4;
919 sp_node_update_handles(node);
920 }
921 }
923 sp_nodepath_set_node_type (node, type);
924 }
926 /**
927 * Move node to point, and adjust its and neighbouring handles.
928 */
929 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
930 {
931 NR::Point delta = p - node->pos;
932 node->pos = p;
934 node->p.pos += delta;
935 node->n.pos += delta;
937 if (node->p.other) {
938 if (node->code == NR_LINETO) {
939 sp_node_adjust_handle(node, 1);
940 sp_node_adjust_handle(node->p.other, -1);
941 }
942 }
943 if (node->n.other) {
944 if (node->n.other->code == NR_LINETO) {
945 sp_node_adjust_handle(node, -1);
946 sp_node_adjust_handle(node->n.other, 1);
947 }
948 }
950 // this function is only called from batch movers that will update display at the end
951 // themselves, so here we just move all the knots without emitting move signals, for speed
952 sp_node_update_handles(node, false);
953 }
955 /**
956 * Call sp_node_moveto() for node selection and handle possible snapping.
957 */
958 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
959 bool const snap = true)
960 {
961 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
962 NR::Point delta(dx, dy);
963 NR::Point best_pt = delta;
965 if (snap) {
966 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
967 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
968 NR::Point p = n->pos + delta;
969 for (int dim = 0; dim < 2; dim++) {
970 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
971 Inkscape::Snapper::SNAP_POINT, p,
972 NR::Dim2(dim), nodepath->path);
973 if (dist < best[dim]) {
974 best[dim] = dist;
975 best_pt[dim] = p[dim] - n->pos[dim];
976 }
977 }
978 }
979 }
981 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
982 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
983 sp_node_moveto(n, n->pos + best_pt);
984 }
986 // do not update repr here so that node dragging is acceptably fast
987 update_object(nodepath);
988 }
990 /**
991 * Move node selection to point, adjust its and neighbouring handles,
992 * handle possible snapping, and commit the change with possible undo.
993 */
994 void
995 sp_node_selected_move(gdouble dx, gdouble dy)
996 {
997 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
998 if (!nodepath) return;
1000 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1002 if (dx == 0) {
1003 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1004 } else if (dy == 0) {
1005 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1006 } else {
1007 sp_nodepath_update_repr(nodepath);
1008 }
1009 }
1011 /**
1012 * Move node selection off screen and commit the change.
1013 */
1014 void
1015 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1016 {
1017 // borrowed from sp_selection_move_screen in selection-chemistry.c
1018 // we find out the current zoom factor and divide deltas by it
1019 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1021 gdouble zoom = desktop->current_zoom();
1022 gdouble zdx = dx / zoom;
1023 gdouble zdy = dy / zoom;
1025 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1026 if (!nodepath) return;
1028 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1030 if (dx == 0) {
1031 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1032 } else if (dy == 0) {
1033 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1034 } else {
1035 sp_nodepath_update_repr(nodepath);
1036 }
1037 }
1039 /** If they don't yet exist, creates knot and line for the given side of the node */
1040 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1041 {
1042 if (!side->knot) {
1043 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"));
1045 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1046 side->knot->setSize (7);
1047 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1048 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1049 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1050 sp_knot_update_ctrl(side->knot);
1052 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1053 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1054 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1055 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1056 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1057 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1058 }
1060 if (!side->line) {
1061 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1062 SP_TYPE_CTRLLINE, NULL);
1063 }
1064 }
1066 /**
1067 * Ensure the given handle of the node is visible/invisible, update its screen position
1068 */
1069 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1070 {
1071 g_assert(node != NULL);
1073 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1074 NRPathcode code = sp_node_path_code_from_side(node, side);
1076 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1078 if (show_handle) {
1079 if (!side->knot) { // No handle knot at all
1080 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1081 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1082 side->knot->pos = side->pos;
1083 if (side->knot->item)
1084 SP_CTRL(side->knot->item)->moveto(side->pos);
1085 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1086 sp_knot_show(side->knot);
1087 } else {
1088 if (side->knot->pos != side->pos) { // only if it's really moved
1089 if (fire_move_signals) {
1090 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1091 } else {
1092 sp_knot_moveto(side->knot, &side->pos);
1093 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1094 }
1095 }
1096 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1097 sp_knot_show(side->knot);
1098 }
1099 }
1100 sp_canvas_item_show(side->line);
1101 } else {
1102 if (side->knot) {
1103 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1104 sp_knot_hide(side->knot);
1105 }
1106 }
1107 if (side->line) {
1108 sp_canvas_item_hide(side->line);
1109 }
1110 }
1111 }
1113 /**
1114 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1115 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1116 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1117 * updated; otherwise, just move the knots silently (used in batch moves).
1118 */
1119 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1120 {
1121 g_assert(node != NULL);
1123 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1124 sp_knot_show(node->knot);
1125 }
1127 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1128 if (fire_move_signals)
1129 sp_knot_set_position(node->knot, &node->pos, 0);
1130 else
1131 sp_knot_moveto(node->knot, &node->pos);
1132 }
1134 gboolean show_handles = node->selected;
1135 if (node->p.other != NULL) {
1136 if (node->p.other->selected) show_handles = TRUE;
1137 }
1138 if (node->n.other != NULL) {
1139 if (node->n.other->selected) show_handles = TRUE;
1140 }
1142 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1143 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1144 }
1146 /**
1147 * Call sp_node_update_handles() for all nodes on subpath.
1148 */
1149 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1150 {
1151 g_assert(subpath != NULL);
1153 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1154 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1155 }
1156 }
1158 /**
1159 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1160 */
1161 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1162 {
1163 g_assert(nodepath != NULL);
1165 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1166 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1167 }
1168 }
1170 /**
1171 * Adds all selected nodes in nodepath to list.
1172 */
1173 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1174 {
1175 StlConv<Node *>::list(l, selected);
1176 /// \todo this adds a copying, rework when the selection becomes a stl list
1177 }
1179 /**
1180 * Align selected nodes on the specified axis.
1181 */
1182 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1183 {
1184 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1185 return;
1186 }
1188 if ( !nodepath->selected->next ) { // only one node selected
1189 return;
1190 }
1191 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1192 NR::Point dest(pNode->pos);
1193 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1194 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1195 if (pNode) {
1196 dest[axis] = pNode->pos[axis];
1197 sp_node_moveto(pNode, dest);
1198 }
1199 }
1201 sp_nodepath_update_repr(nodepath);
1202 }
1204 /// Helper struct.
1205 struct NodeSort
1206 {
1207 Inkscape::NodePath::Node *_node;
1208 NR::Coord _coord;
1209 /// \todo use vectorof pointers instead of calling copy ctor
1210 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1211 _node(node), _coord(node->pos[axis])
1212 {}
1214 };
1216 static bool operator<(NodeSort const &a, NodeSort const &b)
1217 {
1218 return (a._coord < b._coord);
1219 }
1221 /**
1222 * Distribute selected nodes on the specified axis.
1223 */
1224 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1225 {
1226 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1227 return;
1228 }
1230 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1231 return;
1232 }
1234 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1235 std::vector<NodeSort> sorted;
1236 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1237 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1238 if (pNode) {
1239 NodeSort n(pNode, axis);
1240 sorted.push_back(n);
1241 //dest[axis] = pNode->pos[axis];
1242 //sp_node_moveto(pNode, dest);
1243 }
1244 }
1245 std::sort(sorted.begin(), sorted.end());
1246 unsigned int len = sorted.size();
1247 //overall bboxes span
1248 float dist = (sorted.back()._coord -
1249 sorted.front()._coord);
1250 //new distance between each bbox
1251 float step = (dist) / (len - 1);
1252 float pos = sorted.front()._coord;
1253 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1254 it < sorted.end();
1255 it ++ )
1256 {
1257 NR::Point dest((*it)._node->pos);
1258 dest[axis] = pos;
1259 sp_node_moveto((*it)._node, dest);
1260 pos += step;
1261 }
1263 sp_nodepath_update_repr(nodepath);
1264 }
1267 /**
1268 * Call sp_nodepath_line_add_node() for all selected segments.
1269 */
1270 void
1271 sp_node_selected_add_node(void)
1272 {
1273 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1274 if (!nodepath) {
1275 return;
1276 }
1278 GList *nl = NULL;
1280 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1281 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1282 g_assert(t->selected);
1283 if (t->p.other && t->p.other->selected) {
1284 nl = g_list_prepend(nl, t);
1285 }
1286 }
1288 while (nl) {
1289 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1290 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1291 sp_nodepath_node_select(n, TRUE, FALSE);
1292 nl = g_list_remove(nl, t);
1293 }
1295 /** \todo fixme: adjust ? */
1296 sp_nodepath_update_handles(nodepath);
1298 sp_nodepath_update_repr(nodepath);
1300 sp_nodepath_update_statusbar(nodepath);
1301 }
1303 /**
1304 * Select segment nearest to point
1305 */
1306 void
1307 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1308 {
1309 if (!nodepath) {
1310 return;
1311 }
1313 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1315 //find segment to segment
1316 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1318 gboolean force = FALSE;
1319 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1320 force = TRUE;
1321 }
1322 sp_nodepath_node_select(e, (gboolean) toggle, force);
1323 if (e->p.other)
1324 sp_nodepath_node_select(e->p.other, TRUE, force);
1326 sp_nodepath_update_handles(nodepath);
1328 sp_nodepath_update_statusbar(nodepath);
1329 }
1331 /**
1332 * Add a node nearest to point
1333 */
1334 void
1335 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1336 {
1337 if (!nodepath) {
1338 return;
1339 }
1341 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1343 //find segment to split
1344 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1346 //don't know why but t seems to flip for lines
1347 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1348 position.t = 1.0 - position.t;
1349 }
1350 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1351 sp_nodepath_node_select(n, FALSE, TRUE);
1353 /* fixme: adjust ? */
1354 sp_nodepath_update_handles(nodepath);
1356 sp_nodepath_update_repr(nodepath);
1358 sp_nodepath_update_statusbar(nodepath);
1359 }
1361 /*
1362 * Adjusts a segment so that t moves by a certain delta for dragging
1363 * converts lines to curves
1364 *
1365 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1366 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1367 */
1368 void
1369 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1370 {
1371 /* feel good is an arbitrary parameter that distributes the delta between handles
1372 * if t of the drag point is less than 1/6 distance form the endpoint only
1373 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1374 */
1375 double feel_good;
1376 if (t <= 1.0 / 6.0)
1377 feel_good = 0;
1378 else if (t <= 0.5)
1379 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1380 else if (t <= 5.0 / 6.0)
1381 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1382 else
1383 feel_good = 1;
1385 //if we're dragging a line convert it to a curve
1386 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1387 sp_nodepath_set_line_type(e, NR_CURVETO);
1388 }
1390 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1391 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1392 e->p.other->n.pos += offsetcoord0;
1393 e->p.pos += offsetcoord1;
1395 // adjust handles of adjacent nodes where necessary
1396 sp_node_adjust_handle(e,1);
1397 sp_node_adjust_handle(e->p.other,-1);
1399 sp_nodepath_update_handles(e->subpath->nodepath);
1401 update_object(e->subpath->nodepath);
1403 sp_nodepath_update_statusbar(e->subpath->nodepath);
1404 }
1407 /**
1408 * Call sp_nodepath_break() for all selected segments.
1409 */
1410 void sp_node_selected_break()
1411 {
1412 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1413 if (!nodepath) return;
1415 GList *temp = NULL;
1416 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1417 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1418 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1419 if (nn == NULL) continue; // no break, no new node
1420 temp = g_list_prepend(temp, nn);
1421 }
1423 if (temp) {
1424 sp_nodepath_deselect(nodepath);
1425 }
1426 for (GList *l = temp; l != NULL; l = l->next) {
1427 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1428 }
1430 sp_nodepath_update_handles(nodepath);
1432 sp_nodepath_update_repr(nodepath);
1433 }
1435 /**
1436 * Duplicate the selected node(s).
1437 */
1438 void sp_node_selected_duplicate()
1439 {
1440 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1441 if (!nodepath) {
1442 return;
1443 }
1445 GList *temp = NULL;
1446 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1447 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1448 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1449 if (nn == NULL) continue; // could not duplicate
1450 temp = g_list_prepend(temp, nn);
1451 }
1453 if (temp) {
1454 sp_nodepath_deselect(nodepath);
1455 }
1456 for (GList *l = temp; l != NULL; l = l->next) {
1457 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1458 }
1460 sp_nodepath_update_handles(nodepath);
1462 sp_nodepath_update_repr(nodepath);
1463 }
1465 /**
1466 * Join two nodes by merging them into one.
1467 */
1468 void sp_node_selected_join()
1469 {
1470 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1471 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1473 if (g_list_length(nodepath->selected) != 2) {
1474 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1475 return;
1476 }
1478 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1479 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1481 g_assert(a != b);
1482 g_assert(a->p.other || a->n.other);
1483 g_assert(b->p.other || b->n.other);
1485 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1486 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1487 return;
1488 }
1490 /* a and b are endpoints */
1492 NR::Point c = (a->pos + b->pos) / 2;
1494 if (a->subpath == b->subpath) {
1495 Inkscape::NodePath::SubPath *sp = a->subpath;
1496 sp_nodepath_subpath_close(sp);
1498 sp_nodepath_update_handles(sp->nodepath);
1500 sp_nodepath_update_repr(nodepath);
1502 return;
1503 }
1505 /* a and b are separate subpaths */
1506 Inkscape::NodePath::SubPath *sa = a->subpath;
1507 Inkscape::NodePath::SubPath *sb = b->subpath;
1508 NR::Point p;
1509 Inkscape::NodePath::Node *n;
1510 NRPathcode code;
1511 if (a == sa->first) {
1512 p = sa->first->n.pos;
1513 code = (NRPathcode)sa->first->n.other->code;
1514 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1515 n = sa->last;
1516 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1517 n = n->p.other;
1518 while (n) {
1519 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1520 n = n->p.other;
1521 if (n == sa->first) n = NULL;
1522 }
1523 sp_nodepath_subpath_destroy(sa);
1524 sa = t;
1525 } else if (a == sa->last) {
1526 p = sa->last->p.pos;
1527 code = (NRPathcode)sa->last->code;
1528 sp_nodepath_node_destroy(sa->last);
1529 } else {
1530 code = NR_END;
1531 g_assert_not_reached();
1532 }
1534 if (b == sb->first) {
1535 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1536 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1537 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1538 }
1539 } else if (b == sb->last) {
1540 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1541 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1542 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1543 }
1544 } else {
1545 g_assert_not_reached();
1546 }
1547 /* and now destroy sb */
1549 sp_nodepath_subpath_destroy(sb);
1551 sp_nodepath_update_handles(sa->nodepath);
1553 sp_nodepath_update_repr(nodepath);
1555 sp_nodepath_update_statusbar(nodepath);
1556 }
1558 /**
1559 * Join two nodes by adding a segment between them.
1560 */
1561 void sp_node_selected_join_segment()
1562 {
1563 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1564 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1566 if (g_list_length(nodepath->selected) != 2) {
1567 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1568 return;
1569 }
1571 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1572 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1574 g_assert(a != b);
1575 g_assert(a->p.other || a->n.other);
1576 g_assert(b->p.other || b->n.other);
1578 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1579 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1580 return;
1581 }
1583 if (a->subpath == b->subpath) {
1584 Inkscape::NodePath::SubPath *sp = a->subpath;
1586 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1587 sp->closed = TRUE;
1589 sp->first->p.other = sp->last;
1590 sp->last->n.other = sp->first;
1592 sp_node_handle_mirror_p_to_n(sp->last);
1593 sp_node_handle_mirror_n_to_p(sp->first);
1595 sp->first->code = sp->last->code;
1596 sp->first = sp->last;
1598 sp_nodepath_update_handles(sp->nodepath);
1600 sp_nodepath_update_repr(nodepath);
1602 return;
1603 }
1605 /* a and b are separate subpaths */
1606 Inkscape::NodePath::SubPath *sa = a->subpath;
1607 Inkscape::NodePath::SubPath *sb = b->subpath;
1609 Inkscape::NodePath::Node *n;
1610 NR::Point p;
1611 NRPathcode code;
1612 if (a == sa->first) {
1613 code = (NRPathcode) sa->first->n.other->code;
1614 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1615 n = sa->last;
1616 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1617 for (n = n->p.other; n != NULL; n = n->p.other) {
1618 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1619 }
1620 sp_nodepath_subpath_destroy(sa);
1621 sa = t;
1622 } else if (a == sa->last) {
1623 code = (NRPathcode)sa->last->code;
1624 } else {
1625 code = NR_END;
1626 g_assert_not_reached();
1627 }
1629 if (b == sb->first) {
1630 n = sb->first;
1631 sp_node_handle_mirror_p_to_n(sa->last);
1632 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1633 sp_node_handle_mirror_n_to_p(sa->last);
1634 for (n = n->n.other; n != NULL; n = n->n.other) {
1635 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1636 }
1637 } else if (b == sb->last) {
1638 n = sb->last;
1639 sp_node_handle_mirror_p_to_n(sa->last);
1640 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1641 sp_node_handle_mirror_n_to_p(sa->last);
1642 for (n = n->p.other; n != NULL; n = n->p.other) {
1643 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1644 }
1645 } else {
1646 g_assert_not_reached();
1647 }
1648 /* and now destroy sb */
1650 sp_nodepath_subpath_destroy(sb);
1652 sp_nodepath_update_handles(sa->nodepath);
1654 sp_nodepath_update_repr(nodepath);
1655 }
1657 /**
1658 * Delete one or more selected nodes.
1659 */
1660 void sp_node_selected_delete()
1661 {
1662 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1663 if (!nodepath) return;
1664 if (!nodepath->selected) return;
1666 /** \todo fixme: do it the right way */
1667 while (nodepath->selected) {
1668 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1669 sp_nodepath_node_destroy(node);
1670 }
1673 //clean up the nodepath (such as for trivial subpaths)
1674 sp_nodepath_cleanup(nodepath);
1676 sp_nodepath_update_handles(nodepath);
1678 // if the entire nodepath is removed, delete the selected object.
1679 if (nodepath->subpaths == NULL ||
1680 sp_nodepath_get_node_count(nodepath) < 2) {
1681 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1682 sp_nodepath_destroy(nodepath);
1683 sp_selection_delete();
1684 sp_document_done (document);
1685 return;
1686 }
1688 sp_nodepath_update_repr(nodepath);
1690 sp_nodepath_update_statusbar(nodepath);
1691 }
1693 /**
1694 * Delete one or more segments between two selected nodes.
1695 * This is the code for 'split'.
1696 */
1697 void
1698 sp_node_selected_delete_segment(void)
1699 {
1700 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1701 Inkscape::NodePath::Node *curr, *next; //Iterators
1703 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1704 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1706 if (g_list_length(nodepath->selected) != 2) {
1707 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1708 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1709 return;
1710 }
1712 //Selected nodes, not inclusive
1713 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1714 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1716 if ( ( a==b) || //same node
1717 (a->subpath != b->subpath ) || //not the same path
1718 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1719 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1720 {
1721 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1722 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1723 return;
1724 }
1726 //###########################################
1727 //# BEGIN EDITS
1728 //###########################################
1729 //##################################
1730 //# CLOSED PATH
1731 //##################################
1732 if (a->subpath->closed) {
1735 gboolean reversed = FALSE;
1737 //Since we can go in a circle, we need to find the shorter distance.
1738 // a->b or b->a
1739 start = end = NULL;
1740 int distance = 0;
1741 int minDistance = 0;
1742 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1743 if (curr==b) {
1744 //printf("a to b:%d\n", distance);
1745 start = a;//go from a to b
1746 end = b;
1747 minDistance = distance;
1748 //printf("A to B :\n");
1749 break;
1750 }
1751 distance++;
1752 }
1754 //try again, the other direction
1755 distance = 0;
1756 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1757 if (curr==a) {
1758 //printf("b to a:%d\n", distance);
1759 if (distance < minDistance) {
1760 start = b; //we go from b to a
1761 end = a;
1762 reversed = TRUE;
1763 //printf("B to A\n");
1764 }
1765 break;
1766 }
1767 distance++;
1768 }
1771 //Copy everything from 'end' to 'start' to a new subpath
1772 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1773 for (curr=end ; curr ; curr=curr->n.other) {
1774 NRPathcode code = (NRPathcode) curr->code;
1775 if (curr == end)
1776 code = NR_MOVETO;
1777 sp_nodepath_node_new(t, NULL,
1778 (Inkscape::NodePath::NodeType)curr->type, code,
1779 &curr->p.pos, &curr->pos, &curr->n.pos);
1780 if (curr == start)
1781 break;
1782 }
1783 sp_nodepath_subpath_destroy(a->subpath);
1786 }
1790 //##################################
1791 //# OPEN PATH
1792 //##################################
1793 else {
1795 //We need to get the direction of the list between A and B
1796 //Can we walk from a to b?
1797 start = end = NULL;
1798 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1799 if (curr==b) {
1800 start = a; //did it! we go from a to b
1801 end = b;
1802 //printf("A to B\n");
1803 break;
1804 }
1805 }
1806 if (!start) {//didn't work? let's try the other direction
1807 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1808 if (curr==a) {
1809 start = b; //did it! we go from b to a
1810 end = a;
1811 //printf("B to A\n");
1812 break;
1813 }
1814 }
1815 }
1816 if (!start) {
1817 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1818 _("Cannot find path between nodes."));
1819 return;
1820 }
1824 //Copy everything after 'end' to a new subpath
1825 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1826 for (curr=end ; curr ; curr=curr->n.other) {
1827 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1828 &curr->p.pos, &curr->pos, &curr->n.pos);
1829 }
1831 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1832 for (curr = start->n.other ; curr ; curr=next) {
1833 next = curr->n.other;
1834 sp_nodepath_node_destroy(curr);
1835 }
1837 }
1838 //###########################################
1839 //# END EDITS
1840 //###########################################
1842 //clean up the nodepath (such as for trivial subpaths)
1843 sp_nodepath_cleanup(nodepath);
1845 sp_nodepath_update_handles(nodepath);
1847 sp_nodepath_update_repr(nodepath);
1849 // if the entire nodepath is removed, delete the selected object.
1850 if (nodepath->subpaths == NULL ||
1851 sp_nodepath_get_node_count(nodepath) < 2) {
1852 sp_nodepath_destroy(nodepath);
1853 sp_selection_delete();
1854 return;
1855 }
1857 sp_nodepath_update_statusbar(nodepath);
1858 }
1860 /**
1861 * Call sp_nodepath_set_line() for all selected segments.
1862 */
1863 void
1864 sp_node_selected_set_line_type(NRPathcode code)
1865 {
1866 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1867 if (nodepath == NULL) return;
1869 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1870 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1871 g_assert(n->selected);
1872 if (n->p.other && n->p.other->selected) {
1873 sp_nodepath_set_line_type(n, code);
1874 }
1875 }
1877 sp_nodepath_update_repr(nodepath);
1878 }
1880 /**
1881 * Call sp_nodepath_convert_node_type() for all selected nodes.
1882 */
1883 void
1884 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1885 {
1886 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1887 if (nodepath == NULL) return;
1889 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1890 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1891 }
1893 sp_nodepath_update_repr(nodepath);
1894 }
1896 /**
1897 * Change select status of node, update its own and neighbour handles.
1898 */
1899 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1900 {
1901 node->selected = selected;
1903 if (selected) {
1904 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1905 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1906 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1907 sp_knot_update_ctrl(node->knot);
1908 } else {
1909 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1910 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1911 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1912 sp_knot_update_ctrl(node->knot);
1913 }
1915 sp_node_update_handles(node);
1916 if (node->n.other) sp_node_update_handles(node->n.other);
1917 if (node->p.other) sp_node_update_handles(node->p.other);
1918 }
1920 /**
1921 \brief Select a node
1922 \param node The node to select
1923 \param incremental If true, add to selection, otherwise deselect others
1924 \param override If true, always select this node, otherwise toggle selected status
1925 */
1926 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1927 {
1928 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1930 if (incremental) {
1931 if (override) {
1932 if (!g_list_find(nodepath->selected, node)) {
1933 nodepath->selected = g_list_prepend(nodepath->selected, node);
1934 }
1935 sp_node_set_selected(node, TRUE);
1936 } else { // toggle
1937 if (node->selected) {
1938 g_assert(g_list_find(nodepath->selected, node));
1939 nodepath->selected = g_list_remove(nodepath->selected, node);
1940 } else {
1941 g_assert(!g_list_find(nodepath->selected, node));
1942 nodepath->selected = g_list_prepend(nodepath->selected, node);
1943 }
1944 sp_node_set_selected(node, !node->selected);
1945 }
1946 } else {
1947 sp_nodepath_deselect(nodepath);
1948 nodepath->selected = g_list_prepend(nodepath->selected, node);
1949 sp_node_set_selected(node, TRUE);
1950 }
1952 sp_nodepath_update_statusbar(nodepath);
1953 }
1956 /**
1957 \brief Deselect all nodes in the nodepath
1958 */
1959 void
1960 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1961 {
1962 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1964 while (nodepath->selected) {
1965 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1966 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1967 }
1968 sp_nodepath_update_statusbar(nodepath);
1969 }
1971 /**
1972 \brief Select or invert selection of all nodes in the nodepath
1973 */
1974 void
1975 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1976 {
1977 if (!nodepath) return;
1979 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1980 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1981 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1982 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1983 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1984 }
1985 }
1986 }
1988 /**
1989 * If nothing selected, does the same as sp_nodepath_select_all();
1990 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1991 * (i.e., similar to "select all in layer", with the "selected" subpaths
1992 * being treated as "layers" in the path).
1993 */
1994 void
1995 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1996 {
1997 if (!nodepath) return;
1999 if (g_list_length (nodepath->selected) == 0) {
2000 sp_nodepath_select_all (nodepath, invert);
2001 return;
2002 }
2004 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2005 GSList *subpaths = NULL;
2007 for (GList *l = copy; l != NULL; l = l->next) {
2008 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2009 Inkscape::NodePath::SubPath *subpath = n->subpath;
2010 if (!g_slist_find (subpaths, subpath))
2011 subpaths = g_slist_prepend (subpaths, subpath);
2012 }
2014 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2015 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2016 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2017 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2018 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2019 }
2020 }
2022 g_slist_free (subpaths);
2023 g_list_free (copy);
2024 }
2026 /**
2027 * \brief Select the node after the last selected; if none is selected,
2028 * select the first within path.
2029 */
2030 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2031 {
2032 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2034 Inkscape::NodePath::Node *last = NULL;
2035 if (nodepath->selected) {
2036 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2037 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2038 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2039 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2040 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2041 if (node->selected) {
2042 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2043 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2044 if (spl->next) { // there's a next subpath
2045 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2046 last = subpath_next->first;
2047 } else if (spl->prev) { // there's a previous subpath
2048 last = NULL; // to be set later to the first node of first subpath
2049 } else {
2050 last = node->n.other;
2051 }
2052 } else {
2053 last = node->n.other;
2054 }
2055 } else {
2056 if (node->n.other) {
2057 last = node->n.other;
2058 } else {
2059 if (spl->next) { // there's a next subpath
2060 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2061 last = subpath_next->first;
2062 } else if (spl->prev) { // there's a previous subpath
2063 last = NULL; // to be set later to the first node of first subpath
2064 } else {
2065 last = (Inkscape::NodePath::Node *) subpath->first;
2066 }
2067 }
2068 }
2069 }
2070 }
2071 }
2072 sp_nodepath_deselect(nodepath);
2073 }
2075 if (last) { // there's at least one more node after selected
2076 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2077 } else { // no more nodes, select the first one in first subpath
2078 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2079 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2080 }
2081 }
2083 /**
2084 * \brief Select the node before the first selected; if none is selected,
2085 * select the last within path
2086 */
2087 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2088 {
2089 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2091 Inkscape::NodePath::Node *last = NULL;
2092 if (nodepath->selected) {
2093 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2094 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2095 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2096 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2097 if (node->selected) {
2098 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2099 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2100 if (spl->prev) { // there's a prev subpath
2101 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2102 last = subpath_prev->last;
2103 } else if (spl->next) { // there's a next subpath
2104 last = NULL; // to be set later to the last node of last subpath
2105 } else {
2106 last = node->p.other;
2107 }
2108 } else {
2109 last = node->p.other;
2110 }
2111 } else {
2112 if (node->p.other) {
2113 last = node->p.other;
2114 } else {
2115 if (spl->prev) { // there's a prev subpath
2116 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2117 last = subpath_prev->last;
2118 } else if (spl->next) { // there's a next subpath
2119 last = NULL; // to be set later to the last node of last subpath
2120 } else {
2121 last = (Inkscape::NodePath::Node *) subpath->last;
2122 }
2123 }
2124 }
2125 }
2126 }
2127 }
2128 sp_nodepath_deselect(nodepath);
2129 }
2131 if (last) { // there's at least one more node before selected
2132 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2133 } else { // no more nodes, select the last one in last subpath
2134 GList *spl = g_list_last(nodepath->subpaths);
2135 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2136 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2137 }
2138 }
2140 /**
2141 * \brief Select all nodes that are within the rectangle.
2142 */
2143 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2144 {
2145 if (!incremental) {
2146 sp_nodepath_deselect(nodepath);
2147 }
2149 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2150 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2151 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2152 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2154 if (b.contains(node->pos)) {
2155 sp_nodepath_node_select(node, TRUE, TRUE);
2156 }
2157 }
2158 }
2159 }
2161 /**
2162 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2163 */
2164 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2165 {
2166 if (!nodepath->selected) {
2167 return NULL;
2168 }
2170 GList *r = NULL;
2171 guint i = 0;
2172 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2173 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2174 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2175 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2176 i++;
2177 if (node->selected) {
2178 r = g_list_append(r, GINT_TO_POINTER(i));
2179 }
2180 }
2181 }
2182 return r;
2183 }
2185 /**
2186 \brief Restores selection by selecting nodes whose positions are in the list
2187 */
2188 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2189 {
2190 sp_nodepath_deselect(nodepath);
2192 guint i = 0;
2193 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2194 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2195 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2196 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2197 i++;
2198 if (g_list_find(r, GINT_TO_POINTER(i))) {
2199 sp_nodepath_node_select(node, TRUE, TRUE);
2200 }
2201 }
2202 }
2204 }
2206 /**
2207 \brief Adjusts handle according to node type and line code.
2208 */
2209 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2210 {
2211 double len, otherlen, linelen;
2213 g_assert(node);
2215 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2216 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2218 /** \todo fixme: */
2219 if (me->other == NULL) return;
2220 if (other->other == NULL) return;
2222 /* I have line */
2224 NRPathcode mecode, ocode;
2225 if (which_adjust == 1) {
2226 mecode = (NRPathcode)me->other->code;
2227 ocode = (NRPathcode)node->code;
2228 } else {
2229 mecode = (NRPathcode)node->code;
2230 ocode = (NRPathcode)other->other->code;
2231 }
2233 if (mecode == NR_LINETO) return;
2235 /* I am curve */
2237 if (other->other == NULL) return;
2239 /* Other has line */
2241 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2243 NR::Point delta;
2244 if (ocode == NR_LINETO) {
2245 /* other is lineto, we are either smooth or symm */
2246 Inkscape::NodePath::Node *othernode = other->other;
2247 len = NR::L2(me->pos - node->pos);
2248 delta = node->pos - othernode->pos;
2249 linelen = NR::L2(delta);
2250 if (linelen < 1e-18)
2251 return;
2252 me->pos = node->pos + (len / linelen)*delta;
2253 return;
2254 }
2256 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2258 me->pos = 2 * node->pos - other->pos;
2259 return;
2260 }
2262 /* We are smooth */
2264 len = NR::L2(me->pos - node->pos);
2265 delta = other->pos - node->pos;
2266 otherlen = NR::L2(delta);
2267 if (otherlen < 1e-18) return;
2269 me->pos = node->pos - (len / otherlen) * delta;
2270 }
2272 /**
2273 \brief Adjusts both handles according to node type and line code
2274 */
2275 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2276 {
2277 g_assert(node);
2279 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2281 /* we are either smooth or symm */
2283 if (node->p.other == NULL) return;
2285 if (node->n.other == NULL) return;
2287 if (node->code == NR_LINETO) {
2288 if (node->n.other->code == NR_LINETO) return;
2289 sp_node_adjust_handle(node, 1);
2290 return;
2291 }
2293 if (node->n.other->code == NR_LINETO) {
2294 if (node->code == NR_LINETO) return;
2295 sp_node_adjust_handle(node, -1);
2296 return;
2297 }
2299 /* both are curves */
2300 NR::Point const delta( node->n.pos - node->p.pos );
2302 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2303 node->p.pos = node->pos - delta / 2;
2304 node->n.pos = node->pos + delta / 2;
2305 return;
2306 }
2308 /* We are smooth */
2309 double plen = NR::L2(node->p.pos - node->pos);
2310 if (plen < 1e-18) return;
2311 double nlen = NR::L2(node->n.pos - node->pos);
2312 if (nlen < 1e-18) return;
2313 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2314 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2315 }
2317 /**
2318 * Node event callback.
2319 */
2320 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2321 {
2322 gboolean ret = FALSE;
2323 switch (event->type) {
2324 case GDK_ENTER_NOTIFY:
2325 active_node = n;
2326 break;
2327 case GDK_LEAVE_NOTIFY:
2328 active_node = NULL;
2329 break;
2330 case GDK_KEY_PRESS:
2331 switch (get_group0_keyval (&event->key)) {
2332 case GDK_space:
2333 if (event->key.state & GDK_BUTTON1_MASK) {
2334 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2335 stamp_repr(nodepath);
2336 ret = TRUE;
2337 }
2338 break;
2339 default:
2340 break;
2341 }
2342 break;
2343 default:
2344 break;
2345 }
2347 return ret;
2348 }
2350 /**
2351 * Handle keypress on node; directly called.
2352 */
2353 gboolean node_key(GdkEvent *event)
2354 {
2355 Inkscape::NodePath::Path *np;
2357 // there is no way to verify nodes so set active_node to nil when deleting!!
2358 if (active_node == NULL) return FALSE;
2360 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2361 gint ret = FALSE;
2362 switch (get_group0_keyval (&event->key)) {
2363 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2364 case GDK_BackSpace:
2365 np = active_node->subpath->nodepath;
2366 sp_nodepath_node_destroy(active_node);
2367 sp_nodepath_update_repr(np);
2368 active_node = NULL;
2369 ret = TRUE;
2370 break;
2371 case GDK_c:
2372 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2373 ret = TRUE;
2374 break;
2375 case GDK_s:
2376 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2377 ret = TRUE;
2378 break;
2379 case GDK_y:
2380 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2381 ret = TRUE;
2382 break;
2383 case GDK_b:
2384 sp_nodepath_node_break(active_node);
2385 ret = TRUE;
2386 break;
2387 }
2388 return ret;
2389 }
2390 return FALSE;
2391 }
2393 /**
2394 * Mouseclick on node callback.
2395 */
2396 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2397 {
2398 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2400 if (state & GDK_CONTROL_MASK) {
2401 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2403 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2404 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2405 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2406 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2407 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2408 } else {
2409 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2410 }
2411 sp_nodepath_update_repr(nodepath);
2412 sp_nodepath_update_statusbar(nodepath);
2414 } else { //ctrl+alt+click: delete node
2415 sp_nodepath_node_destroy(n);
2416 //clean up the nodepath (such as for trivial subpaths)
2417 sp_nodepath_cleanup(nodepath);
2419 // if the entire nodepath is removed, delete the selected object.
2420 if (nodepath->subpaths == NULL ||
2421 sp_nodepath_get_node_count(nodepath) < 2) {
2422 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2423 sp_nodepath_destroy(nodepath);
2424 sp_selection_delete();
2425 sp_document_done (document);
2427 } else {
2428 sp_nodepath_update_handles(nodepath);
2429 sp_nodepath_update_repr(nodepath);
2430 sp_nodepath_update_statusbar(nodepath);
2431 }
2432 }
2434 } else {
2435 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2436 }
2437 }
2439 /**
2440 * Mouse grabbed node callback.
2441 */
2442 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2443 {
2444 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2446 n->origin = knot->pos;
2448 if (!n->selected) {
2449 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2450 }
2451 }
2453 /**
2454 * Mouse ungrabbed node callback.
2455 */
2456 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2457 {
2458 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2460 n->dragging_out = NULL;
2462 sp_nodepath_update_repr(n->subpath->nodepath);
2463 }
2465 /**
2466 * The point on a line, given by its angle, closest to the given point.
2467 * \param p A point.
2468 * \param a Angle of the line; it is assumed to go through coordinate origin.
2469 * \param closest Pointer to the point struct where the result is stored.
2470 * \todo FIXME: use dot product perhaps?
2471 */
2472 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2473 {
2474 if (a == HUGE_VAL) { // vertical
2475 *closest = NR::Point(0, (*p)[NR::Y]);
2476 } else {
2477 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2478 (*closest)[NR::Y] = a * (*closest)[NR::X];
2479 }
2480 }
2482 /**
2483 * Distance from the point to a line given by its angle.
2484 * \param p A point.
2485 * \param a Angle of the line; it is assumed to go through coordinate origin.
2486 */
2487 static double point_line_distance(NR::Point *p, double a)
2488 {
2489 NR::Point c;
2490 point_line_closest(p, a, &c);
2491 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]));
2492 }
2494 /**
2495 * Callback for node "request" signal.
2496 * \todo fixme: This goes to "moved" event? (lauris)
2497 */
2498 static gboolean
2499 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2500 {
2501 double yn, xn, yp, xp;
2502 double an, ap, na, pa;
2503 double d_an, d_ap, d_na, d_pa;
2504 gboolean collinear = FALSE;
2505 NR::Point c;
2506 NR::Point pr;
2508 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2510 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2511 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2513 NR::Point mouse = (*p);
2515 if (!n->dragging_out) {
2516 // This is the first drag-out event; find out which handle to drag out
2517 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2518 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2520 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2521 return FALSE;
2523 Inkscape::NodePath::NodeSide *opposite;
2524 if (appr_p > appr_n) { // closer to p
2525 n->dragging_out = &n->p;
2526 opposite = &n->n;
2527 n->code = NR_CURVETO;
2528 } else if (appr_p < appr_n) { // closer to n
2529 n->dragging_out = &n->n;
2530 opposite = &n->p;
2531 n->n.other->code = NR_CURVETO;
2532 } else { // p and n nodes are the same
2533 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2534 n->dragging_out = &n->p;
2535 opposite = &n->n;
2536 n->code = NR_CURVETO;
2537 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2538 n->dragging_out = &n->n;
2539 opposite = &n->p;
2540 n->n.other->code = NR_CURVETO;
2541 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2542 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);
2543 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);
2544 if (appr_other_p > appr_other_n) { // closer to other's p handle
2545 n->dragging_out = &n->n;
2546 opposite = &n->p;
2547 n->n.other->code = NR_CURVETO;
2548 } else { // closer to other's n handle
2549 n->dragging_out = &n->p;
2550 opposite = &n->n;
2551 n->code = NR_CURVETO;
2552 }
2553 }
2554 }
2556 // if there's another handle, make sure the one we drag out starts parallel to it
2557 if (opposite->pos != n->pos) {
2558 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2559 }
2561 // knots might not be created yet!
2562 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2563 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2564 }
2566 // pass this on to the handle-moved callback
2567 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2568 sp_node_update_handles(n);
2569 return TRUE;
2570 }
2572 if (state & GDK_CONTROL_MASK) { // constrained motion
2574 // calculate relative distances of handles
2575 // n handle:
2576 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2577 xn = n->n.pos[NR::X] - n->pos[NR::X];
2578 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2579 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2580 if (n->n.other) { // if there is the next point
2581 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2582 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2583 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2584 }
2585 }
2586 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2587 if (yn < 0) { xn = -xn; yn = -yn; }
2589 // p handle:
2590 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2591 xp = n->p.pos[NR::X] - n->pos[NR::X];
2592 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2593 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2594 if (n->p.other) {
2595 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2596 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2597 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2598 }
2599 }
2600 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2601 if (yp < 0) { xp = -xp; yp = -yp; }
2603 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2604 // sliding on handles, only if at least one of the handles is non-vertical
2605 // (otherwise it's the same as ctrl+drag anyway)
2607 // calculate angles of the handles
2608 if (xn == 0) {
2609 if (yn == 0) { // no handle, consider it the continuation of the other one
2610 an = 0;
2611 collinear = TRUE;
2612 }
2613 else an = 0; // vertical; set the angle to horizontal
2614 } else an = yn/xn;
2616 if (xp == 0) {
2617 if (yp == 0) { // no handle, consider it the continuation of the other one
2618 ap = an;
2619 }
2620 else ap = 0; // vertical; set the angle to horizontal
2621 } else ap = yp/xp;
2623 if (collinear) an = ap;
2625 // angles of the perpendiculars; HUGE_VAL means vertical
2626 if (an == 0) na = HUGE_VAL; else na = -1/an;
2627 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2629 //g_print("an %g ap %g\n", an, ap);
2631 // mouse point relative to the node's original pos
2632 pr = (*p) - n->origin;
2634 // distances to the four lines (two handles and two perpendiculars)
2635 d_an = point_line_distance(&pr, an);
2636 d_na = point_line_distance(&pr, na);
2637 d_ap = point_line_distance(&pr, ap);
2638 d_pa = point_line_distance(&pr, pa);
2640 // find out which line is the closest, save its closest point in c
2641 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2642 point_line_closest(&pr, an, &c);
2643 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2644 point_line_closest(&pr, ap, &c);
2645 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2646 point_line_closest(&pr, na, &c);
2647 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2648 point_line_closest(&pr, pa, &c);
2649 }
2651 // move the node to the closest point
2652 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2653 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2654 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2656 } else { // constraining to hor/vert
2658 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2659 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2660 } else { // snap to vert
2661 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2662 }
2663 }
2664 } else { // move freely
2665 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2666 (*p)[NR::X] - n->pos[NR::X],
2667 (*p)[NR::Y] - n->pos[NR::Y],
2668 (state & GDK_SHIFT_MASK) == 0);
2669 }
2671 n->subpath->nodepath->desktop->scroll_to_point(p);
2673 return TRUE;
2674 }
2676 /**
2677 * Node handle clicked callback.
2678 */
2679 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2680 {
2681 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2683 if (state & GDK_CONTROL_MASK) { // "delete" handle
2684 if (n->p.knot == knot) {
2685 n->p.pos = n->pos;
2686 } else if (n->n.knot == knot) {
2687 n->n.pos = n->pos;
2688 }
2689 sp_node_update_handles(n);
2690 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2691 sp_nodepath_update_repr(nodepath);
2692 sp_nodepath_update_statusbar(nodepath);
2694 } else { // just select or add to selection, depending in Shift
2695 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2696 }
2697 }
2699 /**
2700 * Node handle grabbed callback.
2701 */
2702 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2703 {
2704 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2706 if (!n->selected) {
2707 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2708 }
2710 // remember the origin point of the handle
2711 if (n->p.knot == knot) {
2712 n->p.origin = n->p.pos - n->pos;
2713 } else if (n->n.knot == knot) {
2714 n->n.origin = n->n.pos - n->pos;
2715 } else {
2716 g_assert_not_reached();
2717 }
2719 }
2721 /**
2722 * Node handle ungrabbed callback.
2723 */
2724 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2725 {
2726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2728 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2729 if (n->p.knot == knot) {
2730 n->p.origin.a = 0;
2731 sp_knot_set_position(knot, &n->p.pos, state);
2732 } else if (n->n.knot == knot) {
2733 n->n.origin.a = 0;
2734 sp_knot_set_position(knot, &n->n.pos, state);
2735 } else {
2736 g_assert_not_reached();
2737 }
2739 sp_nodepath_update_repr(n->subpath->nodepath);
2740 }
2742 /**
2743 * Node handle "request" signal callback.
2744 */
2745 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2746 {
2747 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2749 Inkscape::NodePath::NodeSide *me, *opposite;
2750 gint which;
2751 if (n->p.knot == knot) {
2752 me = &n->p;
2753 opposite = &n->n;
2754 which = -1;
2755 } else if (n->n.knot == knot) {
2756 me = &n->n;
2757 opposite = &n->p;
2758 which = 1;
2759 } else {
2760 me = opposite = NULL;
2761 which = 0;
2762 g_assert_not_reached();
2763 }
2765 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2767 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2769 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2770 /* We are smooth node adjacent with line */
2771 NR::Point const delta = *p - n->pos;
2772 NR::Coord const len = NR::L2(delta);
2773 Inkscape::NodePath::Node *othernode = opposite->other;
2774 NR::Point const ndelta = n->pos - othernode->pos;
2775 NR::Coord const linelen = NR::L2(ndelta);
2776 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2777 NR::Coord const scal = dot(delta, ndelta) / linelen;
2778 (*p) = n->pos + (scal / linelen) * ndelta;
2779 }
2780 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2781 } else {
2782 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2783 }
2785 sp_node_adjust_handle(n, -which);
2787 return FALSE;
2788 }
2790 /**
2791 * Node handle moved callback.
2792 */
2793 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2794 {
2795 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2797 Inkscape::NodePath::NodeSide *me;
2798 Inkscape::NodePath::NodeSide *other;
2799 if (n->p.knot == knot) {
2800 me = &n->p;
2801 other = &n->n;
2802 } else if (n->n.knot == knot) {
2803 me = &n->n;
2804 other = &n->p;
2805 } else {
2806 me = NULL;
2807 other = NULL;
2808 g_assert_not_reached();
2809 }
2811 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2812 Radial rme(me->pos - n->pos);
2813 Radial rother(other->pos - n->pos);
2814 Radial rnew(*p - n->pos);
2816 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2817 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2818 /* 0 interpreted as "no snapping". */
2820 // The closest PI/snaps angle, starting from zero.
2821 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2822 if (me->origin.a == HUGE_VAL) {
2823 // ortho doesn't exist: original handle was zero length.
2824 rnew.a = a_snapped;
2825 } else {
2826 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2827 * its opposite and perpendiculars). */
2828 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2830 // Snap to the closest.
2831 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2832 ? a_snapped
2833 : a_ortho );
2834 }
2835 }
2837 if (state & GDK_MOD1_MASK) {
2838 // lock handle length
2839 rnew.r = me->origin.r;
2840 }
2842 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2843 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2844 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2845 rother.a += rnew.a - rme.a;
2846 other->pos = NR::Point(rother) + n->pos;
2847 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2848 sp_knot_set_position(other->knot, &other->pos, 0);
2849 }
2851 me->pos = NR::Point(rnew) + n->pos;
2852 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2854 // this is what sp_knot_set_position does, but without emitting the signal:
2855 // we cannot emit a "moved" signal because we're now processing it
2856 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2858 knot->desktop->set_coordinate_status(me->pos);
2860 update_object(n->subpath->nodepath);
2862 /* status text */
2863 SPDesktop *desktop = n->subpath->nodepath->desktop;
2864 if (!desktop) return;
2865 SPEventContext *ec = desktop->event_context;
2866 if (!ec) return;
2867 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2868 if (!mc) return;
2870 double degrees = 180 / M_PI * rnew.a;
2871 if (degrees > 180) degrees -= 360;
2872 if (degrees < -180) degrees += 360;
2873 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2874 degrees = angle_to_compass (degrees);
2876 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2878 mc->setF(Inkscape::NORMAL_MESSAGE,
2879 _("<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);
2881 g_string_free(length, TRUE);
2882 }
2884 /**
2885 * Node handle event callback.
2886 */
2887 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2888 {
2889 gboolean ret = FALSE;
2890 switch (event->type) {
2891 case GDK_KEY_PRESS:
2892 switch (get_group0_keyval (&event->key)) {
2893 case GDK_space:
2894 if (event->key.state & GDK_BUTTON1_MASK) {
2895 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2896 stamp_repr(nodepath);
2897 ret = TRUE;
2898 }
2899 break;
2900 default:
2901 break;
2902 }
2903 break;
2904 default:
2905 break;
2906 }
2908 return ret;
2909 }
2911 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2912 Radial &rme, Radial &rother, gboolean const both)
2913 {
2914 rme.a += angle;
2915 if ( both
2916 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2917 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2918 {
2919 rother.a += angle;
2920 }
2921 }
2923 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2924 Radial &rme, Radial &rother, gboolean const both)
2925 {
2926 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2928 gdouble r;
2929 if ( both
2930 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2931 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2932 {
2933 r = MAX(rme.r, rother.r);
2934 } else {
2935 r = rme.r;
2936 }
2938 gdouble const weird_angle = atan2(norm_angle, r);
2939 /* Bulia says norm_angle is just the visible distance that the
2940 * object's end must travel on the screen. Left as 'angle' for want of
2941 * a better name.*/
2943 rme.a += weird_angle;
2944 if ( both
2945 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2946 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2947 {
2948 rother.a += weird_angle;
2949 }
2950 }
2952 /**
2953 * Rotate one node.
2954 */
2955 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2956 {
2957 Inkscape::NodePath::NodeSide *me, *other;
2958 bool both = false;
2960 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2961 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2963 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2964 me = &(n->p);
2965 other = &(n->n);
2966 } else if (!n->p.other) {
2967 me = &(n->n);
2968 other = &(n->p);
2969 } else {
2970 if (which > 0) { // right handle
2971 if (xn > xp) {
2972 me = &(n->n);
2973 other = &(n->p);
2974 } else {
2975 me = &(n->p);
2976 other = &(n->n);
2977 }
2978 } else if (which < 0){ // left 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 { // both handles
2987 me = &(n->n);
2988 other = &(n->p);
2989 both = true;
2990 }
2991 }
2993 Radial rme(me->pos - n->pos);
2994 Radial rother(other->pos - n->pos);
2996 if (screen) {
2997 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2998 } else {
2999 node_rotate_one_internal (*n, angle, rme, rother, both);
3000 }
3002 me->pos = n->pos + NR::Point(rme);
3004 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3005 other->pos = n->pos + NR::Point(rother);
3006 }
3008 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3009 // so here we just move all the knots without emitting move signals, for speed
3010 sp_node_update_handles(n, false);
3011 }
3013 /**
3014 * Rotate selected nodes.
3015 */
3016 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3017 {
3018 if (!nodepath || !nodepath->selected) return;
3020 if (g_list_length(nodepath->selected) == 1) {
3021 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3022 node_rotate_one (n, angle, which, screen);
3023 } else {
3024 // rotate as an object:
3026 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3027 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3028 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3029 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3030 box.expandTo (n->pos); // contain all selected nodes
3031 }
3033 gdouble rot;
3034 if (screen) {
3035 gdouble const zoom = nodepath->desktop->current_zoom();
3036 gdouble const zmove = angle / zoom;
3037 gdouble const r = NR::L2(box.max() - box.midpoint());
3038 rot = atan2(zmove, r);
3039 } else {
3040 rot = angle;
3041 }
3043 NR::Matrix t =
3044 NR::Matrix (NR::translate(-box.midpoint())) *
3045 NR::Matrix (NR::rotate(rot)) *
3046 NR::Matrix (NR::translate(box.midpoint()));
3048 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3049 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3050 n->pos *= t;
3051 n->n.pos *= t;
3052 n->p.pos *= t;
3053 sp_node_update_handles(n, false);
3054 }
3055 }
3057 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3058 }
3060 /**
3061 * Scale one node.
3062 */
3063 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3064 {
3065 bool both = false;
3066 Inkscape::NodePath::NodeSide *me, *other;
3068 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3069 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3071 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3072 me = &(n->p);
3073 other = &(n->n);
3074 n->code = NR_CURVETO;
3075 } else if (!n->p.other) {
3076 me = &(n->n);
3077 other = &(n->p);
3078 if (n->n.other)
3079 n->n.other->code = NR_CURVETO;
3080 } else {
3081 if (which > 0) { // right handle
3082 if (xn > xp) {
3083 me = &(n->n);
3084 other = &(n->p);
3085 if (n->n.other)
3086 n->n.other->code = NR_CURVETO;
3087 } else {
3088 me = &(n->p);
3089 other = &(n->n);
3090 n->code = NR_CURVETO;
3091 }
3092 } else if (which < 0){ // left handle
3093 if (xn <= xp) {
3094 me = &(n->n);
3095 other = &(n->p);
3096 if (n->n.other)
3097 n->n.other->code = NR_CURVETO;
3098 } else {
3099 me = &(n->p);
3100 other = &(n->n);
3101 n->code = NR_CURVETO;
3102 }
3103 } else { // both handles
3104 me = &(n->n);
3105 other = &(n->p);
3106 both = true;
3107 n->code = NR_CURVETO;
3108 if (n->n.other)
3109 n->n.other->code = NR_CURVETO;
3110 }
3111 }
3113 Radial rme(me->pos - n->pos);
3114 Radial rother(other->pos - n->pos);
3116 rme.r += grow;
3117 if (rme.r < 0) rme.r = 0;
3118 if (rme.a == HUGE_VAL) {
3119 if (me->other) { // if direction is unknown, initialize it towards the next node
3120 Radial rme_next(me->other->pos - n->pos);
3121 rme.a = rme_next.a;
3122 } else { // if there's no next, initialize to 0
3123 rme.a = 0;
3124 }
3125 }
3126 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3127 rother.r += grow;
3128 if (rother.r < 0) rother.r = 0;
3129 if (rother.a == HUGE_VAL) {
3130 rother.a = rme.a + M_PI;
3131 }
3132 }
3134 me->pos = n->pos + NR::Point(rme);
3136 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3137 other->pos = n->pos + NR::Point(rother);
3138 }
3140 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3141 // so here we just move all the knots without emitting move signals, for speed
3142 sp_node_update_handles(n, false);
3143 }
3145 /**
3146 * Scale selected nodes.
3147 */
3148 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3149 {
3150 if (!nodepath || !nodepath->selected) return;
3152 if (g_list_length(nodepath->selected) == 1) {
3153 // scale handles of the single selected node
3154 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3155 node_scale_one (n, grow, which);
3156 } else {
3157 // scale nodes as an "object":
3159 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3160 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3161 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3162 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3163 box.expandTo (n->pos); // contain all selected nodes
3164 }
3166 double scale = (box.maxExtent() + grow)/box.maxExtent();
3168 NR::Matrix t =
3169 NR::Matrix (NR::translate(-box.midpoint())) *
3170 NR::Matrix (NR::scale(scale, scale)) *
3171 NR::Matrix (NR::translate(box.midpoint()));
3173 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3174 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3175 n->pos *= t;
3176 n->n.pos *= t;
3177 n->p.pos *= t;
3178 sp_node_update_handles(n, false);
3179 }
3180 }
3182 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3183 }
3185 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3186 {
3187 if (!nodepath) return;
3188 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3189 }
3191 /**
3192 * Flip selected nodes horizontally/vertically.
3193 */
3194 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3195 {
3196 if (!nodepath || !nodepath->selected) return;
3198 if (g_list_length(nodepath->selected) == 1) {
3199 // flip handles of the single selected node
3200 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3201 double temp = n->p.pos[axis];
3202 n->p.pos[axis] = n->n.pos[axis];
3203 n->n.pos[axis] = temp;
3204 sp_node_update_handles(n, false);
3205 } else {
3206 // scale nodes as an "object":
3208 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3209 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3210 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3211 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3212 box.expandTo (n->pos); // contain all selected nodes
3213 }
3215 NR::Matrix t =
3216 NR::Matrix (NR::translate(-box.midpoint())) *
3217 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3218 NR::Matrix (NR::translate(box.midpoint()));
3220 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3221 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3222 n->pos *= t;
3223 n->n.pos *= t;
3224 n->p.pos *= t;
3225 sp_node_update_handles(n, false);
3226 }
3227 }
3229 sp_nodepath_update_repr(nodepath);
3230 }
3232 //-----------------------------------------------
3233 /**
3234 * Return new subpath under given nodepath.
3235 */
3236 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3237 {
3238 g_assert(nodepath);
3239 g_assert(nodepath->desktop);
3241 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3243 s->nodepath = nodepath;
3244 s->closed = FALSE;
3245 s->nodes = NULL;
3246 s->first = NULL;
3247 s->last = NULL;
3249 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3250 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3251 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3253 return s;
3254 }
3256 /**
3257 * Destroy nodes in subpath, then subpath itself.
3258 */
3259 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3260 {
3261 g_assert(subpath);
3262 g_assert(subpath->nodepath);
3263 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3265 while (subpath->nodes) {
3266 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3267 }
3269 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3271 g_free(subpath);
3272 }
3274 /**
3275 * Link head to tail in subpath.
3276 */
3277 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3278 {
3279 g_assert(!sp->closed);
3280 g_assert(sp->last != sp->first);
3281 g_assert(sp->first->code == NR_MOVETO);
3283 sp->closed = TRUE;
3285 //Link the head to the tail
3286 sp->first->p.other = sp->last;
3287 sp->last->n.other = sp->first;
3288 sp->last->n.pos = sp->first->n.pos;
3289 sp->first = sp->last;
3291 //Remove the extra end node
3292 sp_nodepath_node_destroy(sp->last->n.other);
3293 }
3295 /**
3296 * Open closed (loopy) subpath at node.
3297 */
3298 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3299 {
3300 g_assert(sp->closed);
3301 g_assert(n->subpath == sp);
3302 g_assert(sp->first == sp->last);
3304 /* We create new startpoint, current node will become last one */
3306 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3307 &n->pos, &n->pos, &n->n.pos);
3310 sp->closed = FALSE;
3312 //Unlink to make a head and tail
3313 sp->first = new_path;
3314 sp->last = n;
3315 n->n.other = NULL;
3316 new_path->p.other = NULL;
3317 }
3319 /**
3320 * Returns area in triangle given by points; may be negative.
3321 */
3322 inline double
3323 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3324 {
3325 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]);
3326 }
3328 /**
3329 * Return new node in subpath with given properties.
3330 * \param pos Position of node.
3331 * \param ppos Handle position in previous direction
3332 * \param npos Handle position in previous direction
3333 */
3334 Inkscape::NodePath::Node *
3335 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)
3336 {
3337 g_assert(sp);
3338 g_assert(sp->nodepath);
3339 g_assert(sp->nodepath->desktop);
3341 if (nodechunk == NULL)
3342 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3344 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3346 n->subpath = sp;
3348 if (type != Inkscape::NodePath::NODE_NONE) {
3349 // use the type from sodipodi:nodetypes
3350 n->type = type;
3351 } else {
3352 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3353 // points are (almost) collinear
3354 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3355 // endnode, or a node with a retracted handle
3356 n->type = Inkscape::NodePath::NODE_CUSP;
3357 } else {
3358 n->type = Inkscape::NodePath::NODE_SMOOTH;
3359 }
3360 } else {
3361 n->type = Inkscape::NodePath::NODE_CUSP;
3362 }
3363 }
3365 n->code = code;
3366 n->selected = FALSE;
3367 n->pos = *pos;
3368 n->p.pos = *ppos;
3369 n->n.pos = *npos;
3371 n->dragging_out = NULL;
3373 Inkscape::NodePath::Node *prev;
3374 if (next) {
3375 //g_assert(g_list_find(sp->nodes, next));
3376 prev = next->p.other;
3377 } else {
3378 prev = sp->last;
3379 }
3381 if (prev)
3382 prev->n.other = n;
3383 else
3384 sp->first = n;
3386 if (next)
3387 next->p.other = n;
3388 else
3389 sp->last = n;
3391 n->p.other = prev;
3392 n->n.other = next;
3394 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"));
3395 sp_knot_set_position(n->knot, pos, 0);
3397 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3398 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3399 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3400 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3401 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3402 sp_knot_update_ctrl(n->knot);
3404 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3405 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3406 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3407 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3408 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3409 sp_knot_show(n->knot);
3411 // We only create handle knots and lines on demand
3412 n->p.knot = NULL;
3413 n->p.line = NULL;
3414 n->n.knot = NULL;
3415 n->n.line = NULL;
3417 sp->nodes = g_list_prepend(sp->nodes, n);
3419 return n;
3420 }
3422 /**
3423 * Destroy node and its knots, link neighbors in subpath.
3424 */
3425 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3426 {
3427 g_assert(node);
3428 g_assert(node->subpath);
3429 g_assert(SP_IS_KNOT(node->knot));
3431 Inkscape::NodePath::SubPath *sp = node->subpath;
3433 if (node->selected) { // first, deselect
3434 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3435 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3436 }
3438 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3440 g_object_unref(G_OBJECT(node->knot));
3441 if (node->p.knot)
3442 g_object_unref(G_OBJECT(node->p.knot));
3443 if (node->n.knot)
3444 g_object_unref(G_OBJECT(node->n.knot));
3446 if (node->p.line)
3447 gtk_object_destroy(GTK_OBJECT(node->p.line));
3448 if (node->n.line)
3449 gtk_object_destroy(GTK_OBJECT(node->n.line));
3451 if (sp->nodes) { // there are others nodes on the subpath
3452 if (sp->closed) {
3453 if (sp->first == node) {
3454 g_assert(sp->last == node);
3455 sp->first = node->n.other;
3456 sp->last = sp->first;
3457 }
3458 node->p.other->n.other = node->n.other;
3459 node->n.other->p.other = node->p.other;
3460 } else {
3461 if (sp->first == node) {
3462 sp->first = node->n.other;
3463 sp->first->code = NR_MOVETO;
3464 }
3465 if (sp->last == node) sp->last = node->p.other;
3466 if (node->p.other) node->p.other->n.other = node->n.other;
3467 if (node->n.other) node->n.other->p.other = node->p.other;
3468 }
3469 } else { // this was the last node on subpath
3470 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3471 }
3473 g_mem_chunk_free(nodechunk, node);
3474 }
3476 /**
3477 * Returns one of the node's two sides.
3478 * \param which Indicates which side.
3479 * \return Pointer to previous node side if which==-1, next if which==1.
3480 */
3481 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3482 {
3483 g_assert(node);
3485 switch (which) {
3486 case -1:
3487 return &node->p;
3488 case 1:
3489 return &node->n;
3490 default:
3491 break;
3492 }
3494 g_assert_not_reached();
3496 return NULL;
3497 }
3499 /**
3500 * Return the other side of the node, given one of its sides.
3501 */
3502 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3503 {
3504 g_assert(node);
3506 if (me == &node->p) return &node->n;
3507 if (me == &node->n) return &node->p;
3509 g_assert_not_reached();
3511 return NULL;
3512 }
3514 /**
3515 * Return NRPathcode on the given side of the node.
3516 */
3517 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3518 {
3519 g_assert(node);
3521 if (me == &node->p) {
3522 if (node->p.other) return (NRPathcode)node->code;
3523 return NR_MOVETO;
3524 }
3526 if (me == &node->n) {
3527 if (node->n.other) return (NRPathcode)node->n.other->code;
3528 return NR_MOVETO;
3529 }
3531 g_assert_not_reached();
3533 return NR_END;
3534 }
3536 /**
3537 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3538 */
3539 Inkscape::NodePath::Node *
3540 sp_nodepath_get_node_by_index(int index)
3541 {
3542 Inkscape::NodePath::Node *e = NULL;
3544 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3545 if (!nodepath) {
3546 return e;
3547 }
3549 //find segment
3550 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3552 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3553 int n = g_list_length(sp->nodes);
3554 if (sp->closed) {
3555 n++;
3556 }
3558 //if the piece belongs to this subpath grab it
3559 //otherwise move onto the next subpath
3560 if (index < n) {
3561 e = sp->first;
3562 for (int i = 0; i < index; ++i) {
3563 e = e->n.other;
3564 }
3565 break;
3566 } else {
3567 if (sp->closed) {
3568 index -= (n+1);
3569 } else {
3570 index -= n;
3571 }
3572 }
3573 }
3575 return e;
3576 }
3578 /**
3579 * Returns plain text meaning of node type.
3580 */
3581 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3582 {
3583 unsigned retracted = 0;
3584 bool endnode = false;
3586 for (int which = -1; which <= 1; which += 2) {
3587 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3588 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3589 retracted ++;
3590 if (!side->other)
3591 endnode = true;
3592 }
3594 if (retracted == 0) {
3595 if (endnode) {
3596 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3597 return _("end node");
3598 } else {
3599 switch (node->type) {
3600 case Inkscape::NodePath::NODE_CUSP:
3601 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3602 return _("cusp");
3603 case Inkscape::NodePath::NODE_SMOOTH:
3604 // TRANSLATORS: "smooth" is an adjective here
3605 return _("smooth");
3606 case Inkscape::NodePath::NODE_SYMM:
3607 return _("symmetric");
3608 }
3609 }
3610 } else if (retracted == 1) {
3611 if (endnode) {
3612 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3613 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3614 } else {
3615 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3616 }
3617 } else {
3618 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3619 }
3621 return NULL;
3622 }
3624 /**
3625 * Handles content of statusbar as long as node tool is active.
3626 */
3627 void
3628 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3629 {
3630 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3631 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3633 gint total = 0;
3634 gint selected = 0;
3635 SPDesktop *desktop = NULL;
3637 if (nodepath) {
3638 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3639 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3640 total += g_list_length(subpath->nodes);
3641 }
3642 selected = g_list_length(nodepath->selected);
3643 desktop = nodepath->desktop;
3644 } else {
3645 desktop = SP_ACTIVE_DESKTOP;
3646 }
3648 SPEventContext *ec = desktop->event_context;
3649 if (!ec) return;
3650 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3651 if (!mc) return;
3653 if (selected == 0) {
3654 Inkscape::Selection *sel = desktop->selection;
3655 if (!sel || sel->isEmpty()) {
3656 mc->setF(Inkscape::NORMAL_MESSAGE,
3657 _("Select a single object to edit its nodes or handles."));
3658 } else {
3659 if (nodepath) {
3660 mc->setF(Inkscape::NORMAL_MESSAGE,
3661 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.",
3662 "<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.",
3663 total),
3664 total);
3665 } else {
3666 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3667 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3668 } else {
3669 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3670 }
3671 }
3672 }
3673 } else if (nodepath && selected == 1) {
3674 mc->setF(Inkscape::NORMAL_MESSAGE,
3675 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3676 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3677 total),
3678 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3679 } else {
3680 mc->setF(Inkscape::NORMAL_MESSAGE,
3681 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3682 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3683 total),
3684 selected, total, when_selected);
3685 }
3686 }
3689 /*
3690 Local Variables:
3691 mode:c++
3692 c-file-style:"stroustrup"
3693 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3694 indent-tabs-mode:nil
3695 fill-column:99
3696 End:
3697 */
3698 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :