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 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
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"
43 #include "verbs.h"
44 #include "display/bezier-utils.h"
45 #include <vector>
46 #include <algorithm>
48 class NR::Matrix;
50 /// \todo
51 /// evil evil evil. FIXME: conflict of two different Path classes!
52 /// There is a conflict in the namespace between two classes named Path.
53 /// #include "sp-flowtext.h"
54 /// #include "sp-flowregion.h"
56 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
57 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
58 GType sp_flowregion_get_type (void);
59 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
60 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
61 GType sp_flowtext_get_type (void);
62 // end evil workaround
64 #include "helper/stlport.h"
67 /// \todo fixme: Implement these via preferences */
69 #define NODE_FILL 0xbfbfbf00
70 #define NODE_STROKE 0x000000ff
71 #define NODE_FILL_HI 0xff000000
72 #define NODE_STROKE_HI 0x000000ff
73 #define NODE_FILL_SEL 0x0000ffff
74 #define NODE_STROKE_SEL 0x000000ff
75 #define NODE_FILL_SEL_HI 0xff000000
76 #define NODE_STROKE_SEL_HI 0x000000ff
77 #define KNOT_FILL 0xffffffff
78 #define KNOT_STROKE 0x000000ff
79 #define KNOT_FILL_HI 0xff000000
80 #define KNOT_STROKE_HI 0x000000ff
82 static GMemChunk *nodechunk = NULL;
84 /* Creation from object */
86 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
87 static gchar *parse_nodetypes(gchar const *types, gint length);
89 /* Object updating */
91 static void stamp_repr(Inkscape::NodePath::Path *np);
92 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
93 static gchar *create_typestr(Inkscape::NodePath::Path *np);
95 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
97 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
99 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
101 /* Adjust handle placement, if the node or the other handle is moved */
102 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
103 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
105 /* Node event callbacks */
106 static void node_clicked(SPKnot *knot, guint state, gpointer data);
107 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
108 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
109 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
111 /* Handle event callbacks */
112 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
114 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
115 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
119 /* Constructors and destructors */
121 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
122 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
123 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
124 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
125 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
126 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
127 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
129 /* Helpers */
131 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
132 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
133 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
135 // active_node indicates mouseover node
136 static Inkscape::NodePath::Node *active_node = NULL;
138 /**
139 * \brief Creates new nodepath from item
140 */
141 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
142 {
143 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
145 /** \todo
146 * FIXME: remove this. We don't want to edit paths inside flowtext.
147 * Instead we will build our flowtext with cloned paths, so that the
148 * real paths are outside the flowtext and thus editable as usual.
149 */
150 if (SP_IS_FLOWTEXT(item)) {
151 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
152 if SP_IS_FLOWREGION(child) {
153 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
154 if (grandchild && SP_IS_PATH(grandchild)) {
155 item = SP_ITEM(grandchild);
156 break;
157 }
158 }
159 }
160 }
162 if (!SP_IS_PATH(item))
163 return NULL;
164 SPPath *path = SP_PATH(item);
165 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
166 if (curve == NULL)
167 return NULL;
169 NArtBpath *bpath = sp_curve_first_bpath(curve);
170 gint length = curve->end;
171 if (length == 0)
172 return NULL; // prevent crash for one-node paths
174 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
175 gchar *typestr = parse_nodetypes(nodetypes, length);
177 //Create new nodepath
178 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
179 if (!np)
180 return NULL;
182 // Set defaults
183 np->desktop = desktop;
184 np->path = path;
185 np->subpaths = NULL;
186 np->selected = NULL;
187 np->nodeContext = NULL; //Let the context that makes this set it
188 np->livarot_path = NULL;
189 np->local_change = 0;
190 np->show_handles = show_handles;
192 // we need to update item's transform from the repr here,
193 // because they may be out of sync when we respond
194 // to a change in repr by regenerating nodepath --bb
195 sp_object_read_attr(SP_OBJECT(item), "transform");
197 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
198 np->d2i = np->i2d.inverse();
199 np->repr = repr;
201 // create the subpath(s) from the bpath
202 NArtBpath *b = bpath;
203 while (b->code != NR_END) {
204 b = subpath_from_bpath(np, b, typestr + (b - bpath));
205 }
207 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
208 np->subpaths = g_list_reverse(np->subpaths);
210 g_free(typestr);
211 sp_curve_unref(curve);
213 // create the livarot representation from the same item
214 sp_nodepath_ensure_livarot_path(np);
216 return np;
217 }
219 /**
220 * Destroys nodepath's subpaths, then itself, also tell context about it.
221 */
222 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
224 if (!np) //soft fail, like delete
225 return;
227 while (np->subpaths) {
228 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
229 }
231 //Inform the context that made me, if any, that I am gone.
232 if (np->nodeContext)
233 np->nodeContext->nodepath = NULL;
235 g_assert(!np->selected);
237 if (np->livarot_path) {
238 delete np->livarot_path;
239 np->livarot_path = NULL;
240 }
242 np->desktop = NULL;
244 g_free(np);
245 }
248 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
249 {
250 if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
251 np->livarot_path = Path_for_item (np->path, true, true);
252 if (np->livarot_path)
253 np->livarot_path->ConvertWithBackData(0.01);
254 }
255 }
258 /**
259 * Return the node count of a given NodeSubPath.
260 */
261 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
262 {
263 if (!subpath)
264 return 0;
265 gint nodeCount = g_list_length(subpath->nodes);
266 return nodeCount;
267 }
269 /**
270 * Return the node count of a given NodePath.
271 */
272 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
273 {
274 if (!np)
275 return 0;
276 gint nodeCount = 0;
277 for (GList *item = np->subpaths ; item ; item=item->next) {
278 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
279 nodeCount += g_list_length(subpath->nodes);
280 }
281 return nodeCount;
282 }
284 /**
285 * Return the subpath count of a given NodePath.
286 */
287 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
288 {
289 if (!np)
290 return 0;
291 return g_list_length (np->subpaths);
292 }
294 /**
295 * Return the selected node count of a given NodePath.
296 */
297 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
298 {
299 if (!np)
300 return 0;
301 return g_list_length (np->selected);
302 }
304 /**
305 * Return the number of subpaths where nodes are selected in a given NodePath.
306 */
307 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
308 {
309 if (!np)
310 return 0;
311 if (!np->selected)
312 return 0;
313 if (!np->selected->next)
314 return 1;
315 gint count = 0;
316 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
317 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
318 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
319 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
320 if (node->selected) {
321 count ++;
322 break;
323 }
324 }
325 }
326 return count;
327 }
329 /**
330 * Clean up a nodepath after editing.
331 *
332 * Currently we are deleting trivial subpaths.
333 */
334 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
335 {
336 GList *badSubPaths = NULL;
338 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
339 for (GList *l = nodepath->subpaths; l ; l=l->next) {
340 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
341 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
342 badSubPaths = g_list_append(badSubPaths, sp);
343 }
345 //Delete them. This second step is because sp_nodepath_subpath_destroy()
346 //also removes the subpath from nodepath->subpaths
347 for (GList *l = badSubPaths; l ; l=l->next) {
348 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
349 sp_nodepath_subpath_destroy(sp);
350 }
352 g_list_free(badSubPaths);
353 }
355 /**
356 * Create new nodepath from b, make it subpath of np.
357 * \param t The node type.
358 * \todo Fixme: t should be a proper type, rather than gchar
359 */
360 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
361 {
362 NR::Point ppos, pos, npos;
364 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
366 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
367 bool const closed = (b->code == NR_MOVETO);
369 pos = NR::Point(b->x3, b->y3) * np->i2d;
370 if (b[1].code == NR_CURVETO) {
371 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
372 } else {
373 npos = pos;
374 }
375 Inkscape::NodePath::Node *n;
376 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
377 g_assert(sp->first == n);
378 g_assert(sp->last == n);
380 b++;
381 t++;
382 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
383 pos = NR::Point(b->x3, b->y3) * np->i2d;
384 if (b->code == NR_CURVETO) {
385 ppos = NR::Point(b->x2, b->y2) * np->i2d;
386 } else {
387 ppos = pos;
388 }
389 if (b[1].code == NR_CURVETO) {
390 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
391 } else {
392 npos = pos;
393 }
394 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
395 b++;
396 t++;
397 }
399 if (closed) sp_nodepath_subpath_close(sp);
401 return b;
402 }
404 /**
405 * Convert from sodipodi:nodetypes to new style type string.
406 */
407 static gchar *parse_nodetypes(gchar const *types, gint length)
408 {
409 g_assert(length > 0);
411 gchar *typestr = g_new(gchar, length + 1);
413 gint pos = 0;
415 if (types) {
416 for (gint i = 0; types[i] && ( i < length ); i++) {
417 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
418 if (types[i] != '\0') {
419 switch (types[i]) {
420 case 's':
421 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
422 break;
423 case 'z':
424 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
425 break;
426 case 'c':
427 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
428 break;
429 default:
430 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
431 break;
432 }
433 }
434 }
435 }
437 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
439 return typestr;
440 }
442 /**
443 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
444 * updated but repr is not (for speed). Used during curve and node drag.
445 */
446 static void update_object(Inkscape::NodePath::Path *np)
447 {
448 g_assert(np);
450 SPCurve *curve = create_curve(np);
452 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
454 sp_curve_unref(curve);
455 }
457 /**
458 * Update XML path node with data from path object.
459 */
460 static void update_repr_internal(Inkscape::NodePath::Path *np)
461 {
462 g_assert(np);
464 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
466 SPCurve *curve = create_curve(np);
467 gchar *typestr = create_typestr(np);
468 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
470 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
471 np->local_change++;
472 repr->setAttribute("d", svgpath);
473 }
475 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
476 np->local_change++;
477 repr->setAttribute("sodipodi:nodetypes", typestr);
478 }
480 g_free(svgpath);
481 g_free(typestr);
482 sp_curve_unref(curve);
483 }
485 /**
486 * Update XML path node with data from path object, commit changes forever.
487 */
488 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
489 {
490 //fixme: np can be NULL, so check before proceeding
491 g_return_if_fail(np != NULL);
493 if (np->livarot_path) {
494 delete np->livarot_path;
495 np->livarot_path = NULL;
496 }
498 update_repr_internal(np);
499 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
501 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
502 annotation);
503 }
505 /**
506 * Update XML path node with data from path object, commit changes with undo.
507 */
508 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
509 {
510 if (np->livarot_path) {
511 delete np->livarot_path;
512 np->livarot_path = NULL;
513 }
515 update_repr_internal(np);
516 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
517 annotation);
518 }
520 /**
521 * Make duplicate of path, replace corresponding XML node in tree, commit.
522 */
523 static void stamp_repr(Inkscape::NodePath::Path *np)
524 {
525 g_assert(np);
527 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
528 Inkscape::XML::Node *new_repr = old_repr->duplicate();
530 // remember the position of the item
531 gint pos = old_repr->position();
532 // remember parent
533 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
535 SPCurve *curve = create_curve(np);
536 gchar *typestr = create_typestr(np);
538 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
540 new_repr->setAttribute("d", svgpath);
541 new_repr->setAttribute("sodipodi:nodetypes", typestr);
543 // add the new repr to the parent
544 parent->appendChild(new_repr);
545 // move to the saved position
546 new_repr->setPosition(pos > 0 ? pos : 0);
548 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
549 _("Stamp"));
551 Inkscape::GC::release(new_repr);
552 g_free(svgpath);
553 g_free(typestr);
554 sp_curve_unref(curve);
555 }
557 /**
558 * Create curve from path.
559 */
560 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
561 {
562 SPCurve *curve = sp_curve_new();
564 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
565 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
566 sp_curve_moveto(curve,
567 sp->first->pos * np->d2i);
568 Inkscape::NodePath::Node *n = sp->first->n.other;
569 while (n) {
570 NR::Point const end_pt = n->pos * np->d2i;
571 switch (n->code) {
572 case NR_LINETO:
573 sp_curve_lineto(curve, end_pt);
574 break;
575 case NR_CURVETO:
576 sp_curve_curveto(curve,
577 n->p.other->n.pos * np->d2i,
578 n->p.pos * np->d2i,
579 end_pt);
580 break;
581 default:
582 g_assert_not_reached();
583 break;
584 }
585 if (n != sp->last) {
586 n = n->n.other;
587 } else {
588 n = NULL;
589 }
590 }
591 if (sp->closed) {
592 sp_curve_closepath(curve);
593 }
594 }
596 return curve;
597 }
599 /**
600 * Convert path type string to sodipodi:nodetypes style.
601 */
602 static gchar *create_typestr(Inkscape::NodePath::Path *np)
603 {
604 gchar *typestr = g_new(gchar, 32);
605 gint len = 32;
606 gint pos = 0;
608 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
609 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
611 if (pos >= len) {
612 typestr = g_renew(gchar, typestr, len + 32);
613 len += 32;
614 }
616 typestr[pos++] = 'c';
618 Inkscape::NodePath::Node *n;
619 n = sp->first->n.other;
620 while (n) {
621 gchar code;
623 switch (n->type) {
624 case Inkscape::NodePath::NODE_CUSP:
625 code = 'c';
626 break;
627 case Inkscape::NodePath::NODE_SMOOTH:
628 code = 's';
629 break;
630 case Inkscape::NodePath::NODE_SYMM:
631 code = 'z';
632 break;
633 default:
634 g_assert_not_reached();
635 code = '\0';
636 break;
637 }
639 if (pos >= len) {
640 typestr = g_renew(gchar, typestr, len + 32);
641 len += 32;
642 }
644 typestr[pos++] = code;
646 if (n != sp->last) {
647 n = n->n.other;
648 } else {
649 n = NULL;
650 }
651 }
652 }
654 if (pos >= len) {
655 typestr = g_renew(gchar, typestr, len + 1);
656 len += 1;
657 }
659 typestr[pos++] = '\0';
661 return typestr;
662 }
664 /**
665 * Returns current path in context.
666 */
667 static Inkscape::NodePath::Path *sp_nodepath_current()
668 {
669 if (!SP_ACTIVE_DESKTOP) {
670 return NULL;
671 }
673 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
675 if (!SP_IS_NODE_CONTEXT(event_context)) {
676 return NULL;
677 }
679 return SP_NODE_CONTEXT(event_context)->nodepath;
680 }
684 /**
685 \brief Fills node and handle positions for three nodes, splitting line
686 marked by end at distance t.
687 */
688 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
689 {
690 g_assert(new_path != NULL);
691 g_assert(end != NULL);
693 g_assert(end->p.other == new_path);
694 Inkscape::NodePath::Node *start = new_path->p.other;
695 g_assert(start);
697 if (end->code == NR_LINETO) {
698 new_path->type =Inkscape::NodePath::NODE_CUSP;
699 new_path->code = NR_LINETO;
700 new_path->pos = (t * start->pos + (1 - t) * end->pos);
701 } else {
702 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
703 new_path->code = NR_CURVETO;
704 gdouble s = 1 - t;
705 for (int dim = 0; dim < 2; dim++) {
706 NR::Coord const f000 = start->pos[dim];
707 NR::Coord const f001 = start->n.pos[dim];
708 NR::Coord const f011 = end->p.pos[dim];
709 NR::Coord const f111 = end->pos[dim];
710 NR::Coord const f00t = s * f000 + t * f001;
711 NR::Coord const f01t = s * f001 + t * f011;
712 NR::Coord const f11t = s * f011 + t * f111;
713 NR::Coord const f0tt = s * f00t + t * f01t;
714 NR::Coord const f1tt = s * f01t + t * f11t;
715 NR::Coord const fttt = s * f0tt + t * f1tt;
716 start->n.pos[dim] = f00t;
717 new_path->p.pos[dim] = f0tt;
718 new_path->pos[dim] = fttt;
719 new_path->n.pos[dim] = f1tt;
720 end->p.pos[dim] = f11t;
721 }
722 }
723 }
725 /**
726 * Adds new node on direct line between two nodes, activates handles of all
727 * three nodes.
728 */
729 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
730 {
731 g_assert(end);
732 g_assert(end->subpath);
733 g_assert(g_list_find(end->subpath->nodes, end));
735 Inkscape::NodePath::Node *start = end->p.other;
736 g_assert( start->n.other == end );
737 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
738 end,
739 Inkscape::NodePath::NODE_SMOOTH,
740 (NRPathcode)end->code,
741 &start->pos, &start->pos, &start->n.pos);
742 sp_nodepath_line_midpoint(newnode, end, t);
744 sp_node_update_handles(start);
745 sp_node_update_handles(newnode);
746 sp_node_update_handles(end);
748 return newnode;
749 }
751 /**
752 \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
753 */
754 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
755 {
756 g_assert(node);
757 g_assert(node->subpath);
758 g_assert(g_list_find(node->subpath->nodes, node));
760 Inkscape::NodePath::SubPath *sp = node->subpath;
761 Inkscape::NodePath::Path *np = sp->nodepath;
763 if (sp->closed) {
764 sp_nodepath_subpath_open(sp, node);
765 return sp->first;
766 } else {
767 // no break for end nodes
768 if (node == sp->first) return NULL;
769 if (node == sp->last ) return NULL;
771 // create a new subpath
772 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
774 // duplicate the break node as start of the new subpath
775 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
777 while (node->n.other) { // copy the remaining nodes into the new subpath
778 Inkscape::NodePath::Node *n = node->n.other;
779 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);
780 if (n->selected) {
781 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
782 }
783 sp_nodepath_node_destroy(n); // remove the point on the original subpath
784 }
786 return newnode;
787 }
788 }
790 /**
791 * Duplicate node and connect to neighbours.
792 */
793 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
794 {
795 g_assert(node);
796 g_assert(node->subpath);
797 g_assert(g_list_find(node->subpath->nodes, node));
799 Inkscape::NodePath::SubPath *sp = node->subpath;
801 NRPathcode code = (NRPathcode) node->code;
802 if (code == NR_MOVETO) { // if node is the endnode,
803 node->code = NR_LINETO; // new one is inserted before it, so change that to line
804 }
806 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
808 if (!node->n.other || !node->p.other) // if node is an endnode, select it
809 return node;
810 else
811 return newnode; // otherwise select the newly created node
812 }
814 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
815 {
816 node->p.pos = (node->pos + (node->pos - node->n.pos));
817 }
819 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
820 {
821 node->n.pos = (node->pos + (node->pos - node->p.pos));
822 }
824 /**
825 * Change line type at node, with side effects on neighbours.
826 */
827 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
828 {
829 g_assert(end);
830 g_assert(end->subpath);
831 g_assert(end->p.other);
833 if (end->code == static_cast< guint > ( code ) )
834 return;
836 Inkscape::NodePath::Node *start = end->p.other;
838 end->code = code;
840 if (code == NR_LINETO) {
841 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
842 if (end->n.other) {
843 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
844 }
845 sp_node_adjust_handle(start, -1);
846 sp_node_adjust_handle(end, 1);
847 } else {
848 NR::Point delta = end->pos - start->pos;
849 start->n.pos = start->pos + delta / 3;
850 end->p.pos = end->pos - delta / 3;
851 sp_node_adjust_handle(start, 1);
852 sp_node_adjust_handle(end, -1);
853 }
855 sp_node_update_handles(start);
856 sp_node_update_handles(end);
857 }
859 /**
860 * Change node type, and its handles accordingly.
861 */
862 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
863 {
864 g_assert(node);
865 g_assert(node->subpath);
867 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
868 return node;
870 if ((node->p.other != NULL) && (node->n.other != NULL)) {
871 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
872 type =Inkscape::NodePath::NODE_CUSP;
873 }
874 }
876 node->type = type;
878 if (node->type == Inkscape::NodePath::NODE_CUSP) {
879 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
880 node->knot->setSize (node->selected? 11 : 9);
881 sp_knot_update_ctrl(node->knot);
882 } else {
883 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
884 node->knot->setSize (node->selected? 9 : 7);
885 sp_knot_update_ctrl(node->knot);
886 }
888 // if one of handles is mouseovered, preserve its position
889 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
890 sp_node_adjust_handle(node, 1);
891 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
892 sp_node_adjust_handle(node, -1);
893 } else {
894 sp_node_adjust_handles(node);
895 }
897 sp_node_update_handles(node);
899 sp_nodepath_update_statusbar(node->subpath->nodepath);
901 return node;
902 }
904 /**
905 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
906 * adjacent segments from lines to curves.
907 */
908 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
909 {
910 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
911 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
912 // convert adjacent segment BEFORE to curve
913 node->code = NR_CURVETO;
914 NR::Point delta;
915 if (node->n.other != NULL)
916 delta = node->n.other->pos - node->p.other->pos;
917 else
918 delta = node->pos - node->p.other->pos;
919 node->p.pos = node->pos - delta / 4;
920 sp_node_update_handles(node);
921 }
923 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
924 // convert adjacent segment AFTER to curve
925 node->n.other->code = NR_CURVETO;
926 NR::Point delta;
927 if (node->p.other != NULL)
928 delta = node->p.other->pos - node->n.other->pos;
929 else
930 delta = node->pos - node->n.other->pos;
931 node->n.pos = node->pos - delta / 4;
932 sp_node_update_handles(node);
933 }
934 }
936 sp_nodepath_set_node_type (node, type);
937 }
939 /**
940 * Move node to point, and adjust its and neighbouring handles.
941 */
942 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
943 {
944 NR::Point delta = p - node->pos;
945 node->pos = p;
947 node->p.pos += delta;
948 node->n.pos += delta;
950 if (node->p.other) {
951 if (node->code == NR_LINETO) {
952 sp_node_adjust_handle(node, 1);
953 sp_node_adjust_handle(node->p.other, -1);
954 }
955 }
956 if (node->n.other) {
957 if (node->n.other->code == NR_LINETO) {
958 sp_node_adjust_handle(node, -1);
959 sp_node_adjust_handle(node->n.other, 1);
960 }
961 }
963 // this function is only called from batch movers that will update display at the end
964 // themselves, so here we just move all the knots without emitting move signals, for speed
965 sp_node_update_handles(node, false);
966 }
968 /**
969 * Call sp_node_moveto() for node selection and handle possible snapping.
970 */
971 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
972 bool const snap = true)
973 {
974 NR::Coord best = NR_HUGE;
975 NR::Point delta(dx, dy);
976 NR::Point best_pt = delta;
978 if (snap) {
979 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
981 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
982 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
983 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
984 if (s.getDistance() < best) {
985 best = s.getDistance();
986 best_pt = s.getPoint() - n->pos;
987 }
988 }
989 }
991 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
992 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
993 sp_node_moveto(n, n->pos + best_pt);
994 }
996 // do not update repr here so that node dragging is acceptably fast
997 update_object(nodepath);
998 }
1000 /**
1001 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1002 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1003 near x = 0.
1004 */
1005 double
1006 sculpt_profile (double x, double alpha, guint profile)
1007 {
1008 if (x >= 1)
1009 return 0;
1010 if (x <= 0)
1011 return 1;
1013 switch (profile) {
1014 case SCULPT_PROFILE_LINEAR:
1015 return 1 - x;
1016 case SCULPT_PROFILE_BELL:
1017 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1018 case SCULPT_PROFILE_ELLIPTIC:
1019 return sqrt(1 - x*x);
1020 }
1022 return 1;
1023 }
1025 double
1026 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1027 {
1028 // extremely primitive for now, don't have time to look for the real one
1029 double lower = NR::L2(b - a);
1030 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1031 return (lower + upper)/2;
1032 }
1034 void
1035 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1036 {
1037 n->pos = n->origin + delta;
1038 n->n.pos = n->n.origin + delta_n;
1039 n->p.pos = n->p.origin + delta_p;
1040 sp_node_adjust_handles(n);
1041 sp_node_update_handles(n, false);
1042 }
1044 /**
1045 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1046 * on how far they are from the dragged node n.
1047 */
1048 static void
1049 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1050 {
1051 g_assert (n);
1052 g_assert (nodepath);
1053 g_assert (n->subpath->nodepath == nodepath);
1055 double pressure = n->knot->pressure;
1056 if (pressure == 0)
1057 pressure = 0.5; // default
1058 pressure = CLAMP (pressure, 0.2, 0.8);
1060 // map pressure to alpha = 1/5 ... 5
1061 double alpha = 1 - 2 * fabs(pressure - 0.5);
1062 if (pressure > 0.5)
1063 alpha = 1/alpha;
1065 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1067 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1068 // Only one subpath has selected nodes:
1069 // use linear mode, where the distance from n to node being dragged is calculated along the path
1071 double n_sel_range = 0, p_sel_range = 0;
1072 guint n_nodes = 0, p_nodes = 0;
1073 guint n_sel_nodes = 0, p_sel_nodes = 0;
1075 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1076 {
1077 double n_range = 0, p_range = 0;
1078 bool n_going = true, p_going = true;
1079 Inkscape::NodePath::Node *n_node = n;
1080 Inkscape::NodePath::Node *p_node = n;
1081 do {
1082 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1083 if (n_node && n_going)
1084 n_node = n_node->n.other;
1085 if (n_node == NULL) {
1086 n_going = false;
1087 } else {
1088 n_nodes ++;
1089 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1090 if (n_node->selected) {
1091 n_sel_nodes ++;
1092 n_sel_range = n_range;
1093 }
1094 if (n_node == p_node) {
1095 n_going = false;
1096 p_going = false;
1097 }
1098 }
1099 if (p_node && p_going)
1100 p_node = p_node->p.other;
1101 if (p_node == NULL) {
1102 p_going = false;
1103 } else {
1104 p_nodes ++;
1105 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1106 if (p_node->selected) {
1107 p_sel_nodes ++;
1108 p_sel_range = p_range;
1109 }
1110 if (p_node == n_node) {
1111 n_going = false;
1112 p_going = false;
1113 }
1114 }
1115 } while (n_going || p_going);
1116 }
1118 // Second pass: actually move nodes in this subpath
1119 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1120 {
1121 double n_range = 0, p_range = 0;
1122 bool n_going = true, p_going = true;
1123 Inkscape::NodePath::Node *n_node = n;
1124 Inkscape::NodePath::Node *p_node = n;
1125 do {
1126 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1127 if (n_node && n_going)
1128 n_node = n_node->n.other;
1129 if (n_node == NULL) {
1130 n_going = false;
1131 } else {
1132 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1133 if (n_node->selected) {
1134 sp_nodepath_move_node_and_handles (n_node,
1135 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1136 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1137 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1138 }
1139 if (n_node == p_node) {
1140 n_going = false;
1141 p_going = false;
1142 }
1143 }
1144 if (p_node && p_going)
1145 p_node = p_node->p.other;
1146 if (p_node == NULL) {
1147 p_going = false;
1148 } else {
1149 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1150 if (p_node->selected) {
1151 sp_nodepath_move_node_and_handles (p_node,
1152 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1153 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1154 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1155 }
1156 if (p_node == n_node) {
1157 n_going = false;
1158 p_going = false;
1159 }
1160 }
1161 } while (n_going || p_going);
1162 }
1164 } else {
1165 // Multiple subpaths have selected nodes:
1166 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1167 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1168 // fix the pear-like shape when sculpting e.g. a ring
1170 // First pass: calculate range
1171 gdouble direct_range = 0;
1172 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1173 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1174 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1175 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1176 if (node->selected) {
1177 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1178 }
1179 }
1180 }
1182 // Second pass: actually move nodes
1183 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1184 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1185 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1186 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1187 if (node->selected) {
1188 if (direct_range > 1e-6) {
1189 sp_nodepath_move_node_and_handles (node,
1190 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1191 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1192 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1193 } else {
1194 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1195 }
1197 }
1198 }
1199 }
1200 }
1202 // do not update repr here so that node dragging is acceptably fast
1203 update_object(nodepath);
1204 }
1207 /**
1208 * Move node selection to point, adjust its and neighbouring handles,
1209 * handle possible snapping, and commit the change with possible undo.
1210 */
1211 void
1212 sp_node_selected_move(gdouble dx, gdouble dy)
1213 {
1214 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1215 if (!nodepath) return;
1217 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1219 if (dx == 0) {
1220 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1221 } else if (dy == 0) {
1222 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1223 } else {
1224 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1225 }
1226 }
1228 /**
1229 * Move node selection off screen and commit the change.
1230 */
1231 void
1232 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1233 {
1234 // borrowed from sp_selection_move_screen in selection-chemistry.c
1235 // we find out the current zoom factor and divide deltas by it
1236 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1238 gdouble zoom = desktop->current_zoom();
1239 gdouble zdx = dx / zoom;
1240 gdouble zdy = dy / zoom;
1242 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1243 if (!nodepath) return;
1245 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1247 if (dx == 0) {
1248 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1249 } else if (dy == 0) {
1250 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1251 } else {
1252 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1253 }
1254 }
1256 /** If they don't yet exist, creates knot and line for the given side of the node */
1257 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1258 {
1259 if (!side->knot) {
1260 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"));
1262 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1263 side->knot->setSize (7);
1264 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1265 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1266 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1267 sp_knot_update_ctrl(side->knot);
1269 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1270 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1271 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1272 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1273 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1274 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1275 }
1277 if (!side->line) {
1278 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1279 SP_TYPE_CTRLLINE, NULL);
1280 }
1281 }
1283 /**
1284 * Ensure the given handle of the node is visible/invisible, update its screen position
1285 */
1286 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1287 {
1288 g_assert(node != NULL);
1290 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1291 NRPathcode code = sp_node_path_code_from_side(node, side);
1293 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1295 if (show_handle) {
1296 if (!side->knot) { // No handle knot at all
1297 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1298 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1299 side->knot->pos = side->pos;
1300 if (side->knot->item)
1301 SP_CTRL(side->knot->item)->moveto(side->pos);
1302 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1303 sp_knot_show(side->knot);
1304 } else {
1305 if (side->knot->pos != side->pos) { // only if it's really moved
1306 if (fire_move_signals) {
1307 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1308 } else {
1309 sp_knot_moveto(side->knot, &side->pos);
1310 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1311 }
1312 }
1313 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1314 sp_knot_show(side->knot);
1315 }
1316 }
1317 sp_canvas_item_show(side->line);
1318 } else {
1319 if (side->knot) {
1320 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1321 sp_knot_hide(side->knot);
1322 }
1323 }
1324 if (side->line) {
1325 sp_canvas_item_hide(side->line);
1326 }
1327 }
1328 }
1330 /**
1331 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1332 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1333 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1334 * updated; otherwise, just move the knots silently (used in batch moves).
1335 */
1336 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1337 {
1338 g_assert(node != NULL);
1340 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1341 sp_knot_show(node->knot);
1342 }
1344 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1345 if (fire_move_signals)
1346 sp_knot_set_position(node->knot, &node->pos, 0);
1347 else
1348 sp_knot_moveto(node->knot, &node->pos);
1349 }
1351 gboolean show_handles = node->selected;
1352 if (node->p.other != NULL) {
1353 if (node->p.other->selected) show_handles = TRUE;
1354 }
1355 if (node->n.other != NULL) {
1356 if (node->n.other->selected) show_handles = TRUE;
1357 }
1359 if (node->subpath->nodepath->show_handles == false)
1360 show_handles = FALSE;
1362 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1363 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1364 }
1366 /**
1367 * Call sp_node_update_handles() for all nodes on subpath.
1368 */
1369 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1370 {
1371 g_assert(subpath != NULL);
1373 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1374 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1375 }
1376 }
1378 /**
1379 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1380 */
1381 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1382 {
1383 g_assert(nodepath != NULL);
1385 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1386 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1387 }
1388 }
1390 void
1391 sp_nodepath_show_handles(bool show)
1392 {
1393 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1394 if (nodepath == NULL) return;
1396 nodepath->show_handles = show;
1397 sp_nodepath_update_handles(nodepath);
1398 }
1400 /**
1401 * Adds all selected nodes in nodepath to list.
1402 */
1403 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1404 {
1405 StlConv<Node *>::list(l, selected);
1406 /// \todo this adds a copying, rework when the selection becomes a stl list
1407 }
1409 /**
1410 * Align selected nodes on the specified axis.
1411 */
1412 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1413 {
1414 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1415 return;
1416 }
1418 if ( !nodepath->selected->next ) { // only one node selected
1419 return;
1420 }
1421 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1422 NR::Point dest(pNode->pos);
1423 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1424 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1425 if (pNode) {
1426 dest[axis] = pNode->pos[axis];
1427 sp_node_moveto(pNode, dest);
1428 }
1429 }
1431 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1432 }
1434 /// Helper struct.
1435 struct NodeSort
1436 {
1437 Inkscape::NodePath::Node *_node;
1438 NR::Coord _coord;
1439 /// \todo use vectorof pointers instead of calling copy ctor
1440 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1441 _node(node), _coord(node->pos[axis])
1442 {}
1444 };
1446 static bool operator<(NodeSort const &a, NodeSort const &b)
1447 {
1448 return (a._coord < b._coord);
1449 }
1451 /**
1452 * Distribute selected nodes on the specified axis.
1453 */
1454 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1455 {
1456 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1457 return;
1458 }
1460 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1461 return;
1462 }
1464 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1465 std::vector<NodeSort> sorted;
1466 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1467 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1468 if (pNode) {
1469 NodeSort n(pNode, axis);
1470 sorted.push_back(n);
1471 //dest[axis] = pNode->pos[axis];
1472 //sp_node_moveto(pNode, dest);
1473 }
1474 }
1475 std::sort(sorted.begin(), sorted.end());
1476 unsigned int len = sorted.size();
1477 //overall bboxes span
1478 float dist = (sorted.back()._coord -
1479 sorted.front()._coord);
1480 //new distance between each bbox
1481 float step = (dist) / (len - 1);
1482 float pos = sorted.front()._coord;
1483 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1484 it < sorted.end();
1485 it ++ )
1486 {
1487 NR::Point dest((*it)._node->pos);
1488 dest[axis] = pos;
1489 sp_node_moveto((*it)._node, dest);
1490 pos += step;
1491 }
1493 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1494 }
1497 /**
1498 * Call sp_nodepath_line_add_node() for all selected segments.
1499 */
1500 void
1501 sp_node_selected_add_node(void)
1502 {
1503 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1504 if (!nodepath) {
1505 return;
1506 }
1508 GList *nl = NULL;
1510 int n_added = 0;
1512 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1513 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1514 g_assert(t->selected);
1515 if (t->p.other && t->p.other->selected) {
1516 nl = g_list_prepend(nl, t);
1517 }
1518 }
1520 while (nl) {
1521 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1522 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1523 sp_nodepath_node_select(n, TRUE, FALSE);
1524 n_added ++;
1525 nl = g_list_remove(nl, t);
1526 }
1528 /** \todo fixme: adjust ? */
1529 sp_nodepath_update_handles(nodepath);
1531 if (n_added > 1) {
1532 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1533 } else if (n_added > 0) {
1534 sp_nodepath_update_repr(nodepath, _("Add node"));
1535 }
1537 sp_nodepath_update_statusbar(nodepath);
1538 }
1540 /**
1541 * Select segment nearest to point
1542 */
1543 void
1544 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1545 {
1546 if (!nodepath) {
1547 return;
1548 }
1550 sp_nodepath_ensure_livarot_path(nodepath);
1551 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1553 //find segment to segment
1554 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1556 //fixme: this can return NULL, so check before proceeding.
1557 g_return_if_fail(e != NULL);
1559 gboolean force = FALSE;
1560 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1561 force = TRUE;
1562 }
1563 sp_nodepath_node_select(e, (gboolean) toggle, force);
1564 if (e->p.other)
1565 sp_nodepath_node_select(e->p.other, TRUE, force);
1567 sp_nodepath_update_handles(nodepath);
1569 sp_nodepath_update_statusbar(nodepath);
1570 }
1572 /**
1573 * Add a node nearest to point
1574 */
1575 void
1576 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1577 {
1578 if (!nodepath) {
1579 return;
1580 }
1582 sp_nodepath_ensure_livarot_path(nodepath);
1583 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1585 //find segment to split
1586 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1588 //don't know why but t seems to flip for lines
1589 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1590 position.t = 1.0 - position.t;
1591 }
1592 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1593 sp_nodepath_node_select(n, FALSE, TRUE);
1595 /* fixme: adjust ? */
1596 sp_nodepath_update_handles(nodepath);
1598 sp_nodepath_update_repr(nodepath, _("Add node"));
1600 sp_nodepath_update_statusbar(nodepath);
1601 }
1603 /*
1604 * Adjusts a segment so that t moves by a certain delta for dragging
1605 * converts lines to curves
1606 *
1607 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1608 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1609 */
1610 void
1611 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1612 {
1613 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1615 //fixme: e and e->p can be NULL, so check for those before proceeding
1616 g_return_if_fail(e != NULL);
1617 g_return_if_fail(&e->p != NULL);
1619 /* feel good is an arbitrary parameter that distributes the delta between handles
1620 * if t of the drag point is less than 1/6 distance form the endpoint only
1621 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1622 */
1623 double feel_good;
1624 if (t <= 1.0 / 6.0)
1625 feel_good = 0;
1626 else if (t <= 0.5)
1627 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1628 else if (t <= 5.0 / 6.0)
1629 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1630 else
1631 feel_good = 1;
1633 //if we're dragging a line convert it to a curve
1634 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1635 sp_nodepath_set_line_type(e, NR_CURVETO);
1636 }
1638 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1639 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1640 e->p.other->n.pos += offsetcoord0;
1641 e->p.pos += offsetcoord1;
1643 // adjust handles of adjacent nodes where necessary
1644 sp_node_adjust_handle(e,1);
1645 sp_node_adjust_handle(e->p.other,-1);
1647 sp_nodepath_update_handles(e->subpath->nodepath);
1649 update_object(e->subpath->nodepath);
1651 sp_nodepath_update_statusbar(e->subpath->nodepath);
1652 }
1655 /**
1656 * Call sp_nodepath_break() for all selected segments.
1657 */
1658 void sp_node_selected_break()
1659 {
1660 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1661 if (!nodepath) return;
1663 GList *temp = NULL;
1664 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1665 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1666 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1667 if (nn == NULL) continue; // no break, no new node
1668 temp = g_list_prepend(temp, nn);
1669 }
1671 if (temp) {
1672 sp_nodepath_deselect(nodepath);
1673 }
1674 for (GList *l = temp; l != NULL; l = l->next) {
1675 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1676 }
1678 sp_nodepath_update_handles(nodepath);
1680 sp_nodepath_update_repr(nodepath, _("Break path"));
1681 }
1683 /**
1684 * Duplicate the selected node(s).
1685 */
1686 void sp_node_selected_duplicate()
1687 {
1688 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1689 if (!nodepath) {
1690 return;
1691 }
1693 GList *temp = NULL;
1694 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1695 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1696 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1697 if (nn == NULL) continue; // could not duplicate
1698 temp = g_list_prepend(temp, nn);
1699 }
1701 if (temp) {
1702 sp_nodepath_deselect(nodepath);
1703 }
1704 for (GList *l = temp; l != NULL; l = l->next) {
1705 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1706 }
1708 sp_nodepath_update_handles(nodepath);
1710 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1711 }
1713 /**
1714 * Join two nodes by merging them into one.
1715 */
1716 void sp_node_selected_join()
1717 {
1718 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1719 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1721 if (g_list_length(nodepath->selected) != 2) {
1722 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1723 return;
1724 }
1726 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1727 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1729 g_assert(a != b);
1730 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1731 // someone tried to join an orphan node (i.e. a single-node subpath).
1732 // this is not worth an error message, just fail silently.
1733 return;
1734 }
1736 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1737 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1738 return;
1739 }
1741 /* a and b are endpoints */
1743 NR::Point c;
1744 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1745 c = a->pos;
1746 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1747 c = b->pos;
1748 } else {
1749 c = (a->pos + b->pos) / 2;
1750 }
1752 if (a->subpath == b->subpath) {
1753 Inkscape::NodePath::SubPath *sp = a->subpath;
1754 sp_nodepath_subpath_close(sp);
1755 sp_node_moveto (sp->first, c);
1757 sp_nodepath_update_handles(sp->nodepath);
1758 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1759 return;
1760 }
1762 /* a and b are separate subpaths */
1763 Inkscape::NodePath::SubPath *sa = a->subpath;
1764 Inkscape::NodePath::SubPath *sb = b->subpath;
1765 NR::Point p;
1766 Inkscape::NodePath::Node *n;
1767 NRPathcode code;
1768 if (a == sa->first) {
1769 p = sa->first->n.pos;
1770 code = (NRPathcode)sa->first->n.other->code;
1771 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1772 n = sa->last;
1773 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1774 n = n->p.other;
1775 while (n) {
1776 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1777 n = n->p.other;
1778 if (n == sa->first) n = NULL;
1779 }
1780 sp_nodepath_subpath_destroy(sa);
1781 sa = t;
1782 } else if (a == sa->last) {
1783 p = sa->last->p.pos;
1784 code = (NRPathcode)sa->last->code;
1785 sp_nodepath_node_destroy(sa->last);
1786 } else {
1787 code = NR_END;
1788 g_assert_not_reached();
1789 }
1791 if (b == sb->first) {
1792 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1793 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1794 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1795 }
1796 } else if (b == sb->last) {
1797 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1798 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1799 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1800 }
1801 } else {
1802 g_assert_not_reached();
1803 }
1804 /* and now destroy sb */
1806 sp_nodepath_subpath_destroy(sb);
1808 sp_nodepath_update_handles(sa->nodepath);
1810 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1812 sp_nodepath_update_statusbar(nodepath);
1813 }
1815 /**
1816 * Join two nodes by adding a segment between them.
1817 */
1818 void sp_node_selected_join_segment()
1819 {
1820 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1821 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1823 if (g_list_length(nodepath->selected) != 2) {
1824 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1825 return;
1826 }
1828 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1829 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1831 g_assert(a != b);
1832 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1833 // someone tried to join an orphan node (i.e. a single-node subpath).
1834 // this is not worth an error message, just fail silently.
1835 return;
1836 }
1838 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1839 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1840 return;
1841 }
1843 if (a->subpath == b->subpath) {
1844 Inkscape::NodePath::SubPath *sp = a->subpath;
1846 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1847 sp->closed = TRUE;
1849 sp->first->p.other = sp->last;
1850 sp->last->n.other = sp->first;
1852 sp_node_handle_mirror_p_to_n(sp->last);
1853 sp_node_handle_mirror_n_to_p(sp->first);
1855 sp->first->code = sp->last->code;
1856 sp->first = sp->last;
1858 sp_nodepath_update_handles(sp->nodepath);
1860 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1862 return;
1863 }
1865 /* a and b are separate subpaths */
1866 Inkscape::NodePath::SubPath *sa = a->subpath;
1867 Inkscape::NodePath::SubPath *sb = b->subpath;
1869 Inkscape::NodePath::Node *n;
1870 NR::Point p;
1871 NRPathcode code;
1872 if (a == sa->first) {
1873 code = (NRPathcode) sa->first->n.other->code;
1874 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1875 n = sa->last;
1876 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1877 for (n = n->p.other; n != NULL; n = n->p.other) {
1878 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1879 }
1880 sp_nodepath_subpath_destroy(sa);
1881 sa = t;
1882 } else if (a == sa->last) {
1883 code = (NRPathcode)sa->last->code;
1884 } else {
1885 code = NR_END;
1886 g_assert_not_reached();
1887 }
1889 if (b == sb->first) {
1890 n = sb->first;
1891 sp_node_handle_mirror_p_to_n(sa->last);
1892 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1893 sp_node_handle_mirror_n_to_p(sa->last);
1894 for (n = n->n.other; n != NULL; n = n->n.other) {
1895 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1896 }
1897 } else if (b == sb->last) {
1898 n = sb->last;
1899 sp_node_handle_mirror_p_to_n(sa->last);
1900 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1901 sp_node_handle_mirror_n_to_p(sa->last);
1902 for (n = n->p.other; n != NULL; n = n->p.other) {
1903 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1904 }
1905 } else {
1906 g_assert_not_reached();
1907 }
1908 /* and now destroy sb */
1910 sp_nodepath_subpath_destroy(sb);
1912 sp_nodepath_update_handles(sa->nodepath);
1914 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1915 }
1917 /**
1918 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1919 */
1920 void sp_node_delete_preserve(GList *nodes_to_delete)
1921 {
1922 GSList *nodepaths = NULL;
1924 while (nodes_to_delete) {
1925 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1926 Inkscape::NodePath::SubPath *sp = node->subpath;
1927 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1928 Inkscape::NodePath::Node *sample_cursor = NULL;
1929 Inkscape::NodePath::Node *sample_end = NULL;
1930 Inkscape::NodePath::Node *delete_cursor = node;
1931 bool just_delete = false;
1933 //find the start of this contiguous selection
1934 //move left to the first node that is not selected
1935 //or the start of the non-closed path
1936 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1937 delete_cursor = curr;
1938 }
1940 //just delete at the beginning of an open path
1941 if (!delete_cursor->p.other) {
1942 sample_cursor = delete_cursor;
1943 just_delete = true;
1944 } else {
1945 sample_cursor = delete_cursor->p.other;
1946 }
1948 //calculate points for each segment
1949 int rate = 5;
1950 float period = 1.0 / rate;
1951 std::vector<NR::Point> data;
1952 if (!just_delete) {
1953 data.push_back(sample_cursor->pos);
1954 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1955 //just delete at the end of an open path
1956 if (!sp->closed && curr == sp->last) {
1957 just_delete = true;
1958 break;
1959 }
1961 //sample points on the contiguous selected segment
1962 NR::Point *bez;
1963 bez = new NR::Point [4];
1964 bez[0] = curr->pos;
1965 bez[1] = curr->n.pos;
1966 bez[2] = curr->n.other->p.pos;
1967 bez[3] = curr->n.other->pos;
1968 for (int i=1; i<rate; i++) {
1969 gdouble t = i * period;
1970 NR::Point p = bezier_pt(3, bez, t);
1971 data.push_back(p);
1972 }
1973 data.push_back(curr->n.other->pos);
1975 sample_end = curr->n.other;
1976 //break if we've come full circle or hit the end of the selection
1977 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1978 break;
1979 }
1980 }
1981 }
1983 if (!just_delete) {
1984 //calculate the best fitting single segment and adjust the endpoints
1985 NR::Point *adata;
1986 adata = new NR::Point [data.size()];
1987 copy(data.begin(), data.end(), adata);
1989 NR::Point *bez;
1990 bez = new NR::Point [4];
1991 //would decreasing error create a better fitting approximation?
1992 gdouble error = 1.0;
1993 gint ret;
1994 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1996 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1997 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1998 //the resulting nodes behave as expected.
1999 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2000 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2002 //adjust endpoints
2003 sample_cursor->n.pos = bez[1];
2004 sample_end->p.pos = bez[2];
2005 }
2007 //destroy this contiguous selection
2008 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2009 Inkscape::NodePath::Node *temp = delete_cursor;
2010 if (delete_cursor->n.other == delete_cursor) {
2011 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2012 delete_cursor = NULL;
2013 } else {
2014 delete_cursor = delete_cursor->n.other;
2015 }
2016 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2017 sp_nodepath_node_destroy(temp);
2018 }
2020 sp_nodepath_update_handles(nodepath);
2022 if (!g_slist_find(nodepaths, nodepath))
2023 nodepaths = g_slist_prepend (nodepaths, nodepath);
2024 }
2026 for (GSList *i = nodepaths; i; i = i->next) {
2027 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2028 // different nodepaths will give us one undo event per nodepath
2029 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2031 // if the entire nodepath is removed, delete the selected object.
2032 if (nodepath->subpaths == NULL ||
2033 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2034 //at least 2
2035 sp_nodepath_get_node_count(nodepath) < 2) {
2036 SPDocument *document = sp_desktop_document (nodepath->desktop);
2037 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2038 //delete this nodepath's object, not the entire selection! (though at this time, this
2039 //does not matter)
2040 sp_selection_delete();
2041 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2042 _("Delete nodes"));
2043 } else {
2044 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2045 sp_nodepath_update_statusbar(nodepath);
2046 }
2047 }
2049 g_slist_free (nodepaths);
2050 }
2052 /**
2053 * Delete one or more selected nodes.
2054 */
2055 void sp_node_selected_delete()
2056 {
2057 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2058 if (!nodepath) return;
2059 if (!nodepath->selected) return;
2061 /** \todo fixme: do it the right way */
2062 while (nodepath->selected) {
2063 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2064 sp_nodepath_node_destroy(node);
2065 }
2068 //clean up the nodepath (such as for trivial subpaths)
2069 sp_nodepath_cleanup(nodepath);
2071 sp_nodepath_update_handles(nodepath);
2073 // if the entire nodepath is removed, delete the selected object.
2074 if (nodepath->subpaths == NULL ||
2075 sp_nodepath_get_node_count(nodepath) < 2) {
2076 SPDocument *document = sp_desktop_document (nodepath->desktop);
2077 sp_selection_delete();
2078 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2079 _("Delete nodes"));
2080 return;
2081 }
2083 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2085 sp_nodepath_update_statusbar(nodepath);
2086 }
2088 /**
2089 * Delete one or more segments between two selected nodes.
2090 * This is the code for 'split'.
2091 */
2092 void
2093 sp_node_selected_delete_segment(void)
2094 {
2095 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2096 Inkscape::NodePath::Node *curr, *next; //Iterators
2098 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2099 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2101 if (g_list_length(nodepath->selected) != 2) {
2102 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2103 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2104 return;
2105 }
2107 //Selected nodes, not inclusive
2108 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2109 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2111 if ( ( a==b) || //same node
2112 (a->subpath != b->subpath ) || //not the same path
2113 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2114 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2115 {
2116 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2117 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2118 return;
2119 }
2121 //###########################################
2122 //# BEGIN EDITS
2123 //###########################################
2124 //##################################
2125 //# CLOSED PATH
2126 //##################################
2127 if (a->subpath->closed) {
2130 gboolean reversed = FALSE;
2132 //Since we can go in a circle, we need to find the shorter distance.
2133 // a->b or b->a
2134 start = end = NULL;
2135 int distance = 0;
2136 int minDistance = 0;
2137 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2138 if (curr==b) {
2139 //printf("a to b:%d\n", distance);
2140 start = a;//go from a to b
2141 end = b;
2142 minDistance = distance;
2143 //printf("A to B :\n");
2144 break;
2145 }
2146 distance++;
2147 }
2149 //try again, the other direction
2150 distance = 0;
2151 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2152 if (curr==a) {
2153 //printf("b to a:%d\n", distance);
2154 if (distance < minDistance) {
2155 start = b; //we go from b to a
2156 end = a;
2157 reversed = TRUE;
2158 //printf("B to A\n");
2159 }
2160 break;
2161 }
2162 distance++;
2163 }
2166 //Copy everything from 'end' to 'start' to a new subpath
2167 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2168 for (curr=end ; curr ; curr=curr->n.other) {
2169 NRPathcode code = (NRPathcode) curr->code;
2170 if (curr == end)
2171 code = NR_MOVETO;
2172 sp_nodepath_node_new(t, NULL,
2173 (Inkscape::NodePath::NodeType)curr->type, code,
2174 &curr->p.pos, &curr->pos, &curr->n.pos);
2175 if (curr == start)
2176 break;
2177 }
2178 sp_nodepath_subpath_destroy(a->subpath);
2181 }
2185 //##################################
2186 //# OPEN PATH
2187 //##################################
2188 else {
2190 //We need to get the direction of the list between A and B
2191 //Can we walk from a to b?
2192 start = end = NULL;
2193 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2194 if (curr==b) {
2195 start = a; //did it! we go from a to b
2196 end = b;
2197 //printf("A to B\n");
2198 break;
2199 }
2200 }
2201 if (!start) {//didn't work? let's try the other direction
2202 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2203 if (curr==a) {
2204 start = b; //did it! we go from b to a
2205 end = a;
2206 //printf("B to A\n");
2207 break;
2208 }
2209 }
2210 }
2211 if (!start) {
2212 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2213 _("Cannot find path between nodes."));
2214 return;
2215 }
2219 //Copy everything after 'end' to a new subpath
2220 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2221 for (curr=end ; curr ; curr=curr->n.other) {
2222 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2223 &curr->p.pos, &curr->pos, &curr->n.pos);
2224 }
2226 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2227 for (curr = start->n.other ; curr ; curr=next) {
2228 next = curr->n.other;
2229 sp_nodepath_node_destroy(curr);
2230 }
2232 }
2233 //###########################################
2234 //# END EDITS
2235 //###########################################
2237 //clean up the nodepath (such as for trivial subpaths)
2238 sp_nodepath_cleanup(nodepath);
2240 sp_nodepath_update_handles(nodepath);
2242 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2244 sp_nodepath_update_statusbar(nodepath);
2245 }
2247 /**
2248 * Call sp_nodepath_set_line() for all selected segments.
2249 */
2250 void
2251 sp_node_selected_set_line_type(NRPathcode code)
2252 {
2253 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2254 if (nodepath == NULL) return;
2256 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2257 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2258 g_assert(n->selected);
2259 if (n->p.other && n->p.other->selected) {
2260 sp_nodepath_set_line_type(n, code);
2261 }
2262 }
2264 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2265 }
2267 /**
2268 * Call sp_nodepath_convert_node_type() for all selected nodes.
2269 */
2270 void
2271 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2272 {
2273 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2274 if (nodepath == NULL) return;
2276 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2277 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2278 }
2280 sp_nodepath_update_repr(nodepath, _("Change node type"));
2281 }
2283 /**
2284 * Change select status of node, update its own and neighbour handles.
2285 */
2286 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2287 {
2288 node->selected = selected;
2290 if (selected) {
2291 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2292 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2293 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2294 sp_knot_update_ctrl(node->knot);
2295 } else {
2296 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2297 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2298 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2299 sp_knot_update_ctrl(node->knot);
2300 }
2302 sp_node_update_handles(node);
2303 if (node->n.other) sp_node_update_handles(node->n.other);
2304 if (node->p.other) sp_node_update_handles(node->p.other);
2305 }
2307 /**
2308 \brief Select a node
2309 \param node The node to select
2310 \param incremental If true, add to selection, otherwise deselect others
2311 \param override If true, always select this node, otherwise toggle selected status
2312 */
2313 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2314 {
2315 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2317 if (incremental) {
2318 if (override) {
2319 if (!g_list_find(nodepath->selected, node)) {
2320 nodepath->selected = g_list_prepend(nodepath->selected, node);
2321 }
2322 sp_node_set_selected(node, TRUE);
2323 } else { // toggle
2324 if (node->selected) {
2325 g_assert(g_list_find(nodepath->selected, node));
2326 nodepath->selected = g_list_remove(nodepath->selected, node);
2327 } else {
2328 g_assert(!g_list_find(nodepath->selected, node));
2329 nodepath->selected = g_list_prepend(nodepath->selected, node);
2330 }
2331 sp_node_set_selected(node, !node->selected);
2332 }
2333 } else {
2334 sp_nodepath_deselect(nodepath);
2335 nodepath->selected = g_list_prepend(nodepath->selected, node);
2336 sp_node_set_selected(node, TRUE);
2337 }
2339 sp_nodepath_update_statusbar(nodepath);
2340 }
2343 /**
2344 \brief Deselect all nodes in the nodepath
2345 */
2346 void
2347 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2348 {
2349 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2351 while (nodepath->selected) {
2352 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2353 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2354 }
2355 sp_nodepath_update_statusbar(nodepath);
2356 }
2358 /**
2359 \brief Select or invert selection of all nodes in the nodepath
2360 */
2361 void
2362 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2363 {
2364 if (!nodepath) return;
2366 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2367 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2368 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2369 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2370 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2371 }
2372 }
2373 }
2375 /**
2376 * If nothing selected, does the same as sp_nodepath_select_all();
2377 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2378 * (i.e., similar to "select all in layer", with the "selected" subpaths
2379 * being treated as "layers" in the path).
2380 */
2381 void
2382 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2383 {
2384 if (!nodepath) return;
2386 if (g_list_length (nodepath->selected) == 0) {
2387 sp_nodepath_select_all (nodepath, invert);
2388 return;
2389 }
2391 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2392 GSList *subpaths = NULL;
2394 for (GList *l = copy; l != NULL; l = l->next) {
2395 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2396 Inkscape::NodePath::SubPath *subpath = n->subpath;
2397 if (!g_slist_find (subpaths, subpath))
2398 subpaths = g_slist_prepend (subpaths, subpath);
2399 }
2401 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2402 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2403 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2404 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2405 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2406 }
2407 }
2409 g_slist_free (subpaths);
2410 g_list_free (copy);
2411 }
2413 /**
2414 * \brief Select the node after the last selected; if none is selected,
2415 * select the first within path.
2416 */
2417 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2418 {
2419 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2421 Inkscape::NodePath::Node *last = NULL;
2422 if (nodepath->selected) {
2423 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2424 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2425 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2426 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2427 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2428 if (node->selected) {
2429 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2430 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2431 if (spl->next) { // there's a next subpath
2432 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2433 last = subpath_next->first;
2434 } else if (spl->prev) { // there's a previous subpath
2435 last = NULL; // to be set later to the first node of first subpath
2436 } else {
2437 last = node->n.other;
2438 }
2439 } else {
2440 last = node->n.other;
2441 }
2442 } else {
2443 if (node->n.other) {
2444 last = node->n.other;
2445 } else {
2446 if (spl->next) { // there's a next subpath
2447 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2448 last = subpath_next->first;
2449 } else if (spl->prev) { // there's a previous subpath
2450 last = NULL; // to be set later to the first node of first subpath
2451 } else {
2452 last = (Inkscape::NodePath::Node *) subpath->first;
2453 }
2454 }
2455 }
2456 }
2457 }
2458 }
2459 sp_nodepath_deselect(nodepath);
2460 }
2462 if (last) { // there's at least one more node after selected
2463 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2464 } else { // no more nodes, select the first one in first subpath
2465 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2466 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2467 }
2468 }
2470 /**
2471 * \brief Select the node before the first selected; if none is selected,
2472 * select the last within path
2473 */
2474 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2475 {
2476 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2478 Inkscape::NodePath::Node *last = NULL;
2479 if (nodepath->selected) {
2480 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2481 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2482 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2483 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2484 if (node->selected) {
2485 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2486 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2487 if (spl->prev) { // there's a prev subpath
2488 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2489 last = subpath_prev->last;
2490 } else if (spl->next) { // there's a next subpath
2491 last = NULL; // to be set later to the last node of last subpath
2492 } else {
2493 last = node->p.other;
2494 }
2495 } else {
2496 last = node->p.other;
2497 }
2498 } else {
2499 if (node->p.other) {
2500 last = node->p.other;
2501 } else {
2502 if (spl->prev) { // there's a prev subpath
2503 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2504 last = subpath_prev->last;
2505 } else if (spl->next) { // there's a next subpath
2506 last = NULL; // to be set later to the last node of last subpath
2507 } else {
2508 last = (Inkscape::NodePath::Node *) subpath->last;
2509 }
2510 }
2511 }
2512 }
2513 }
2514 }
2515 sp_nodepath_deselect(nodepath);
2516 }
2518 if (last) { // there's at least one more node before selected
2519 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2520 } else { // no more nodes, select the last one in last subpath
2521 GList *spl = g_list_last(nodepath->subpaths);
2522 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2523 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2524 }
2525 }
2527 /**
2528 * \brief Select all nodes that are within the rectangle.
2529 */
2530 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2531 {
2532 if (!incremental) {
2533 sp_nodepath_deselect(nodepath);
2534 }
2536 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2537 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2538 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2539 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2541 if (b.contains(node->pos)) {
2542 sp_nodepath_node_select(node, TRUE, TRUE);
2543 }
2544 }
2545 }
2546 }
2549 void
2550 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2551 {
2552 g_assert (n);
2553 g_assert (nodepath);
2554 g_assert (n->subpath->nodepath == nodepath);
2556 if (g_list_length (nodepath->selected) == 0) {
2557 if (grow > 0) {
2558 sp_nodepath_node_select(n, TRUE, TRUE);
2559 }
2560 return;
2561 }
2563 if (g_list_length (nodepath->selected) == 1) {
2564 if (grow < 0) {
2565 sp_nodepath_deselect (nodepath);
2566 return;
2567 }
2568 }
2570 double n_sel_range = 0, p_sel_range = 0;
2571 Inkscape::NodePath::Node *farthest_n_node = n;
2572 Inkscape::NodePath::Node *farthest_p_node = n;
2574 // Calculate ranges
2575 {
2576 double n_range = 0, p_range = 0;
2577 bool n_going = true, p_going = true;
2578 Inkscape::NodePath::Node *n_node = n;
2579 Inkscape::NodePath::Node *p_node = n;
2580 do {
2581 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2582 if (n_node && n_going)
2583 n_node = n_node->n.other;
2584 if (n_node == NULL) {
2585 n_going = false;
2586 } else {
2587 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2588 if (n_node->selected) {
2589 n_sel_range = n_range;
2590 farthest_n_node = n_node;
2591 }
2592 if (n_node == p_node) {
2593 n_going = false;
2594 p_going = false;
2595 }
2596 }
2597 if (p_node && p_going)
2598 p_node = p_node->p.other;
2599 if (p_node == NULL) {
2600 p_going = false;
2601 } else {
2602 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2603 if (p_node->selected) {
2604 p_sel_range = p_range;
2605 farthest_p_node = p_node;
2606 }
2607 if (p_node == n_node) {
2608 n_going = false;
2609 p_going = false;
2610 }
2611 }
2612 } while (n_going || p_going);
2613 }
2615 if (grow > 0) {
2616 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2617 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2618 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2619 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2620 }
2621 } else {
2622 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2623 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2624 } else if (farthest_p_node && farthest_p_node->selected) {
2625 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2626 }
2627 }
2628 }
2630 void
2631 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2632 {
2633 g_assert (n);
2634 g_assert (nodepath);
2635 g_assert (n->subpath->nodepath == nodepath);
2637 if (g_list_length (nodepath->selected) == 0) {
2638 if (grow > 0) {
2639 sp_nodepath_node_select(n, TRUE, TRUE);
2640 }
2641 return;
2642 }
2644 if (g_list_length (nodepath->selected) == 1) {
2645 if (grow < 0) {
2646 sp_nodepath_deselect (nodepath);
2647 return;
2648 }
2649 }
2651 Inkscape::NodePath::Node *farthest_selected = NULL;
2652 double farthest_dist = 0;
2654 Inkscape::NodePath::Node *closest_unselected = NULL;
2655 double closest_dist = NR_HUGE;
2657 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2658 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2659 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2660 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2661 if (node == n)
2662 continue;
2663 if (node->selected) {
2664 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2665 farthest_dist = NR::L2(node->pos - n->pos);
2666 farthest_selected = node;
2667 }
2668 } else {
2669 if (NR::L2(node->pos - n->pos) < closest_dist) {
2670 closest_dist = NR::L2(node->pos - n->pos);
2671 closest_unselected = node;
2672 }
2673 }
2674 }
2675 }
2677 if (grow > 0) {
2678 if (closest_unselected) {
2679 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2680 }
2681 } else {
2682 if (farthest_selected) {
2683 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2684 }
2685 }
2686 }
2689 /**
2690 \brief Saves all nodes' and handles' current positions in their origin members
2691 */
2692 void
2693 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2694 {
2695 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2696 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2697 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2698 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2699 n->origin = n->pos;
2700 n->p.origin = n->p.pos;
2701 n->n.origin = n->n.pos;
2702 }
2703 }
2704 }
2706 /**
2707 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2708 */
2709 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2710 {
2711 if (!nodepath->selected) {
2712 return NULL;
2713 }
2715 GList *r = NULL;
2716 guint i = 0;
2717 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2718 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2719 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2720 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2721 i++;
2722 if (node->selected) {
2723 r = g_list_append(r, GINT_TO_POINTER(i));
2724 }
2725 }
2726 }
2727 return r;
2728 }
2730 /**
2731 \brief Restores selection by selecting nodes whose positions are in the list
2732 */
2733 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2734 {
2735 sp_nodepath_deselect(nodepath);
2737 guint i = 0;
2738 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2739 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2740 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2741 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2742 i++;
2743 if (g_list_find(r, GINT_TO_POINTER(i))) {
2744 sp_nodepath_node_select(node, TRUE, TRUE);
2745 }
2746 }
2747 }
2749 }
2751 /**
2752 \brief Adjusts handle according to node type and line code.
2753 */
2754 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2755 {
2756 double len, otherlen, linelen;
2758 g_assert(node);
2760 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2761 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2763 /** \todo fixme: */
2764 if (me->other == NULL) return;
2765 if (other->other == NULL) return;
2767 /* I have line */
2769 NRPathcode mecode, ocode;
2770 if (which_adjust == 1) {
2771 mecode = (NRPathcode)me->other->code;
2772 ocode = (NRPathcode)node->code;
2773 } else {
2774 mecode = (NRPathcode)node->code;
2775 ocode = (NRPathcode)other->other->code;
2776 }
2778 if (mecode == NR_LINETO) return;
2780 /* I am curve */
2782 if (other->other == NULL) return;
2784 /* Other has line */
2786 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2788 NR::Point delta;
2789 if (ocode == NR_LINETO) {
2790 /* other is lineto, we are either smooth or symm */
2791 Inkscape::NodePath::Node *othernode = other->other;
2792 len = NR::L2(me->pos - node->pos);
2793 delta = node->pos - othernode->pos;
2794 linelen = NR::L2(delta);
2795 if (linelen < 1e-18)
2796 return;
2797 me->pos = node->pos + (len / linelen)*delta;
2798 return;
2799 }
2801 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2803 me->pos = 2 * node->pos - other->pos;
2804 return;
2805 }
2807 /* We are smooth */
2809 len = NR::L2(me->pos - node->pos);
2810 delta = other->pos - node->pos;
2811 otherlen = NR::L2(delta);
2812 if (otherlen < 1e-18) return;
2814 me->pos = node->pos - (len / otherlen) * delta;
2815 }
2817 /**
2818 \brief Adjusts both handles according to node type and line code
2819 */
2820 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2821 {
2822 g_assert(node);
2824 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2826 /* we are either smooth or symm */
2828 if (node->p.other == NULL) return;
2830 if (node->n.other == NULL) return;
2832 if (node->code == NR_LINETO) {
2833 if (node->n.other->code == NR_LINETO) return;
2834 sp_node_adjust_handle(node, 1);
2835 return;
2836 }
2838 if (node->n.other->code == NR_LINETO) {
2839 if (node->code == NR_LINETO) return;
2840 sp_node_adjust_handle(node, -1);
2841 return;
2842 }
2844 /* both are curves */
2845 NR::Point const delta( node->n.pos - node->p.pos );
2847 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2848 node->p.pos = node->pos - delta / 2;
2849 node->n.pos = node->pos + delta / 2;
2850 return;
2851 }
2853 /* We are smooth */
2854 double plen = NR::L2(node->p.pos - node->pos);
2855 if (plen < 1e-18) return;
2856 double nlen = NR::L2(node->n.pos - node->pos);
2857 if (nlen < 1e-18) return;
2858 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2859 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2860 }
2862 /**
2863 * Node event callback.
2864 */
2865 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2866 {
2867 gboolean ret = FALSE;
2868 switch (event->type) {
2869 case GDK_ENTER_NOTIFY:
2870 active_node = n;
2871 break;
2872 case GDK_LEAVE_NOTIFY:
2873 active_node = NULL;
2874 break;
2875 case GDK_SCROLL:
2876 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2877 switch (event->scroll.direction) {
2878 case GDK_SCROLL_UP:
2879 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2880 break;
2881 case GDK_SCROLL_DOWN:
2882 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2883 break;
2884 default:
2885 break;
2886 }
2887 ret = TRUE;
2888 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2889 switch (event->scroll.direction) {
2890 case GDK_SCROLL_UP:
2891 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2892 break;
2893 case GDK_SCROLL_DOWN:
2894 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2895 break;
2896 default:
2897 break;
2898 }
2899 ret = TRUE;
2900 }
2901 break;
2902 case GDK_KEY_PRESS:
2903 switch (get_group0_keyval (&event->key)) {
2904 case GDK_space:
2905 if (event->key.state & GDK_BUTTON1_MASK) {
2906 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2907 stamp_repr(nodepath);
2908 ret = TRUE;
2909 }
2910 break;
2911 case GDK_Page_Up:
2912 if (event->key.state & GDK_CONTROL_MASK) {
2913 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2914 } else {
2915 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2916 }
2917 break;
2918 case GDK_Page_Down:
2919 if (event->key.state & GDK_CONTROL_MASK) {
2920 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2921 } else {
2922 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2923 }
2924 break;
2925 default:
2926 break;
2927 }
2928 break;
2929 default:
2930 break;
2931 }
2933 return ret;
2934 }
2936 /**
2937 * Handle keypress on node; directly called.
2938 */
2939 gboolean node_key(GdkEvent *event)
2940 {
2941 Inkscape::NodePath::Path *np;
2943 // there is no way to verify nodes so set active_node to nil when deleting!!
2944 if (active_node == NULL) return FALSE;
2946 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2947 gint ret = FALSE;
2948 switch (get_group0_keyval (&event->key)) {
2949 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2950 case GDK_BackSpace:
2951 np = active_node->subpath->nodepath;
2952 sp_nodepath_node_destroy(active_node);
2953 sp_nodepath_update_repr(np, _("Delete node"));
2954 active_node = NULL;
2955 ret = TRUE;
2956 break;
2957 case GDK_c:
2958 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2959 ret = TRUE;
2960 break;
2961 case GDK_s:
2962 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2963 ret = TRUE;
2964 break;
2965 case GDK_y:
2966 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2967 ret = TRUE;
2968 break;
2969 case GDK_b:
2970 sp_nodepath_node_break(active_node);
2971 ret = TRUE;
2972 break;
2973 }
2974 return ret;
2975 }
2976 return FALSE;
2977 }
2979 /**
2980 * Mouseclick on node callback.
2981 */
2982 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2983 {
2984 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2986 if (state & GDK_CONTROL_MASK) {
2987 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2989 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2990 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2991 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2992 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2993 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2994 } else {
2995 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2996 }
2997 sp_nodepath_update_repr(nodepath, _("Change node type"));
2998 sp_nodepath_update_statusbar(nodepath);
3000 } else { //ctrl+alt+click: delete node
3001 GList *node_to_delete = NULL;
3002 node_to_delete = g_list_append(node_to_delete, n);
3003 sp_node_delete_preserve(node_to_delete);
3004 }
3006 } else {
3007 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3008 }
3009 }
3011 /**
3012 * Mouse grabbed node callback.
3013 */
3014 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3015 {
3016 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3018 if (!n->selected) {
3019 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3020 }
3022 n->is_dragging = true;
3023 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3025 sp_nodepath_remember_origins (n->subpath->nodepath);
3026 }
3028 /**
3029 * Mouse ungrabbed node callback.
3030 */
3031 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3032 {
3033 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3035 n->dragging_out = NULL;
3036 n->is_dragging = false;
3037 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3039 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3040 }
3042 /**
3043 * The point on a line, given by its angle, closest to the given point.
3044 * \param p A point.
3045 * \param a Angle of the line; it is assumed to go through coordinate origin.
3046 * \param closest Pointer to the point struct where the result is stored.
3047 * \todo FIXME: use dot product perhaps?
3048 */
3049 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3050 {
3051 if (a == HUGE_VAL) { // vertical
3052 *closest = NR::Point(0, (*p)[NR::Y]);
3053 } else {
3054 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3055 (*closest)[NR::Y] = a * (*closest)[NR::X];
3056 }
3057 }
3059 /**
3060 * Distance from the point to a line given by its angle.
3061 * \param p A point.
3062 * \param a Angle of the line; it is assumed to go through coordinate origin.
3063 */
3064 static double point_line_distance(NR::Point *p, double a)
3065 {
3066 NR::Point c;
3067 point_line_closest(p, a, &c);
3068 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]));
3069 }
3071 /**
3072 * Callback for node "request" signal.
3073 * \todo fixme: This goes to "moved" event? (lauris)
3074 */
3075 static gboolean
3076 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3077 {
3078 double yn, xn, yp, xp;
3079 double an, ap, na, pa;
3080 double d_an, d_ap, d_na, d_pa;
3081 gboolean collinear = FALSE;
3082 NR::Point c;
3083 NR::Point pr;
3085 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3087 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3088 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3090 NR::Point mouse = (*p);
3092 if (!n->dragging_out) {
3093 // This is the first drag-out event; find out which handle to drag out
3094 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3095 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3097 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3098 return FALSE;
3100 Inkscape::NodePath::NodeSide *opposite;
3101 if (appr_p > appr_n) { // closer to p
3102 n->dragging_out = &n->p;
3103 opposite = &n->n;
3104 n->code = NR_CURVETO;
3105 } else if (appr_p < appr_n) { // closer to n
3106 n->dragging_out = &n->n;
3107 opposite = &n->p;
3108 n->n.other->code = NR_CURVETO;
3109 } else { // p and n nodes are the same
3110 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3111 n->dragging_out = &n->p;
3112 opposite = &n->n;
3113 n->code = NR_CURVETO;
3114 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3115 n->dragging_out = &n->n;
3116 opposite = &n->p;
3117 n->n.other->code = NR_CURVETO;
3118 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3119 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);
3120 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);
3121 if (appr_other_p > appr_other_n) { // closer to other's p handle
3122 n->dragging_out = &n->n;
3123 opposite = &n->p;
3124 n->n.other->code = NR_CURVETO;
3125 } else { // closer to other's n handle
3126 n->dragging_out = &n->p;
3127 opposite = &n->n;
3128 n->code = NR_CURVETO;
3129 }
3130 }
3131 }
3133 // if there's another handle, make sure the one we drag out starts parallel to it
3134 if (opposite->pos != n->pos) {
3135 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3136 }
3138 // knots might not be created yet!
3139 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3140 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3141 }
3143 // pass this on to the handle-moved callback
3144 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3145 sp_node_update_handles(n);
3146 return TRUE;
3147 }
3149 if (state & GDK_CONTROL_MASK) { // constrained motion
3151 // calculate relative distances of handles
3152 // n handle:
3153 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3154 xn = n->n.pos[NR::X] - n->pos[NR::X];
3155 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3156 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3157 if (n->n.other) { // if there is the next point
3158 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3159 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3160 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3161 }
3162 }
3163 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3164 if (yn < 0) { xn = -xn; yn = -yn; }
3166 // p handle:
3167 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3168 xp = n->p.pos[NR::X] - n->pos[NR::X];
3169 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3170 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3171 if (n->p.other) {
3172 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3173 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3174 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3175 }
3176 }
3177 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3178 if (yp < 0) { xp = -xp; yp = -yp; }
3180 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3181 // sliding on handles, only if at least one of the handles is non-vertical
3182 // (otherwise it's the same as ctrl+drag anyway)
3184 // calculate angles of the handles
3185 if (xn == 0) {
3186 if (yn == 0) { // no handle, consider it the continuation of the other one
3187 an = 0;
3188 collinear = TRUE;
3189 }
3190 else an = 0; // vertical; set the angle to horizontal
3191 } else an = yn/xn;
3193 if (xp == 0) {
3194 if (yp == 0) { // no handle, consider it the continuation of the other one
3195 ap = an;
3196 }
3197 else ap = 0; // vertical; set the angle to horizontal
3198 } else ap = yp/xp;
3200 if (collinear) an = ap;
3202 // angles of the perpendiculars; HUGE_VAL means vertical
3203 if (an == 0) na = HUGE_VAL; else na = -1/an;
3204 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3206 // mouse point relative to the node's original pos
3207 pr = (*p) - n->origin;
3209 // distances to the four lines (two handles and two perpendiculars)
3210 d_an = point_line_distance(&pr, an);
3211 d_na = point_line_distance(&pr, na);
3212 d_ap = point_line_distance(&pr, ap);
3213 d_pa = point_line_distance(&pr, pa);
3215 // find out which line is the closest, save its closest point in c
3216 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3217 point_line_closest(&pr, an, &c);
3218 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3219 point_line_closest(&pr, ap, &c);
3220 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3221 point_line_closest(&pr, na, &c);
3222 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3223 point_line_closest(&pr, pa, &c);
3224 }
3226 // move the node to the closest point
3227 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3228 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3229 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3231 } else { // constraining to hor/vert
3233 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3234 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3235 } else { // snap to vert
3236 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3237 }
3238 }
3239 } else { // move freely
3240 if (n->is_dragging) {
3241 if (state & GDK_MOD1_MASK) { // sculpt
3242 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3243 } else {
3244 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3245 (*p)[NR::X] - n->pos[NR::X],
3246 (*p)[NR::Y] - n->pos[NR::Y],
3247 (state & GDK_SHIFT_MASK) == 0);
3248 }
3249 }
3250 }
3252 n->subpath->nodepath->desktop->scroll_to_point(p);
3254 return TRUE;
3255 }
3257 /**
3258 * Node handle clicked callback.
3259 */
3260 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3261 {
3262 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3264 if (state & GDK_CONTROL_MASK) { // "delete" handle
3265 if (n->p.knot == knot) {
3266 n->p.pos = n->pos;
3267 } else if (n->n.knot == knot) {
3268 n->n.pos = n->pos;
3269 }
3270 sp_node_update_handles(n);
3271 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3272 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3273 sp_nodepath_update_statusbar(nodepath);
3275 } else { // just select or add to selection, depending in Shift
3276 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3277 }
3278 }
3280 /**
3281 * Node handle grabbed callback.
3282 */
3283 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3284 {
3285 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3287 if (!n->selected) {
3288 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3289 }
3291 // remember the origin point of the handle
3292 if (n->p.knot == knot) {
3293 n->p.origin_radial = n->p.pos - n->pos;
3294 } else if (n->n.knot == knot) {
3295 n->n.origin_radial = n->n.pos - n->pos;
3296 } else {
3297 g_assert_not_reached();
3298 }
3300 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3301 }
3303 /**
3304 * Node handle ungrabbed callback.
3305 */
3306 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3307 {
3308 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3310 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3311 if (n->p.knot == knot) {
3312 n->p.origin_radial.a = 0;
3313 sp_knot_set_position(knot, &n->p.pos, state);
3314 } else if (n->n.knot == knot) {
3315 n->n.origin_radial.a = 0;
3316 sp_knot_set_position(knot, &n->n.pos, state);
3317 } else {
3318 g_assert_not_reached();
3319 }
3321 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3322 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3323 }
3325 /**
3326 * Node handle "request" signal callback.
3327 */
3328 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3329 {
3330 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3332 Inkscape::NodePath::NodeSide *me, *opposite;
3333 gint which;
3334 if (n->p.knot == knot) {
3335 me = &n->p;
3336 opposite = &n->n;
3337 which = -1;
3338 } else if (n->n.knot == knot) {
3339 me = &n->n;
3340 opposite = &n->p;
3341 which = 1;
3342 } else {
3343 me = opposite = NULL;
3344 which = 0;
3345 g_assert_not_reached();
3346 }
3348 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3350 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3352 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3353 /* We are smooth node adjacent with line */
3354 NR::Point const delta = *p - n->pos;
3355 NR::Coord const len = NR::L2(delta);
3356 Inkscape::NodePath::Node *othernode = opposite->other;
3357 NR::Point const ndelta = n->pos - othernode->pos;
3358 NR::Coord const linelen = NR::L2(ndelta);
3359 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3360 NR::Coord const scal = dot(delta, ndelta) / linelen;
3361 (*p) = n->pos + (scal / linelen) * ndelta;
3362 }
3363 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3364 } else {
3365 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3366 }
3368 sp_node_adjust_handle(n, -which);
3370 return FALSE;
3371 }
3373 /**
3374 * Node handle moved callback.
3375 */
3376 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3377 {
3378 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3380 Inkscape::NodePath::NodeSide *me;
3381 Inkscape::NodePath::NodeSide *other;
3382 if (n->p.knot == knot) {
3383 me = &n->p;
3384 other = &n->n;
3385 } else if (n->n.knot == knot) {
3386 me = &n->n;
3387 other = &n->p;
3388 } else {
3389 me = NULL;
3390 other = NULL;
3391 g_assert_not_reached();
3392 }
3394 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3395 Radial rme(me->pos - n->pos);
3396 Radial rother(other->pos - n->pos);
3397 Radial rnew(*p - n->pos);
3399 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3400 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3401 /* 0 interpreted as "no snapping". */
3403 // The closest PI/snaps angle, starting from zero.
3404 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3405 if (me->origin_radial.a == HUGE_VAL) {
3406 // ortho doesn't exist: original handle was zero length.
3407 rnew.a = a_snapped;
3408 } else {
3409 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3410 * its opposite and perpendiculars). */
3411 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3413 // Snap to the closest.
3414 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3415 ? a_snapped
3416 : a_ortho );
3417 }
3418 }
3420 if (state & GDK_MOD1_MASK) {
3421 // lock handle length
3422 rnew.r = me->origin_radial.r;
3423 }
3425 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3426 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3427 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3428 rother.a += rnew.a - rme.a;
3429 other->pos = NR::Point(rother) + n->pos;
3430 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3431 sp_knot_set_position(other->knot, &other->pos, 0);
3432 }
3434 me->pos = NR::Point(rnew) + n->pos;
3435 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3437 // this is what sp_knot_set_position does, but without emitting the signal:
3438 // we cannot emit a "moved" signal because we're now processing it
3439 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3441 knot->desktop->set_coordinate_status(me->pos);
3443 update_object(n->subpath->nodepath);
3445 /* status text */
3446 SPDesktop *desktop = n->subpath->nodepath->desktop;
3447 if (!desktop) return;
3448 SPEventContext *ec = desktop->event_context;
3449 if (!ec) return;
3450 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3451 if (!mc) return;
3453 double degrees = 180 / M_PI * rnew.a;
3454 if (degrees > 180) degrees -= 360;
3455 if (degrees < -180) degrees += 360;
3456 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3457 degrees = angle_to_compass (degrees);
3459 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3461 mc->setF(Inkscape::NORMAL_MESSAGE,
3462 _("<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);
3464 g_string_free(length, TRUE);
3465 }
3467 /**
3468 * Node handle event callback.
3469 */
3470 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3471 {
3472 gboolean ret = FALSE;
3473 switch (event->type) {
3474 case GDK_KEY_PRESS:
3475 switch (get_group0_keyval (&event->key)) {
3476 case GDK_space:
3477 if (event->key.state & GDK_BUTTON1_MASK) {
3478 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3479 stamp_repr(nodepath);
3480 ret = TRUE;
3481 }
3482 break;
3483 default:
3484 break;
3485 }
3486 break;
3487 default:
3488 break;
3489 }
3491 return ret;
3492 }
3494 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3495 Radial &rme, Radial &rother, gboolean const both)
3496 {
3497 rme.a += angle;
3498 if ( both
3499 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3500 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3501 {
3502 rother.a += angle;
3503 }
3504 }
3506 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3507 Radial &rme, Radial &rother, gboolean const both)
3508 {
3509 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3511 gdouble r;
3512 if ( both
3513 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3514 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3515 {
3516 r = MAX(rme.r, rother.r);
3517 } else {
3518 r = rme.r;
3519 }
3521 gdouble const weird_angle = atan2(norm_angle, r);
3522 /* Bulia says norm_angle is just the visible distance that the
3523 * object's end must travel on the screen. Left as 'angle' for want of
3524 * a better name.*/
3526 rme.a += weird_angle;
3527 if ( both
3528 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3529 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3530 {
3531 rother.a += weird_angle;
3532 }
3533 }
3535 /**
3536 * Rotate one node.
3537 */
3538 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3539 {
3540 Inkscape::NodePath::NodeSide *me, *other;
3541 bool both = false;
3543 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3544 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3546 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3547 me = &(n->p);
3548 other = &(n->n);
3549 } else if (!n->p.other) {
3550 me = &(n->n);
3551 other = &(n->p);
3552 } else {
3553 if (which > 0) { // right handle
3554 if (xn > xp) {
3555 me = &(n->n);
3556 other = &(n->p);
3557 } else {
3558 me = &(n->p);
3559 other = &(n->n);
3560 }
3561 } else if (which < 0){ // left handle
3562 if (xn <= xp) {
3563 me = &(n->n);
3564 other = &(n->p);
3565 } else {
3566 me = &(n->p);
3567 other = &(n->n);
3568 }
3569 } else { // both handles
3570 me = &(n->n);
3571 other = &(n->p);
3572 both = true;
3573 }
3574 }
3576 Radial rme(me->pos - n->pos);
3577 Radial rother(other->pos - n->pos);
3579 if (screen) {
3580 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3581 } else {
3582 node_rotate_one_internal (*n, angle, rme, rother, both);
3583 }
3585 me->pos = n->pos + NR::Point(rme);
3587 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3588 other->pos = n->pos + NR::Point(rother);
3589 }
3591 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3592 // so here we just move all the knots without emitting move signals, for speed
3593 sp_node_update_handles(n, false);
3594 }
3596 /**
3597 * Rotate selected nodes.
3598 */
3599 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3600 {
3601 if (!nodepath || !nodepath->selected) return;
3603 if (g_list_length(nodepath->selected) == 1) {
3604 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3605 node_rotate_one (n, angle, which, screen);
3606 } else {
3607 // rotate as an object:
3609 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3610 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3611 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3612 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3613 box.expandTo (n->pos); // contain all selected nodes
3614 }
3616 gdouble rot;
3617 if (screen) {
3618 gdouble const zoom = nodepath->desktop->current_zoom();
3619 gdouble const zmove = angle / zoom;
3620 gdouble const r = NR::L2(box.max() - box.midpoint());
3621 rot = atan2(zmove, r);
3622 } else {
3623 rot = angle;
3624 }
3626 NR::Matrix t =
3627 NR::Matrix (NR::translate(-box.midpoint())) *
3628 NR::Matrix (NR::rotate(rot)) *
3629 NR::Matrix (NR::translate(box.midpoint()));
3631 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3632 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3633 n->pos *= t;
3634 n->n.pos *= t;
3635 n->p.pos *= t;
3636 sp_node_update_handles(n, false);
3637 }
3638 }
3640 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3641 }
3643 /**
3644 * Scale one node.
3645 */
3646 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3647 {
3648 bool both = false;
3649 Inkscape::NodePath::NodeSide *me, *other;
3651 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3652 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3654 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3655 me = &(n->p);
3656 other = &(n->n);
3657 n->code = NR_CURVETO;
3658 } else if (!n->p.other) {
3659 me = &(n->n);
3660 other = &(n->p);
3661 if (n->n.other)
3662 n->n.other->code = NR_CURVETO;
3663 } else {
3664 if (which > 0) { // right handle
3665 if (xn > xp) {
3666 me = &(n->n);
3667 other = &(n->p);
3668 if (n->n.other)
3669 n->n.other->code = NR_CURVETO;
3670 } else {
3671 me = &(n->p);
3672 other = &(n->n);
3673 n->code = NR_CURVETO;
3674 }
3675 } else if (which < 0){ // left handle
3676 if (xn <= xp) {
3677 me = &(n->n);
3678 other = &(n->p);
3679 if (n->n.other)
3680 n->n.other->code = NR_CURVETO;
3681 } else {
3682 me = &(n->p);
3683 other = &(n->n);
3684 n->code = NR_CURVETO;
3685 }
3686 } else { // both handles
3687 me = &(n->n);
3688 other = &(n->p);
3689 both = true;
3690 n->code = NR_CURVETO;
3691 if (n->n.other)
3692 n->n.other->code = NR_CURVETO;
3693 }
3694 }
3696 Radial rme(me->pos - n->pos);
3697 Radial rother(other->pos - n->pos);
3699 rme.r += grow;
3700 if (rme.r < 0) rme.r = 0;
3701 if (rme.a == HUGE_VAL) {
3702 if (me->other) { // if direction is unknown, initialize it towards the next node
3703 Radial rme_next(me->other->pos - n->pos);
3704 rme.a = rme_next.a;
3705 } else { // if there's no next, initialize to 0
3706 rme.a = 0;
3707 }
3708 }
3709 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3710 rother.r += grow;
3711 if (rother.r < 0) rother.r = 0;
3712 if (rother.a == HUGE_VAL) {
3713 rother.a = rme.a + M_PI;
3714 }
3715 }
3717 me->pos = n->pos + NR::Point(rme);
3719 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3720 other->pos = n->pos + NR::Point(rother);
3721 }
3723 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3724 // so here we just move all the knots without emitting move signals, for speed
3725 sp_node_update_handles(n, false);
3726 }
3728 /**
3729 * Scale selected nodes.
3730 */
3731 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3732 {
3733 if (!nodepath || !nodepath->selected) return;
3735 if (g_list_length(nodepath->selected) == 1) {
3736 // scale handles of the single selected node
3737 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3738 node_scale_one (n, grow, which);
3739 } else {
3740 // scale nodes as an "object":
3742 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3743 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3744 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3745 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3746 box.expandTo (n->pos); // contain all selected nodes
3747 }
3749 double scale = (box.maxExtent() + grow)/box.maxExtent();
3751 NR::Matrix t =
3752 NR::Matrix (NR::translate(-box.midpoint())) *
3753 NR::Matrix (NR::scale(scale, scale)) *
3754 NR::Matrix (NR::translate(box.midpoint()));
3756 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3757 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3758 n->pos *= t;
3759 n->n.pos *= t;
3760 n->p.pos *= t;
3761 sp_node_update_handles(n, false);
3762 }
3763 }
3765 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3766 }
3768 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3769 {
3770 if (!nodepath) return;
3771 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3772 }
3774 /**
3775 * Flip selected nodes horizontally/vertically.
3776 */
3777 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3778 {
3779 if (!nodepath || !nodepath->selected) return;
3781 if (g_list_length(nodepath->selected) == 1) {
3782 // flip handles of the single selected node
3783 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3784 double temp = n->p.pos[axis];
3785 n->p.pos[axis] = n->n.pos[axis];
3786 n->n.pos[axis] = temp;
3787 sp_node_update_handles(n, false);
3788 } else {
3789 // scale nodes as an "object":
3791 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3792 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3793 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3794 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3795 box.expandTo (n->pos); // contain all selected nodes
3796 }
3798 NR::Matrix t =
3799 NR::Matrix (NR::translate(-box.midpoint())) *
3800 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3801 NR::Matrix (NR::translate(box.midpoint()));
3803 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3804 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3805 n->pos *= t;
3806 n->n.pos *= t;
3807 n->p.pos *= t;
3808 sp_node_update_handles(n, false);
3809 }
3810 }
3812 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3813 }
3815 //-----------------------------------------------
3816 /**
3817 * Return new subpath under given nodepath.
3818 */
3819 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3820 {
3821 g_assert(nodepath);
3822 g_assert(nodepath->desktop);
3824 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3826 s->nodepath = nodepath;
3827 s->closed = FALSE;
3828 s->nodes = NULL;
3829 s->first = NULL;
3830 s->last = NULL;
3832 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3833 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3834 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3836 return s;
3837 }
3839 /**
3840 * Destroy nodes in subpath, then subpath itself.
3841 */
3842 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3843 {
3844 g_assert(subpath);
3845 g_assert(subpath->nodepath);
3846 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3848 while (subpath->nodes) {
3849 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3850 }
3852 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3854 g_free(subpath);
3855 }
3857 /**
3858 * Link head to tail in subpath.
3859 */
3860 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3861 {
3862 g_assert(!sp->closed);
3863 g_assert(sp->last != sp->first);
3864 g_assert(sp->first->code == NR_MOVETO);
3866 sp->closed = TRUE;
3868 //Link the head to the tail
3869 sp->first->p.other = sp->last;
3870 sp->last->n.other = sp->first;
3871 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3872 sp->first = sp->last;
3874 //Remove the extra end node
3875 sp_nodepath_node_destroy(sp->last->n.other);
3876 }
3878 /**
3879 * Open closed (loopy) subpath at node.
3880 */
3881 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3882 {
3883 g_assert(sp->closed);
3884 g_assert(n->subpath == sp);
3885 g_assert(sp->first == sp->last);
3887 /* We create new startpoint, current node will become last one */
3889 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3890 &n->pos, &n->pos, &n->n.pos);
3893 sp->closed = FALSE;
3895 //Unlink to make a head and tail
3896 sp->first = new_path;
3897 sp->last = n;
3898 n->n.other = NULL;
3899 new_path->p.other = NULL;
3900 }
3902 /**
3903 * Returns area in triangle given by points; may be negative.
3904 */
3905 inline double
3906 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3907 {
3908 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]);
3909 }
3911 /**
3912 * Return new node in subpath with given properties.
3913 * \param pos Position of node.
3914 * \param ppos Handle position in previous direction
3915 * \param npos Handle position in previous direction
3916 */
3917 Inkscape::NodePath::Node *
3918 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)
3919 {
3920 g_assert(sp);
3921 g_assert(sp->nodepath);
3922 g_assert(sp->nodepath->desktop);
3924 if (nodechunk == NULL)
3925 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3927 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3929 n->subpath = sp;
3931 if (type != Inkscape::NodePath::NODE_NONE) {
3932 // use the type from sodipodi:nodetypes
3933 n->type = type;
3934 } else {
3935 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3936 // points are (almost) collinear
3937 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3938 // endnode, or a node with a retracted handle
3939 n->type = Inkscape::NodePath::NODE_CUSP;
3940 } else {
3941 n->type = Inkscape::NodePath::NODE_SMOOTH;
3942 }
3943 } else {
3944 n->type = Inkscape::NodePath::NODE_CUSP;
3945 }
3946 }
3948 n->code = code;
3949 n->selected = FALSE;
3950 n->pos = *pos;
3951 n->p.pos = *ppos;
3952 n->n.pos = *npos;
3954 n->dragging_out = NULL;
3956 Inkscape::NodePath::Node *prev;
3957 if (next) {
3958 //g_assert(g_list_find(sp->nodes, next));
3959 prev = next->p.other;
3960 } else {
3961 prev = sp->last;
3962 }
3964 if (prev)
3965 prev->n.other = n;
3966 else
3967 sp->first = n;
3969 if (next)
3970 next->p.other = n;
3971 else
3972 sp->last = n;
3974 n->p.other = prev;
3975 n->n.other = next;
3977 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"));
3978 sp_knot_set_position(n->knot, pos, 0);
3980 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3981 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3982 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3983 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3984 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3985 sp_knot_update_ctrl(n->knot);
3987 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3988 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3989 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3990 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3991 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3992 sp_knot_show(n->knot);
3994 // We only create handle knots and lines on demand
3995 n->p.knot = NULL;
3996 n->p.line = NULL;
3997 n->n.knot = NULL;
3998 n->n.line = NULL;
4000 sp->nodes = g_list_prepend(sp->nodes, n);
4002 return n;
4003 }
4005 /**
4006 * Destroy node and its knots, link neighbors in subpath.
4007 */
4008 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4009 {
4010 g_assert(node);
4011 g_assert(node->subpath);
4012 g_assert(SP_IS_KNOT(node->knot));
4014 Inkscape::NodePath::SubPath *sp = node->subpath;
4016 if (node->selected) { // first, deselect
4017 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4018 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4019 }
4021 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4023 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4024 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4025 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4026 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4027 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4028 g_object_unref(G_OBJECT(node->knot));
4030 if (node->p.knot) {
4031 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4032 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4033 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4034 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4035 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4036 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4037 g_object_unref(G_OBJECT(node->p.knot));
4038 }
4040 if (node->n.knot) {
4041 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4042 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4043 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4044 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4045 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4046 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4047 g_object_unref(G_OBJECT(node->n.knot));
4048 }
4050 if (node->p.line)
4051 gtk_object_destroy(GTK_OBJECT(node->p.line));
4052 if (node->n.line)
4053 gtk_object_destroy(GTK_OBJECT(node->n.line));
4055 if (sp->nodes) { // there are others nodes on the subpath
4056 if (sp->closed) {
4057 if (sp->first == node) {
4058 g_assert(sp->last == node);
4059 sp->first = node->n.other;
4060 sp->last = sp->first;
4061 }
4062 node->p.other->n.other = node->n.other;
4063 node->n.other->p.other = node->p.other;
4064 } else {
4065 if (sp->first == node) {
4066 sp->first = node->n.other;
4067 sp->first->code = NR_MOVETO;
4068 }
4069 if (sp->last == node) sp->last = node->p.other;
4070 if (node->p.other) node->p.other->n.other = node->n.other;
4071 if (node->n.other) node->n.other->p.other = node->p.other;
4072 }
4073 } else { // this was the last node on subpath
4074 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4075 }
4077 g_mem_chunk_free(nodechunk, node);
4078 }
4080 /**
4081 * Returns one of the node's two sides.
4082 * \param which Indicates which side.
4083 * \return Pointer to previous node side if which==-1, next if which==1.
4084 */
4085 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4086 {
4087 g_assert(node);
4089 switch (which) {
4090 case -1:
4091 return &node->p;
4092 case 1:
4093 return &node->n;
4094 default:
4095 break;
4096 }
4098 g_assert_not_reached();
4100 return NULL;
4101 }
4103 /**
4104 * Return the other side of the node, given one of its sides.
4105 */
4106 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4107 {
4108 g_assert(node);
4110 if (me == &node->p) return &node->n;
4111 if (me == &node->n) return &node->p;
4113 g_assert_not_reached();
4115 return NULL;
4116 }
4118 /**
4119 * Return NRPathcode on the given side of the node.
4120 */
4121 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4122 {
4123 g_assert(node);
4125 if (me == &node->p) {
4126 if (node->p.other) return (NRPathcode)node->code;
4127 return NR_MOVETO;
4128 }
4130 if (me == &node->n) {
4131 if (node->n.other) return (NRPathcode)node->n.other->code;
4132 return NR_MOVETO;
4133 }
4135 g_assert_not_reached();
4137 return NR_END;
4138 }
4140 /**
4141 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4142 */
4143 Inkscape::NodePath::Node *
4144 sp_nodepath_get_node_by_index(int index)
4145 {
4146 Inkscape::NodePath::Node *e = NULL;
4148 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4149 if (!nodepath) {
4150 return e;
4151 }
4153 //find segment
4154 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4156 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4157 int n = g_list_length(sp->nodes);
4158 if (sp->closed) {
4159 n++;
4160 }
4162 //if the piece belongs to this subpath grab it
4163 //otherwise move onto the next subpath
4164 if (index < n) {
4165 e = sp->first;
4166 for (int i = 0; i < index; ++i) {
4167 e = e->n.other;
4168 }
4169 break;
4170 } else {
4171 if (sp->closed) {
4172 index -= (n+1);
4173 } else {
4174 index -= n;
4175 }
4176 }
4177 }
4179 return e;
4180 }
4182 /**
4183 * Returns plain text meaning of node type.
4184 */
4185 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4186 {
4187 unsigned retracted = 0;
4188 bool endnode = false;
4190 for (int which = -1; which <= 1; which += 2) {
4191 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4192 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4193 retracted ++;
4194 if (!side->other)
4195 endnode = true;
4196 }
4198 if (retracted == 0) {
4199 if (endnode) {
4200 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4201 return _("end node");
4202 } else {
4203 switch (node->type) {
4204 case Inkscape::NodePath::NODE_CUSP:
4205 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4206 return _("cusp");
4207 case Inkscape::NodePath::NODE_SMOOTH:
4208 // TRANSLATORS: "smooth" is an adjective here
4209 return _("smooth");
4210 case Inkscape::NodePath::NODE_SYMM:
4211 return _("symmetric");
4212 }
4213 }
4214 } else if (retracted == 1) {
4215 if (endnode) {
4216 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4217 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4218 } else {
4219 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4220 }
4221 } else {
4222 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4223 }
4225 return NULL;
4226 }
4228 /**
4229 * Handles content of statusbar as long as node tool is active.
4230 */
4231 void
4232 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4233 {
4234 gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>< ></b> to scale, <b>[ ]</b> to rotate");
4235 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4237 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4238 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4239 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4240 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4242 SPDesktop *desktop = NULL;
4243 if (nodepath) {
4244 desktop = nodepath->desktop;
4245 } else {
4246 desktop = SP_ACTIVE_DESKTOP;
4247 }
4249 SPEventContext *ec = desktop->event_context;
4250 if (!ec) return;
4251 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4252 if (!mc) return;
4254 if (selected_nodes == 0) {
4255 Inkscape::Selection *sel = desktop->selection;
4256 if (!sel || sel->isEmpty()) {
4257 mc->setF(Inkscape::NORMAL_MESSAGE,
4258 _("Select a single object to edit its nodes or handles."));
4259 } else {
4260 if (nodepath) {
4261 mc->setF(Inkscape::NORMAL_MESSAGE,
4262 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.",
4263 "<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.",
4264 total_nodes),
4265 total_nodes);
4266 } else {
4267 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4268 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4269 } else {
4270 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4271 }
4272 }
4273 }
4274 } else if (nodepath && selected_nodes == 1) {
4275 mc->setF(Inkscape::NORMAL_MESSAGE,
4276 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4277 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4278 total_nodes),
4279 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4280 } else {
4281 if (selected_subpaths > 1) {
4282 mc->setF(Inkscape::NORMAL_MESSAGE,
4283 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4284 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4285 total_nodes),
4286 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4287 } else {
4288 mc->setF(Inkscape::NORMAL_MESSAGE,
4289 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4290 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4291 total_nodes),
4292 selected_nodes, total_nodes, when_selected);
4293 }
4294 }
4295 }
4298 /*
4299 Local Variables:
4300 mode:c++
4301 c-file-style:"stroustrup"
4302 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4303 indent-tabs-mode:nil
4304 fill-column:99
4305 End:
4306 */
4307 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :