b50ee90c7b6a0acae5ae6540a89024627b0eca5d
1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * This code is in public domain
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include <libnr/nr-matrix-ops.h>
41 #include "splivarot.h"
42 #include "svg/svg.h"
44 class NR::Matrix;
46 /// \todo
47 /// evil evil evil. FIXME: conflict of two different Path classes!
48 /// There is a conflict in the namespace between two classes named Path.
49 /// #include "sp-flowtext.h"
50 /// #include "sp-flowregion.h"
52 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
53 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
54 GType sp_flowregion_get_type (void);
55 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
56 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
57 GType sp_flowtext_get_type (void);
58 // end evil workaround
60 #include "helper/stlport.h"
63 /// \todo fixme: Implement these via preferences */
65 #define NODE_FILL 0xbfbfbf00
66 #define NODE_STROKE 0x000000ff
67 #define NODE_FILL_HI 0xff000000
68 #define NODE_STROKE_HI 0x000000ff
69 #define NODE_FILL_SEL 0x0000ffff
70 #define NODE_STROKE_SEL 0x000000ff
71 #define NODE_FILL_SEL_HI 0xff000000
72 #define NODE_STROKE_SEL_HI 0x000000ff
73 #define KNOT_FILL 0xffffffff
74 #define KNOT_STROKE 0x000000ff
75 #define KNOT_FILL_HI 0xff000000
76 #define KNOT_STROKE_HI 0x000000ff
78 static GMemChunk *nodechunk = NULL;
80 /* Creation from object */
82 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
83 static gchar *parse_nodetypes(gchar const *types, gint length);
85 /* Object updating */
87 static void stamp_repr(Inkscape::NodePath::Path *np);
88 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
89 static gchar *create_typestr(Inkscape::NodePath::Path *np);
91 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
93 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
95 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
97 /* Adjust handle placement, if the node or the other handle is moved */
98 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
99 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
101 /* Node event callbacks */
102 static void node_clicked(SPKnot *knot, guint state, gpointer data);
103 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
104 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
105 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
107 /* Handle event callbacks */
108 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
109 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
110 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
111 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
113 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
115 /* Constructors and destructors */
117 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
118 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
119 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
120 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
121 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
122 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
123 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
125 /* Helpers */
127 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
128 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
129 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
131 // active_node indicates mouseover node
132 static Inkscape::NodePath::Node *active_node = NULL;
134 /**
135 * \brief Creates new nodepath from item
136 */
137 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item)
138 {
139 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
141 /** \todo
142 * FIXME: remove this. We don't want to edit paths inside flowtext.
143 * Instead we will build our flowtext with cloned paths, so that the
144 * real paths are outside the flowtext and thus editable as usual.
145 */
146 if (SP_IS_FLOWTEXT(item)) {
147 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
148 if SP_IS_FLOWREGION(child) {
149 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
150 if (grandchild && SP_IS_PATH(grandchild)) {
151 item = SP_ITEM(grandchild);
152 break;
153 }
154 }
155 }
156 }
158 if (!SP_IS_PATH(item))
159 return NULL;
160 SPPath *path = SP_PATH(item);
161 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
162 if (curve == NULL)
163 return NULL;
165 NArtBpath *bpath = sp_curve_first_bpath(curve);
166 gint length = curve->end;
167 if (length == 0)
168 return NULL; // prevent crash for one-node paths
170 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
171 gchar *typestr = parse_nodetypes(nodetypes, length);
173 //Create new nodepath
174 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
175 if (!np)
176 return NULL;
178 // Set defaults
179 np->desktop = desktop;
180 np->path = path;
181 np->subpaths = NULL;
182 np->selected = NULL;
183 np->nodeContext = NULL; //Let the context that makes this set it
184 np->livarot_path = NULL;
185 np->local_change = 0;
187 // we need to update item's transform from the repr here,
188 // because they may be out of sync when we respond
189 // to a change in repr by regenerating nodepath --bb
190 sp_object_read_attr(SP_OBJECT(item), "transform");
192 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
193 np->d2i = np->i2d.inverse();
194 np->repr = repr;
196 // create the subpath(s) from the bpath
197 NArtBpath *b = bpath;
198 while (b->code != NR_END) {
199 b = subpath_from_bpath(np, b, typestr + (b - bpath));
200 }
202 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
203 np->subpaths = g_list_reverse(np->subpaths);
205 g_free(typestr);
206 sp_curve_unref(curve);
208 // create the livarot representation from the same item
209 np->livarot_path = Path_for_item(item, true, true);
210 if (np->livarot_path)
211 np->livarot_path->ConvertWithBackData(0.01);
213 return np;
214 }
216 /**
217 * Destroys nodepath's subpaths, then itself, also tell context about it.
218 */
219 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
221 if (!np) //soft fail, like delete
222 return;
224 while (np->subpaths) {
225 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
226 }
228 //Inform the context that made me, if any, that I am gone.
229 if (np->nodeContext)
230 np->nodeContext->nodepath = NULL;
232 g_assert(!np->selected);
234 if (np->livarot_path) {
235 delete np->livarot_path;
236 np->livarot_path = NULL;
237 }
239 np->desktop = NULL;
241 g_free(np);
242 }
245 /**
246 * Return the node count of a given NodeSubPath.
247 */
248 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
249 {
250 if (!subpath)
251 return 0;
252 gint nodeCount = g_list_length(subpath->nodes);
253 return nodeCount;
254 }
256 /**
257 * Return the node count of a given NodePath.
258 */
259 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
260 {
261 if (!np)
262 return 0;
263 gint nodeCount = 0;
264 for (GList *item = np->subpaths ; item ; item=item->next) {
265 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
266 nodeCount += g_list_length(subpath->nodes);
267 }
268 return nodeCount;
269 }
272 /**
273 * Clean up a nodepath after editing.
274 *
275 * Currently we are deleting trivial subpaths.
276 */
277 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
278 {
279 GList *badSubPaths = NULL;
281 //Check all subpaths to be >=2 nodes
282 for (GList *l = nodepath->subpaths; l ; l=l->next) {
283 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
284 if (sp_nodepath_subpath_get_node_count(sp)<2)
285 badSubPaths = g_list_append(badSubPaths, sp);
286 }
288 //Delete them. This second step is because sp_nodepath_subpath_destroy()
289 //also removes the subpath from nodepath->subpaths
290 for (GList *l = badSubPaths; l ; l=l->next) {
291 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
292 sp_nodepath_subpath_destroy(sp);
293 }
295 g_list_free(badSubPaths);
296 }
298 /**
299 * Create new nodepath from b, make it subpath of np.
300 * \param t The node type.
301 * \todo Fixme: t should be a proper type, rather than gchar
302 */
303 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
304 {
305 NR::Point ppos, pos, npos;
307 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
309 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
310 bool const closed = (b->code == NR_MOVETO);
312 pos = NR::Point(b->x3, b->y3) * np->i2d;
313 if (b[1].code == NR_CURVETO) {
314 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
315 } else {
316 npos = pos;
317 }
318 Inkscape::NodePath::Node *n;
319 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
320 g_assert(sp->first == n);
321 g_assert(sp->last == n);
323 b++;
324 t++;
325 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
326 pos = NR::Point(b->x3, b->y3) * np->i2d;
327 if (b->code == NR_CURVETO) {
328 ppos = NR::Point(b->x2, b->y2) * np->i2d;
329 } else {
330 ppos = pos;
331 }
332 if (b[1].code == NR_CURVETO) {
333 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
334 } else {
335 npos = pos;
336 }
337 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
338 b++;
339 t++;
340 }
342 if (closed) sp_nodepath_subpath_close(sp);
344 return b;
345 }
347 /**
348 * Convert from sodipodi:nodetypes to new style type string.
349 */
350 static gchar *parse_nodetypes(gchar const *types, gint length)
351 {
352 g_assert(length > 0);
354 gchar *typestr = g_new(gchar, length + 1);
356 gint pos = 0;
358 if (types) {
359 for (gint i = 0; types[i] && ( i < length ); i++) {
360 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
361 if (types[i] != '\0') {
362 switch (types[i]) {
363 case 's':
364 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
365 break;
366 case 'z':
367 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
368 break;
369 case 'c':
370 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
371 break;
372 default:
373 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
374 break;
375 }
376 }
377 }
378 }
380 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
382 return typestr;
383 }
385 /**
386 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
387 * updated but repr is not (for speed). Used during curve and node drag.
388 */
389 static void update_object(Inkscape::NodePath::Path *np)
390 {
391 g_assert(np);
393 SPCurve *curve = create_curve(np);
395 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
397 sp_curve_unref(curve);
398 }
400 /**
401 * Update XML path node with data from path object.
402 */
403 static void update_repr_internal(Inkscape::NodePath::Path *np)
404 {
405 g_assert(np);
407 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
409 SPCurve *curve = create_curve(np);
410 gchar *typestr = create_typestr(np);
411 gchar *svgpath = sp_svg_write_path(curve->bpath);
413 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
414 np->local_change++;
415 repr->setAttribute("d", svgpath);
416 }
418 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
419 np->local_change++;
420 repr->setAttribute("sodipodi:nodetypes", typestr);
421 }
423 g_free(svgpath);
424 g_free(typestr);
425 sp_curve_unref(curve);
426 }
428 /**
429 * Update XML path node with data from path object, commit changes forever.
430 */
431 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
432 {
433 update_repr_internal(np);
434 sp_document_done(SP_DT_DOCUMENT(np->desktop));
436 if (np->livarot_path) {
437 delete np->livarot_path;
438 np->livarot_path = NULL;
439 }
441 if (np->path && SP_IS_ITEM(np->path)) {
442 np->livarot_path = Path_for_item (np->path, true, true);
443 if (np->livarot_path)
444 np->livarot_path->ConvertWithBackData(0.01);
445 }
446 }
448 /**
449 * Update XML path node with data from path object, commit changes with undo.
450 */
451 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
452 {
453 update_repr_internal(np);
454 sp_document_maybe_done(SP_DT_DOCUMENT(np->desktop), key);
456 if (np->livarot_path) {
457 delete np->livarot_path;
458 np->livarot_path = NULL;
459 }
461 if (np->path && SP_IS_ITEM(np->path)) {
462 np->livarot_path = Path_for_item (np->path, true, true);
463 if (np->livarot_path)
464 np->livarot_path->ConvertWithBackData(0.01);
465 }
466 }
468 /**
469 * Make duplicate of path, replace corresponding XML node in tree, commit.
470 */
471 static void stamp_repr(Inkscape::NodePath::Path *np)
472 {
473 g_assert(np);
475 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
476 Inkscape::XML::Node *new_repr = old_repr->duplicate();
478 // remember the position of the item
479 gint pos = old_repr->position();
480 // remember parent
481 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
483 SPCurve *curve = create_curve(np);
484 gchar *typestr = create_typestr(np);
486 gchar *svgpath = sp_svg_write_path(curve->bpath);
488 new_repr->setAttribute("d", svgpath);
489 new_repr->setAttribute("sodipodi:nodetypes", typestr);
491 // add the new repr to the parent
492 parent->appendChild(new_repr);
493 // move to the saved position
494 new_repr->setPosition(pos > 0 ? pos : 0);
496 sp_document_done(SP_DT_DOCUMENT(np->desktop));
498 Inkscape::GC::release(new_repr);
499 g_free(svgpath);
500 g_free(typestr);
501 sp_curve_unref(curve);
502 }
504 /**
505 * Create curve from path.
506 */
507 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
508 {
509 SPCurve *curve = sp_curve_new();
511 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
512 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
513 sp_curve_moveto(curve,
514 sp->first->pos * np->d2i);
515 Inkscape::NodePath::Node *n = sp->first->n.other;
516 while (n) {
517 NR::Point const end_pt = n->pos * np->d2i;
518 switch (n->code) {
519 case NR_LINETO:
520 sp_curve_lineto(curve, end_pt);
521 break;
522 case NR_CURVETO:
523 sp_curve_curveto(curve,
524 n->p.other->n.pos * np->d2i,
525 n->p.pos * np->d2i,
526 end_pt);
527 break;
528 default:
529 g_assert_not_reached();
530 break;
531 }
532 if (n != sp->last) {
533 n = n->n.other;
534 } else {
535 n = NULL;
536 }
537 }
538 if (sp->closed) {
539 sp_curve_closepath(curve);
540 }
541 }
543 return curve;
544 }
546 /**
547 * Convert path type string to sodipodi:nodetypes style.
548 */
549 static gchar *create_typestr(Inkscape::NodePath::Path *np)
550 {
551 gchar *typestr = g_new(gchar, 32);
552 gint len = 32;
553 gint pos = 0;
555 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
556 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
558 if (pos >= len) {
559 typestr = g_renew(gchar, typestr, len + 32);
560 len += 32;
561 }
563 typestr[pos++] = 'c';
565 Inkscape::NodePath::Node *n;
566 n = sp->first->n.other;
567 while (n) {
568 gchar code;
570 switch (n->type) {
571 case Inkscape::NodePath::NODE_CUSP:
572 code = 'c';
573 break;
574 case Inkscape::NodePath::NODE_SMOOTH:
575 code = 's';
576 break;
577 case Inkscape::NodePath::NODE_SYMM:
578 code = 'z';
579 break;
580 default:
581 g_assert_not_reached();
582 code = '\0';
583 break;
584 }
586 if (pos >= len) {
587 typestr = g_renew(gchar, typestr, len + 32);
588 len += 32;
589 }
591 typestr[pos++] = code;
593 if (n != sp->last) {
594 n = n->n.other;
595 } else {
596 n = NULL;
597 }
598 }
599 }
601 if (pos >= len) {
602 typestr = g_renew(gchar, typestr, len + 1);
603 len += 1;
604 }
606 typestr[pos++] = '\0';
608 return typestr;
609 }
611 /**
612 * Returns current path in context.
613 */
614 static Inkscape::NodePath::Path *sp_nodepath_current()
615 {
616 if (!SP_ACTIVE_DESKTOP) {
617 return NULL;
618 }
620 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
622 if (!SP_IS_NODE_CONTEXT(event_context)) {
623 return NULL;
624 }
626 return SP_NODE_CONTEXT(event_context)->nodepath;
627 }
631 /**
632 \brief Fills node and handle positions for three nodes, splitting line
633 marked by end at distance t.
634 */
635 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
636 {
637 g_assert(new_path != NULL);
638 g_assert(end != NULL);
640 g_assert(end->p.other == new_path);
641 Inkscape::NodePath::Node *start = new_path->p.other;
642 g_assert(start);
644 if (end->code == NR_LINETO) {
645 new_path->type =Inkscape::NodePath::NODE_CUSP;
646 new_path->code = NR_LINETO;
647 new_path->pos = (t * start->pos + (1 - t) * end->pos);
648 } else {
649 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
650 new_path->code = NR_CURVETO;
651 gdouble s = 1 - t;
652 for (int dim = 0; dim < 2; dim++) {
653 NR::Coord const f000 = start->pos[dim];
654 NR::Coord const f001 = start->n.pos[dim];
655 NR::Coord const f011 = end->p.pos[dim];
656 NR::Coord const f111 = end->pos[dim];
657 NR::Coord const f00t = s * f000 + t * f001;
658 NR::Coord const f01t = s * f001 + t * f011;
659 NR::Coord const f11t = s * f011 + t * f111;
660 NR::Coord const f0tt = s * f00t + t * f01t;
661 NR::Coord const f1tt = s * f01t + t * f11t;
662 NR::Coord const fttt = s * f0tt + t * f1tt;
663 start->n.pos[dim] = f00t;
664 new_path->p.pos[dim] = f0tt;
665 new_path->pos[dim] = fttt;
666 new_path->n.pos[dim] = f1tt;
667 end->p.pos[dim] = f11t;
668 }
669 }
670 }
672 /**
673 * Adds new node on direct line between two nodes, activates handles of all
674 * three nodes.
675 */
676 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
677 {
678 g_assert(end);
679 g_assert(end->subpath);
680 g_assert(g_list_find(end->subpath->nodes, end));
682 Inkscape::NodePath::Node *start = end->p.other;
683 g_assert( start->n.other == end );
684 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
685 end,
686 Inkscape::NodePath::NODE_SMOOTH,
687 (NRPathcode)end->code,
688 &start->pos, &start->pos, &start->n.pos);
689 sp_nodepath_line_midpoint(newnode, end, t);
691 sp_node_update_handles(start);
692 sp_node_update_handles(newnode);
693 sp_node_update_handles(end);
695 return newnode;
696 }
698 /**
699 \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
700 */
701 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
702 {
703 g_assert(node);
704 g_assert(node->subpath);
705 g_assert(g_list_find(node->subpath->nodes, node));
707 Inkscape::NodePath::SubPath *sp = node->subpath;
708 Inkscape::NodePath::Path *np = sp->nodepath;
710 if (sp->closed) {
711 sp_nodepath_subpath_open(sp, node);
712 return sp->first;
713 } else {
714 // no break for end nodes
715 if (node == sp->first) return NULL;
716 if (node == sp->last ) return NULL;
718 // create a new subpath
719 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
721 // duplicate the break node as start of the new subpath
722 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
724 while (node->n.other) { // copy the remaining nodes into the new subpath
725 Inkscape::NodePath::Node *n = node->n.other;
726 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);
727 if (n->selected) {
728 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
729 }
730 sp_nodepath_node_destroy(n); // remove the point on the original subpath
731 }
733 return newnode;
734 }
735 }
737 /**
738 * Duplicate node and connect to neighbours.
739 */
740 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
741 {
742 g_assert(node);
743 g_assert(node->subpath);
744 g_assert(g_list_find(node->subpath->nodes, node));
746 Inkscape::NodePath::SubPath *sp = node->subpath;
748 NRPathcode code = (NRPathcode) node->code;
749 if (code == NR_MOVETO) { // if node is the endnode,
750 node->code = NR_LINETO; // new one is inserted before it, so change that to line
751 }
753 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
755 if (!node->n.other || !node->p.other) // if node is an endnode, select it
756 return node;
757 else
758 return newnode; // otherwise select the newly created node
759 }
761 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
762 {
763 node->p.pos = (node->pos + (node->pos - node->n.pos));
764 }
766 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
767 {
768 node->n.pos = (node->pos + (node->pos - node->p.pos));
769 }
771 /**
772 * Change line type at node, with side effects on neighbours.
773 */
774 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
775 {
776 g_assert(end);
777 g_assert(end->subpath);
778 g_assert(end->p.other);
780 if (end->code == static_cast< guint > ( code ) )
781 return;
783 Inkscape::NodePath::Node *start = end->p.other;
785 end->code = code;
787 if (code == NR_LINETO) {
788 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
789 if (end->n.other) {
790 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
791 }
792 sp_node_adjust_handle(start, -1);
793 sp_node_adjust_handle(end, 1);
794 } else {
795 NR::Point delta = end->pos - start->pos;
796 start->n.pos = start->pos + delta / 3;
797 end->p.pos = end->pos - delta / 3;
798 sp_node_adjust_handle(start, 1);
799 sp_node_adjust_handle(end, -1);
800 }
802 sp_node_update_handles(start);
803 sp_node_update_handles(end);
804 }
806 /**
807 * Change node type, and its handles accordingly.
808 */
809 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
810 {
811 g_assert(node);
812 g_assert(node->subpath);
814 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
815 return node;
817 if ((node->p.other != NULL) && (node->n.other != NULL)) {
818 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
819 type =Inkscape::NodePath::NODE_CUSP;
820 }
821 }
823 node->type = type;
825 if (node->type == Inkscape::NodePath::NODE_CUSP) {
826 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
827 node->knot->setSize (node->selected? 11 : 9);
828 sp_knot_update_ctrl(node->knot);
829 } else {
830 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
831 node->knot->setSize (node->selected? 9 : 7);
832 sp_knot_update_ctrl(node->knot);
833 }
835 // if one of handles is mouseovered, preserve its position
836 if (node->p.knot && SP_KNOT_IS_MOSEOVER(node->p.knot)) {
837 sp_node_adjust_handle(node, 1);
838 } else if (node->n.knot && SP_KNOT_IS_MOSEOVER(node->n.knot)) {
839 sp_node_adjust_handle(node, -1);
840 } else {
841 sp_node_adjust_handles(node);
842 }
844 sp_node_update_handles(node);
846 sp_nodepath_update_statusbar(node->subpath->nodepath);
848 return node;
849 }
851 /**
852 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
853 * adjacent segments from lines to curves.
854 */
855 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
856 {
857 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
858 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
859 // convert adjacent segment BEFORE to curve
860 node->code = NR_CURVETO;
861 NR::Point delta;
862 if (node->n.other != NULL)
863 delta = node->n.other->pos - node->p.other->pos;
864 else
865 delta = node->pos - node->p.other->pos;
866 node->p.pos = node->pos - delta / 4;
867 sp_node_update_handles(node);
868 }
870 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
871 // convert adjacent segment AFTER to curve
872 node->n.other->code = NR_CURVETO;
873 NR::Point delta;
874 if (node->p.other != NULL)
875 delta = node->p.other->pos - node->n.other->pos;
876 else
877 delta = node->pos - node->n.other->pos;
878 node->n.pos = node->pos - delta / 4;
879 sp_node_update_handles(node);
880 }
881 }
883 sp_nodepath_set_node_type (node, type);
884 }
886 /**
887 * Move node to point, and adjust its and neighbouring handles.
888 */
889 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
890 {
891 NR::Point delta = p - node->pos;
892 node->pos = p;
894 node->p.pos += delta;
895 node->n.pos += delta;
897 if (node->p.other) {
898 if (node->code == NR_LINETO) {
899 sp_node_adjust_handle(node, 1);
900 sp_node_adjust_handle(node->p.other, -1);
901 }
902 }
903 if (node->n.other) {
904 if (node->n.other->code == NR_LINETO) {
905 sp_node_adjust_handle(node, -1);
906 sp_node_adjust_handle(node->n.other, 1);
907 }
908 }
910 // this function is only called from batch movers that will update display at the end
911 // themselves, so here we just move all the knots without emitting move signals, for speed
912 sp_node_update_handles(node, false);
913 }
915 /**
916 * Call sp_node_moveto() for node selection and handle possible snapping.
917 */
918 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
919 bool const snap = true)
920 {
921 NR::Coord best[2] = { NR_HUGE, NR_HUGE };
922 NR::Point delta(dx, dy);
923 NR::Point best_pt = delta;
925 if (snap) {
926 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
927 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
928 NR::Point p = n->pos + delta;
929 for (int dim = 0; dim < 2; dim++) {
930 NR::Coord dist = namedview_dim_snap(nodepath->desktop->namedview,
931 Inkscape::Snapper::SNAP_POINT, p,
932 NR::Dim2(dim), nodepath->path);
933 if (dist < best[dim]) {
934 best[dim] = dist;
935 best_pt[dim] = p[dim] - n->pos[dim];
936 }
937 }
938 }
939 }
941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
942 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
943 sp_node_moveto(n, n->pos + best_pt);
944 }
946 // do not update repr here so that node dragging is acceptably fast
947 update_object(nodepath);
948 }
950 /**
951 * Move node selection to point, adjust its and neighbouring handles,
952 * handle possible snapping, and commit the change with possible undo.
953 */
954 void
955 sp_node_selected_move(gdouble dx, gdouble dy)
956 {
957 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
958 if (!nodepath) return;
960 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
962 if (dx == 0) {
963 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
964 } else if (dy == 0) {
965 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
966 } else {
967 sp_nodepath_update_repr(nodepath);
968 }
969 }
971 /**
972 * Move node selection off screen and commit the change.
973 */
974 void
975 sp_node_selected_move_screen(gdouble dx, gdouble dy)
976 {
977 // borrowed from sp_selection_move_screen in selection-chemistry.c
978 // we find out the current zoom factor and divide deltas by it
979 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
981 gdouble zoom = desktop->current_zoom();
982 gdouble zdx = dx / zoom;
983 gdouble zdy = dy / zoom;
985 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
986 if (!nodepath) return;
988 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
990 if (dx == 0) {
991 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
992 } else if (dy == 0) {
993 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
994 } else {
995 sp_nodepath_update_repr(nodepath);
996 }
997 }
999 /** If they don't yet exist, creates knot and line for the given side of the node */
1000 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1001 {
1002 if (!side->knot) {
1003 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"));
1005 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1006 side->knot->setSize (7);
1007 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1008 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1009 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1010 sp_knot_update_ctrl(side->knot);
1012 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1013 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1014 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1015 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1016 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1017 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1018 }
1020 if (!side->line) {
1021 side->line = sp_canvas_item_new(SP_DT_CONTROLS(desktop),
1022 SP_TYPE_CTRLLINE, NULL);
1023 }
1024 }
1026 /**
1027 * Ensure the given handle of the node is visible/invisible, update its screen position
1028 */
1029 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1030 {
1031 g_assert(node != NULL);
1033 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1034 NRPathcode code = sp_node_path_code_from_side(node, side);
1036 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1038 if (show_handle) {
1039 if (!side->knot) { // No handle knot at all
1040 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1041 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1042 side->knot->pos = side->pos;
1043 if (side->knot->item)
1044 SP_CTRL(side->knot->item)->moveto(side->pos);
1045 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1046 sp_knot_show(side->knot);
1047 } else {
1048 if (side->knot->pos != side->pos) { // only if it's really moved
1049 if (fire_move_signals) {
1050 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1051 } else {
1052 sp_knot_moveto(side->knot, &side->pos);
1053 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1054 }
1055 }
1056 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1057 sp_knot_show(side->knot);
1058 }
1059 }
1060 sp_canvas_item_show(side->line);
1061 } else {
1062 if (side->knot) {
1063 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1064 sp_knot_hide(side->knot);
1065 }
1066 }
1067 if (side->line) {
1068 sp_canvas_item_hide(side->line);
1069 }
1070 }
1071 }
1073 /**
1074 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1075 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1076 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1077 * updated; otherwise, just move the knots silently (used in batch moves).
1078 */
1079 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1080 {
1081 g_assert(node != NULL);
1083 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1084 sp_knot_show(node->knot);
1085 }
1087 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1088 if (fire_move_signals)
1089 sp_knot_set_position(node->knot, &node->pos, 0);
1090 else
1091 sp_knot_moveto(node->knot, &node->pos);
1092 }
1094 gboolean show_handles = node->selected;
1095 if (node->p.other != NULL) {
1096 if (node->p.other->selected) show_handles = TRUE;
1097 }
1098 if (node->n.other != NULL) {
1099 if (node->n.other->selected) show_handles = TRUE;
1100 }
1102 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1103 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1104 }
1106 /**
1107 * Call sp_node_update_handles() for all nodes on subpath.
1108 */
1109 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1110 {
1111 g_assert(subpath != NULL);
1113 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1114 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1115 }
1116 }
1118 /**
1119 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1120 */
1121 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1122 {
1123 g_assert(nodepath != NULL);
1125 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1126 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1127 }
1128 }
1130 /**
1131 * Adds all selected nodes in nodepath to list.
1132 */
1133 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1134 {
1135 StlConv<Node *>::list(l, selected);
1136 /// \todo this adds a copying, rework when the selection becomes a stl list
1137 }
1139 /**
1140 * Align selected nodes on the specified axis.
1141 */
1142 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1143 {
1144 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1145 return;
1146 }
1148 if ( !nodepath->selected->next ) { // only one node selected
1149 return;
1150 }
1151 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1152 NR::Point dest(pNode->pos);
1153 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1154 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1155 if (pNode) {
1156 dest[axis] = pNode->pos[axis];
1157 sp_node_moveto(pNode, dest);
1158 }
1159 }
1161 sp_nodepath_update_repr(nodepath);
1162 }
1164 /// Helper struct.
1165 struct NodeSort
1166 {
1167 Inkscape::NodePath::Node *_node;
1168 NR::Coord _coord;
1169 /// \todo use vectorof pointers instead of calling copy ctor
1170 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1171 _node(node), _coord(node->pos[axis])
1172 {}
1174 };
1176 static bool operator<(NodeSort const &a, NodeSort const &b)
1177 {
1178 return (a._coord < b._coord);
1179 }
1181 /**
1182 * Distribute selected nodes on the specified axis.
1183 */
1184 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1185 {
1186 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1187 return;
1188 }
1190 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1191 return;
1192 }
1194 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1195 std::vector<NodeSort> sorted;
1196 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1197 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1198 if (pNode) {
1199 NodeSort n(pNode, axis);
1200 sorted.push_back(n);
1201 //dest[axis] = pNode->pos[axis];
1202 //sp_node_moveto(pNode, dest);
1203 }
1204 }
1205 std::sort(sorted.begin(), sorted.end());
1206 unsigned int len = sorted.size();
1207 //overall bboxes span
1208 float dist = (sorted.back()._coord -
1209 sorted.front()._coord);
1210 //new distance between each bbox
1211 float step = (dist) / (len - 1);
1212 float pos = sorted.front()._coord;
1213 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1214 it < sorted.end();
1215 it ++ )
1216 {
1217 NR::Point dest((*it)._node->pos);
1218 dest[axis] = pos;
1219 sp_node_moveto((*it)._node, dest);
1220 pos += step;
1221 }
1223 sp_nodepath_update_repr(nodepath);
1224 }
1227 /**
1228 * Call sp_nodepath_line_add_node() for all selected segments.
1229 */
1230 void
1231 sp_node_selected_add_node(void)
1232 {
1233 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1234 if (!nodepath) {
1235 return;
1236 }
1238 GList *nl = NULL;
1240 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1241 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1242 g_assert(t->selected);
1243 if (t->p.other && t->p.other->selected) {
1244 nl = g_list_prepend(nl, t);
1245 }
1246 }
1248 while (nl) {
1249 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1250 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1251 sp_nodepath_node_select(n, TRUE, FALSE);
1252 nl = g_list_remove(nl, t);
1253 }
1255 /** \todo fixme: adjust ? */
1256 sp_nodepath_update_handles(nodepath);
1258 sp_nodepath_update_repr(nodepath);
1260 sp_nodepath_update_statusbar(nodepath);
1261 }
1263 /**
1264 * Select segment nearest to point
1265 */
1266 void
1267 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1268 {
1269 if (!nodepath) {
1270 return;
1271 }
1273 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1275 //find segment to segment
1276 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1278 gboolean force = FALSE;
1279 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1280 force = TRUE;
1281 }
1282 sp_nodepath_node_select(e, (gboolean) toggle, force);
1283 if (e->p.other)
1284 sp_nodepath_node_select(e->p.other, TRUE, force);
1286 sp_nodepath_update_handles(nodepath);
1288 sp_nodepath_update_statusbar(nodepath);
1289 }
1291 /**
1292 * Add a node nearest to point
1293 */
1294 void
1295 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1296 {
1297 if (!nodepath) {
1298 return;
1299 }
1301 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1303 //find segment to split
1304 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1306 //don't know why but t seems to flip for lines
1307 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1308 position.t = 1.0 - position.t;
1309 }
1310 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1311 sp_nodepath_node_select(n, FALSE, TRUE);
1313 /* fixme: adjust ? */
1314 sp_nodepath_update_handles(nodepath);
1316 sp_nodepath_update_repr(nodepath);
1318 sp_nodepath_update_statusbar(nodepath);
1319 }
1321 /*
1322 * Adjusts a segment so that t moves by a certain delta for dragging
1323 * converts lines to curves
1324 *
1325 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1326 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1327 */
1328 void
1329 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1330 {
1331 /* feel good is an arbitrary parameter that distributes the delta between handles
1332 * if t of the drag point is less than 1/6 distance form the endpoint only
1333 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1334 */
1335 double feel_good;
1336 if (t <= 1.0 / 6.0)
1337 feel_good = 0;
1338 else if (t <= 0.5)
1339 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1340 else if (t <= 5.0 / 6.0)
1341 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1342 else
1343 feel_good = 1;
1345 //if we're dragging a line convert it to a curve
1346 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1347 sp_nodepath_set_line_type(e, NR_CURVETO);
1348 }
1350 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1351 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1352 e->p.other->n.pos += offsetcoord0;
1353 e->p.pos += offsetcoord1;
1355 // adjust handles of adjacent nodes where necessary
1356 sp_node_adjust_handle(e,1);
1357 sp_node_adjust_handle(e->p.other,-1);
1359 sp_nodepath_update_handles(e->subpath->nodepath);
1361 update_object(e->subpath->nodepath);
1363 sp_nodepath_update_statusbar(e->subpath->nodepath);
1364 }
1367 /**
1368 * Call sp_nodepath_break() for all selected segments.
1369 */
1370 void sp_node_selected_break()
1371 {
1372 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1373 if (!nodepath) return;
1375 GList *temp = NULL;
1376 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1377 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1378 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1379 if (nn == NULL) continue; // no break, no new node
1380 temp = g_list_prepend(temp, nn);
1381 }
1383 if (temp) {
1384 sp_nodepath_deselect(nodepath);
1385 }
1386 for (GList *l = temp; l != NULL; l = l->next) {
1387 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1388 }
1390 sp_nodepath_update_handles(nodepath);
1392 sp_nodepath_update_repr(nodepath);
1393 }
1395 /**
1396 * Duplicate the selected node(s).
1397 */
1398 void sp_node_selected_duplicate()
1399 {
1400 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1401 if (!nodepath) {
1402 return;
1403 }
1405 GList *temp = NULL;
1406 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1408 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1409 if (nn == NULL) continue; // could not duplicate
1410 temp = g_list_prepend(temp, nn);
1411 }
1413 if (temp) {
1414 sp_nodepath_deselect(nodepath);
1415 }
1416 for (GList *l = temp; l != NULL; l = l->next) {
1417 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1418 }
1420 sp_nodepath_update_handles(nodepath);
1422 sp_nodepath_update_repr(nodepath);
1423 }
1425 /**
1426 * Join two nodes by merging them into one.
1427 */
1428 void sp_node_selected_join()
1429 {
1430 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1431 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1433 if (g_list_length(nodepath->selected) != 2) {
1434 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1435 return;
1436 }
1438 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1439 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1441 g_assert(a != b);
1442 g_assert(a->p.other || a->n.other);
1443 g_assert(b->p.other || b->n.other);
1445 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1446 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1447 return;
1448 }
1450 /* a and b are endpoints */
1452 NR::Point c = (a->pos + b->pos) / 2;
1454 if (a->subpath == b->subpath) {
1455 Inkscape::NodePath::SubPath *sp = a->subpath;
1456 sp_nodepath_subpath_close(sp);
1458 sp_nodepath_update_handles(sp->nodepath);
1460 sp_nodepath_update_repr(nodepath);
1462 return;
1463 }
1465 /* a and b are separate subpaths */
1466 Inkscape::NodePath::SubPath *sa = a->subpath;
1467 Inkscape::NodePath::SubPath *sb = b->subpath;
1468 NR::Point p;
1469 Inkscape::NodePath::Node *n;
1470 NRPathcode code;
1471 if (a == sa->first) {
1472 p = sa->first->n.pos;
1473 code = (NRPathcode)sa->first->n.other->code;
1474 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1475 n = sa->last;
1476 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1477 n = n->p.other;
1478 while (n) {
1479 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1480 n = n->p.other;
1481 if (n == sa->first) n = NULL;
1482 }
1483 sp_nodepath_subpath_destroy(sa);
1484 sa = t;
1485 } else if (a == sa->last) {
1486 p = sa->last->p.pos;
1487 code = (NRPathcode)sa->last->code;
1488 sp_nodepath_node_destroy(sa->last);
1489 } else {
1490 code = NR_END;
1491 g_assert_not_reached();
1492 }
1494 if (b == sb->first) {
1495 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1496 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1497 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1498 }
1499 } else if (b == sb->last) {
1500 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1501 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1502 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1503 }
1504 } else {
1505 g_assert_not_reached();
1506 }
1507 /* and now destroy sb */
1509 sp_nodepath_subpath_destroy(sb);
1511 sp_nodepath_update_handles(sa->nodepath);
1513 sp_nodepath_update_repr(nodepath);
1515 sp_nodepath_update_statusbar(nodepath);
1516 }
1518 /**
1519 * Join two nodes by adding a segment between them.
1520 */
1521 void sp_node_selected_join_segment()
1522 {
1523 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1524 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1526 if (g_list_length(nodepath->selected) != 2) {
1527 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1528 return;
1529 }
1531 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1532 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1534 g_assert(a != b);
1535 g_assert(a->p.other || a->n.other);
1536 g_assert(b->p.other || b->n.other);
1538 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1539 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1540 return;
1541 }
1543 if (a->subpath == b->subpath) {
1544 Inkscape::NodePath::SubPath *sp = a->subpath;
1546 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1547 sp->closed = TRUE;
1549 sp->first->p.other = sp->last;
1550 sp->last->n.other = sp->first;
1552 sp_node_handle_mirror_p_to_n(sp->last);
1553 sp_node_handle_mirror_n_to_p(sp->first);
1555 sp->first->code = sp->last->code;
1556 sp->first = sp->last;
1558 sp_nodepath_update_handles(sp->nodepath);
1560 sp_nodepath_update_repr(nodepath);
1562 return;
1563 }
1565 /* a and b are separate subpaths */
1566 Inkscape::NodePath::SubPath *sa = a->subpath;
1567 Inkscape::NodePath::SubPath *sb = b->subpath;
1569 Inkscape::NodePath::Node *n;
1570 NR::Point p;
1571 NRPathcode code;
1572 if (a == sa->first) {
1573 code = (NRPathcode) sa->first->n.other->code;
1574 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1575 n = sa->last;
1576 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1577 for (n = n->p.other; n != NULL; n = n->p.other) {
1578 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1579 }
1580 sp_nodepath_subpath_destroy(sa);
1581 sa = t;
1582 } else if (a == sa->last) {
1583 code = (NRPathcode)sa->last->code;
1584 } else {
1585 code = NR_END;
1586 g_assert_not_reached();
1587 }
1589 if (b == sb->first) {
1590 n = sb->first;
1591 sp_node_handle_mirror_p_to_n(sa->last);
1592 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1593 sp_node_handle_mirror_n_to_p(sa->last);
1594 for (n = n->n.other; n != NULL; n = n->n.other) {
1595 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1596 }
1597 } else if (b == sb->last) {
1598 n = sb->last;
1599 sp_node_handle_mirror_p_to_n(sa->last);
1600 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1601 sp_node_handle_mirror_n_to_p(sa->last);
1602 for (n = n->p.other; n != NULL; n = n->p.other) {
1603 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1604 }
1605 } else {
1606 g_assert_not_reached();
1607 }
1608 /* and now destroy sb */
1610 sp_nodepath_subpath_destroy(sb);
1612 sp_nodepath_update_handles(sa->nodepath);
1614 sp_nodepath_update_repr(nodepath);
1615 }
1617 /**
1618 * Delete one or more selected nodes.
1619 */
1620 void sp_node_selected_delete()
1621 {
1622 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1623 if (!nodepath) return;
1624 if (!nodepath->selected) return;
1626 /** \todo fixme: do it the right way */
1627 while (nodepath->selected) {
1628 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
1629 sp_nodepath_node_destroy(node);
1630 }
1633 //clean up the nodepath (such as for trivial subpaths)
1634 sp_nodepath_cleanup(nodepath);
1636 sp_nodepath_update_handles(nodepath);
1638 // if the entire nodepath is removed, delete the selected object.
1639 if (nodepath->subpaths == NULL ||
1640 sp_nodepath_get_node_count(nodepath) < 2) {
1641 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
1642 sp_nodepath_destroy(nodepath);
1643 sp_selection_delete();
1644 sp_document_done (document);
1645 return;
1646 }
1648 sp_nodepath_update_repr(nodepath);
1650 sp_nodepath_update_statusbar(nodepath);
1651 }
1653 /**
1654 * Delete one or more segments between two selected nodes.
1655 * This is the code for 'split'.
1656 */
1657 void
1658 sp_node_selected_delete_segment(void)
1659 {
1660 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
1661 Inkscape::NodePath::Node *curr, *next; //Iterators
1663 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1664 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1666 if (g_list_length(nodepath->selected) != 2) {
1667 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1668 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1669 return;
1670 }
1672 //Selected nodes, not inclusive
1673 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1674 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1676 if ( ( a==b) || //same node
1677 (a->subpath != b->subpath ) || //not the same path
1678 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
1679 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
1680 {
1681 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1682 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
1683 return;
1684 }
1686 //###########################################
1687 //# BEGIN EDITS
1688 //###########################################
1689 //##################################
1690 //# CLOSED PATH
1691 //##################################
1692 if (a->subpath->closed) {
1695 gboolean reversed = FALSE;
1697 //Since we can go in a circle, we need to find the shorter distance.
1698 // a->b or b->a
1699 start = end = NULL;
1700 int distance = 0;
1701 int minDistance = 0;
1702 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1703 if (curr==b) {
1704 //printf("a to b:%d\n", distance);
1705 start = a;//go from a to b
1706 end = b;
1707 minDistance = distance;
1708 //printf("A to B :\n");
1709 break;
1710 }
1711 distance++;
1712 }
1714 //try again, the other direction
1715 distance = 0;
1716 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1717 if (curr==a) {
1718 //printf("b to a:%d\n", distance);
1719 if (distance < minDistance) {
1720 start = b; //we go from b to a
1721 end = a;
1722 reversed = TRUE;
1723 //printf("B to A\n");
1724 }
1725 break;
1726 }
1727 distance++;
1728 }
1731 //Copy everything from 'end' to 'start' to a new subpath
1732 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1733 for (curr=end ; curr ; curr=curr->n.other) {
1734 NRPathcode code = (NRPathcode) curr->code;
1735 if (curr == end)
1736 code = NR_MOVETO;
1737 sp_nodepath_node_new(t, NULL,
1738 (Inkscape::NodePath::NodeType)curr->type, code,
1739 &curr->p.pos, &curr->pos, &curr->n.pos);
1740 if (curr == start)
1741 break;
1742 }
1743 sp_nodepath_subpath_destroy(a->subpath);
1746 }
1750 //##################################
1751 //# OPEN PATH
1752 //##################################
1753 else {
1755 //We need to get the direction of the list between A and B
1756 //Can we walk from a to b?
1757 start = end = NULL;
1758 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
1759 if (curr==b) {
1760 start = a; //did it! we go from a to b
1761 end = b;
1762 //printf("A to B\n");
1763 break;
1764 }
1765 }
1766 if (!start) {//didn't work? let's try the other direction
1767 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
1768 if (curr==a) {
1769 start = b; //did it! we go from b to a
1770 end = a;
1771 //printf("B to A\n");
1772 break;
1773 }
1774 }
1775 }
1776 if (!start) {
1777 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
1778 _("Cannot find path between nodes."));
1779 return;
1780 }
1784 //Copy everything after 'end' to a new subpath
1785 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
1786 for (curr=end ; curr ; curr=curr->n.other) {
1787 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
1788 &curr->p.pos, &curr->pos, &curr->n.pos);
1789 }
1791 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
1792 for (curr = start->n.other ; curr ; curr=next) {
1793 next = curr->n.other;
1794 sp_nodepath_node_destroy(curr);
1795 }
1797 }
1798 //###########################################
1799 //# END EDITS
1800 //###########################################
1802 //clean up the nodepath (such as for trivial subpaths)
1803 sp_nodepath_cleanup(nodepath);
1805 sp_nodepath_update_handles(nodepath);
1807 sp_nodepath_update_repr(nodepath);
1809 // if the entire nodepath is removed, delete the selected object.
1810 if (nodepath->subpaths == NULL ||
1811 sp_nodepath_get_node_count(nodepath) < 2) {
1812 sp_nodepath_destroy(nodepath);
1813 sp_selection_delete();
1814 return;
1815 }
1817 sp_nodepath_update_statusbar(nodepath);
1818 }
1820 /**
1821 * Call sp_nodepath_set_line() for all selected segments.
1822 */
1823 void
1824 sp_node_selected_set_line_type(NRPathcode code)
1825 {
1826 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1827 if (nodepath == NULL) return;
1829 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1830 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1831 g_assert(n->selected);
1832 if (n->p.other && n->p.other->selected) {
1833 sp_nodepath_set_line_type(n, code);
1834 }
1835 }
1837 sp_nodepath_update_repr(nodepath);
1838 }
1840 /**
1841 * Call sp_nodepath_convert_node_type() for all selected nodes.
1842 */
1843 void
1844 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
1845 {
1846 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1847 if (nodepath == NULL) return;
1849 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1850 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
1851 }
1853 sp_nodepath_update_repr(nodepath);
1854 }
1856 /**
1857 * Change select status of node, update its own and neighbour handles.
1858 */
1859 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
1860 {
1861 node->selected = selected;
1863 if (selected) {
1864 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
1865 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
1866 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
1867 sp_knot_update_ctrl(node->knot);
1868 } else {
1869 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
1870 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
1871 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
1872 sp_knot_update_ctrl(node->knot);
1873 }
1875 sp_node_update_handles(node);
1876 if (node->n.other) sp_node_update_handles(node->n.other);
1877 if (node->p.other) sp_node_update_handles(node->p.other);
1878 }
1880 /**
1881 \brief Select a node
1882 \param node The node to select
1883 \param incremental If true, add to selection, otherwise deselect others
1884 \param override If true, always select this node, otherwise toggle selected status
1885 */
1886 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
1887 {
1888 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
1890 if (incremental) {
1891 if (override) {
1892 if (!g_list_find(nodepath->selected, node)) {
1893 nodepath->selected = g_list_prepend(nodepath->selected, node);
1894 }
1895 sp_node_set_selected(node, TRUE);
1896 } else { // toggle
1897 if (node->selected) {
1898 g_assert(g_list_find(nodepath->selected, node));
1899 nodepath->selected = g_list_remove(nodepath->selected, node);
1900 } else {
1901 g_assert(!g_list_find(nodepath->selected, node));
1902 nodepath->selected = g_list_prepend(nodepath->selected, node);
1903 }
1904 sp_node_set_selected(node, !node->selected);
1905 }
1906 } else {
1907 sp_nodepath_deselect(nodepath);
1908 nodepath->selected = g_list_prepend(nodepath->selected, node);
1909 sp_node_set_selected(node, TRUE);
1910 }
1912 sp_nodepath_update_statusbar(nodepath);
1913 }
1916 /**
1917 \brief Deselect all nodes in the nodepath
1918 */
1919 void
1920 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
1921 {
1922 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1924 while (nodepath->selected) {
1925 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
1926 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
1927 }
1928 sp_nodepath_update_statusbar(nodepath);
1929 }
1931 /**
1932 \brief Select or invert selection of all nodes in the nodepath
1933 */
1934 void
1935 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
1936 {
1937 if (!nodepath) return;
1939 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1940 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1941 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1942 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1943 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
1944 }
1945 }
1946 }
1948 /**
1949 * If nothing selected, does the same as sp_nodepath_select_all();
1950 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
1951 * (i.e., similar to "select all in layer", with the "selected" subpaths
1952 * being treated as "layers" in the path).
1953 */
1954 void
1955 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
1956 {
1957 if (!nodepath) return;
1959 if (g_list_length (nodepath->selected) == 0) {
1960 sp_nodepath_select_all (nodepath, invert);
1961 return;
1962 }
1964 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
1965 GSList *subpaths = NULL;
1967 for (GList *l = copy; l != NULL; l = l->next) {
1968 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1969 Inkscape::NodePath::SubPath *subpath = n->subpath;
1970 if (!g_slist_find (subpaths, subpath))
1971 subpaths = g_slist_prepend (subpaths, subpath);
1972 }
1974 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
1975 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
1976 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1977 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1978 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
1979 }
1980 }
1982 g_slist_free (subpaths);
1983 g_list_free (copy);
1984 }
1986 /**
1987 * \brief Select the node after the last selected; if none is selected,
1988 * select the first within path.
1989 */
1990 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
1991 {
1992 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1994 Inkscape::NodePath::Node *last = NULL;
1995 if (nodepath->selected) {
1996 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1997 Inkscape::NodePath::SubPath *subpath, *subpath_next;
1998 subpath = (Inkscape::NodePath::SubPath *) spl->data;
1999 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2000 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2001 if (node->selected) {
2002 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2003 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2004 if (spl->next) { // there's a next subpath
2005 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2006 last = subpath_next->first;
2007 } else if (spl->prev) { // there's a previous subpath
2008 last = NULL; // to be set later to the first node of first subpath
2009 } else {
2010 last = node->n.other;
2011 }
2012 } else {
2013 last = node->n.other;
2014 }
2015 } else {
2016 if (node->n.other) {
2017 last = node->n.other;
2018 } else {
2019 if (spl->next) { // there's a next subpath
2020 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2021 last = subpath_next->first;
2022 } else if (spl->prev) { // there's a previous subpath
2023 last = NULL; // to be set later to the first node of first subpath
2024 } else {
2025 last = (Inkscape::NodePath::Node *) subpath->first;
2026 }
2027 }
2028 }
2029 }
2030 }
2031 }
2032 sp_nodepath_deselect(nodepath);
2033 }
2035 if (last) { // there's at least one more node after selected
2036 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2037 } else { // no more nodes, select the first one in first subpath
2038 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2039 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2040 }
2041 }
2043 /**
2044 * \brief Select the node before the first selected; if none is selected,
2045 * select the last within path
2046 */
2047 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2048 {
2049 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2051 Inkscape::NodePath::Node *last = NULL;
2052 if (nodepath->selected) {
2053 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2054 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2055 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2056 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2057 if (node->selected) {
2058 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2059 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2060 if (spl->prev) { // there's a prev subpath
2061 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2062 last = subpath_prev->last;
2063 } else if (spl->next) { // there's a next subpath
2064 last = NULL; // to be set later to the last node of last subpath
2065 } else {
2066 last = node->p.other;
2067 }
2068 } else {
2069 last = node->p.other;
2070 }
2071 } else {
2072 if (node->p.other) {
2073 last = node->p.other;
2074 } else {
2075 if (spl->prev) { // there's a prev subpath
2076 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2077 last = subpath_prev->last;
2078 } else if (spl->next) { // there's a next subpath
2079 last = NULL; // to be set later to the last node of last subpath
2080 } else {
2081 last = (Inkscape::NodePath::Node *) subpath->last;
2082 }
2083 }
2084 }
2085 }
2086 }
2087 }
2088 sp_nodepath_deselect(nodepath);
2089 }
2091 if (last) { // there's at least one more node before selected
2092 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2093 } else { // no more nodes, select the last one in last subpath
2094 GList *spl = g_list_last(nodepath->subpaths);
2095 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2096 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2097 }
2098 }
2100 /**
2101 * \brief Select all nodes that are within the rectangle.
2102 */
2103 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2104 {
2105 if (!incremental) {
2106 sp_nodepath_deselect(nodepath);
2107 }
2109 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2110 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2111 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2112 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2114 if (b.contains(node->pos)) {
2115 sp_nodepath_node_select(node, TRUE, TRUE);
2116 }
2117 }
2118 }
2119 }
2121 /**
2122 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2123 */
2124 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2125 {
2126 if (!nodepath->selected) {
2127 return NULL;
2128 }
2130 GList *r = NULL;
2131 guint i = 0;
2132 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2133 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2134 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2135 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2136 i++;
2137 if (node->selected) {
2138 r = g_list_append(r, GINT_TO_POINTER(i));
2139 }
2140 }
2141 }
2142 return r;
2143 }
2145 /**
2146 \brief Restores selection by selecting nodes whose positions are in the list
2147 */
2148 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2149 {
2150 sp_nodepath_deselect(nodepath);
2152 guint i = 0;
2153 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2154 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2155 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2156 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2157 i++;
2158 if (g_list_find(r, GINT_TO_POINTER(i))) {
2159 sp_nodepath_node_select(node, TRUE, TRUE);
2160 }
2161 }
2162 }
2164 }
2166 /**
2167 \brief Adjusts handle according to node type and line code.
2168 */
2169 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2170 {
2171 double len, otherlen, linelen;
2173 g_assert(node);
2175 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2176 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2178 /** \todo fixme: */
2179 if (me->other == NULL) return;
2180 if (other->other == NULL) return;
2182 /* I have line */
2184 NRPathcode mecode, ocode;
2185 if (which_adjust == 1) {
2186 mecode = (NRPathcode)me->other->code;
2187 ocode = (NRPathcode)node->code;
2188 } else {
2189 mecode = (NRPathcode)node->code;
2190 ocode = (NRPathcode)other->other->code;
2191 }
2193 if (mecode == NR_LINETO) return;
2195 /* I am curve */
2197 if (other->other == NULL) return;
2199 /* Other has line */
2201 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2203 NR::Point delta;
2204 if (ocode == NR_LINETO) {
2205 /* other is lineto, we are either smooth or symm */
2206 Inkscape::NodePath::Node *othernode = other->other;
2207 len = NR::L2(me->pos - node->pos);
2208 delta = node->pos - othernode->pos;
2209 linelen = NR::L2(delta);
2210 if (linelen < 1e-18)
2211 return;
2212 me->pos = node->pos + (len / linelen)*delta;
2213 return;
2214 }
2216 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2218 me->pos = 2 * node->pos - other->pos;
2219 return;
2220 }
2222 /* We are smooth */
2224 len = NR::L2(me->pos - node->pos);
2225 delta = other->pos - node->pos;
2226 otherlen = NR::L2(delta);
2227 if (otherlen < 1e-18) return;
2229 me->pos = node->pos - (len / otherlen) * delta;
2230 }
2232 /**
2233 \brief Adjusts both handles according to node type and line code
2234 */
2235 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2236 {
2237 g_assert(node);
2239 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2241 /* we are either smooth or symm */
2243 if (node->p.other == NULL) return;
2245 if (node->n.other == NULL) return;
2247 if (node->code == NR_LINETO) {
2248 if (node->n.other->code == NR_LINETO) return;
2249 sp_node_adjust_handle(node, 1);
2250 return;
2251 }
2253 if (node->n.other->code == NR_LINETO) {
2254 if (node->code == NR_LINETO) return;
2255 sp_node_adjust_handle(node, -1);
2256 return;
2257 }
2259 /* both are curves */
2260 NR::Point const delta( node->n.pos - node->p.pos );
2262 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2263 node->p.pos = node->pos - delta / 2;
2264 node->n.pos = node->pos + delta / 2;
2265 return;
2266 }
2268 /* We are smooth */
2269 double plen = NR::L2(node->p.pos - node->pos);
2270 if (plen < 1e-18) return;
2271 double nlen = NR::L2(node->n.pos - node->pos);
2272 if (nlen < 1e-18) return;
2273 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2274 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2275 }
2277 /**
2278 * Node event callback.
2279 */
2280 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2281 {
2282 gboolean ret = FALSE;
2283 switch (event->type) {
2284 case GDK_ENTER_NOTIFY:
2285 active_node = n;
2286 break;
2287 case GDK_LEAVE_NOTIFY:
2288 active_node = NULL;
2289 break;
2290 case GDK_KEY_PRESS:
2291 switch (get_group0_keyval (&event->key)) {
2292 case GDK_space:
2293 if (event->key.state & GDK_BUTTON1_MASK) {
2294 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2295 stamp_repr(nodepath);
2296 ret = TRUE;
2297 }
2298 break;
2299 default:
2300 break;
2301 }
2302 break;
2303 default:
2304 break;
2305 }
2307 return ret;
2308 }
2310 /**
2311 * Handle keypress on node; directly called.
2312 */
2313 gboolean node_key(GdkEvent *event)
2314 {
2315 Inkscape::NodePath::Path *np;
2317 // there is no way to verify nodes so set active_node to nil when deleting!!
2318 if (active_node == NULL) return FALSE;
2320 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2321 gint ret = FALSE;
2322 switch (get_group0_keyval (&event->key)) {
2323 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2324 case GDK_BackSpace:
2325 np = active_node->subpath->nodepath;
2326 sp_nodepath_node_destroy(active_node);
2327 sp_nodepath_update_repr(np);
2328 active_node = NULL;
2329 ret = TRUE;
2330 break;
2331 case GDK_c:
2332 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2333 ret = TRUE;
2334 break;
2335 case GDK_s:
2336 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2337 ret = TRUE;
2338 break;
2339 case GDK_y:
2340 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2341 ret = TRUE;
2342 break;
2343 case GDK_b:
2344 sp_nodepath_node_break(active_node);
2345 ret = TRUE;
2346 break;
2347 }
2348 return ret;
2349 }
2350 return FALSE;
2351 }
2353 /**
2354 * Mouseclick on node callback.
2355 */
2356 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2357 {
2358 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2360 if (state & GDK_CONTROL_MASK) {
2361 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2363 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2364 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2365 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2366 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2367 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2368 } else {
2369 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2370 }
2371 sp_nodepath_update_repr(nodepath);
2372 sp_nodepath_update_statusbar(nodepath);
2374 } else { //ctrl+alt+click: delete node
2375 sp_nodepath_node_destroy(n);
2376 //clean up the nodepath (such as for trivial subpaths)
2377 sp_nodepath_cleanup(nodepath);
2379 // if the entire nodepath is removed, delete the selected object.
2380 if (nodepath->subpaths == NULL ||
2381 sp_nodepath_get_node_count(nodepath) < 2) {
2382 SPDocument *document = SP_DT_DOCUMENT (nodepath->desktop);
2383 sp_nodepath_destroy(nodepath);
2384 sp_selection_delete();
2385 sp_document_done (document);
2387 } else {
2388 sp_nodepath_update_handles(nodepath);
2389 sp_nodepath_update_repr(nodepath);
2390 sp_nodepath_update_statusbar(nodepath);
2391 }
2392 }
2394 } else {
2395 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2396 }
2397 }
2399 /**
2400 * Mouse grabbed node callback.
2401 */
2402 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2403 {
2404 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2406 n->origin = knot->pos;
2408 if (!n->selected) {
2409 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2410 }
2411 }
2413 /**
2414 * Mouse ungrabbed node callback.
2415 */
2416 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2417 {
2418 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2420 n->dragging_out = NULL;
2422 sp_nodepath_update_repr(n->subpath->nodepath);
2423 }
2425 /**
2426 * The point on a line, given by its angle, closest to the given point.
2427 * \param p A point.
2428 * \param a Angle of the line; it is assumed to go through coordinate origin.
2429 * \param closest Pointer to the point struct where the result is stored.
2430 * \todo FIXME: use dot product perhaps?
2431 */
2432 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2433 {
2434 if (a == HUGE_VAL) { // vertical
2435 *closest = NR::Point(0, (*p)[NR::Y]);
2436 } else {
2437 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2438 (*closest)[NR::Y] = a * (*closest)[NR::X];
2439 }
2440 }
2442 /**
2443 * Distance from the point to a line given by its angle.
2444 * \param p A point.
2445 * \param a Angle of the line; it is assumed to go through coordinate origin.
2446 */
2447 static double point_line_distance(NR::Point *p, double a)
2448 {
2449 NR::Point c;
2450 point_line_closest(p, a, &c);
2451 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]));
2452 }
2454 /**
2455 * Callback for node "request" signal.
2456 * \todo fixme: This goes to "moved" event? (lauris)
2457 */
2458 static gboolean
2459 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2460 {
2461 double yn, xn, yp, xp;
2462 double an, ap, na, pa;
2463 double d_an, d_ap, d_na, d_pa;
2464 gboolean collinear = FALSE;
2465 NR::Point c;
2466 NR::Point pr;
2468 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2470 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2471 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2473 NR::Point mouse = (*p);
2475 if (!n->dragging_out) {
2476 // This is the first drag-out event; find out which handle to drag out
2477 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2478 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2480 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2481 return FALSE;
2483 Inkscape::NodePath::NodeSide *opposite;
2484 if (appr_p > appr_n) { // closer to p
2485 n->dragging_out = &n->p;
2486 opposite = &n->n;
2487 n->code = NR_CURVETO;
2488 } else if (appr_p < appr_n) { // closer to n
2489 n->dragging_out = &n->n;
2490 opposite = &n->p;
2491 n->n.other->code = NR_CURVETO;
2492 } else { // p and n nodes are the same
2493 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2494 n->dragging_out = &n->p;
2495 opposite = &n->n;
2496 n->code = NR_CURVETO;
2497 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2498 n->dragging_out = &n->n;
2499 opposite = &n->p;
2500 n->n.other->code = NR_CURVETO;
2501 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2502 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);
2503 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);
2504 if (appr_other_p > appr_other_n) { // closer to other's p handle
2505 n->dragging_out = &n->n;
2506 opposite = &n->p;
2507 n->n.other->code = NR_CURVETO;
2508 } else { // closer to other's n handle
2509 n->dragging_out = &n->p;
2510 opposite = &n->n;
2511 n->code = NR_CURVETO;
2512 }
2513 }
2514 }
2516 // if there's another handle, make sure the one we drag out starts parallel to it
2517 if (opposite->pos != n->pos) {
2518 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2519 }
2521 // knots might not be created yet!
2522 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2523 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2524 }
2526 // pass this on to the handle-moved callback
2527 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2528 sp_node_update_handles(n);
2529 return TRUE;
2530 }
2532 if (state & GDK_CONTROL_MASK) { // constrained motion
2534 // calculate relative distances of handles
2535 // n handle:
2536 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2537 xn = n->n.pos[NR::X] - n->pos[NR::X];
2538 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2539 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2540 if (n->n.other) { // if there is the next point
2541 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2542 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2543 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2544 }
2545 }
2546 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2547 if (yn < 0) { xn = -xn; yn = -yn; }
2549 // p handle:
2550 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2551 xp = n->p.pos[NR::X] - n->pos[NR::X];
2552 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2553 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2554 if (n->p.other) {
2555 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2556 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2557 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2558 }
2559 }
2560 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2561 if (yp < 0) { xp = -xp; yp = -yp; }
2563 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2564 // sliding on handles, only if at least one of the handles is non-vertical
2565 // (otherwise it's the same as ctrl+drag anyway)
2567 // calculate angles of the handles
2568 if (xn == 0) {
2569 if (yn == 0) { // no handle, consider it the continuation of the other one
2570 an = 0;
2571 collinear = TRUE;
2572 }
2573 else an = 0; // vertical; set the angle to horizontal
2574 } else an = yn/xn;
2576 if (xp == 0) {
2577 if (yp == 0) { // no handle, consider it the continuation of the other one
2578 ap = an;
2579 }
2580 else ap = 0; // vertical; set the angle to horizontal
2581 } else ap = yp/xp;
2583 if (collinear) an = ap;
2585 // angles of the perpendiculars; HUGE_VAL means vertical
2586 if (an == 0) na = HUGE_VAL; else na = -1/an;
2587 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2589 //g_print("an %g ap %g\n", an, ap);
2591 // mouse point relative to the node's original pos
2592 pr = (*p) - n->origin;
2594 // distances to the four lines (two handles and two perpendiculars)
2595 d_an = point_line_distance(&pr, an);
2596 d_na = point_line_distance(&pr, na);
2597 d_ap = point_line_distance(&pr, ap);
2598 d_pa = point_line_distance(&pr, pa);
2600 // find out which line is the closest, save its closest point in c
2601 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2602 point_line_closest(&pr, an, &c);
2603 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2604 point_line_closest(&pr, ap, &c);
2605 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2606 point_line_closest(&pr, na, &c);
2607 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2608 point_line_closest(&pr, pa, &c);
2609 }
2611 // move the node to the closest point
2612 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2613 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
2614 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
2616 } else { // constraining to hor/vert
2618 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
2619 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
2620 } else { // snap to vert
2621 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
2622 }
2623 }
2624 } else { // move freely
2625 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
2626 (*p)[NR::X] - n->pos[NR::X],
2627 (*p)[NR::Y] - n->pos[NR::Y],
2628 (state & GDK_SHIFT_MASK) == 0);
2629 }
2631 n->subpath->nodepath->desktop->scroll_to_point(p);
2633 return TRUE;
2634 }
2636 /**
2637 * Node handle clicked callback.
2638 */
2639 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
2640 {
2641 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2643 if (state & GDK_CONTROL_MASK) { // "delete" handle
2644 if (n->p.knot == knot) {
2645 n->p.pos = n->pos;
2646 } else if (n->n.knot == knot) {
2647 n->n.pos = n->pos;
2648 }
2649 sp_node_update_handles(n);
2650 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2651 sp_nodepath_update_repr(nodepath);
2652 sp_nodepath_update_statusbar(nodepath);
2654 } else { // just select or add to selection, depending in Shift
2655 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2656 }
2657 }
2659 /**
2660 * Node handle grabbed callback.
2661 */
2662 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
2663 {
2664 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2666 if (!n->selected) {
2667 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2668 }
2670 // remember the origin point of the handle
2671 if (n->p.knot == knot) {
2672 n->p.origin = n->p.pos - n->pos;
2673 } else if (n->n.knot == knot) {
2674 n->n.origin = n->n.pos - n->pos;
2675 } else {
2676 g_assert_not_reached();
2677 }
2679 }
2681 /**
2682 * Node handle ungrabbed callback.
2683 */
2684 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
2685 {
2686 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2688 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
2689 if (n->p.knot == knot) {
2690 n->p.origin.a = 0;
2691 sp_knot_set_position(knot, &n->p.pos, state);
2692 } else if (n->n.knot == knot) {
2693 n->n.origin.a = 0;
2694 sp_knot_set_position(knot, &n->n.pos, state);
2695 } else {
2696 g_assert_not_reached();
2697 }
2699 sp_nodepath_update_repr(n->subpath->nodepath);
2700 }
2702 /**
2703 * Node handle "request" signal callback.
2704 */
2705 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2706 {
2707 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2709 Inkscape::NodePath::NodeSide *me, *opposite;
2710 gint which;
2711 if (n->p.knot == knot) {
2712 me = &n->p;
2713 opposite = &n->n;
2714 which = -1;
2715 } else if (n->n.knot == knot) {
2716 me = &n->n;
2717 opposite = &n->p;
2718 which = 1;
2719 } else {
2720 me = opposite = NULL;
2721 which = 0;
2722 g_assert_not_reached();
2723 }
2725 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
2727 SnapManager const m(n->subpath->nodepath->desktop->namedview);
2729 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
2730 /* We are smooth node adjacent with line */
2731 NR::Point const delta = *p - n->pos;
2732 NR::Coord const len = NR::L2(delta);
2733 Inkscape::NodePath::Node *othernode = opposite->other;
2734 NR::Point const ndelta = n->pos - othernode->pos;
2735 NR::Coord const linelen = NR::L2(ndelta);
2736 if (len > NR_EPSILON && linelen > NR_EPSILON) {
2737 NR::Coord const scal = dot(delta, ndelta) / linelen;
2738 (*p) = n->pos + (scal / linelen) * ndelta;
2739 }
2740 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, ndelta, NULL).getPoint();
2741 } else {
2742 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
2743 }
2745 sp_node_adjust_handle(n, -which);
2747 return FALSE;
2748 }
2750 /**
2751 * Node handle moved callback.
2752 */
2753 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2754 {
2755 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2757 Inkscape::NodePath::NodeSide *me;
2758 Inkscape::NodePath::NodeSide *other;
2759 if (n->p.knot == knot) {
2760 me = &n->p;
2761 other = &n->n;
2762 } else if (n->n.knot == knot) {
2763 me = &n->n;
2764 other = &n->p;
2765 } else {
2766 me = NULL;
2767 other = NULL;
2768 g_assert_not_reached();
2769 }
2771 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
2772 Radial rme(me->pos - n->pos);
2773 Radial rother(other->pos - n->pos);
2774 Radial rnew(*p - n->pos);
2776 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
2777 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
2778 /* 0 interpreted as "no snapping". */
2780 // The closest PI/snaps angle, starting from zero.
2781 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
2782 if (me->origin.a == HUGE_VAL) {
2783 // ortho doesn't exist: original handle was zero length.
2784 rnew.a = a_snapped;
2785 } else {
2786 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
2787 * its opposite and perpendiculars). */
2788 double const a_ortho = me->origin.a + floor((rnew.a - me->origin.a)/(M_PI/2) + 0.5) * (M_PI/2);
2790 // Snap to the closest.
2791 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
2792 ? a_snapped
2793 : a_ortho );
2794 }
2795 }
2797 if (state & GDK_MOD1_MASK) {
2798 // lock handle length
2799 rnew.r = me->origin.r;
2800 }
2802 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
2803 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
2804 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
2805 rother.a += rnew.a - rme.a;
2806 other->pos = NR::Point(rother) + n->pos;
2807 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
2808 sp_knot_set_position(other->knot, &other->pos, 0);
2809 }
2811 me->pos = NR::Point(rnew) + n->pos;
2812 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
2814 // this is what sp_knot_set_position does, but without emitting the signal:
2815 // we cannot emit a "moved" signal because we're now processing it
2816 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
2818 knot->desktop->set_coordinate_status(me->pos);
2820 update_object(n->subpath->nodepath);
2822 /* status text */
2823 SPDesktop *desktop = n->subpath->nodepath->desktop;
2824 if (!desktop) return;
2825 SPEventContext *ec = desktop->event_context;
2826 if (!ec) return;
2827 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
2828 if (!mc) return;
2830 double degrees = 180 / M_PI * rnew.a;
2831 if (degrees > 180) degrees -= 360;
2832 if (degrees < -180) degrees += 360;
2833 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
2834 degrees = angle_to_compass (degrees);
2836 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
2838 mc->setF(Inkscape::NORMAL_MESSAGE,
2839 _("<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);
2841 g_string_free(length, TRUE);
2842 }
2844 /**
2845 * Node handle event callback.
2846 */
2847 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2848 {
2849 gboolean ret = FALSE;
2850 switch (event->type) {
2851 case GDK_KEY_PRESS:
2852 switch (get_group0_keyval (&event->key)) {
2853 case GDK_space:
2854 if (event->key.state & GDK_BUTTON1_MASK) {
2855 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2856 stamp_repr(nodepath);
2857 ret = TRUE;
2858 }
2859 break;
2860 default:
2861 break;
2862 }
2863 break;
2864 default:
2865 break;
2866 }
2868 return ret;
2869 }
2871 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
2872 Radial &rme, Radial &rother, gboolean const both)
2873 {
2874 rme.a += angle;
2875 if ( both
2876 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2877 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2878 {
2879 rother.a += angle;
2880 }
2881 }
2883 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
2884 Radial &rme, Radial &rother, gboolean const both)
2885 {
2886 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
2888 gdouble r;
2889 if ( both
2890 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2891 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2892 {
2893 r = MAX(rme.r, rother.r);
2894 } else {
2895 r = rme.r;
2896 }
2898 gdouble const weird_angle = atan2(norm_angle, r);
2899 /* Bulia says norm_angle is just the visible distance that the
2900 * object's end must travel on the screen. Left as 'angle' for want of
2901 * a better name.*/
2903 rme.a += weird_angle;
2904 if ( both
2905 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
2906 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
2907 {
2908 rother.a += weird_angle;
2909 }
2910 }
2912 /**
2913 * Rotate one node.
2914 */
2915 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
2916 {
2917 Inkscape::NodePath::NodeSide *me, *other;
2918 bool both = false;
2920 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
2921 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
2923 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
2924 me = &(n->p);
2925 other = &(n->n);
2926 } else if (!n->p.other) {
2927 me = &(n->n);
2928 other = &(n->p);
2929 } else {
2930 if (which > 0) { // right handle
2931 if (xn > xp) {
2932 me = &(n->n);
2933 other = &(n->p);
2934 } else {
2935 me = &(n->p);
2936 other = &(n->n);
2937 }
2938 } else if (which < 0){ // left handle
2939 if (xn <= xp) {
2940 me = &(n->n);
2941 other = &(n->p);
2942 } else {
2943 me = &(n->p);
2944 other = &(n->n);
2945 }
2946 } else { // both handles
2947 me = &(n->n);
2948 other = &(n->p);
2949 both = true;
2950 }
2951 }
2953 Radial rme(me->pos - n->pos);
2954 Radial rother(other->pos - n->pos);
2956 if (screen) {
2957 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
2958 } else {
2959 node_rotate_one_internal (*n, angle, rme, rother, both);
2960 }
2962 me->pos = n->pos + NR::Point(rme);
2964 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
2965 other->pos = n->pos + NR::Point(rother);
2966 }
2968 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
2969 // so here we just move all the knots without emitting move signals, for speed
2970 sp_node_update_handles(n, false);
2971 }
2973 /**
2974 * Rotate selected nodes.
2975 */
2976 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
2977 {
2978 if (!nodepath || !nodepath->selected) return;
2980 if (g_list_length(nodepath->selected) == 1) {
2981 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
2982 node_rotate_one (n, angle, which, screen);
2983 } else {
2984 // rotate as an object:
2986 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
2987 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
2988 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2989 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2990 box.expandTo (n->pos); // contain all selected nodes
2991 }
2993 gdouble rot;
2994 if (screen) {
2995 gdouble const zoom = nodepath->desktop->current_zoom();
2996 gdouble const zmove = angle / zoom;
2997 gdouble const r = NR::L2(box.max() - box.midpoint());
2998 rot = atan2(zmove, r);
2999 } else {
3000 rot = angle;
3001 }
3003 NR::Matrix t =
3004 NR::Matrix (NR::translate(-box.midpoint())) *
3005 NR::Matrix (NR::rotate(rot)) *
3006 NR::Matrix (NR::translate(box.midpoint()));
3008 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3009 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3010 n->pos *= t;
3011 n->n.pos *= t;
3012 n->p.pos *= t;
3013 sp_node_update_handles(n, false);
3014 }
3015 }
3017 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3018 }
3020 /**
3021 * Scale one node.
3022 */
3023 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3024 {
3025 bool both = false;
3026 Inkscape::NodePath::NodeSide *me, *other;
3028 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3029 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3031 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3032 me = &(n->p);
3033 other = &(n->n);
3034 n->code = NR_CURVETO;
3035 } else if (!n->p.other) {
3036 me = &(n->n);
3037 other = &(n->p);
3038 if (n->n.other)
3039 n->n.other->code = NR_CURVETO;
3040 } else {
3041 if (which > 0) { // right handle
3042 if (xn > xp) {
3043 me = &(n->n);
3044 other = &(n->p);
3045 if (n->n.other)
3046 n->n.other->code = NR_CURVETO;
3047 } else {
3048 me = &(n->p);
3049 other = &(n->n);
3050 n->code = NR_CURVETO;
3051 }
3052 } else if (which < 0){ // left handle
3053 if (xn <= xp) {
3054 me = &(n->n);
3055 other = &(n->p);
3056 if (n->n.other)
3057 n->n.other->code = NR_CURVETO;
3058 } else {
3059 me = &(n->p);
3060 other = &(n->n);
3061 n->code = NR_CURVETO;
3062 }
3063 } else { // both handles
3064 me = &(n->n);
3065 other = &(n->p);
3066 both = true;
3067 n->code = NR_CURVETO;
3068 if (n->n.other)
3069 n->n.other->code = NR_CURVETO;
3070 }
3071 }
3073 Radial rme(me->pos - n->pos);
3074 Radial rother(other->pos - n->pos);
3076 rme.r += grow;
3077 if (rme.r < 0) rme.r = 0;
3078 if (rme.a == HUGE_VAL) {
3079 if (me->other) { // if direction is unknown, initialize it towards the next node
3080 Radial rme_next(me->other->pos - n->pos);
3081 rme.a = rme_next.a;
3082 } else { // if there's no next, initialize to 0
3083 rme.a = 0;
3084 }
3085 }
3086 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3087 rother.r += grow;
3088 if (rother.r < 0) rother.r = 0;
3089 if (rother.a == HUGE_VAL) {
3090 rother.a = rme.a + M_PI;
3091 }
3092 }
3094 me->pos = n->pos + NR::Point(rme);
3096 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3097 other->pos = n->pos + NR::Point(rother);
3098 }
3100 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3101 // so here we just move all the knots without emitting move signals, for speed
3102 sp_node_update_handles(n, false);
3103 }
3105 /**
3106 * Scale selected nodes.
3107 */
3108 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3109 {
3110 if (!nodepath || !nodepath->selected) return;
3112 if (g_list_length(nodepath->selected) == 1) {
3113 // scale handles of the single selected node
3114 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3115 node_scale_one (n, grow, which);
3116 } else {
3117 // scale nodes as an "object":
3119 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3120 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3121 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3122 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3123 box.expandTo (n->pos); // contain all selected nodes
3124 }
3126 double scale = (box.maxExtent() + grow)/box.maxExtent();
3128 NR::Matrix t =
3129 NR::Matrix (NR::translate(-box.midpoint())) *
3130 NR::Matrix (NR::scale(scale, scale)) *
3131 NR::Matrix (NR::translate(box.midpoint()));
3133 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3134 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3135 n->pos *= t;
3136 n->n.pos *= t;
3137 n->p.pos *= t;
3138 sp_node_update_handles(n, false);
3139 }
3140 }
3142 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3143 }
3145 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3146 {
3147 if (!nodepath) return;
3148 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3149 }
3151 /**
3152 * Flip selected nodes horizontally/vertically.
3153 */
3154 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3155 {
3156 if (!nodepath || !nodepath->selected) return;
3158 if (g_list_length(nodepath->selected) == 1) {
3159 // flip handles of the single selected node
3160 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3161 double temp = n->p.pos[axis];
3162 n->p.pos[axis] = n->n.pos[axis];
3163 n->n.pos[axis] = temp;
3164 sp_node_update_handles(n, false);
3165 } else {
3166 // scale nodes as an "object":
3168 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3169 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3170 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3171 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3172 box.expandTo (n->pos); // contain all selected nodes
3173 }
3175 NR::Matrix t =
3176 NR::Matrix (NR::translate(-box.midpoint())) *
3177 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3178 NR::Matrix (NR::translate(box.midpoint()));
3180 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3181 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3182 n->pos *= t;
3183 n->n.pos *= t;
3184 n->p.pos *= t;
3185 sp_node_update_handles(n, false);
3186 }
3187 }
3189 sp_nodepath_update_repr(nodepath);
3190 }
3192 //-----------------------------------------------
3193 /**
3194 * Return new subpath under given nodepath.
3195 */
3196 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3197 {
3198 g_assert(nodepath);
3199 g_assert(nodepath->desktop);
3201 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3203 s->nodepath = nodepath;
3204 s->closed = FALSE;
3205 s->nodes = NULL;
3206 s->first = NULL;
3207 s->last = NULL;
3209 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3210 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3211 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3213 return s;
3214 }
3216 /**
3217 * Destroy nodes in subpath, then subpath itself.
3218 */
3219 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3220 {
3221 g_assert(subpath);
3222 g_assert(subpath->nodepath);
3223 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3225 while (subpath->nodes) {
3226 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3227 }
3229 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3231 g_free(subpath);
3232 }
3234 /**
3235 * Link head to tail in subpath.
3236 */
3237 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3238 {
3239 g_assert(!sp->closed);
3240 g_assert(sp->last != sp->first);
3241 g_assert(sp->first->code == NR_MOVETO);
3243 sp->closed = TRUE;
3245 //Link the head to the tail
3246 sp->first->p.other = sp->last;
3247 sp->last->n.other = sp->first;
3248 sp->last->n.pos = sp->first->n.pos;
3249 sp->first = sp->last;
3251 //Remove the extra end node
3252 sp_nodepath_node_destroy(sp->last->n.other);
3253 }
3255 /**
3256 * Open closed (loopy) subpath at node.
3257 */
3258 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3259 {
3260 g_assert(sp->closed);
3261 g_assert(n->subpath == sp);
3262 g_assert(sp->first == sp->last);
3264 /* We create new startpoint, current node will become last one */
3266 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3267 &n->pos, &n->pos, &n->n.pos);
3270 sp->closed = FALSE;
3272 //Unlink to make a head and tail
3273 sp->first = new_path;
3274 sp->last = n;
3275 n->n.other = NULL;
3276 new_path->p.other = NULL;
3277 }
3279 /**
3280 * Returns area in triangle given by points; may be negative.
3281 */
3282 inline double
3283 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3284 {
3285 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]);
3286 }
3288 /**
3289 * Return new node in subpath with given properties.
3290 * \param pos Position of node.
3291 * \param ppos Handle position in previous direction
3292 * \param npos Handle position in previous direction
3293 */
3294 Inkscape::NodePath::Node *
3295 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)
3296 {
3297 g_assert(sp);
3298 g_assert(sp->nodepath);
3299 g_assert(sp->nodepath->desktop);
3301 if (nodechunk == NULL)
3302 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3304 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3306 n->subpath = sp;
3308 if (type != Inkscape::NodePath::NODE_NONE) {
3309 // use the type from sodipodi:nodetypes
3310 n->type = type;
3311 } else {
3312 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3313 // points are (almost) collinear
3314 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3315 // endnode, or a node with a retracted handle
3316 n->type = Inkscape::NodePath::NODE_CUSP;
3317 } else {
3318 n->type = Inkscape::NodePath::NODE_SMOOTH;
3319 }
3320 } else {
3321 n->type = Inkscape::NodePath::NODE_CUSP;
3322 }
3323 }
3325 n->code = code;
3326 n->selected = FALSE;
3327 n->pos = *pos;
3328 n->p.pos = *ppos;
3329 n->n.pos = *npos;
3331 n->dragging_out = NULL;
3333 Inkscape::NodePath::Node *prev;
3334 if (next) {
3335 //g_assert(g_list_find(sp->nodes, next));
3336 prev = next->p.other;
3337 } else {
3338 prev = sp->last;
3339 }
3341 if (prev)
3342 prev->n.other = n;
3343 else
3344 sp->first = n;
3346 if (next)
3347 next->p.other = n;
3348 else
3349 sp->last = n;
3351 n->p.other = prev;
3352 n->n.other = next;
3354 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"));
3355 sp_knot_set_position(n->knot, pos, 0);
3357 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3358 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3359 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3360 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3361 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3362 sp_knot_update_ctrl(n->knot);
3364 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3365 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3366 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3367 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3368 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3369 sp_knot_show(n->knot);
3371 // We only create handle knots and lines on demand
3372 n->p.knot = NULL;
3373 n->p.line = NULL;
3374 n->n.knot = NULL;
3375 n->n.line = NULL;
3377 sp->nodes = g_list_prepend(sp->nodes, n);
3379 return n;
3380 }
3382 /**
3383 * Destroy node and its knots, link neighbors in subpath.
3384 */
3385 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3386 {
3387 g_assert(node);
3388 g_assert(node->subpath);
3389 g_assert(SP_IS_KNOT(node->knot));
3391 Inkscape::NodePath::SubPath *sp = node->subpath;
3393 if (node->selected) { // first, deselect
3394 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3395 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3396 }
3398 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3400 g_object_unref(G_OBJECT(node->knot));
3401 if (node->p.knot)
3402 g_object_unref(G_OBJECT(node->p.knot));
3403 if (node->n.knot)
3404 g_object_unref(G_OBJECT(node->n.knot));
3406 if (node->p.line)
3407 gtk_object_destroy(GTK_OBJECT(node->p.line));
3408 if (node->n.line)
3409 gtk_object_destroy(GTK_OBJECT(node->n.line));
3411 if (sp->nodes) { // there are others nodes on the subpath
3412 if (sp->closed) {
3413 if (sp->first == node) {
3414 g_assert(sp->last == node);
3415 sp->first = node->n.other;
3416 sp->last = sp->first;
3417 }
3418 node->p.other->n.other = node->n.other;
3419 node->n.other->p.other = node->p.other;
3420 } else {
3421 if (sp->first == node) {
3422 sp->first = node->n.other;
3423 sp->first->code = NR_MOVETO;
3424 }
3425 if (sp->last == node) sp->last = node->p.other;
3426 if (node->p.other) node->p.other->n.other = node->n.other;
3427 if (node->n.other) node->n.other->p.other = node->p.other;
3428 }
3429 } else { // this was the last node on subpath
3430 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3431 }
3433 g_mem_chunk_free(nodechunk, node);
3434 }
3436 /**
3437 * Returns one of the node's two sides.
3438 * \param which Indicates which side.
3439 * \return Pointer to previous node side if which==-1, next if which==1.
3440 */
3441 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3442 {
3443 g_assert(node);
3445 switch (which) {
3446 case -1:
3447 return &node->p;
3448 case 1:
3449 return &node->n;
3450 default:
3451 break;
3452 }
3454 g_assert_not_reached();
3456 return NULL;
3457 }
3459 /**
3460 * Return the other side of the node, given one of its sides.
3461 */
3462 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3463 {
3464 g_assert(node);
3466 if (me == &node->p) return &node->n;
3467 if (me == &node->n) return &node->p;
3469 g_assert_not_reached();
3471 return NULL;
3472 }
3474 /**
3475 * Return NRPathcode on the given side of the node.
3476 */
3477 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3478 {
3479 g_assert(node);
3481 if (me == &node->p) {
3482 if (node->p.other) return (NRPathcode)node->code;
3483 return NR_MOVETO;
3484 }
3486 if (me == &node->n) {
3487 if (node->n.other) return (NRPathcode)node->n.other->code;
3488 return NR_MOVETO;
3489 }
3491 g_assert_not_reached();
3493 return NR_END;
3494 }
3496 /**
3497 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3498 */
3499 Inkscape::NodePath::Node *
3500 sp_nodepath_get_node_by_index(int index)
3501 {
3502 Inkscape::NodePath::Node *e = NULL;
3504 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3505 if (!nodepath) {
3506 return e;
3507 }
3509 //find segment
3510 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3512 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3513 int n = g_list_length(sp->nodes);
3514 if (sp->closed) {
3515 n++;
3516 }
3518 //if the piece belongs to this subpath grab it
3519 //otherwise move onto the next subpath
3520 if (index < n) {
3521 e = sp->first;
3522 for (int i = 0; i < index; ++i) {
3523 e = e->n.other;
3524 }
3525 break;
3526 } else {
3527 if (sp->closed) {
3528 index -= (n+1);
3529 } else {
3530 index -= n;
3531 }
3532 }
3533 }
3535 return e;
3536 }
3538 /**
3539 * Returns plain text meaning of node type.
3540 */
3541 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3542 {
3543 unsigned retracted = 0;
3544 bool endnode = false;
3546 for (int which = -1; which <= 1; which += 2) {
3547 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3548 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3549 retracted ++;
3550 if (!side->other)
3551 endnode = true;
3552 }
3554 if (retracted == 0) {
3555 if (endnode) {
3556 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3557 return _("end node");
3558 } else {
3559 switch (node->type) {
3560 case Inkscape::NodePath::NODE_CUSP:
3561 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3562 return _("cusp");
3563 case Inkscape::NodePath::NODE_SMOOTH:
3564 // TRANSLATORS: "smooth" is an adjective here
3565 return _("smooth");
3566 case Inkscape::NodePath::NODE_SYMM:
3567 return _("symmetric");
3568 }
3569 }
3570 } else if (retracted == 1) {
3571 if (endnode) {
3572 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3573 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3574 } else {
3575 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3576 }
3577 } else {
3578 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3579 }
3581 return NULL;
3582 }
3584 /**
3585 * Handles content of statusbar as long as node tool is active.
3586 */
3587 void
3588 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3589 {
3590 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>arrow</b> keys to move nodes");
3591 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3593 gint total = 0;
3594 gint selected = 0;
3595 SPDesktop *desktop = NULL;
3597 if (nodepath) {
3598 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3599 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3600 total += g_list_length(subpath->nodes);
3601 }
3602 selected = g_list_length(nodepath->selected);
3603 desktop = nodepath->desktop;
3604 } else {
3605 desktop = SP_ACTIVE_DESKTOP;
3606 }
3608 SPEventContext *ec = desktop->event_context;
3609 if (!ec) return;
3610 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3611 if (!mc) return;
3613 if (selected == 0) {
3614 Inkscape::Selection *sel = desktop->selection;
3615 if (!sel || sel->isEmpty()) {
3616 mc->setF(Inkscape::NORMAL_MESSAGE,
3617 _("Select a single object to edit its nodes or handles."));
3618 } else {
3619 if (nodepath) {
3620 mc->setF(Inkscape::NORMAL_MESSAGE,
3621 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.",
3622 "<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.",
3623 total),
3624 total);
3625 } else {
3626 if (g_slist_length((GSList *)sel->itemList()) == 1) {
3627 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
3628 } else {
3629 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
3630 }
3631 }
3632 }
3633 } else if (nodepath && selected == 1) {
3634 mc->setF(Inkscape::NORMAL_MESSAGE,
3635 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
3636 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
3637 total),
3638 selected, total, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
3639 } else {
3640 mc->setF(Inkscape::NORMAL_MESSAGE,
3641 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
3642 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
3643 total),
3644 selected, total, when_selected);
3645 }
3646 }
3649 /*
3650 Local Variables:
3651 mode:c++
3652 c-file-style:"stroustrup"
3653 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
3654 indent-tabs-mode:nil
3655 fill-column:99
3656 End:
3657 */
3658 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :