0a66ff85be99e3af2397e2deec04f2719f580a83
1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * This code is in public domain
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include <libnr/nr-matrix-ops.h>
41 #include "splivarot.h"
42 #include "svg/svg.h"
44 class NR::Matrix;
46 /// \todo
47 /// evil evil evil. FIXME: conflict of two different Path classes!
48 /// There is a conflict in the namespace between two classes named Path.
49 /// #include "sp-flowtext.h"
50 /// #include "sp-flowregion.h"
52 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
53 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
54 GType sp_flowregion_get_type (void);
55 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
56 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
57 GType sp_flowtext_get_type (void);
58 // end evil workaround
60 #include "helper/stlport.h"
63 /// \todo fixme: Implement these via preferences */
65 #define NODE_FILL 0xbfbfbf00
66 #define NODE_STROKE 0x000000ff
67 #define NODE_FILL_HI 0xff000000
68 #define NODE_STROKE_HI 0x000000ff
69 #define NODE_FILL_SEL 0x0000ffff
70 #define NODE_STROKE_SEL 0x000000ff
71 #define NODE_FILL_SEL_HI 0xff000000
72 #define NODE_STROKE_SEL_HI 0x000000ff
73 #define KNOT_FILL 0xffffffff
74 #define KNOT_STROKE 0x000000ff
75 #define KNOT_FILL_HI 0xff000000
76 #define KNOT_STROKE_HI 0x000000ff
78 static GMemChunk *nodechunk = NULL;
80 /* Creation from object */
82 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
83 static gchar *parse_nodetypes(gchar const *types, gint length);
85 /* Object updating */
87 static void stamp_repr(Inkscape::NodePath::Path *np);
88 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
89 static gchar *create_typestr(Inkscape::NodePath::Path *np);
91 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node);
93 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
95 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
97 /* Control knot placement, if node or other knot is moved */
99 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust);
100 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node);
102 /* Knot event handlers */
104 static void node_clicked(SPKnot *knot, guint state, gpointer data);
105 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
106 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
107 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
108 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data);
109 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data);
110 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data);
111 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
113 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
115 /* Constructors and destructors */
117 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
118 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
119 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
120 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
121 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
122 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
123 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
125 /* Helpers */
127 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
128 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
129 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
131 // active_node indicates mouseover node
132 static Inkscape::NodePath::Node *active_node = NULL;
134 /**
135 * \brief Creates new nodepath from item
136 */
137 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
138 {
139 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
141 /** \todo
142 * FIXME: remove this. We don't want to edit paths inside flowtext.
143 * Instead we will build our flowtext with cloned paths, so that the
144 * real paths are outside the flowtext and thus editable as usual.
145 */
146 if (SP_IS_FLOWTEXT(item)) {
147 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
148 if SP_IS_FLOWREGION(child) {
149 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
150 if (grandchild && SP_IS_PATH(grandchild)) {
151 item = SP_ITEM(grandchild);
152 break;
153 }
154 }
155 }
156 }
158 if (!SP_IS_PATH(item))
159 return NULL;
160 SPPath *path = SP_PATH(item);
161 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
162 if (curve == NULL)
163 return NULL;
165 NArtBpath *bpath = sp_curve_first_bpath(curve);
166 gint length = curve->end;
167 if (length == 0)
168 return NULL; // prevent crash for one-node paths
170 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
171 gchar *typestr = parse_nodetypes(nodetypes, length);
173 //Create new nodepath
174 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
175 if (!np)
176 return NULL;
178 // Set defaults
179 np->desktop = desktop;
180 np->path = path;
181 np->subpaths = NULL;
182 np->selected = NULL;
183 np->nodeContext = NULL; //Let the context that makes this set it
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 /* Now the bitchy part (lauris) */
197 NArtBpath *b = bpath;
199 while (b->code != NR_END) {
200 b = subpath_from_bpath(np, b, typestr + (b - bpath));
201 }
203 g_free(typestr);
204 sp_curve_unref(curve);
206 np->livarot_path = Path_for_item(item, true, true);
207 if (np->livarot_path)
208 np->livarot_path->ConvertWithBackData(0.01);
210 return np;
211 }
213 /**
214 * Destroys nodepath's subpaths, then itself, also tell context about it.
215 */
216 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
218 if (!np) //soft fail, like delete
219 return;
221 while (np->subpaths) {
222 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
223 }
225 //Inform the context that made me, if any, that I am gone.
226 if (np->nodeContext)
227 np->nodeContext->nodepath = NULL;
229 g_assert(!np->selected);
231 if (np->livarot_path) {
232 delete np->livarot_path;
233 np->livarot_path = NULL;
234 }
236 np->desktop = NULL;
238 g_free(np);
239 }
242 /**
243 * Return the node count of a given NodeSubPath.
244 */
245 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
246 {
247 if (!subpath)
248 return 0;
249 gint nodeCount = g_list_length(subpath->nodes);
250 return nodeCount;
251 }
253 /**
254 * Return the node count of a given NodePath.
255 */
256 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
257 {
258 if (!np)
259 return 0;
260 gint nodeCount = 0;
261 for (GList *item = np->subpaths ; item ; item=item->next) {
262 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
263 nodeCount += g_list_length(subpath->nodes);
264 }
265 return nodeCount;
266 }
269 /**
270 * Clean up a nodepath after editing.
271 *
272 * Currently we are deleting trivial subpaths.
273 */
274 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
275 {
276 GList *badSubPaths = NULL;
278 //Check all subpaths to be >=2 nodes
279 for (GList *l = nodepath->subpaths; l ; l=l->next) {
280 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
281 if (sp_nodepath_subpath_get_node_count(sp)<2)
282 badSubPaths = g_list_append(badSubPaths, sp);
283 }
285 //Delete them. This second step is because sp_nodepath_subpath_destroy()
286 //also removes the subpath from nodepath->subpaths
287 for (GList *l = badSubPaths; l ; l=l->next) {
288 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
289 sp_nodepath_subpath_destroy(sp);
290 }
292 g_list_free(badSubPaths);
293 }
297 /**
298 * \brief Returns true if the argument nodepath and the d attribute in
299 * its repr do not match.
300 *
301 * This may happen if repr was changed in, e.g., XML editor or by undo.
302 *
303 * \todo
304 * UGLY HACK, think how we can eliminate it.
305 */
306 gboolean nodepath_repr_d_changed(Inkscape::NodePath::Path *np, char const *newd)
307 {
308 g_assert(np);
310 SPCurve *curve = create_curve(np);
312 gchar *svgpath = sp_svg_write_path(curve->bpath);
314 char const *attr_d = ( newd
315 ? newd
316 : SP_OBJECT(np->path)->repr->attribute("d") );
318 gboolean ret;
319 if (attr_d && svgpath)
320 ret = strcmp(attr_d, svgpath);
321 else
322 ret = TRUE;
324 g_free(svgpath);
325 sp_curve_unref(curve);
327 return ret;
328 }
330 /**
331 * \brief Returns true if the argument nodepath and the sodipodi:nodetypes
332 * attribute in its repr do not match.
333 *
334 * This may happen if repr was changed in, e.g., the XML editor or by undo.
335 */
336 gboolean nodepath_repr_typestr_changed(Inkscape::NodePath::Path *np, char const *newtypestr)
337 {
338 g_assert(np);
339 gchar *typestr = create_typestr(np);
340 char const *attr_typestr = ( newtypestr
341 ? newtypestr
342 : SP_OBJECT(np->path)->repr->attribute("sodipodi:nodetypes") );
343 gboolean const ret = (attr_typestr && strcmp(attr_typestr, typestr));
345 g_free(typestr);
347 return ret;
348 }
350 /**
351 * Create new nodepath from b, make it subpath of np.
352 * \param t The node type.
353 * \todo Fixme: t should be a proper type, rather than gchar
354 */
355 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
356 {
357 NR::Point ppos, pos, npos;
359 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
361 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
362 bool const closed = (b->code == NR_MOVETO);
364 pos = NR::Point(b->x3, b->y3) * np->i2d;
365 if (b[1].code == NR_CURVETO) {
366 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
367 } else {
368 npos = pos;
369 }
370 Inkscape::NodePath::Node *n;
371 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
372 g_assert(sp->first == n);
373 g_assert(sp->last == n);
375 b++;
376 t++;
377 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
378 pos = NR::Point(b->x3, b->y3) * np->i2d;
379 if (b->code == NR_CURVETO) {
380 ppos = NR::Point(b->x2, b->y2) * np->i2d;
381 } else {
382 ppos = pos;
383 }
384 if (b[1].code == NR_CURVETO) {
385 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
386 } else {
387 npos = pos;
388 }
389 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
390 b++;
391 t++;
392 }
394 if (closed) sp_nodepath_subpath_close(sp);
396 return b;
397 }
399 /**
400 * Convert from sodipodi:nodetypes to new style type string.
401 */
402 static gchar *parse_nodetypes(gchar const *types, gint length)
403 {
404 g_assert(length > 0);
406 gchar *typestr = g_new(gchar, length + 1);
408 gint pos = 0;
410 if (types) {
411 for (gint i = 0; types[i] && ( i < length ); i++) {
412 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
413 if (types[i] != '\0') {
414 switch (types[i]) {
415 case 's':
416 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
417 break;
418 case 'z':
419 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
420 break;
421 case 'c':
422 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
423 break;
424 default:
425 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
426 break;
427 }
428 }
429 }
430 }
432 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
434 return typestr;
435 }
437 /**
438 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
439 * updated but repr is not (for speed). Used during curve and node drag.
440 */
441 static void update_object(Inkscape::NodePath::Path *np)
442 {
443 g_assert(np);
445 SPCurve *curve = create_curve(np);
447 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
449 sp_curve_unref(curve);
450 }
452 /**
453 * Update XML path node with data from path object.
454 */
455 static void update_repr_internal(Inkscape::NodePath::Path *np)
456 {
457 g_assert(np);
459 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
461 SPCurve *curve = create_curve(np);
462 gchar *typestr = create_typestr(np);
463 gchar *svgpath = sp_svg_write_path(curve->bpath);
465 repr->setAttribute("d", svgpath);
466 repr->setAttribute("sodipodi:nodetypes", typestr);
468 g_free(svgpath);
469 g_free(typestr);
470 sp_curve_unref(curve);
471 }
473 /**
474 * Update XML path node with data from path object, commit changes forever.
475 */
476 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
477 {
478 update_repr_internal(np);
479 sp_document_done(SP_DT_DOCUMENT(np->desktop));
481 if (np->livarot_path) {
482 delete np->livarot_path;
483 np->livarot_path = NULL;
484 }
486 if (np->path && SP_IS_ITEM(np->path)) {
487 np->livarot_path = Path_for_item (np->path, true, true);
488 if (np->livarot_path)
489 np->livarot_path->ConvertWithBackData(0.01);
490 }
491 }
493 /**
494 * Update XML path node with data from path object, commit changes with undo.
495 */
496 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
497 {
498 update_repr_internal(np);
499 sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
501 if (np->livarot_path) {
502 delete np->livarot_path;
503 np->livarot_path = NULL;
504 }
506 if (np->path && SP_IS_ITEM(np->path)) {
507 np->livarot_path = Path_for_item (np->path, true, true);
508 if (np->livarot_path)
509 np->livarot_path->ConvertWithBackData(0.01);
510 }
511 }
513 /**
514 * Make duplicate of path, replace corresponding XML node in tree, commit.
515 */
516 static void stamp_repr(Inkscape::NodePath::Path *np)
517 {
518 g_assert(np);
520 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
521 Inkscape::XML::Node *new_repr = old_repr->duplicate();
523 // remember the position of the item
524 gint pos = old_repr->position();
525 // remember parent
526 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
528 SPCurve *curve = create_curve(np);
529 gchar *typestr = create_typestr(np);
531 gchar *svgpath = sp_svg_write_path(curve->bpath);
533 new_repr->setAttribute("d", svgpath);
534 new_repr->setAttribute("sodipodi:nodetypes", typestr);
536 // add the new repr to the parent
537 parent->appendChild(new_repr);
538 // move to the saved position
539 new_repr->setPosition(pos > 0 ? pos : 0);
541 sp_document_done(SP_DT_DOCUMENT(np->desktop));
543 Inkscape::GC::release(new_repr);
544 g_free(svgpath);
545 g_free(typestr);
546 sp_curve_unref(curve);
547 }
549 /**
550 * Create curve from path.
551 */
552 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
553 {
554 SPCurve *curve = sp_curve_new();
556 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
557 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
558 sp_curve_moveto(curve,
559 sp->first->pos * np->d2i);
560 Inkscape::NodePath::Node *n = sp->first->n.other;
561 while (n) {
562 NR::Point const end_pt = n->pos * np->d2i;
563 switch (n->code) {
564 case NR_LINETO:
565 sp_curve_lineto(curve, end_pt);
566 break;
567 case NR_CURVETO:
568 sp_curve_curveto(curve,
569 n->p.other->n.pos * np->d2i,
570 n->p.pos * np->d2i,
571 end_pt);
572 break;
573 default:
574 g_assert_not_reached();
575 break;
576 }
577 if (n != sp->last) {
578 n = n->n.other;
579 } else {
580 n = NULL;
581 }
582 }
583 if (sp->closed) {
584 sp_curve_closepath(curve);
585 }
586 }
588 return curve;
589 }
591 /**
592 * Convert path type string to sodipodi:nodetypes style.
593 */
594 static gchar *create_typestr(Inkscape::NodePath::Path *np)
595 {
596 gchar *typestr = g_new(gchar, 32);
597 gint len = 32;
598 gint pos = 0;
600 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
601 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
603 if (pos >= len) {
604 typestr = g_renew(gchar, typestr, len + 32);
605 len += 32;
606 }
608 typestr[pos++] = 'c';
610 Inkscape::NodePath::Node *n;
611 n = sp->first->n.other;
612 while (n) {
613 gchar code;
615 switch (n->type) {
616 case Inkscape::NodePath::NODE_CUSP:
617 code = 'c';
618 break;
619 case Inkscape::NodePath::NODE_SMOOTH:
620 code = 's';
621 break;
622 case Inkscape::NodePath::NODE_SYMM:
623 code = 'z';
624 break;
625 default:
626 g_assert_not_reached();
627 code = '\0';
628 break;
629 }
631 if (pos >= len) {
632 typestr = g_renew(gchar, typestr, len + 32);
633 len += 32;
634 }
636 typestr[pos++] = code;
638 if (n != sp->last) {
639 n = n->n.other;
640 } else {
641 n = NULL;
642 }
643 }
644 }
646 if (pos >= len) {
647 typestr = g_renew(gchar, typestr, len + 1);
648 len += 1;
649 }
651 typestr[pos++] = '\0';
653 return typestr;
654 }
656 /**
657 * Returns current path in context.
658 */
659 static Inkscape::NodePath::Path *sp_nodepath_current()
660 {
661 if (!SP_ACTIVE_DESKTOP) {
662 return NULL;
663 }
665 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
667 if (!SP_IS_NODE_CONTEXT(event_context)) {
668 return NULL;
669 }
671 return SP_NODE_CONTEXT(event_context)->nodepath;
672 }
676 /**
677 \brief Fills node and control positions for three nodes, splitting line
678 marked by end at distance t.
679 */
680 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
681 {
682 g_assert(new_path != NULL);
683 g_assert(end != NULL);
685 g_assert(end->p.other == new_path);
686 Inkscape::NodePath::Node *start = new_path->p.other;
687 g_assert(start);
689 if (end->code == NR_LINETO) {
690 new_path->type =Inkscape::NodePath::NODE_CUSP;
691 new_path->code = NR_LINETO;
692 new_path->pos = (t * start->pos + (1 - t) * end->pos);
693 } else {
694 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
695 new_path->code = NR_CURVETO;
696 gdouble s = 1 - t;
697 for (int dim = 0; dim < 2; dim++) {
698 NR::Coord const f000 = start->pos[dim];
699 NR::Coord const f001 = start->n.pos[dim];
700 NR::Coord const f011 = end->p.pos[dim];
701 NR::Coord const f111 = end->pos[dim];
702 NR::Coord const f00t = s * f000 + t * f001;
703 NR::Coord const f01t = s * f001 + t * f011;
704 NR::Coord const f11t = s * f011 + t * f111;
705 NR::Coord const f0tt = s * f00t + t * f01t;
706 NR::Coord const f1tt = s * f01t + t * f11t;
707 NR::Coord const fttt = s * f0tt + t * f1tt;
708 start->n.pos[dim] = f00t;
709 new_path->p.pos[dim] = f0tt;
710 new_path->pos[dim] = fttt;
711 new_path->n.pos[dim] = f1tt;
712 end->p.pos[dim] = f11t;
713 }
714 }
715 }
717 /**
718 * Adds new node on direct line between two nodes, activates handles of all
719 * three nodes.
720 */
721 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
722 {
723 g_assert(end);
724 g_assert(end->subpath);
725 g_assert(g_list_find(end->subpath->nodes, end));
727 Inkscape::NodePath::Node *start = end->p.other;
728 g_assert( start->n.other == end );
729 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
730 end,
731 Inkscape::NodePath::NODE_SMOOTH,
732 (NRPathcode)end->code,
733 &start->pos, &start->pos, &start->n.pos);
734 sp_nodepath_line_midpoint(newnode, end, t);
736 sp_node_ensure_ctrls(start);
737 sp_node_ensure_ctrls(newnode);
738 sp_node_ensure_ctrls(end);
740 return newnode;
741 }
743 /**
744 \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
745 */
746 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
747 {
748 g_assert(node);
749 g_assert(node->subpath);
750 g_assert(g_list_find(node->subpath->nodes, node));
752 Inkscape::NodePath::SubPath *sp = node->subpath;
753 Inkscape::NodePath::Path *np = sp->nodepath;
755 if (sp->closed) {
756 sp_nodepath_subpath_open(sp, node);
757 return sp->first;
758 } else {
759 // no break for end nodes
760 if (node == sp->first) return NULL;
761 if (node == sp->last ) return NULL;
763 // create a new subpath
764 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
766 // duplicate the break node as start of the new subpath
767 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
769 while (node->n.other) { // copy the remaining nodes into the new subpath
770 Inkscape::NodePath::Node *n = node->n.other;
771 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);
772 if (n->selected) {
773 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
774 }
775 sp_nodepath_node_destroy(n); // remove the point on the original subpath
776 }
778 return newnode;
779 }
780 }
782 /**
783 * Duplicate node and connect to neighbours.
784 */
785 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
786 {
787 g_assert(node);
788 g_assert(node->subpath);
789 g_assert(g_list_find(node->subpath->nodes, node));
791 Inkscape::NodePath::SubPath *sp = node->subpath;
793 NRPathcode code = (NRPathcode) node->code;
794 if (code == NR_MOVETO) { // if node is the endnode,
795 node->code = NR_LINETO; // new one is inserted before it, so change that to line
796 }
798 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
800 if (!node->n.other || !node->p.other) // if node is an endnode, select it
801 return node;
802 else
803 return newnode; // otherwise select the newly created node
804 }
806 static void sp_node_control_mirror_n_to_p(Inkscape::NodePath::Node *node)
807 {
808 node->p.pos = (node->pos + (node->pos - node->n.pos));
809 }
811 static void sp_node_control_mirror_p_to_n(Inkscape::NodePath::Node *node)
812 {
813 node->n.pos = (node->pos + (node->pos - node->p.pos));
814 }
816 /**
817 * Change line type at node, with side effects on neighbours.
818 */
819 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
820 {
821 g_assert(end);
822 g_assert(end->subpath);
823 g_assert(end->p.other);
825 if (end->code == static_cast< guint > ( code ) )
826 return;
828 Inkscape::NodePath::Node *start = end->p.other;
830 end->code = code;
832 if (code == NR_LINETO) {
833 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
834 if (end->n.other) {
835 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
836 }
837 sp_node_adjust_knot(start, -1);
838 sp_node_adjust_knot(end, 1);
839 } else {
840 NR::Point delta = end->pos - start->pos;
841 start->n.pos = start->pos + delta / 3;
842 end->p.pos = end->pos - delta / 3;
843 sp_node_adjust_knot(start, 1);
844 sp_node_adjust_knot(end, -1);
845 }
847 sp_node_ensure_ctrls(start);
848 sp_node_ensure_ctrls(end);
849 }
851 /**
852 * Change node type, and its handles accordingly.
853 */
854 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeType type)
855 {
856 g_assert(node);
857 g_assert(node->subpath);
859 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
860 return node;
862 if ((node->p.other != NULL) && (node->n.other != NULL)) {
863 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
864 type =Inkscape::NodePath::NODE_CUSP;
865 }
866 }
868 node->type = type;
870 if (node->type == Inkscape::NodePath::NODE_CUSP) {
871 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
872 node->knot->setSize (node->selected? 11 : 9);
873 sp_knot_update_ctrl(node->knot);
874 } else {
875 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
876 node->knot->setSize (node->selected? 9 : 7);
877 sp_knot_update_ctrl(node->knot);
878 }
880 sp_node_adjust_knots(node);
882 sp_nodepath_update_statusbar(node->subpath->nodepath);
884 return node;
885 }
887 /**
888 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
889 * adjacent segments from lines to curves.
890 */
891 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
892 {
893 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
894 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
895 // convert adjacent segment BEFORE to curve
896 node->code = NR_CURVETO;
897 NR::Point delta;
898 if (node->n.other != NULL)
899 delta = node->n.other->pos - node->p.other->pos;
900 else
901 delta = node->pos - node->p.other->pos;
902 node->p.pos = node->pos - delta / 4;
903 sp_node_ensure_ctrls(node);
904 }
906 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
907 // convert adjacent segment AFTER to curve
908 node->n.other->code = NR_CURVETO;
909 NR::Point delta;
910 if (node->p.other != NULL)
911 delta = node->p.other->pos - node->n.other->pos;
912 else
913 delta = node->pos - node->n.other->pos;
914 node->n.pos = node->pos - delta / 4;
915 sp_node_ensure_ctrls(node);
916 }
917 }
919 sp_nodepath_set_node_type (node, type);
920 }
922 /**
923 * Move node to point, and adjust its and neighbouring handles.
924 */
925 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
926 {
927 NR::Point delta = p - node->pos;
928 node->pos = p;
930 node->p.pos += delta;
931 node->n.pos += delta;
933 if (node->p.other) {
934 if (node->code == NR_LINETO) {
935 sp_node_adjust_knot(node, 1);
936 sp_node_adjust_knot(node->p.other, -1);
937 }
938 }
939 if (node->n.other) {
940 if (node->n.other->code == NR_LINETO) {
941 sp_node_adjust_knot(node, -1);
942 sp_node_adjust_knot(node->n.other, 1);
943 }
944 }
946 sp_node_ensure_ctrls(node);
947 }
949 /**
950 * Call sp_node_moveto() for node selection and handle possible snapping.
951 */
952 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
953 bool const snap = true)
954 {
955 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
956 NR::Point delta(dx, dy);
957 NR::Point best_pt = delta;
959 if (snap) {
960 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
961 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
962 NR::Point p = n->pos + delta;
963 for (int dim = 0; dim < 2; dim++) {
964 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
965 Inkscape::Snapper::SNAP_POINT, p,
966 NR::Dim2(dim), nodepath->path);
967 if (dist < best[dim]) {
968 best[dim] = dist;
969 best_pt[dim] = p[dim] - n->pos[dim];
970 }
971 }
972 }
973 }
975 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
976 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
977 sp_node_moveto(n, n->pos + best_pt);
978 }
980 update_object(nodepath);
981 }
983 /**
984 * Move node selection to point, adjust its and neighbouring handles,
985 * handle possible snapping, and commit the change with possible undo.
986 */
987 void
988 sp_node_selected_move(gdouble dx, gdouble dy)
989 {
990 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
991 if (!nodepath) return;
993 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
995 if (dx == 0) {
996 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
997 } else if (dy == 0) {
998 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
999 } else {
1000 sp_nodepath_update_repr(nodepath);
1001 }
1002 }
1004 /**
1005 * Move node selection off screen and commit the change.
1006 */
1007 void
1008 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1009 {
1010 // borrowed from sp_selection_move_screen in selection-chemistry.c
1011 // we find out the current zoom factor and divide deltas by it
1012 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1014 gdouble zoom = desktop->current_zoom();
1015 gdouble zdx = dx / zoom;
1016 gdouble zdy = dy / zoom;
1018 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1019 if (!nodepath) return;
1021 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1023 if (dx == 0) {
1024 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1025 } else if (dy == 0) {
1026 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1027 } else {
1028 sp_nodepath_update_repr(nodepath);
1029 }
1030 }
1032 /** If they don't yet exist, creates knot and line for the given side of the node */
1033 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1034 {
1035 if (!side->knot) {
1036 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"));
1038 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1039 side->knot->setSize (7);
1040 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1041 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1042 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1043 sp_knot_update_ctrl(side->knot);
1045 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_ctrl_clicked), node);
1046 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_ctrl_grabbed), node);
1047 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_ctrl_ungrabbed), node);
1048 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_ctrl_request), node);
1049 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_ctrl_moved), node);
1050 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_ctrl_event), node);
1051 }
1053 if (!side->line) {
1054 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1055 SP_TYPE_CTRLLINE, NULL);
1056 }
1057 }
1059 /**
1060 * Ensure knot on side of node is visible/invisible.
1061 */
1062 static void sp_node_ensure_knot(Inkscape::NodePath::Node *node, gint which, gboolean show_knot)
1063 {
1064 g_assert(node != NULL);
1066 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1067 NRPathcode code = sp_node_path_code_from_side(node, side);
1069 show_knot = show_knot && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1071 if (show_knot) {
1072 if (!side->knot) {
1073 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1074 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1075 side->knot->pos = side->pos;
1076 if (side->knot->item) SP_CTRL(side->knot->item)->moveto(side->pos);
1077 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1078 sp_knot_show(side->knot);
1079 } else {
1080 if (side->knot->pos != side->pos) { // only if it's really moved
1081 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1082 }
1083 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1084 sp_knot_show(side->knot);
1085 }
1086 }
1087 sp_canvas_item_show(side->line);
1088 } else {
1089 if (side->knot) {
1090 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1091 sp_knot_hide(side->knot);
1092 }
1093 }
1094 if (side->line) {
1095 sp_canvas_item_hide(side->line);
1096 }
1097 }
1098 }
1100 /**
1101 * Ensure handles on node and neighbours of node are visible if selected.
1102 */
1103 static void sp_node_ensure_ctrls(Inkscape::NodePath::Node *node)
1104 {
1105 g_assert(node != NULL);
1107 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1108 sp_knot_show(node->knot);
1109 }
1111 sp_knot_set_position(node->knot, &node->pos, 0);
1113 gboolean show_knots = node->selected;
1114 if (node->p.other != NULL) {
1115 if (node->p.other->selected) show_knots = TRUE;
1116 }
1117 if (node->n.other != NULL) {
1118 if (node->n.other->selected) show_knots = TRUE;
1119 }
1121 sp_node_ensure_knot(node, -1, show_knots);
1122 sp_node_ensure_knot(node, 1, show_knots);
1123 }
1125 /**
1126 * Call sp_node_ensure_ctrls() for all nodes on subpath.
1127 */
1128 static void sp_nodepath_subpath_ensure_ctrls(Inkscape::NodePath::SubPath *subpath)
1129 {
1130 g_assert(subpath != NULL);
1132 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1133 sp_node_ensure_ctrls((Inkscape::NodePath::Node *) l->data);
1134 }
1135 }
1137 /**
1138 * Call sp_nodepath_subpath_ensure_ctrls() for all subpaths of nodepath.
1139 */
1140 static void sp_nodepath_ensure_ctrls(Inkscape::NodePath::Path *nodepath)
1141 {
1142 g_assert(nodepath != NULL);
1144 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1145 sp_nodepath_subpath_ensure_ctrls((Inkscape::NodePath::SubPath *) l->data);
1146 }
1147 }
1149 /**
1150 * Adds all selected nodes in nodepath to list.
1151 */
1152 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1153 {
1154 StlConv<Node *>::list(l, selected);
1155 /// \todo this adds a copying, rework when the selection becomes a stl list
1156 }
1158 /**
1159 * Align selected nodes on the specified axis.
1160 */
1161 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1162 {
1163 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1164 return;
1165 }
1167 if ( !nodepath->selected->next ) { // only one node selected
1168 return;
1169 }
1170 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1171 NR::Point dest(pNode->pos);
1172 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1173 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1174 if (pNode) {
1175 dest[axis] = pNode->pos[axis];
1176 sp_node_moveto(pNode, dest);
1177 }
1178 }
1179 if (axis == NR::X) {
1180 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1181 } else {
1182 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1183 }
1184 }
1186 /// Helper struct.
1187 struct NodeSort
1188 {
1189 Inkscape::NodePath::Node *_node;
1190 NR::Coord _coord;
1191 /// \todo use vectorof pointers instead of calling copy ctor
1192 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1193 _node(node), _coord(node->pos[axis])
1194 {}
1196 };
1198 static bool operator<(NodeSort const &a, NodeSort const &b)
1199 {
1200 return (a._coord < b._coord);
1201 }
1203 /**
1204 * Distribute selected nodes on the specified axis.
1205 */
1206 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1207 {
1208 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1209 return;
1210 }
1212 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1213 return;
1214 }
1216 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1217 std::vector<NodeSort> sorted;
1218 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1219 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1220 if (pNode) {
1221 NodeSort n(pNode, axis);
1222 sorted.push_back(n);
1223 //dest[axis] = pNode->pos[axis];
1224 //sp_node_moveto(pNode, dest);
1225 }
1226 }
1227 std::sort(sorted.begin(), sorted.end());
1228 unsigned int len = sorted.size();
1229 //overall bboxes span
1230 float dist = (sorted.back()._coord -
1231 sorted.front()._coord);
1232 //new distance between each bbox
1233 float step = (dist) / (len - 1);
1234 float pos = sorted.front()._coord;
1235 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1236 it < sorted.end();
1237 it ++ )
1238 {
1239 NR::Point dest((*it)._node->pos);
1240 dest[axis] = pos;
1241 sp_node_moveto((*it)._node, dest);
1242 pos += step;
1243 }
1245 if (axis == NR::X) {
1246 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1247 } else {
1248 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1249 }
1250 }
1253 /**
1254 * Call sp_nodepath_line_add_node() for all selected segments.
1255 */
1256 void
1257 sp_node_selected_add_node(void)
1258 {
1259 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1260 if (!nodepath) {
1261 return;
1262 }
1264 GList *nl = NULL;
1266 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1267 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1268 g_assert(t->selected);
1269 if (t->p.other && t->p.other->selected) {
1270 nl = g_list_prepend(nl, t);
1271 }
1272 }
1274 while (nl) {
1275 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1276 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1277 sp_nodepath_node_select(n, TRUE, FALSE);
1278 nl = g_list_remove(nl, t);
1279 }
1281 /** \todo fixme: adjust ? */
1282 sp_nodepath_ensure_ctrls(nodepath);
1284 sp_nodepath_update_repr(nodepath);
1286 sp_nodepath_update_statusbar(nodepath);
1287 }
1289 /**
1290 * Select segment nearest to point
1291 */
1292 void
1293 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1294 {
1295 if (!nodepath) {
1296 return;
1297 }
1299 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1301 //find segment to segment
1302 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1304 gboolean force = FALSE;
1305 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1306 force = TRUE;
1307 }
1308 sp_nodepath_node_select(e, (gboolean) toggle, force);
1309 if (e->p.other)
1310 sp_nodepath_node_select(e->p.other, TRUE, force);
1312 sp_nodepath_ensure_ctrls(nodepath);
1314 sp_nodepath_update_statusbar(nodepath);
1315 }
1317 /**
1318 * Add a node nearest to point
1319 */
1320 void
1321 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1322 {
1323 if (!nodepath) {
1324 return;
1325 }
1327 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1329 //find segment to split
1330 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1332 //don't know why but t seems to flip for lines
1333 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1334 position.t = 1.0 - position.t;
1335 }
1336 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1337 sp_nodepath_node_select(n, FALSE, TRUE);
1339 /* fixme: adjust ? */
1340 sp_nodepath_ensure_ctrls(nodepath);
1342 sp_nodepath_update_repr(nodepath);
1344 sp_nodepath_update_statusbar(nodepath);
1345 }
1347 /*
1348 * Adjusts a segment so that t moves by a certain delta for dragging
1349 * converts lines to curves
1350 *
1351 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1352 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1353 */
1354 void
1355 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1356 {
1357 /* feel good is an arbitrary parameter that distributes the delta between handles
1358 * if t of the drag point is less than 1/6 distance form the endpoint only
1359 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1360 */
1361 double feel_good;
1362 if (t <= 1.0 / 6.0)
1363 feel_good = 0;
1364 else if (t <= 0.5)
1365 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1366 else if (t <= 5.0 / 6.0)
1367 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1368 else
1369 feel_good = 1;
1371 //if we're dragging a line convert it to a curve
1372 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1373 sp_nodepath_set_line_type(e, NR_CURVETO);
1374 }
1376 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1377 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1378 e->p.other->n.pos += offsetcoord0;
1379 e->p.pos += offsetcoord1;
1381 // adjust controls of adjacent segments where necessary
1382 sp_node_adjust_knot(e,1);
1383 sp_node_adjust_knot(e->p.other,-1);
1385 sp_nodepath_ensure_ctrls(e->subpath->nodepath);
1387 update_object(e->subpath->nodepath);
1389 sp_nodepath_update_statusbar(e->subpath->nodepath);
1390 }
1393 /**
1394 * Call sp_nodepath_break() for all selected segments.
1395 */
1396 void sp_node_selected_break()
1397 {
1398 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1399 if (!nodepath) return;
1401 GList *temp = NULL;
1402 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1403 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1404 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1405 if (nn == NULL) continue; // no break, no new node
1406 temp = g_list_prepend(temp, nn);
1407 }
1409 if (temp) {
1410 sp_nodepath_deselect(nodepath);
1411 }
1412 for (GList *l = temp; l != NULL; l = l->next) {
1413 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1414 }
1416 sp_nodepath_ensure_ctrls(nodepath);
1418 sp_nodepath_update_repr(nodepath);
1419 }
1421 /**
1422 * Duplicate the selected node(s).
1423 */
1424 void sp_node_selected_duplicate()
1425 {
1426 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1427 if (!nodepath) {
1428 return;
1429 }
1431 GList *temp = NULL;
1432 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1433 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1434 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1435 if (nn == NULL) continue; // could not duplicate
1436 temp = g_list_prepend(temp, nn);
1437 }
1439 if (temp) {
1440 sp_nodepath_deselect(nodepath);
1441 }
1442 for (GList *l = temp; l != NULL; l = l->next) {
1443 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1444 }
1446 sp_nodepath_ensure_ctrls(nodepath);
1448 sp_nodepath_update_repr(nodepath);
1449 }
1451 /**
1452 * Join two nodes by merging them into one.
1453 */
1454 void sp_node_selected_join()
1455 {
1456 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1457 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1459 if (g_list_length(nodepath->selected) != 2) {
1460 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1461 return;
1462 }
1464 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1465 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1467 g_assert(a != b);
1468 g_assert(a->p.other || a->n.other);
1469 g_assert(b->p.other || b->n.other);
1471 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1472 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1473 return;
1474 }
1476 /* a and b are endpoints */
1478 NR::Point c = (a->pos + b->pos) / 2;
1480 if (a->subpath == b->subpath) {
1481 Inkscape::NodePath::SubPath *sp = a->subpath;
1482 sp_nodepath_subpath_close(sp);
1484 sp_nodepath_ensure_ctrls(sp->nodepath);
1486 sp_nodepath_update_repr(nodepath);
1488 return;
1489 }
1491 /* a and b are separate subpaths */
1492 Inkscape::NodePath::SubPath *sa = a->subpath;
1493 Inkscape::NodePath::SubPath *sb = b->subpath;
1494 NR::Point p;
1495 Inkscape::NodePath::Node *n;
1496 NRPathcode code;
1497 if (a == sa->first) {
1498 p = sa->first->n.pos;
1499 code = (NRPathcode)sa->first->n.other->code;
1500 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1501 n = sa->last;
1502 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1503 n = n->p.other;
1504 while (n) {
1505 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1506 n = n->p.other;
1507 if (n == sa->first) n = NULL;
1508 }
1509 sp_nodepath_subpath_destroy(sa);
1510 sa = t;
1511 } else if (a == sa->last) {
1512 p = sa->last->p.pos;
1513 code = (NRPathcode)sa->last->code;
1514 sp_nodepath_node_destroy(sa->last);
1515 } else {
1516 code = NR_END;
1517 g_assert_not_reached();
1518 }
1520 if (b == sb->first) {
1521 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1522 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1523 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1524 }
1525 } else if (b == sb->last) {
1526 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1527 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1528 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1529 }
1530 } else {
1531 g_assert_not_reached();
1532 }
1533 /* and now destroy sb */
1535 sp_nodepath_subpath_destroy(sb);
1537 sp_nodepath_ensure_ctrls(sa->nodepath);
1539 sp_nodepath_update_repr(nodepath);
1541 sp_nodepath_update_statusbar(nodepath);
1542 }
1544 /**
1545 * Join two nodes by adding a segment between them.
1546 */
1547 void sp_node_selected_join_segment()
1548 {
1549 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1550 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1552 if (g_list_length(nodepath->selected) != 2) {
1553 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1554 return;
1555 }
1557 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1558 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1560 g_assert(a != b);
1561 g_assert(a->p.other || a->n.other);
1562 g_assert(b->p.other || b->n.other);
1564 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1565 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1566 return;
1567 }
1569 if (a->subpath == b->subpath) {
1570 Inkscape::NodePath::SubPath *sp = a->subpath;
1572 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1573 sp->closed = TRUE;
1575 sp->first->p.other = sp->last;
1576 sp->last->n.other = sp->first;
1578 sp_node_control_mirror_p_to_n(sp->last);
1579 sp_node_control_mirror_n_to_p(sp->first);
1581 sp->first->code = sp->last->code;
1582 sp->first = sp->last;
1584 sp_nodepath_ensure_ctrls(sp->nodepath);
1586 sp_nodepath_update_repr(nodepath);
1588 return;
1589 }
1591 /* a and b are separate subpaths */
1592 Inkscape::NodePath::SubPath *sa = a->subpath;
1593 Inkscape::NodePath::SubPath *sb = b->subpath;
1595 Inkscape::NodePath::Node *n;
1596 NR::Point p;
1597 NRPathcode code;
1598 if (a == sa->first) {
1599 code = (NRPathcode) sa->first->n.other->code;
1600 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1601 n = sa->last;
1602 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1603 for (n = n->p.other; n != NULL; n = n->p.other) {
1604 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1605 }
1606 sp_nodepath_subpath_destroy(sa);
1607 sa = t;
1608 } else if (a == sa->last) {
1609 code = (NRPathcode)sa->last->code;
1610 } else {
1611 code = NR_END;
1612 g_assert_not_reached();
1613 }
1615 if (b == sb->first) {
1616 n = sb->first;
1617 sp_node_control_mirror_p_to_n(sa->last);
1618 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1619 sp_node_control_mirror_n_to_p(sa->last);
1620 for (n = n->n.other; n != NULL; n = n->n.other) {
1621 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1622 }
1623 } else if (b == sb->last) {
1624 n = sb->last;
1625 sp_node_control_mirror_p_to_n(sa->last);
1626 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1627 sp_node_control_mirror_n_to_p(sa->last);
1628 for (n = n->p.other; n != NULL; n = n->p.other) {
1629 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1630 }
1631 } else {
1632 g_assert_not_reached();
1633 }
1634 /* and now destroy sb */
1636 sp_nodepath_subpath_destroy(sb);
1638 sp_nodepath_ensure_ctrls(sa->nodepath);
1640 sp_nodepath_update_repr(nodepath);
1641 }
1643 /**
1644 * Delete one or more selected nodes.
1645 */
1646 void sp_node_selected_delete()
1647 {
1648 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1649 if (!nodepath) return;
1650 if (!nodepath->selected) return;
1652 /** \todo fixme: do it the right way */
1653 while (nodepath->selected) {
1654 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1655 sp_nodepath_node_destroy(node);
1656 }
1659 //clean up the nodepath (such as for trivial subpaths)
1660 sp_nodepath_cleanup(nodepath);
1662 sp_nodepath_ensure_ctrls(nodepath);
1664 // if the entire nodepath is removed, delete the selected object.
1665 if (nodepath->subpaths == NULL ||
1666 sp_nodepath_get_node_count(nodepath) < 2) {
1667 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1668 sp_nodepath_destroy(nodepath);
1669 sp_selection_delete();
1670 sp_document_done (document);
1671 return;
1672 }
1674 sp_nodepath_update_repr(nodepath);
1676 sp_nodepath_update_statusbar(nodepath);
1677 }
1679 /**
1680 * Delete one or more segments between two selected nodes.
1681 * This is the code for 'split'.
1682 */
1683 void
1684 sp_node_selected_delete_segment(void)
1685 {
1686 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1687 Inkscape::NodePath::Node *curr, *next; //Iterators
1689 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1690 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1692 if (g_list_length(nodepath->selected) != 2) {
1693 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1694 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1695 return;
1696 }
1698 //Selected nodes, not inclusive
1699 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1700 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1702 if ( ( a==b) || //same node
1703 (a->subpath != b->subpath ) || //not the same path
1704 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1705 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1706 {
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 //###########################################
1713 //# BEGIN EDITS
1714 //###########################################
1715 //##################################
1716 //# CLOSED PATH
1717 //##################################
1718 if (a->subpath->closed) {
1721 gboolean reversed = FALSE;
1723 //Since we can go in a circle, we need to find the shorter distance.
1724 // a->b or b->a
1725 start = end = NULL;
1726 int distance = 0;
1727 int minDistance = 0;
1728 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1729 if (curr==b) {
1730 //printf("a to b:%d\n", distance);
1731 start = a;//go from a to b
1732 end = b;
1733 minDistance = distance;
1734 //printf("A to B :\n");
1735 break;
1736 }
1737 distance++;
1738 }
1740 //try again, the other direction
1741 distance = 0;
1742 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1743 if (curr==a) {
1744 //printf("b to a:%d\n", distance);
1745 if (distance < minDistance) {
1746 start = b; //we go from b to a
1747 end = a;
1748 reversed = TRUE;
1749 //printf("B to A\n");
1750 }
1751 break;
1752 }
1753 distance++;
1754 }
1757 //Copy everything from 'end' to 'start' to a new subpath
1758 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1759 for (curr=end ; curr ; curr=curr->n.other) {
1760 NRPathcode code = (NRPathcode) curr->code;
1761 if (curr == end)
1762 code = NR_MOVETO;
1763 sp_nodepath_node_new(t, NULL,
1764 (Inkscape::NodePath::NodeType)curr->type, code,
1765 &curr->p.pos, &curr->pos, &curr->n.pos);
1766 if (curr == start)
1767 break;
1768 }
1769 sp_nodepath_subpath_destroy(a->subpath);
1772 }
1776 //##################################
1777 //# OPEN PATH
1778 //##################################
1779 else {
1781 //We need to get the direction of the list between A and B
1782 //Can we walk from a to b?
1783 start = end = NULL;
1784 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1785 if (curr==b) {
1786 start = a; //did it! we go from a to b
1787 end = b;
1788 //printf("A to B\n");
1789 break;
1790 }
1791 }
1792 if (!start) {//didn't work? let's try the other direction
1793 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1794 if (curr==a) {
1795 start = b; //did it! we go from b to a
1796 end = a;
1797 //printf("B to A\n");
1798 break;
1799 }
1800 }
1801 }
1802 if (!start) {
1803 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1804 _("Cannot find path between nodes."));
1805 return;
1806 }
1810 //Copy everything after 'end' to a new subpath
1811 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1812 for (curr=end ; curr ; curr=curr->n.other) {
1813 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1814 &curr->p.pos, &curr->pos, &curr->n.pos);
1815 }
1817 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1818 for (curr = start->n.other ; curr ; curr=next) {
1819 next = curr->n.other;
1820 sp_nodepath_node_destroy(curr);
1821 }
1823 }
1824 //###########################################
1825 //# END EDITS
1826 //###########################################
1828 //clean up the nodepath (such as for trivial subpaths)
1829 sp_nodepath_cleanup(nodepath);
1831 sp_nodepath_ensure_ctrls(nodepath);
1833 sp_nodepath_update_repr(nodepath);
1835 // if the entire nodepath is removed, delete the selected object.
1836 if (nodepath->subpaths == NULL ||
1837 sp_nodepath_get_node_count(nodepath) < 2) {
1838 sp_nodepath_destroy(nodepath);
1839 sp_selection_delete();
1840 return;
1841 }
1843 sp_nodepath_update_statusbar(nodepath);
1844 }
1846 /**
1847 * Call sp_nodepath_set_line() for all selected segments.
1848 */
1849 void
1850 sp_node_selected_set_line_type(NRPathcode code)
1851 {
1852 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1853 if (nodepath == NULL) return;
1855 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1856 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1857 g_assert(n->selected);
1858 if (n->p.other && n->p.other->selected) {
1859 sp_nodepath_set_line_type(n, code);
1860 }
1861 }
1863 sp_nodepath_update_repr(nodepath);
1864 }
1866 /**
1867 * Call sp_nodepath_convert_node_type() for all selected nodes.
1868 */
1869 void
1870 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1871 {
1872 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1873 if (nodepath == NULL) return;
1875 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1876 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1877 }
1879 sp_nodepath_update_repr(nodepath);
1880 }
1882 /**
1883 * Change select status of node, update its own and neighbour handles.
1884 */
1885 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1886 {
1887 node->selected = selected;
1889 if (selected) {
1890 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1891 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1892 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1893 sp_knot_update_ctrl(node->knot);
1894 } else {
1895 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1896 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1897 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1898 sp_knot_update_ctrl(node->knot);
1899 }
1901 sp_node_ensure_ctrls(node);
1902 if (node->n.other) sp_node_ensure_ctrls(node->n.other);
1903 if (node->p.other) sp_node_ensure_ctrls(node->p.other);
1904 }
1906 /**
1907 \brief Select a node
1908 \param node The node to select
1909 \param incremental If true, add to selection, otherwise deselect others
1910 \param override If true, always select this node, otherwise toggle selected status
1911 */
1912 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1913 {
1914 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1916 if (incremental) {
1917 if (override) {
1918 if (!g_list_find(nodepath->selected, node)) {
1919 nodepath->selected = g_list_prepend(nodepath->selected, node);
1920 }
1921 sp_node_set_selected(node, TRUE);
1922 } else { // toggle
1923 if (node->selected) {
1924 g_assert(g_list_find(nodepath->selected, node));
1925 nodepath->selected = g_list_remove(nodepath->selected, node);
1926 } else {
1927 g_assert(!g_list_find(nodepath->selected, node));
1928 nodepath->selected = g_list_prepend(nodepath->selected, node);
1929 }
1930 sp_node_set_selected(node, !node->selected);
1931 }
1932 } else {
1933 sp_nodepath_deselect(nodepath);
1934 nodepath->selected = g_list_prepend(nodepath->selected, node);
1935 sp_node_set_selected(node, TRUE);
1936 }
1938 sp_nodepath_update_statusbar(nodepath);
1939 }
1942 /**
1943 \brief Deselect all nodes in the nodepath
1944 */
1945 void
1946 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1947 {
1948 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1950 while (nodepath->selected) {
1951 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1952 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1953 }
1954 sp_nodepath_update_statusbar(nodepath);
1955 }
1957 /**
1958 \brief Select or invert selection of all nodes in the nodepath
1959 */
1960 void
1961 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1962 {
1963 if (!nodepath) return;
1965 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1966 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1967 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1968 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1969 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1970 }
1971 }
1972 }
1974 /**
1975 * If nothing selected, does the same as sp_nodepath_select_all();
1976 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1977 * (i.e., similar to "select all in layer", with the "selected" subpaths
1978 * being treated as "layers" in the path).
1979 */
1980 void
1981 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1982 {
1983 if (!nodepath) return;
1985 if (g_list_length (nodepath->selected) == 0) {
1986 sp_nodepath_select_all (nodepath, invert);
1987 return;
1988 }
1990 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1991 GSList *subpaths = NULL;
1993 for (GList *l = copy; l != NULL; l = l->next) {
1994 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1995 Inkscape::NodePath::SubPath *subpath = n->subpath;
1996 if (!g_slist_find (subpaths, subpath))
1997 subpaths = g_slist_prepend (subpaths, subpath);
1998 }
2000 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2001 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2002 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2003 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2004 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2005 }
2006 }
2008 g_slist_free (subpaths);
2009 g_list_free (copy);
2010 }
2012 /**
2013 * \brief Select the node after the last selected; if none is selected,
2014 * select the first within path.
2015 */
2016 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2017 {
2018 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2020 Inkscape::NodePath::Node *last = NULL;
2021 if (nodepath->selected) {
2022 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2023 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2024 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2025 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2026 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2027 if (node->selected) {
2028 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2029 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2030 if (spl->next) { // there's a next subpath
2031 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2032 last = subpath_next->first;
2033 } else if (spl->prev) { // there's a previous subpath
2034 last = NULL; // to be set later to the first node of first subpath
2035 } else {
2036 last = node->n.other;
2037 }
2038 } else {
2039 last = node->n.other;
2040 }
2041 } else {
2042 if (node->n.other) {
2043 last = node->n.other;
2044 } else {
2045 if (spl->next) { // there's a next subpath
2046 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2047 last = subpath_next->first;
2048 } else if (spl->prev) { // there's a previous subpath
2049 last = NULL; // to be set later to the first node of first subpath
2050 } else {
2051 last = (Inkscape::NodePath::Node *) subpath->first;
2052 }
2053 }
2054 }
2055 }
2056 }
2057 }
2058 sp_nodepath_deselect(nodepath);
2059 }
2061 if (last) { // there's at least one more node after selected
2062 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2063 } else { // no more nodes, select the first one in first subpath
2064 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2065 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2066 }
2067 }
2069 /**
2070 * \brief Select the node before the first selected; if none is selected,
2071 * select the last within path
2072 */
2073 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2074 {
2075 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2077 Inkscape::NodePath::Node *last = NULL;
2078 if (nodepath->selected) {
2079 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2080 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2081 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2082 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2083 if (node->selected) {
2084 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2085 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2086 if (spl->prev) { // there's a prev subpath
2087 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2088 last = subpath_prev->last;
2089 } else if (spl->next) { // there's a next subpath
2090 last = NULL; // to be set later to the last node of last subpath
2091 } else {
2092 last = node->p.other;
2093 }
2094 } else {
2095 last = node->p.other;
2096 }
2097 } else {
2098 if (node->p.other) {
2099 last = node->p.other;
2100 } else {
2101 if (spl->prev) { // there's a prev subpath
2102 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2103 last = subpath_prev->last;
2104 } else if (spl->next) { // there's a next subpath
2105 last = NULL; // to be set later to the last node of last subpath
2106 } else {
2107 last = (Inkscape::NodePath::Node *) subpath->last;
2108 }
2109 }
2110 }
2111 }
2112 }
2113 }
2114 sp_nodepath_deselect(nodepath);
2115 }
2117 if (last) { // there's at least one more node before selected
2118 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2119 } else { // no more nodes, select the last one in last subpath
2120 GList *spl = g_list_last(nodepath->subpaths);
2121 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2122 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2123 }
2124 }
2126 /**
2127 * \brief Select all nodes that are within the rectangle.
2128 */
2129 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2130 {
2131 if (!incremental) {
2132 sp_nodepath_deselect(nodepath);
2133 }
2135 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2136 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2137 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2138 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2140 if (b.contains(node->pos)) {
2141 sp_nodepath_node_select(node, TRUE, TRUE);
2142 }
2143 }
2144 }
2145 }
2147 /**
2148 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2149 */
2150 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2151 {
2152 if (!nodepath->selected) {
2153 return NULL;
2154 }
2156 GList *r = NULL;
2157 guint i = 0;
2158 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2159 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2160 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2161 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2162 i++;
2163 if (node->selected) {
2164 r = g_list_append(r, GINT_TO_POINTER(i));
2165 }
2166 }
2167 }
2168 return r;
2169 }
2171 /**
2172 \brief Restores selection by selecting nodes whose positions are in the list
2173 */
2174 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2175 {
2176 sp_nodepath_deselect(nodepath);
2178 guint i = 0;
2179 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2180 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2181 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2182 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2183 i++;
2184 if (g_list_find(r, GINT_TO_POINTER(i))) {
2185 sp_nodepath_node_select(node, TRUE, TRUE);
2186 }
2187 }
2188 }
2190 }
2192 /**
2193 \brief Adjusts control point according to node type and line code.
2194 */
2195 static void sp_node_adjust_knot(Inkscape::NodePath::Node *node, gint which_adjust)
2196 {
2197 double len, otherlen, linelen;
2199 g_assert(node);
2201 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2202 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2204 /** \todo fixme: */
2205 if (me->other == NULL) return;
2206 if (other->other == NULL) return;
2208 /* I have line */
2210 NRPathcode mecode, ocode;
2211 if (which_adjust == 1) {
2212 mecode = (NRPathcode)me->other->code;
2213 ocode = (NRPathcode)node->code;
2214 } else {
2215 mecode = (NRPathcode)node->code;
2216 ocode = (NRPathcode)other->other->code;
2217 }
2219 if (mecode == NR_LINETO) return;
2221 /* I am curve */
2223 if (other->other == NULL) return;
2225 /* Other has line */
2227 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2229 NR::Point delta;
2230 if (ocode == NR_LINETO) {
2231 /* other is lineto, we are either smooth or symm */
2232 Inkscape::NodePath::Node *othernode = other->other;
2233 len = NR::L2(me->pos - node->pos);
2234 delta = node->pos - othernode->pos;
2235 linelen = NR::L2(delta);
2236 if (linelen < 1e-18) return;
2238 me->pos = node->pos + (len / linelen)*delta;
2240 sp_node_ensure_ctrls(node);
2241 return;
2242 }
2244 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2246 me->pos = 2 * node->pos - other->pos;
2248 sp_node_ensure_ctrls(node);
2249 return;
2250 }
2252 /* We are smooth */
2254 len = NR::L2(me->pos - node->pos);
2255 delta = other->pos - node->pos;
2256 otherlen = NR::L2(delta);
2257 if (otherlen < 1e-18) return;
2259 me->pos = node->pos - (len / otherlen) * delta;
2261 sp_node_ensure_ctrls(node);
2262 }
2264 /**
2265 \brief Adjusts control point according to node type and line code
2266 */
2267 static void sp_node_adjust_knots(Inkscape::NodePath::Node *node)
2268 {
2269 g_assert(node);
2271 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2273 /* we are either smooth or symm */
2275 if (node->p.other == NULL) return;
2277 if (node->n.other == NULL) return;
2279 if (node->code == NR_LINETO) {
2280 if (node->n.other->code == NR_LINETO) return;
2281 sp_node_adjust_knot(node, 1);
2282 sp_node_ensure_ctrls(node);
2283 return;
2284 }
2286 if (node->n.other->code == NR_LINETO) {
2287 if (node->code == NR_LINETO) return;
2288 sp_node_adjust_knot(node, -1);
2289 sp_node_ensure_ctrls(node);
2290 return;
2291 }
2293 /* both are curves */
2295 NR::Point const delta( node->n.pos - node->p.pos );
2297 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2298 node->p.pos = node->pos - delta / 2;
2299 node->n.pos = node->pos + delta / 2;
2300 sp_node_ensure_ctrls(node);
2301 return;
2302 }
2304 /* We are smooth */
2306 double plen = NR::L2(node->p.pos - node->pos);
2307 if (plen < 1e-18) return;
2308 double nlen = NR::L2(node->n.pos - node->pos);
2309 if (nlen < 1e-18) return;
2310 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2311 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2312 sp_node_ensure_ctrls(node);
2313 }
2315 /**
2316 * Knot events handler callback.
2317 */
2318 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2319 {
2320 gboolean ret = FALSE;
2321 switch (event->type) {
2322 case GDK_ENTER_NOTIFY:
2323 active_node = n;
2324 break;
2325 case GDK_LEAVE_NOTIFY:
2326 active_node = NULL;
2327 break;
2328 case GDK_KEY_PRESS:
2329 switch (get_group0_keyval (&event->key)) {
2330 case GDK_space:
2331 if (event->key.state & GDK_BUTTON1_MASK) {
2332 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2333 stamp_repr(nodepath);
2334 ret = TRUE;
2335 }
2336 break;
2337 default:
2338 break;
2339 }
2340 break;
2341 default:
2342 break;
2343 }
2345 return ret;
2346 }
2348 /**
2349 * Handle keypress on node; directly called.
2350 */
2351 gboolean node_key(GdkEvent *event)
2352 {
2353 Inkscape::NodePath::Path *np;
2355 // there is no way to verify nodes so set active_node to nil when deleting!!
2356 if (active_node == NULL) return FALSE;
2358 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2359 gint ret = FALSE;
2360 switch (get_group0_keyval (&event->key)) {
2361 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2362 case GDK_BackSpace:
2363 np = active_node->subpath->nodepath;
2364 sp_nodepath_node_destroy(active_node);
2365 sp_nodepath_update_repr(np);
2366 active_node = NULL;
2367 ret = TRUE;
2368 break;
2369 case GDK_c:
2370 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2371 ret = TRUE;
2372 break;
2373 case GDK_s:
2374 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2375 ret = TRUE;
2376 break;
2377 case GDK_y:
2378 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2379 ret = TRUE;
2380 break;
2381 case GDK_b:
2382 sp_nodepath_node_break(active_node);
2383 ret = TRUE;
2384 break;
2385 }
2386 return ret;
2387 }
2388 return FALSE;
2389 }
2391 /**
2392 * Mouseclick on node callback.
2393 */
2394 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2395 {
2396 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2398 if (state & GDK_CONTROL_MASK) {
2399 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2401 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2402 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2403 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2404 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2405 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2406 } else {
2407 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2408 }
2409 sp_nodepath_update_repr(nodepath);
2410 sp_nodepath_update_statusbar(nodepath);
2412 } else { //ctrl+alt+click: delete node
2413 sp_nodepath_node_destroy(n);
2414 //clean up the nodepath (such as for trivial subpaths)
2415 sp_nodepath_cleanup(nodepath);
2417 // if the entire nodepath is removed, delete the selected object.
2418 if (nodepath->subpaths == NULL ||
2419 sp_nodepath_get_node_count(nodepath) < 2) {
2420 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2421 sp_nodepath_destroy(nodepath);
2422 sp_selection_delete();
2423 sp_document_done (document);
2425 } else {
2426 sp_nodepath_ensure_ctrls(nodepath);
2427 sp_nodepath_update_repr(nodepath);
2428 sp_nodepath_update_statusbar(nodepath);
2429 }
2430 }
2432 } else {
2433 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2434 }
2435 }
2437 /**
2438 * Mouse grabbed node callback.
2439 */
2440 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2441 {
2442 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2444 n->origin = knot->pos;
2446 if (!n->selected) {
2447 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2448 }
2449 }
2451 /**
2452 * Mouse ungrabbed node callback.
2453 */
2454 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2455 {
2456 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2458 n->dragging_out = NULL;
2460 sp_nodepath_update_repr(n->subpath->nodepath);
2461 }
2463 /**
2464 * The point on a line, given by its angle, closest to the given point.
2465 * \param p A point.
2466 * \param a Angle of the line; it is assumed to go through coordinate origin.
2467 * \param closest Pointer to the point struct where the result is stored.
2468 * \todo FIXME: use dot product perhaps?
2469 */
2470 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2471 {
2472 if (a == HUGE_VAL) { // vertical
2473 *closest = NR::Point(0, (*p)[NR::Y]);
2474 } else {
2475 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2476 (*closest)[NR::Y] = a * (*closest)[NR::X];
2477 }
2478 }
2480 /**
2481 * Distance from the point to a line given by its angle.
2482 * \param p A point.
2483 * \param a Angle of the line; it is assumed to go through coordinate origin.
2484 */
2485 static double point_line_distance(NR::Point *p, double a)
2486 {
2487 NR::Point c;
2488 point_line_closest(p, a, &c);
2489 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]));
2490 }
2492 /**
2493 * Callback for node "request" signal.
2494 * \todo fixme: This goes to "moved" event? (lauris)
2495 */
2496 static gboolean
2497 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2498 {
2499 double yn, xn, yp, xp;
2500 double an, ap, na, pa;
2501 double d_an, d_ap, d_na, d_pa;
2502 gboolean collinear = FALSE;
2503 NR::Point c;
2504 NR::Point pr;
2506 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2508 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2509 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2511 NR::Point mouse = (*p);
2513 if (!n->dragging_out) {
2514 // This is the first drag-out event; find out which handle to drag out
2515 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2516 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2518 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2519 return FALSE;
2521 Inkscape::NodePath::NodeSide *opposite;
2522 if (appr_p > appr_n) { // closer to p
2523 n->dragging_out = &n->p;
2524 opposite = &n->n;
2525 n->code = NR_CURVETO;
2526 } else if (appr_p < appr_n) { // closer to n
2527 n->dragging_out = &n->n;
2528 opposite = &n->p;
2529 n->n.other->code = NR_CURVETO;
2530 } else { // p and n nodes are the same
2531 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2532 n->dragging_out = &n->p;
2533 opposite = &n->n;
2534 n->code = NR_CURVETO;
2535 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2536 n->dragging_out = &n->n;
2537 opposite = &n->p;
2538 n->n.other->code = NR_CURVETO;
2539 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2540 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);
2541 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);
2542 if (appr_other_p > appr_other_n) { // closer to other's p handle
2543 n->dragging_out = &n->n;
2544 opposite = &n->p;
2545 n->n.other->code = NR_CURVETO;
2546 } else { // closer to other's n handle
2547 n->dragging_out = &n->p;
2548 opposite = &n->n;
2549 n->code = NR_CURVETO;
2550 }
2551 }
2552 }
2554 // if there's another handle, make sure the one we drag out starts parallel to it
2555 if (opposite->pos != n->pos) {
2556 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2557 }
2559 // knots might not be created yet!
2560 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2561 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2562 }
2564 // pass this on to the handle-moved callback
2565 node_ctrl_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2566 sp_node_ensure_ctrls(n);
2567 return TRUE;
2568 }
2570 if (state & GDK_CONTROL_MASK) { // constrained motion
2572 // calculate relative distances of handles
2573 // n handle:
2574 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2575 xn = n->n.pos[NR::X] - n->pos[NR::X];
2576 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2577 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2578 if (n->n.other) { // if there is the next point
2579 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2580 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2581 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2582 }
2583 }
2584 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2585 if (yn < 0) { xn = -xn; yn = -yn; }
2587 // p handle:
2588 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2589 xp = n->p.pos[NR::X] - n->pos[NR::X];
2590 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2591 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2592 if (n->p.other) {
2593 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2594 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2595 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2596 }
2597 }
2598 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2599 if (yp < 0) { xp = -xp; yp = -yp; }
2601 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2602 // sliding on handles, only if at least one of the handles is non-vertical
2603 // (otherwise it's the same as ctrl+drag anyway)
2605 // calculate angles of the control handles
2606 if (xn == 0) {
2607 if (yn == 0) { // no handle, consider it the continuation of the other one
2608 an = 0;
2609 collinear = TRUE;
2610 }
2611 else an = 0; // vertical; set the angle to horizontal
2612 } else an = yn/xn;
2614 if (xp == 0) {
2615 if (yp == 0) { // no handle, consider it the continuation of the other one
2616 ap = an;
2617 }
2618 else ap = 0; // vertical; set the angle to horizontal
2619 } else ap = yp/xp;
2621 if (collinear) an = ap;
2623 // angles of the perpendiculars; HUGE_VAL means vertical
2624 if (an == 0) na = HUGE_VAL; else na = -1/an;
2625 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2627 //g_print("an %g ap %g\n", an, ap);
2629 // mouse point relative to the node's original pos
2630 pr = (*p) - n->origin;
2632 // distances to the four lines (two handles and two perpendiculars)
2633 d_an = point_line_distance(&pr, an);
2634 d_na = point_line_distance(&pr, na);
2635 d_ap = point_line_distance(&pr, ap);
2636 d_pa = point_line_distance(&pr, pa);
2638 // find out which line is the closest, save its closest point in c
2639 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2640 point_line_closest(&pr, an, &c);
2641 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2642 point_line_closest(&pr, ap, &c);
2643 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2644 point_line_closest(&pr, na, &c);
2645 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2646 point_line_closest(&pr, pa, &c);
2647 }
2649 // move the node to the closest point
2650 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2651 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2652 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2654 } else { // constraining to hor/vert
2656 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2657 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2658 } else { // snap to vert
2659 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2660 }
2661 }
2662 } else { // move freely
2663 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2664 (*p)[NR::X] - n->pos[NR::X],
2665 (*p)[NR::Y] - n->pos[NR::Y],
2666 (state & GDK_SHIFT_MASK) == 0);
2667 }
2669 n->subpath->nodepath->desktop->scroll_to_point(p);
2671 return TRUE;
2672 }
2674 /**
2675 * Node handle clicked callback.
2676 */
2677 static void node_ctrl_clicked(SPKnot *knot, guint state, gpointer data)
2678 {
2679 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2681 if (state & GDK_CONTROL_MASK) { // "delete" handle
2682 if (n->p.knot == knot) {
2683 n->p.pos = n->pos;
2684 } else if (n->n.knot == knot) {
2685 n->n.pos = n->pos;
2686 }
2687 sp_node_ensure_ctrls(n);
2688 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2689 sp_nodepath_update_repr(nodepath);
2690 sp_nodepath_update_statusbar(nodepath);
2692 } else { // just select or add to selection, depending in Shift
2693 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2694 }
2695 }
2697 /**
2698 * Node handle grabbed callback.
2699 */
2700 static void node_ctrl_grabbed(SPKnot *knot, guint state, gpointer data)
2701 {
2702 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2704 if (!n->selected) {
2705 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2706 }
2708 // remember the origin of the control
2709 if (n->p.knot == knot) {
2710 n->p.origin = n->p.pos - n->pos;
2711 } else if (n->n.knot == knot) {
2712 n->n.origin = n->n.pos - n->pos;
2713 } else {
2714 g_assert_not_reached();
2715 }
2717 }
2719 /**
2720 * Node handle ungrabbed callback.
2721 */
2722 static void node_ctrl_ungrabbed(SPKnot *knot, guint state, gpointer data)
2723 {
2724 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2726 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2727 if (n->p.knot == knot) {
2728 n->p.origin.a = 0;
2729 sp_knot_set_position(knot, &n->p.pos, state);
2730 } else if (n->n.knot == knot) {
2731 n->n.origin.a = 0;
2732 sp_knot_set_position(knot, &n->n.pos, state);
2733 } else {
2734 g_assert_not_reached();
2735 }
2737 sp_nodepath_update_repr(n->subpath->nodepath);
2738 }
2740 /**
2741 * Node handle "request" signal callback.
2742 */
2743 static gboolean node_ctrl_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2744 {
2745 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2747 Inkscape::NodePath::NodeSide *me, *opposite;
2748 gint which;
2749 if (n->p.knot == knot) {
2750 me = &n->p;
2751 opposite = &n->n;
2752 which = -1;
2753 } else if (n->n.knot == knot) {
2754 me = &n->n;
2755 opposite = &n->p;
2756 which = 1;
2757 } else {
2758 me = opposite = NULL;
2759 which = 0;
2760 g_assert_not_reached();
2761 }
2763 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2765 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2767 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2768 /* We are smooth node adjacent with line */
2769 NR::Point const delta = *p - n->pos;
2770 NR::Coord const len = NR::L2(delta);
2771 Inkscape::NodePath::Node *othernode = opposite->other;
2772 NR::Point const ndelta = n->pos - othernode->pos;
2773 NR::Coord const linelen = NR::L2(ndelta);
2774 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2775 NR::Coord const scal = dot(delta, ndelta) / linelen;
2776 (*p) = n->pos + (scal / linelen) * ndelta;
2777 }
2778 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2779 } else {
2780 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2781 }
2783 sp_node_adjust_knot(n, -which);
2785 return FALSE;
2786 }
2788 /**
2789 * Node handle moved callback.
2790 */
2791 static void node_ctrl_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2792 {
2793 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2795 Inkscape::NodePath::NodeSide *me;
2796 Inkscape::NodePath::NodeSide *other;
2797 if (n->p.knot == knot) {
2798 me = &n->p;
2799 other = &n->n;
2800 } else if (n->n.knot == knot) {
2801 me = &n->n;
2802 other = &n->p;
2803 } else {
2804 me = NULL;
2805 other = NULL;
2806 g_assert_not_reached();
2807 }
2809 // calculate radial coordinates of the grabbed control, other control, and the mouse point
2810 Radial rme(me->pos - n->pos);
2811 Radial rother(other->pos - n->pos);
2812 Radial rnew(*p - n->pos);
2814 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2815 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2816 /* 0 interpreted as "no snapping". */
2818 // The closest PI/snaps angle, starting from zero.
2819 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2820 if (me->origin.a == HUGE_VAL) {
2821 // ortho doesn't exist: original control was zero length.
2822 rnew.a = a_snapped;
2823 } else {
2824 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2825 * its opposite and perpendiculars). */
2826 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2828 // Snap to the closest.
2829 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2830 ? a_snapped
2831 : a_ortho );
2832 }
2833 }
2835 if (state & GDK_MOD1_MASK) {
2836 // lock handle length
2837 rnew.r = me->origin.r;
2838 }
2840 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2841 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2842 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2843 rother.a += rnew.a - rme.a;
2844 other->pos = NR::Point(rother) + n->pos;
2845 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2846 sp_knot_set_position(other->knot, &other->pos, 0);
2847 }
2849 me->pos = NR::Point(rnew) + n->pos;
2850 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2852 // this is what sp_knot_set_position does, but without emitting the signal:
2853 // we cannot emit a "moved" signal because we're now processing it
2854 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2856 knot->desktop->set_coordinate_status(me->pos);
2858 update_object(n->subpath->nodepath);
2860 /* status text */
2861 SPDesktop *desktop = n->subpath->nodepath->desktop;
2862 if (!desktop) return;
2863 SPEventContext *ec = desktop->event_context;
2864 if (!ec) return;
2865 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2866 if (!mc) return;
2868 double degrees = 180 / M_PI * rnew.a;
2869 if (degrees > 180) degrees -= 360;
2870 if (degrees < -180) degrees += 360;
2871 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2872 degrees = angle_to_compass (degrees);
2874 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2876 mc->setF(Inkscape::NORMAL_MESSAGE,
2877 _("<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);
2879 g_string_free(length, TRUE);
2880 }
2882 /**
2883 * Node handle event callback.
2884 */
2885 static gboolean node_ctrl_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2886 {
2887 gboolean ret = FALSE;
2888 switch (event->type) {
2889 case GDK_KEY_PRESS:
2890 switch (get_group0_keyval (&event->key)) {
2891 case GDK_space:
2892 if (event->key.state & GDK_BUTTON1_MASK) {
2893 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2894 stamp_repr(nodepath);
2895 ret = TRUE;
2896 }
2897 break;
2898 default:
2899 break;
2900 }
2901 break;
2902 default:
2903 break;
2904 }
2906 return ret;
2907 }
2909 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2910 Radial &rme, Radial &rother, gboolean const both)
2911 {
2912 rme.a += angle;
2913 if ( both
2914 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2915 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2916 {
2917 rother.a += angle;
2918 }
2919 }
2921 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2922 Radial &rme, Radial &rother, gboolean const both)
2923 {
2924 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2926 gdouble r;
2927 if ( both
2928 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2929 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2930 {
2931 r = MAX(rme.r, rother.r);
2932 } else {
2933 r = rme.r;
2934 }
2936 gdouble const weird_angle = atan2(norm_angle, r);
2937 /* Bulia says norm_angle is just the visible distance that the
2938 * object's end must travel on the screen. Left as 'angle' for want of
2939 * a better name.*/
2941 rme.a += weird_angle;
2942 if ( both
2943 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2944 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2945 {
2946 rother.a += weird_angle;
2947 }
2948 }
2950 /**
2951 * Rotate one node.
2952 */
2953 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2954 {
2955 Inkscape::NodePath::NodeSide *me, *other;
2956 bool both = false;
2958 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2959 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2961 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2962 me = &(n->p);
2963 other = &(n->n);
2964 } else if (!n->p.other) {
2965 me = &(n->n);
2966 other = &(n->p);
2967 } else {
2968 if (which > 0) { // right handle
2969 if (xn > xp) {
2970 me = &(n->n);
2971 other = &(n->p);
2972 } else {
2973 me = &(n->p);
2974 other = &(n->n);
2975 }
2976 } else if (which < 0){ // left handle
2977 if (xn <= xp) {
2978 me = &(n->n);
2979 other = &(n->p);
2980 } else {
2981 me = &(n->p);
2982 other = &(n->n);
2983 }
2984 } else { // both handles
2985 me = &(n->n);
2986 other = &(n->p);
2987 both = true;
2988 }
2989 }
2991 Radial rme(me->pos - n->pos);
2992 Radial rother(other->pos - n->pos);
2994 if (screen) {
2995 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2996 } else {
2997 node_rotate_one_internal (*n, angle, rme, rother, both);
2998 }
3000 me->pos = n->pos + NR::Point(rme);
3002 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3003 other->pos = n->pos + NR::Point(rother);
3004 }
3006 sp_node_ensure_ctrls(n);
3007 }
3009 /**
3010 * Rotate selected nodes.
3011 */
3012 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3013 {
3014 if (!nodepath || !nodepath->selected) return;
3016 if (g_list_length(nodepath->selected) == 1) {
3017 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3018 node_rotate_one (n, angle, which, screen);
3019 } else {
3020 // rotate as an object:
3022 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3023 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3024 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3025 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3026 box.expandTo (n->pos); // contain all selected nodes
3027 }
3029 gdouble rot;
3030 if (screen) {
3031 gdouble const zoom = nodepath->desktop->current_zoom();
3032 gdouble const zmove = angle / zoom;
3033 gdouble const r = NR::L2(box.max() - box.midpoint());
3034 rot = atan2(zmove, r);
3035 } else {
3036 rot = angle;
3037 }
3039 NR::Matrix t =
3040 NR::Matrix (NR::translate(-box.midpoint())) *
3041 NR::Matrix (NR::rotate(rot)) *
3042 NR::Matrix (NR::translate(box.midpoint()));
3044 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3045 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3046 n->pos *= t;
3047 n->n.pos *= t;
3048 n->p.pos *= t;
3049 sp_node_ensure_ctrls(n);
3050 }
3051 }
3053 update_object(nodepath);
3054 /// \todo fixme: use _keyed
3055 sp_nodepath_update_repr(nodepath);
3056 }
3058 /**
3059 * Scale one node.
3060 */
3061 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3062 {
3063 bool both = false;
3064 Inkscape::NodePath::NodeSide *me, *other;
3066 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3067 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3069 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3070 me = &(n->p);
3071 other = &(n->n);
3072 n->code = NR_CURVETO;
3073 } else if (!n->p.other) {
3074 me = &(n->n);
3075 other = &(n->p);
3076 if (n->n.other)
3077 n->n.other->code = NR_CURVETO;
3078 } else {
3079 if (which > 0) { // right handle
3080 if (xn > xp) {
3081 me = &(n->n);
3082 other = &(n->p);
3083 if (n->n.other)
3084 n->n.other->code = NR_CURVETO;
3085 } else {
3086 me = &(n->p);
3087 other = &(n->n);
3088 n->code = NR_CURVETO;
3089 }
3090 } else if (which < 0){ // left handle
3091 if (xn <= xp) {
3092 me = &(n->n);
3093 other = &(n->p);
3094 if (n->n.other)
3095 n->n.other->code = NR_CURVETO;
3096 } else {
3097 me = &(n->p);
3098 other = &(n->n);
3099 n->code = NR_CURVETO;
3100 }
3101 } else { // both handles
3102 me = &(n->n);
3103 other = &(n->p);
3104 both = true;
3105 n->code = NR_CURVETO;
3106 if (n->n.other)
3107 n->n.other->code = NR_CURVETO;
3108 }
3109 }
3111 Radial rme(me->pos - n->pos);
3112 Radial rother(other->pos - n->pos);
3114 rme.r += grow;
3115 if (rme.r < 0) rme.r = 0;
3116 if (rme.a == HUGE_VAL) {
3117 if (me->other) { // if direction is unknown, initialize it towards the next node
3118 Radial rme_next(me->other->pos - n->pos);
3119 rme.a = rme_next.a;
3120 } else { // if there's no next, initialize to 0
3121 rme.a = 0;
3122 }
3123 }
3124 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3125 rother.r += grow;
3126 if (rother.r < 0) rother.r = 0;
3127 if (rother.a == HUGE_VAL) {
3128 rother.a = rme.a + M_PI;
3129 }
3130 }
3132 me->pos = n->pos + NR::Point(rme);
3134 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3135 other->pos = n->pos + NR::Point(rother);
3136 }
3138 sp_node_ensure_ctrls(n);
3139 }
3141 /**
3142 * Scale selected nodes.
3143 */
3144 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3145 {
3146 if (!nodepath || !nodepath->selected) return;
3148 if (g_list_length(nodepath->selected) == 1) {
3149 // scale handles of the single selected node
3150 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3151 node_scale_one (n, grow, which);
3152 } else {
3153 // scale nodes as an "object":
3155 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3156 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3157 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3158 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3159 box.expandTo (n->pos); // contain all selected nodes
3160 }
3162 double scale = (box.maxExtent() + grow)/box.maxExtent();
3164 NR::Matrix t =
3165 NR::Matrix (NR::translate(-box.midpoint())) *
3166 NR::Matrix (NR::scale(scale, scale)) *
3167 NR::Matrix (NR::translate(box.midpoint()));
3169 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3170 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3171 n->pos *= t;
3172 n->n.pos *= t;
3173 n->p.pos *= t;
3174 sp_node_ensure_ctrls(n);
3175 }
3176 }
3178 update_object(nodepath);
3179 /// \todo fixme: use _keyed
3180 sp_nodepath_update_repr(nodepath);
3181 }
3183 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3184 {
3185 if (!nodepath) return;
3186 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3187 }
3189 /**
3190 * Flip selected nodes horizontally/vertically.
3191 */
3192 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3193 {
3194 if (!nodepath || !nodepath->selected) return;
3196 if (g_list_length(nodepath->selected) == 1) {
3197 // flip handles of the single selected node
3198 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3199 double temp = n->p.pos[axis];
3200 n->p.pos[axis] = n->n.pos[axis];
3201 n->n.pos[axis] = temp;
3202 sp_node_ensure_ctrls(n);
3203 } else {
3204 // scale nodes as an "object":
3206 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3207 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3208 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3209 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3210 box.expandTo (n->pos); // contain all selected nodes
3211 }
3213 NR::Matrix t =
3214 NR::Matrix (NR::translate(-box.midpoint())) *
3215 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3216 NR::Matrix (NR::translate(box.midpoint()));
3218 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3219 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3220 n->pos *= t;
3221 n->n.pos *= t;
3222 n->p.pos *= t;
3223 sp_node_ensure_ctrls(n);
3224 }
3225 }
3227 update_object(nodepath);
3228 /// \todo fixme: use _keyed
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 // do not use prepend here because:
3250 // if you have a path like "subpath_1 subpath_2 ... subpath_k" in the svg, you end up with
3251 // subpath_k -> ... ->subpath_1 in the nodepath structure. thus the i-th node of the svg is not
3252 // the i-th node in the nodepath (only if there are multiple subpaths)
3253 // note that the problem only arise when called from subpath_from_bpath(), since for all the other
3254 // cases, the repr is updated after the call to sp_nodepath_subpath_new()
3255 nodepath->subpaths = g_list_append /*g_list_prepend*/ (nodepath->subpaths, s);
3257 return s;
3258 }
3260 /**
3261 * Destroy nodes in subpath, then subpath itself.
3262 */
3263 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3264 {
3265 g_assert(subpath);
3266 g_assert(subpath->nodepath);
3267 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3269 while (subpath->nodes) {
3270 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3271 }
3273 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3275 g_free(subpath);
3276 }
3278 /**
3279 * Link head to tail in subpath.
3280 */
3281 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3282 {
3283 g_assert(!sp->closed);
3284 g_assert(sp->last != sp->first);
3285 g_assert(sp->first->code == NR_MOVETO);
3287 sp->closed = TRUE;
3289 //Link the head to the tail
3290 sp->first->p.other = sp->last;
3291 sp->last->n.other = sp->first;
3292 sp->last->n.pos = sp->first->n.pos;
3293 sp->first = sp->last;
3295 //Remove the extra end node
3296 sp_nodepath_node_destroy(sp->last->n.other);
3297 }
3299 /**
3300 * Open closed (loopy) subpath at node.
3301 */
3302 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3303 {
3304 g_assert(sp->closed);
3305 g_assert(n->subpath == sp);
3306 g_assert(sp->first == sp->last);
3308 /* We create new startpoint, current node will become last one */
3310 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3311 &n->pos, &n->pos, &n->n.pos);
3314 sp->closed = FALSE;
3316 //Unlink to make a head and tail
3317 sp->first = new_path;
3318 sp->last = n;
3319 n->n.other = NULL;
3320 new_path->p.other = NULL;
3321 }
3323 /**
3324 * Returns area in triangle given by points; may be negative.
3325 */
3326 inline double
3327 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3328 {
3329 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]);
3330 }
3332 /**
3333 * Return new node in subpath with given properties.
3334 * \param pos Position of node.
3335 * \param ppos Handle position in previous direction
3336 * \param npos Handle position in previous direction
3337 */
3338 Inkscape::NodePath::Node *
3339 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)
3340 {
3341 g_assert(sp);
3342 g_assert(sp->nodepath);
3343 g_assert(sp->nodepath->desktop);
3345 if (nodechunk == NULL)
3346 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3348 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3350 n->subpath = sp;
3352 if (type != Inkscape::NodePath::NODE_NONE) {
3353 // use the type from sodipodi:nodetypes
3354 n->type = type;
3355 } else {
3356 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3357 // points are (almost) collinear
3358 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3359 // endnode, or a node with a retracted handle
3360 n->type = Inkscape::NodePath::NODE_CUSP;
3361 } else {
3362 n->type = Inkscape::NodePath::NODE_SMOOTH;
3363 }
3364 } else {
3365 n->type = Inkscape::NodePath::NODE_CUSP;
3366 }
3367 }
3369 n->code = code;
3370 n->selected = FALSE;
3371 n->pos = *pos;
3372 n->p.pos = *ppos;
3373 n->n.pos = *npos;
3375 n->dragging_out = NULL;
3377 Inkscape::NodePath::Node *prev;
3378 if (next) {
3379 //g_assert(g_list_find(sp->nodes, next));
3380 prev = next->p.other;
3381 } else {
3382 prev = sp->last;
3383 }
3385 if (prev)
3386 prev->n.other = n;
3387 else
3388 sp->first = n;
3390 if (next)
3391 next->p.other = n;
3392 else
3393 sp->last = n;
3395 n->p.other = prev;
3396 n->n.other = next;
3398 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"));
3399 sp_knot_set_position(n->knot, pos, 0);
3401 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3402 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3403 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3404 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3405 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3406 sp_knot_update_ctrl(n->knot);
3408 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3409 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3410 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3411 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3412 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3413 sp_knot_show(n->knot);
3415 // We only create side knots and lines on demand
3416 n->p.knot = NULL;
3417 n->p.line = NULL;
3418 n->n.knot = NULL;
3419 n->n.line = NULL;
3421 sp->nodes = g_list_prepend(sp->nodes, n);
3423 return n;
3424 }
3426 /**
3427 * Destroy node and its knots, link neighbors in subpath.
3428 */
3429 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3430 {
3431 g_assert(node);
3432 g_assert(node->subpath);
3433 g_assert(SP_IS_KNOT(node->knot));
3434 // g_assert(g_list_find(node->subpath->nodes, node));
3436 Inkscape::NodePath::SubPath *sp = node->subpath;
3438 if (node->selected) { // first, deselect
3439 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3440 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3441 }
3443 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3445 g_object_unref(G_OBJECT(node->knot));
3446 if (node->p.knot)
3447 g_object_unref(G_OBJECT(node->p.knot));
3448 if (node->n.knot)
3449 g_object_unref(G_OBJECT(node->n.knot));
3451 if (node->p.line)
3452 gtk_object_destroy(GTK_OBJECT(node->p.line));
3453 if (node->n.line)
3454 gtk_object_destroy(GTK_OBJECT(node->n.line));
3456 if (sp->nodes) { // there are others nodes on the subpath
3457 if (sp->closed) {
3458 if (sp->first == node) {
3459 g_assert(sp->last == node);
3460 sp->first = node->n.other;
3461 sp->last = sp->first;
3462 }
3463 node->p.other->n.other = node->n.other;
3464 node->n.other->p.other = node->p.other;
3465 } else {
3466 if (sp->first == node) {
3467 sp->first = node->n.other;
3468 sp->first->code = NR_MOVETO;
3469 }
3470 if (sp->last == node) sp->last = node->p.other;
3471 if (node->p.other) node->p.other->n.other = node->n.other;
3472 if (node->n.other) node->n.other->p.other = node->p.other;
3473 }
3474 } else { // this was the last node on subpath
3475 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3476 }
3478 g_mem_chunk_free(nodechunk, node);
3479 }
3481 /**
3482 * Returns one of the node's two knots (node sides).
3483 * \param which Indicates which side.
3484 * \return Pointer to previous node side if which==-1, next if which==1.
3485 */
3486 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3487 {
3488 g_assert(node);
3490 switch (which) {
3491 case -1:
3492 return &node->p;
3493 case 1:
3494 return &node->n;
3495 default:
3496 break;
3497 }
3499 g_assert_not_reached();
3501 return NULL;
3502 }
3504 /**
3505 * Return knot on other side of node.
3506 */
3507 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3508 {
3509 g_assert(node);
3511 if (me == &node->p) return &node->n;
3512 if (me == &node->n) return &node->p;
3514 g_assert_not_reached();
3516 return NULL;
3517 }
3519 /**
3520 * Return NRPathcode on this knot's side of the node.
3521 */
3522 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3523 {
3524 g_assert(node);
3526 if (me == &node->p) {
3527 if (node->p.other) return (NRPathcode)node->code;
3528 return NR_MOVETO;
3529 }
3531 if (me == &node->n) {
3532 if (node->n.other) return (NRPathcode)node->n.other->code;
3533 return NR_MOVETO;
3534 }
3536 g_assert_not_reached();
3538 return NR_END;
3539 }
3541 /**
3542 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3543 */
3544 Inkscape::NodePath::Node *
3545 sp_nodepath_get_node_by_index(int index)
3546 {
3547 Inkscape::NodePath::Node *e = NULL;
3549 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3550 if (!nodepath) {
3551 return e;
3552 }
3554 //find segment
3555 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3557 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3558 int n = g_list_length(sp->nodes);
3559 if (sp->closed) {
3560 n++;
3561 }
3563 //if the piece belongs to this subpath grab it
3564 //otherwise move onto the next subpath
3565 if (index < n) {
3566 e = sp->first;
3567 for (int i = 0; i < index; ++i) {
3568 e = e->n.other;
3569 }
3570 break;
3571 } else {
3572 if (sp->closed) {
3573 index -= (n+1);
3574 } else {
3575 index -= n;
3576 }
3577 }
3578 }
3580 return e;
3581 }
3583 /**
3584 * Returns plain text meaning of node type.
3585 */
3586 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3587 {
3588 unsigned retracted = 0;
3589 bool endnode = false;
3591 for (int which = -1; which <= 1; which += 2) {
3592 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3593 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3594 retracted ++;
3595 if (!side->other)
3596 endnode = true;
3597 }
3599 if (retracted == 0) {
3600 if (endnode) {
3601 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3602 return _("end node");
3603 } else {
3604 switch (node->type) {
3605 case Inkscape::NodePath::NODE_CUSP:
3606 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3607 return _("cusp");
3608 case Inkscape::NodePath::NODE_SMOOTH:
3609 // TRANSLATORS: "smooth" is an adjective here
3610 return _("smooth");
3611 case Inkscape::NodePath::NODE_SYMM:
3612 return _("symmetric");
3613 }
3614 }
3615 } else if (retracted == 1) {
3616 if (endnode) {
3617 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3618 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3619 } else {
3620 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3621 }
3622 } else {
3623 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3624 }
3626 return NULL;
3627 }
3629 /**
3630 * Handles content of statusbar as long as node tool is active.
3631 */
3632 void
3633 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3634 {
3635 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3636 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3638 gint total = 0;
3639 gint selected = 0;
3640 SPDesktop *desktop = NULL;
3642 if (nodepath) {
3643 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3644 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3645 total += g_list_length(subpath->nodes);
3646 }
3647 selected = g_list_length(nodepath->selected);
3648 desktop = nodepath->desktop;
3649 } else {
3650 desktop = SP_ACTIVE_DESKTOP;
3651 }
3653 SPEventContext *ec = desktop->event_context;
3654 if (!ec) return;
3655 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3656 if (!mc) return;
3658 if (selected == 0) {
3659 Inkscape::Selection *sel = desktop->selection;
3660 if (!sel || sel->isEmpty()) {
3661 mc->setF(Inkscape::NORMAL_MESSAGE,
3662 _("Select a single object to edit its nodes or handles."));
3663 } else {
3664 if (nodepath) {
3665 mc->setF(Inkscape::NORMAL_MESSAGE,
3666 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.",
3667 "<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.",
3668 total),
3669 total);
3670 } else {
3671 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3672 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3673 } else {
3674 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3675 }
3676 }
3677 }
3678 } else if (nodepath && selected == 1) {
3679 mc->setF(Inkscape::NORMAL_MESSAGE,
3680 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3681 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3682 total),
3683 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3684 } else {
3685 mc->setF(Inkscape::NORMAL_MESSAGE,
3686 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3687 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3688 total),
3689 selected, total, when_selected);
3690 }
3691 }
3694 /*
3695 Local Variables:
3696 mode:c++
3697 c-file-style:"stroustrup"
3698 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3699 indent-tabs-mode:nil
3700 fill-column:99
3701 End:
3702 */
3703 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :