78d76404df2b0aacbb732d59eac5558760b35edb
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 "shape-editor.h"
35 #include "selection-chemistry.h"
36 #include "selection.h"
37 #include "xml/repr.h"
38 #include "prefs-utils.h"
39 #include "sp-metrics.h"
40 #include "sp-path.h"
41 #include "libnr/nr-matrix-ops.h"
42 #include "splivarot.h"
43 #include "svg/svg.h"
44 #include "verbs.h"
45 #include "display/bezier-utils.h"
46 #include <vector>
47 #include <algorithm>
49 class NR::Matrix;
51 /// \todo
52 /// evil evil evil. FIXME: conflict of two different Path classes!
53 /// There is a conflict in the namespace between two classes named Path.
54 /// #include "sp-flowtext.h"
55 /// #include "sp-flowregion.h"
57 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
58 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
59 GType sp_flowregion_get_type (void);
60 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
61 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
62 GType sp_flowtext_get_type (void);
63 // end evil workaround
65 #include "helper/stlport.h"
68 /// \todo fixme: Implement these via preferences */
70 #define NODE_FILL 0xbfbfbf00
71 #define NODE_STROKE 0x000000ff
72 #define NODE_FILL_HI 0xff000000
73 #define NODE_STROKE_HI 0x000000ff
74 #define NODE_FILL_SEL 0x0000ffff
75 #define NODE_STROKE_SEL 0x000000ff
76 #define NODE_FILL_SEL_HI 0xff000000
77 #define NODE_STROKE_SEL_HI 0x000000ff
78 #define KNOT_FILL 0xffffffff
79 #define KNOT_STROKE 0x000000ff
80 #define KNOT_FILL_HI 0xff000000
81 #define KNOT_STROKE_HI 0x000000ff
83 static GMemChunk *nodechunk = NULL;
85 /* Creation from object */
87 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
88 static gchar *parse_nodetypes(gchar const *types, gint length);
90 /* Object updating */
92 static void stamp_repr(Inkscape::NodePath::Path *np);
93 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
94 static gchar *create_typestr(Inkscape::NodePath::Path *np);
96 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
98 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
100 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
102 /* Adjust handle placement, if the node or the other handle is moved */
103 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
104 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
106 /* Node event callbacks */
107 static void node_clicked(SPKnot *knot, guint state, gpointer data);
108 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
109 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
110 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
112 /* Handle event callbacks */
113 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
114 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
115 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
116 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
118 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
120 /* Constructors and destructors */
122 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
123 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
124 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
125 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
126 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
127 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
128 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
130 /* Helpers */
132 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
133 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
136 // active_node indicates mouseover node
137 static Inkscape::NodePath::Node *active_node = NULL;
139 /**
140 * \brief Creates new nodepath from item
141 */
142 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
143 {
144 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
146 /** \todo
147 * FIXME: remove this. We don't want to edit paths inside flowtext.
148 * Instead we will build our flowtext with cloned paths, so that the
149 * real paths are outside the flowtext and thus editable as usual.
150 */
151 if (SP_IS_FLOWTEXT(item)) {
152 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
153 if SP_IS_FLOWREGION(child) {
154 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
155 if (grandchild && SP_IS_PATH(grandchild)) {
156 item = SP_ITEM(grandchild);
157 break;
158 }
159 }
160 }
161 }
163 if (!SP_IS_PATH(item))
164 return NULL;
165 SPPath *path = SP_PATH(item);
166 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
167 if (curve == NULL)
168 return NULL;
170 NArtBpath *bpath = sp_curve_first_bpath(curve);
171 gint length = curve->end;
172 if (length == 0)
173 return NULL; // prevent crash for one-node paths
175 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
176 gchar *typestr = parse_nodetypes(nodetypes, length);
178 //Create new nodepath
179 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
180 if (!np)
181 return NULL;
183 // Set defaults
184 np->desktop = desktop;
185 np->path = path;
186 np->subpaths = NULL;
187 np->selected = NULL;
188 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
189 np->livarot_path = NULL;
190 np->local_change = 0;
191 np->show_handles = show_handles;
193 // we need to update item's transform from the repr here,
194 // because they may be out of sync when we respond
195 // to a change in repr by regenerating nodepath --bb
196 sp_object_read_attr(SP_OBJECT(item), "transform");
198 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
199 np->d2i = np->i2d.inverse();
200 np->repr = repr;
202 // create the subpath(s) from the bpath
203 NArtBpath *b = bpath;
204 while (b->code != NR_END) {
205 b = subpath_from_bpath(np, b, typestr + (b - bpath));
206 }
208 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
209 np->subpaths = g_list_reverse(np->subpaths);
211 g_free(typestr);
212 sp_curve_unref(curve);
214 // create the livarot representation from the same item
215 sp_nodepath_ensure_livarot_path(np);
217 return np;
218 }
220 /**
221 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
222 */
223 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
225 if (!np) //soft fail, like delete
226 return;
228 while (np->subpaths) {
229 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
230 }
232 //Inform the ShapeEditor that made me, if any, that I am gone.
233 if (np->shape_editor)
234 np->shape_editor->nodepath_destroyed();
236 g_assert(!np->selected);
238 if (np->livarot_path) {
239 delete np->livarot_path;
240 np->livarot_path = NULL;
241 }
243 np->desktop = NULL;
245 g_free(np);
246 }
249 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
250 {
251 if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
252 np->livarot_path = Path_for_item (np->path, true, true);
253 if (np->livarot_path)
254 np->livarot_path->ConvertWithBackData(0.01);
255 }
256 }
259 /**
260 * Return the node count of a given NodeSubPath.
261 */
262 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
263 {
264 if (!subpath)
265 return 0;
266 gint nodeCount = g_list_length(subpath->nodes);
267 return nodeCount;
268 }
270 /**
271 * Return the node count of a given NodePath.
272 */
273 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
274 {
275 if (!np)
276 return 0;
277 gint nodeCount = 0;
278 for (GList *item = np->subpaths ; item ; item=item->next) {
279 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
280 nodeCount += g_list_length(subpath->nodes);
281 }
282 return nodeCount;
283 }
285 /**
286 * Return the subpath count of a given NodePath.
287 */
288 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
289 {
290 if (!np)
291 return 0;
292 return g_list_length (np->subpaths);
293 }
295 /**
296 * Return the selected node count of a given NodePath.
297 */
298 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
299 {
300 if (!np)
301 return 0;
302 return g_list_length (np->selected);
303 }
305 /**
306 * Return the number of subpaths where nodes are selected in a given NodePath.
307 */
308 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
309 {
310 if (!np)
311 return 0;
312 if (!np->selected)
313 return 0;
314 if (!np->selected->next)
315 return 1;
316 gint count = 0;
317 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
318 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
319 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
320 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
321 if (node->selected) {
322 count ++;
323 break;
324 }
325 }
326 }
327 return count;
328 }
330 /**
331 * Clean up a nodepath after editing.
332 *
333 * Currently we are deleting trivial subpaths.
334 */
335 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
336 {
337 GList *badSubPaths = NULL;
339 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
340 for (GList *l = nodepath->subpaths; l ; l=l->next) {
341 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
342 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
343 badSubPaths = g_list_append(badSubPaths, sp);
344 }
346 //Delete them. This second step is because sp_nodepath_subpath_destroy()
347 //also removes the subpath from nodepath->subpaths
348 for (GList *l = badSubPaths; l ; l=l->next) {
349 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
350 sp_nodepath_subpath_destroy(sp);
351 }
353 g_list_free(badSubPaths);
354 }
356 /**
357 * Create new nodepath from b, make it subpath of np.
358 * \param t The node type.
359 * \todo Fixme: t should be a proper type, rather than gchar
360 */
361 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
362 {
363 NR::Point ppos, pos, npos;
365 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
367 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
368 bool const closed = (b->code == NR_MOVETO);
370 pos = NR::Point(b->x3, b->y3) * np->i2d;
371 if (b[1].code == NR_CURVETO) {
372 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
373 } else {
374 npos = pos;
375 }
376 Inkscape::NodePath::Node *n;
377 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
378 g_assert(sp->first == n);
379 g_assert(sp->last == n);
381 b++;
382 t++;
383 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
384 pos = NR::Point(b->x3, b->y3) * np->i2d;
385 if (b->code == NR_CURVETO) {
386 ppos = NR::Point(b->x2, b->y2) * np->i2d;
387 } else {
388 ppos = pos;
389 }
390 if (b[1].code == NR_CURVETO) {
391 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
392 } else {
393 npos = pos;
394 }
395 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
396 b++;
397 t++;
398 }
400 if (closed) sp_nodepath_subpath_close(sp);
402 return b;
403 }
405 /**
406 * Convert from sodipodi:nodetypes to new style type string.
407 */
408 static gchar *parse_nodetypes(gchar const *types, gint length)
409 {
410 g_assert(length > 0);
412 gchar *typestr = g_new(gchar, length + 1);
414 gint pos = 0;
416 if (types) {
417 for (gint i = 0; types[i] && ( i < length ); i++) {
418 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
419 if (types[i] != '\0') {
420 switch (types[i]) {
421 case 's':
422 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
423 break;
424 case 'z':
425 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
426 break;
427 case 'c':
428 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
429 break;
430 default:
431 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
432 break;
433 }
434 }
435 }
436 }
438 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
440 return typestr;
441 }
443 /**
444 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
445 * updated but repr is not (for speed). Used during curve and node drag.
446 */
447 static void update_object(Inkscape::NodePath::Path *np)
448 {
449 g_assert(np);
451 SPCurve *curve = create_curve(np);
453 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
455 sp_curve_unref(curve);
456 }
458 /**
459 * Update XML path node with data from path object.
460 */
461 static void update_repr_internal(Inkscape::NodePath::Path *np)
462 {
463 g_assert(np);
465 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
467 SPCurve *curve = create_curve(np);
468 gchar *typestr = create_typestr(np);
469 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
471 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
472 np->local_change++;
473 repr->setAttribute("d", svgpath);
474 }
476 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
477 np->local_change++;
478 repr->setAttribute("sodipodi:nodetypes", typestr);
479 }
481 g_free(svgpath);
482 g_free(typestr);
483 sp_curve_unref(curve);
484 }
486 /**
487 * Update XML path node with data from path object, commit changes forever.
488 */
489 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
490 {
491 //fixme: np can be NULL, so check before proceeding
492 g_return_if_fail(np != NULL);
494 if (np->livarot_path) {
495 delete np->livarot_path;
496 np->livarot_path = NULL;
497 }
499 update_repr_internal(np);
500 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
502 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
503 annotation);
504 }
506 /**
507 * Update XML path node with data from path object, commit changes with undo.
508 */
509 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
510 {
511 if (np->livarot_path) {
512 delete np->livarot_path;
513 np->livarot_path = NULL;
514 }
516 update_repr_internal(np);
517 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
518 annotation);
519 }
521 /**
522 * Make duplicate of path, replace corresponding XML node in tree, commit.
523 */
524 static void stamp_repr(Inkscape::NodePath::Path *np)
525 {
526 g_assert(np);
528 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
529 Inkscape::XML::Node *new_repr = old_repr->duplicate();
531 // remember the position of the item
532 gint pos = old_repr->position();
533 // remember parent
534 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
536 SPCurve *curve = create_curve(np);
537 gchar *typestr = create_typestr(np);
539 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
541 new_repr->setAttribute("d", svgpath);
542 new_repr->setAttribute("sodipodi:nodetypes", typestr);
544 // add the new repr to the parent
545 parent->appendChild(new_repr);
546 // move to the saved position
547 new_repr->setPosition(pos > 0 ? pos : 0);
549 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
550 _("Stamp"));
552 Inkscape::GC::release(new_repr);
553 g_free(svgpath);
554 g_free(typestr);
555 sp_curve_unref(curve);
556 }
558 /**
559 * Create curve from path.
560 */
561 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
562 {
563 SPCurve *curve = sp_curve_new();
565 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
566 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
567 sp_curve_moveto(curve,
568 sp->first->pos * np->d2i);
569 Inkscape::NodePath::Node *n = sp->first->n.other;
570 while (n) {
571 NR::Point const end_pt = n->pos * np->d2i;
572 switch (n->code) {
573 case NR_LINETO:
574 sp_curve_lineto(curve, end_pt);
575 break;
576 case NR_CURVETO:
577 sp_curve_curveto(curve,
578 n->p.other->n.pos * np->d2i,
579 n->p.pos * np->d2i,
580 end_pt);
581 break;
582 default:
583 g_assert_not_reached();
584 break;
585 }
586 if (n != sp->last) {
587 n = n->n.other;
588 } else {
589 n = NULL;
590 }
591 }
592 if (sp->closed) {
593 sp_curve_closepath(curve);
594 }
595 }
597 return curve;
598 }
600 /**
601 * Convert path type string to sodipodi:nodetypes style.
602 */
603 static gchar *create_typestr(Inkscape::NodePath::Path *np)
604 {
605 gchar *typestr = g_new(gchar, 32);
606 gint len = 32;
607 gint pos = 0;
609 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
610 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
612 if (pos >= len) {
613 typestr = g_renew(gchar, typestr, len + 32);
614 len += 32;
615 }
617 typestr[pos++] = 'c';
619 Inkscape::NodePath::Node *n;
620 n = sp->first->n.other;
621 while (n) {
622 gchar code;
624 switch (n->type) {
625 case Inkscape::NodePath::NODE_CUSP:
626 code = 'c';
627 break;
628 case Inkscape::NodePath::NODE_SMOOTH:
629 code = 's';
630 break;
631 case Inkscape::NodePath::NODE_SYMM:
632 code = 'z';
633 break;
634 default:
635 g_assert_not_reached();
636 code = '\0';
637 break;
638 }
640 if (pos >= len) {
641 typestr = g_renew(gchar, typestr, len + 32);
642 len += 32;
643 }
645 typestr[pos++] = code;
647 if (n != sp->last) {
648 n = n->n.other;
649 } else {
650 n = NULL;
651 }
652 }
653 }
655 if (pos >= len) {
656 typestr = g_renew(gchar, typestr, len + 1);
657 len += 1;
658 }
660 typestr[pos++] = '\0';
662 return typestr;
663 }
665 /**
666 * Returns current path in context. // later eliminate this function at all!
667 */
668 static Inkscape::NodePath::Path *sp_nodepath_current()
669 {
670 if (!SP_ACTIVE_DESKTOP) {
671 return NULL;
672 }
674 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
676 if (!SP_IS_NODE_CONTEXT(event_context)) {
677 return NULL;
678 }
680 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
681 }
685 /**
686 \brief Fills node and handle positions for three nodes, splitting line
687 marked by end at distance t.
688 */
689 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
690 {
691 g_assert(new_path != NULL);
692 g_assert(end != NULL);
694 g_assert(end->p.other == new_path);
695 Inkscape::NodePath::Node *start = new_path->p.other;
696 g_assert(start);
698 if (end->code == NR_LINETO) {
699 new_path->type =Inkscape::NodePath::NODE_CUSP;
700 new_path->code = NR_LINETO;
701 new_path->pos = (t * start->pos + (1 - t) * end->pos);
702 } else {
703 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
704 new_path->code = NR_CURVETO;
705 gdouble s = 1 - t;
706 for (int dim = 0; dim < 2; dim++) {
707 NR::Coord const f000 = start->pos[dim];
708 NR::Coord const f001 = start->n.pos[dim];
709 NR::Coord const f011 = end->p.pos[dim];
710 NR::Coord const f111 = end->pos[dim];
711 NR::Coord const f00t = s * f000 + t * f001;
712 NR::Coord const f01t = s * f001 + t * f011;
713 NR::Coord const f11t = s * f011 + t * f111;
714 NR::Coord const f0tt = s * f00t + t * f01t;
715 NR::Coord const f1tt = s * f01t + t * f11t;
716 NR::Coord const fttt = s * f0tt + t * f1tt;
717 start->n.pos[dim] = f00t;
718 new_path->p.pos[dim] = f0tt;
719 new_path->pos[dim] = fttt;
720 new_path->n.pos[dim] = f1tt;
721 end->p.pos[dim] = f11t;
722 }
723 }
724 }
726 /**
727 * Adds new node on direct line between two nodes, activates handles of all
728 * three nodes.
729 */
730 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
731 {
732 g_assert(end);
733 g_assert(end->subpath);
734 g_assert(g_list_find(end->subpath->nodes, end));
736 Inkscape::NodePath::Node *start = end->p.other;
737 g_assert( start->n.other == end );
738 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
739 end,
740 Inkscape::NodePath::NODE_SMOOTH,
741 (NRPathcode)end->code,
742 &start->pos, &start->pos, &start->n.pos);
743 sp_nodepath_line_midpoint(newnode, end, t);
745 sp_node_update_handles(start);
746 sp_node_update_handles(newnode);
747 sp_node_update_handles(end);
749 return newnode;
750 }
752 /**
753 \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
754 */
755 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
756 {
757 g_assert(node);
758 g_assert(node->subpath);
759 g_assert(g_list_find(node->subpath->nodes, node));
761 Inkscape::NodePath::SubPath *sp = node->subpath;
762 Inkscape::NodePath::Path *np = sp->nodepath;
764 if (sp->closed) {
765 sp_nodepath_subpath_open(sp, node);
766 return sp->first;
767 } else {
768 // no break for end nodes
769 if (node == sp->first) return NULL;
770 if (node == sp->last ) return NULL;
772 // create a new subpath
773 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
775 // duplicate the break node as start of the new subpath
776 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
778 while (node->n.other) { // copy the remaining nodes into the new subpath
779 Inkscape::NodePath::Node *n = node->n.other;
780 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);
781 if (n->selected) {
782 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
783 }
784 sp_nodepath_node_destroy(n); // remove the point on the original subpath
785 }
787 return newnode;
788 }
789 }
791 /**
792 * Duplicate node and connect to neighbours.
793 */
794 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
795 {
796 g_assert(node);
797 g_assert(node->subpath);
798 g_assert(g_list_find(node->subpath->nodes, node));
800 Inkscape::NodePath::SubPath *sp = node->subpath;
802 NRPathcode code = (NRPathcode) node->code;
803 if (code == NR_MOVETO) { // if node is the endnode,
804 node->code = NR_LINETO; // new one is inserted before it, so change that to line
805 }
807 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
809 if (!node->n.other || !node->p.other) // if node is an endnode, select it
810 return node;
811 else
812 return newnode; // otherwise select the newly created node
813 }
815 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
816 {
817 node->p.pos = (node->pos + (node->pos - node->n.pos));
818 }
820 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
821 {
822 node->n.pos = (node->pos + (node->pos - node->p.pos));
823 }
825 /**
826 * Change line type at node, with side effects on neighbours.
827 */
828 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
829 {
830 g_assert(end);
831 g_assert(end->subpath);
832 g_assert(end->p.other);
834 if (end->code == static_cast< guint > ( code ) )
835 return;
837 Inkscape::NodePath::Node *start = end->p.other;
839 end->code = code;
841 if (code == NR_LINETO) {
842 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
843 if (end->n.other) {
844 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
845 }
846 sp_node_adjust_handle(start, -1);
847 sp_node_adjust_handle(end, 1);
848 } else {
849 NR::Point delta = end->pos - start->pos;
850 start->n.pos = start->pos + delta / 3;
851 end->p.pos = end->pos - delta / 3;
852 sp_node_adjust_handle(start, 1);
853 sp_node_adjust_handle(end, -1);
854 }
856 sp_node_update_handles(start);
857 sp_node_update_handles(end);
858 }
860 /**
861 * Change node type, and its handles accordingly.
862 */
863 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
864 {
865 g_assert(node);
866 g_assert(node->subpath);
868 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
869 return node;
871 if ((node->p.other != NULL) && (node->n.other != NULL)) {
872 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
873 type =Inkscape::NodePath::NODE_CUSP;
874 }
875 }
877 node->type = type;
879 if (node->type == Inkscape::NodePath::NODE_CUSP) {
880 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
881 node->knot->setSize (node->selected? 11 : 9);
882 sp_knot_update_ctrl(node->knot);
883 } else {
884 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
885 node->knot->setSize (node->selected? 9 : 7);
886 sp_knot_update_ctrl(node->knot);
887 }
889 // if one of handles is mouseovered, preserve its position
890 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
891 sp_node_adjust_handle(node, 1);
892 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
893 sp_node_adjust_handle(node, -1);
894 } else {
895 sp_node_adjust_handles(node);
896 }
898 sp_node_update_handles(node);
900 sp_nodepath_update_statusbar(node->subpath->nodepath);
902 return node;
903 }
905 /**
906 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
907 * adjacent segments from lines to curves.
908 */
909 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
910 {
911 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
912 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
913 // convert adjacent segment BEFORE to curve
914 node->code = NR_CURVETO;
915 NR::Point delta;
916 if (node->n.other != NULL)
917 delta = node->n.other->pos - node->p.other->pos;
918 else
919 delta = node->pos - node->p.other->pos;
920 node->p.pos = node->pos - delta / 4;
921 sp_node_update_handles(node);
922 }
924 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
925 // convert adjacent segment AFTER to curve
926 node->n.other->code = NR_CURVETO;
927 NR::Point delta;
928 if (node->p.other != NULL)
929 delta = node->p.other->pos - node->n.other->pos;
930 else
931 delta = node->pos - node->n.other->pos;
932 node->n.pos = node->pos - delta / 4;
933 sp_node_update_handles(node);
934 }
935 }
937 sp_nodepath_set_node_type (node, type);
938 }
940 /**
941 * Move node to point, and adjust its and neighbouring handles.
942 */
943 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
944 {
945 NR::Point delta = p - node->pos;
946 node->pos = p;
948 node->p.pos += delta;
949 node->n.pos += delta;
951 if (node->p.other) {
952 if (node->code == NR_LINETO) {
953 sp_node_adjust_handle(node, 1);
954 sp_node_adjust_handle(node->p.other, -1);
955 }
956 }
957 if (node->n.other) {
958 if (node->n.other->code == NR_LINETO) {
959 sp_node_adjust_handle(node, -1);
960 sp_node_adjust_handle(node->n.other, 1);
961 }
962 }
964 // this function is only called from batch movers that will update display at the end
965 // themselves, so here we just move all the knots without emitting move signals, for speed
966 sp_node_update_handles(node, false);
967 }
969 /**
970 * Call sp_node_moveto() for node selection and handle possible snapping.
971 */
972 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
973 bool const snap = true)
974 {
975 NR::Coord best = NR_HUGE;
976 NR::Point delta(dx, dy);
977 NR::Point best_pt = delta;
979 if (snap) {
980 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
982 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
983 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
984 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
985 if (s.getDistance() < best) {
986 best = s.getDistance();
987 best_pt = s.getPoint() - n->pos;
988 }
989 }
990 }
992 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
993 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
994 sp_node_moveto(n, n->pos + best_pt);
995 }
997 // do not update repr here so that node dragging is acceptably fast
998 update_object(nodepath);
999 }
1001 /**
1002 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1003 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1004 near x = 0.
1005 */
1006 double
1007 sculpt_profile (double x, double alpha, guint profile)
1008 {
1009 if (x >= 1)
1010 return 0;
1011 if (x <= 0)
1012 return 1;
1014 switch (profile) {
1015 case SCULPT_PROFILE_LINEAR:
1016 return 1 - x;
1017 case SCULPT_PROFILE_BELL:
1018 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1019 case SCULPT_PROFILE_ELLIPTIC:
1020 return sqrt(1 - x*x);
1021 }
1023 return 1;
1024 }
1026 double
1027 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1028 {
1029 // extremely primitive for now, don't have time to look for the real one
1030 double lower = NR::L2(b - a);
1031 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1032 return (lower + upper)/2;
1033 }
1035 void
1036 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1037 {
1038 n->pos = n->origin + delta;
1039 n->n.pos = n->n.origin + delta_n;
1040 n->p.pos = n->p.origin + delta_p;
1041 sp_node_adjust_handles(n);
1042 sp_node_update_handles(n, false);
1043 }
1045 /**
1046 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1047 * on how far they are from the dragged node n.
1048 */
1049 static void
1050 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1051 {
1052 g_assert (n);
1053 g_assert (nodepath);
1054 g_assert (n->subpath->nodepath == nodepath);
1056 double pressure = n->knot->pressure;
1057 if (pressure == 0)
1058 pressure = 0.5; // default
1059 pressure = CLAMP (pressure, 0.2, 0.8);
1061 // map pressure to alpha = 1/5 ... 5
1062 double alpha = 1 - 2 * fabs(pressure - 0.5);
1063 if (pressure > 0.5)
1064 alpha = 1/alpha;
1066 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1068 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1069 // Only one subpath has selected nodes:
1070 // use linear mode, where the distance from n to node being dragged is calculated along the path
1072 double n_sel_range = 0, p_sel_range = 0;
1073 guint n_nodes = 0, p_nodes = 0;
1074 guint n_sel_nodes = 0, p_sel_nodes = 0;
1076 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1077 {
1078 double n_range = 0, p_range = 0;
1079 bool n_going = true, p_going = true;
1080 Inkscape::NodePath::Node *n_node = n;
1081 Inkscape::NodePath::Node *p_node = n;
1082 do {
1083 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1084 if (n_node && n_going)
1085 n_node = n_node->n.other;
1086 if (n_node == NULL) {
1087 n_going = false;
1088 } else {
1089 n_nodes ++;
1090 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1091 if (n_node->selected) {
1092 n_sel_nodes ++;
1093 n_sel_range = n_range;
1094 }
1095 if (n_node == p_node) {
1096 n_going = false;
1097 p_going = false;
1098 }
1099 }
1100 if (p_node && p_going)
1101 p_node = p_node->p.other;
1102 if (p_node == NULL) {
1103 p_going = false;
1104 } else {
1105 p_nodes ++;
1106 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1107 if (p_node->selected) {
1108 p_sel_nodes ++;
1109 p_sel_range = p_range;
1110 }
1111 if (p_node == n_node) {
1112 n_going = false;
1113 p_going = false;
1114 }
1115 }
1116 } while (n_going || p_going);
1117 }
1119 // Second pass: actually move nodes in this subpath
1120 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1121 {
1122 double n_range = 0, p_range = 0;
1123 bool n_going = true, p_going = true;
1124 Inkscape::NodePath::Node *n_node = n;
1125 Inkscape::NodePath::Node *p_node = n;
1126 do {
1127 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1128 if (n_node && n_going)
1129 n_node = n_node->n.other;
1130 if (n_node == NULL) {
1131 n_going = false;
1132 } else {
1133 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1134 if (n_node->selected) {
1135 sp_nodepath_move_node_and_handles (n_node,
1136 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1137 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1138 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1139 }
1140 if (n_node == p_node) {
1141 n_going = false;
1142 p_going = false;
1143 }
1144 }
1145 if (p_node && p_going)
1146 p_node = p_node->p.other;
1147 if (p_node == NULL) {
1148 p_going = false;
1149 } else {
1150 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1151 if (p_node->selected) {
1152 sp_nodepath_move_node_and_handles (p_node,
1153 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1154 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1155 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1156 }
1157 if (p_node == n_node) {
1158 n_going = false;
1159 p_going = false;
1160 }
1161 }
1162 } while (n_going || p_going);
1163 }
1165 } else {
1166 // Multiple subpaths have selected nodes:
1167 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1168 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1169 // fix the pear-like shape when sculpting e.g. a ring
1171 // First pass: calculate range
1172 gdouble direct_range = 0;
1173 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1174 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1175 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1176 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1177 if (node->selected) {
1178 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1179 }
1180 }
1181 }
1183 // Second pass: actually move nodes
1184 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1185 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1186 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1187 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1188 if (node->selected) {
1189 if (direct_range > 1e-6) {
1190 sp_nodepath_move_node_and_handles (node,
1191 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1192 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1193 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1194 } else {
1195 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1196 }
1198 }
1199 }
1200 }
1201 }
1203 // do not update repr here so that node dragging is acceptably fast
1204 update_object(nodepath);
1205 }
1208 /**
1209 * Move node selection to point, adjust its and neighbouring handles,
1210 * handle possible snapping, and commit the change with possible undo.
1211 */
1212 void
1213 sp_node_selected_move(gdouble dx, gdouble dy)
1214 {
1215 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1216 if (!nodepath) return;
1218 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1220 if (dx == 0) {
1221 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1222 } else if (dy == 0) {
1223 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1224 } else {
1225 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1226 }
1227 }
1229 /**
1230 * Move node selection off screen and commit the change.
1231 */
1232 void
1233 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1234 {
1235 // borrowed from sp_selection_move_screen in selection-chemistry.c
1236 // we find out the current zoom factor and divide deltas by it
1237 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1239 gdouble zoom = desktop->current_zoom();
1240 gdouble zdx = dx / zoom;
1241 gdouble zdy = dy / zoom;
1243 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1244 if (!nodepath) return;
1246 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1248 if (dx == 0) {
1249 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1250 } else if (dy == 0) {
1251 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1252 } else {
1253 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1254 }
1255 }
1257 /** If they don't yet exist, creates knot and line for the given side of the node */
1258 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1259 {
1260 if (!side->knot) {
1261 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"));
1263 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1264 side->knot->setSize (7);
1265 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1266 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1267 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1268 sp_knot_update_ctrl(side->knot);
1270 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1271 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1272 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1273 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1274 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1275 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1276 }
1278 if (!side->line) {
1279 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1280 SP_TYPE_CTRLLINE, NULL);
1281 }
1282 }
1284 /**
1285 * Ensure the given handle of the node is visible/invisible, update its screen position
1286 */
1287 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1288 {
1289 g_assert(node != NULL);
1291 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1292 NRPathcode code = sp_node_path_code_from_side(node, side);
1294 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1296 if (show_handle) {
1297 if (!side->knot) { // No handle knot at all
1298 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1299 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1300 side->knot->pos = side->pos;
1301 if (side->knot->item)
1302 SP_CTRL(side->knot->item)->moveto(side->pos);
1303 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1304 sp_knot_show(side->knot);
1305 } else {
1306 if (side->knot->pos != side->pos) { // only if it's really moved
1307 if (fire_move_signals) {
1308 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1309 } else {
1310 sp_knot_moveto(side->knot, &side->pos);
1311 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1312 }
1313 }
1314 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1315 sp_knot_show(side->knot);
1316 }
1317 }
1318 sp_canvas_item_show(side->line);
1319 } else {
1320 if (side->knot) {
1321 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1322 sp_knot_hide(side->knot);
1323 }
1324 }
1325 if (side->line) {
1326 sp_canvas_item_hide(side->line);
1327 }
1328 }
1329 }
1331 /**
1332 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1333 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1334 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1335 * updated; otherwise, just move the knots silently (used in batch moves).
1336 */
1337 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1338 {
1339 g_assert(node != NULL);
1341 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1342 sp_knot_show(node->knot);
1343 }
1345 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1346 if (fire_move_signals)
1347 sp_knot_set_position(node->knot, &node->pos, 0);
1348 else
1349 sp_knot_moveto(node->knot, &node->pos);
1350 }
1352 gboolean show_handles = node->selected;
1353 if (node->p.other != NULL) {
1354 if (node->p.other->selected) show_handles = TRUE;
1355 }
1356 if (node->n.other != NULL) {
1357 if (node->n.other->selected) show_handles = TRUE;
1358 }
1360 if (node->subpath->nodepath->show_handles == false)
1361 show_handles = FALSE;
1363 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1364 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1365 }
1367 /**
1368 * Call sp_node_update_handles() for all nodes on subpath.
1369 */
1370 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1371 {
1372 g_assert(subpath != NULL);
1374 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1375 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1376 }
1377 }
1379 /**
1380 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1381 */
1382 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1383 {
1384 g_assert(nodepath != NULL);
1386 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1387 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1388 }
1389 }
1391 void
1392 sp_nodepath_show_handles(bool show)
1393 {
1394 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1395 if (nodepath == NULL) return;
1397 nodepath->show_handles = show;
1398 sp_nodepath_update_handles(nodepath);
1399 }
1401 /**
1402 * Adds all selected nodes in nodepath to list.
1403 */
1404 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1405 {
1406 StlConv<Node *>::list(l, selected);
1407 /// \todo this adds a copying, rework when the selection becomes a stl list
1408 }
1410 /**
1411 * Align selected nodes on the specified axis.
1412 */
1413 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1414 {
1415 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1416 return;
1417 }
1419 if ( !nodepath->selected->next ) { // only one node selected
1420 return;
1421 }
1422 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1423 NR::Point dest(pNode->pos);
1424 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1425 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1426 if (pNode) {
1427 dest[axis] = pNode->pos[axis];
1428 sp_node_moveto(pNode, dest);
1429 }
1430 }
1432 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1433 }
1435 /// Helper struct.
1436 struct NodeSort
1437 {
1438 Inkscape::NodePath::Node *_node;
1439 NR::Coord _coord;
1440 /// \todo use vectorof pointers instead of calling copy ctor
1441 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1442 _node(node), _coord(node->pos[axis])
1443 {}
1445 };
1447 static bool operator<(NodeSort const &a, NodeSort const &b)
1448 {
1449 return (a._coord < b._coord);
1450 }
1452 /**
1453 * Distribute selected nodes on the specified axis.
1454 */
1455 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1456 {
1457 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1458 return;
1459 }
1461 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1462 return;
1463 }
1465 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1466 std::vector<NodeSort> sorted;
1467 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1468 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1469 if (pNode) {
1470 NodeSort n(pNode, axis);
1471 sorted.push_back(n);
1472 //dest[axis] = pNode->pos[axis];
1473 //sp_node_moveto(pNode, dest);
1474 }
1475 }
1476 std::sort(sorted.begin(), sorted.end());
1477 unsigned int len = sorted.size();
1478 //overall bboxes span
1479 float dist = (sorted.back()._coord -
1480 sorted.front()._coord);
1481 //new distance between each bbox
1482 float step = (dist) / (len - 1);
1483 float pos = sorted.front()._coord;
1484 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1485 it < sorted.end();
1486 it ++ )
1487 {
1488 NR::Point dest((*it)._node->pos);
1489 dest[axis] = pos;
1490 sp_node_moveto((*it)._node, dest);
1491 pos += step;
1492 }
1494 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1495 }
1498 /**
1499 * Call sp_nodepath_line_add_node() for all selected segments.
1500 */
1501 void
1502 sp_node_selected_add_node(void)
1503 {
1504 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1505 if (!nodepath) {
1506 return;
1507 }
1509 GList *nl = NULL;
1511 int n_added = 0;
1513 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1514 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1515 g_assert(t->selected);
1516 if (t->p.other && t->p.other->selected) {
1517 nl = g_list_prepend(nl, t);
1518 }
1519 }
1521 while (nl) {
1522 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1523 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1524 sp_nodepath_node_select(n, TRUE, FALSE);
1525 n_added ++;
1526 nl = g_list_remove(nl, t);
1527 }
1529 /** \todo fixme: adjust ? */
1530 sp_nodepath_update_handles(nodepath);
1532 if (n_added > 1) {
1533 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1534 } else if (n_added > 0) {
1535 sp_nodepath_update_repr(nodepath, _("Add node"));
1536 }
1538 sp_nodepath_update_statusbar(nodepath);
1539 }
1541 /**
1542 * Select segment nearest to point
1543 */
1544 void
1545 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1546 {
1547 if (!nodepath) {
1548 return;
1549 }
1551 sp_nodepath_ensure_livarot_path(nodepath);
1552 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1554 //find segment to segment
1555 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1557 //fixme: this can return NULL, so check before proceeding.
1558 g_return_if_fail(e != NULL);
1560 gboolean force = FALSE;
1561 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1562 force = TRUE;
1563 }
1564 sp_nodepath_node_select(e, (gboolean) toggle, force);
1565 if (e->p.other)
1566 sp_nodepath_node_select(e->p.other, TRUE, force);
1568 sp_nodepath_update_handles(nodepath);
1570 sp_nodepath_update_statusbar(nodepath);
1571 }
1573 /**
1574 * Add a node nearest to point
1575 */
1576 void
1577 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1578 {
1579 if (!nodepath) {
1580 return;
1581 }
1583 sp_nodepath_ensure_livarot_path(nodepath);
1584 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1586 //find segment to split
1587 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1589 //don't know why but t seems to flip for lines
1590 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1591 position.t = 1.0 - position.t;
1592 }
1593 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1594 sp_nodepath_node_select(n, FALSE, TRUE);
1596 /* fixme: adjust ? */
1597 sp_nodepath_update_handles(nodepath);
1599 sp_nodepath_update_repr(nodepath, _("Add node"));
1601 sp_nodepath_update_statusbar(nodepath);
1602 }
1604 /*
1605 * Adjusts a segment so that t moves by a certain delta for dragging
1606 * converts lines to curves
1607 *
1608 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1609 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1610 */
1611 void
1612 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1613 {
1614 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1616 //fixme: e and e->p can be NULL, so check for those before proceeding
1617 g_return_if_fail(e != NULL);
1618 g_return_if_fail(&e->p != NULL);
1620 /* feel good is an arbitrary parameter that distributes the delta between handles
1621 * if t of the drag point is less than 1/6 distance form the endpoint only
1622 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1623 */
1624 double feel_good;
1625 if (t <= 1.0 / 6.0)
1626 feel_good = 0;
1627 else if (t <= 0.5)
1628 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1629 else if (t <= 5.0 / 6.0)
1630 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1631 else
1632 feel_good = 1;
1634 //if we're dragging a line convert it to a curve
1635 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1636 sp_nodepath_set_line_type(e, NR_CURVETO);
1637 }
1639 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1640 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1641 e->p.other->n.pos += offsetcoord0;
1642 e->p.pos += offsetcoord1;
1644 // adjust handles of adjacent nodes where necessary
1645 sp_node_adjust_handle(e,1);
1646 sp_node_adjust_handle(e->p.other,-1);
1648 sp_nodepath_update_handles(e->subpath->nodepath);
1650 update_object(e->subpath->nodepath);
1652 sp_nodepath_update_statusbar(e->subpath->nodepath);
1653 }
1656 /**
1657 * Call sp_nodepath_break() for all selected segments.
1658 */
1659 void sp_node_selected_break()
1660 {
1661 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1662 if (!nodepath) return;
1664 GList *temp = NULL;
1665 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1666 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1667 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1668 if (nn == NULL) continue; // no break, no new node
1669 temp = g_list_prepend(temp, nn);
1670 }
1672 if (temp) {
1673 sp_nodepath_deselect(nodepath);
1674 }
1675 for (GList *l = temp; l != NULL; l = l->next) {
1676 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1677 }
1679 sp_nodepath_update_handles(nodepath);
1681 sp_nodepath_update_repr(nodepath, _("Break path"));
1682 }
1684 /**
1685 * Duplicate the selected node(s).
1686 */
1687 void sp_node_selected_duplicate()
1688 {
1689 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1690 if (!nodepath) {
1691 return;
1692 }
1694 GList *temp = NULL;
1695 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1696 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1697 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1698 if (nn == NULL) continue; // could not duplicate
1699 temp = g_list_prepend(temp, nn);
1700 }
1702 if (temp) {
1703 sp_nodepath_deselect(nodepath);
1704 }
1705 for (GList *l = temp; l != NULL; l = l->next) {
1706 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1707 }
1709 sp_nodepath_update_handles(nodepath);
1711 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1712 }
1714 /**
1715 * Join two nodes by merging them into one.
1716 */
1717 void sp_node_selected_join()
1718 {
1719 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1720 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1722 if (g_list_length(nodepath->selected) != 2) {
1723 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1724 return;
1725 }
1727 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1728 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1730 g_assert(a != b);
1731 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1732 // someone tried to join an orphan node (i.e. a single-node subpath).
1733 // this is not worth an error message, just fail silently.
1734 return;
1735 }
1737 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1738 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1739 return;
1740 }
1742 /* a and b are endpoints */
1744 NR::Point c;
1745 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1746 c = a->pos;
1747 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1748 c = b->pos;
1749 } else {
1750 c = (a->pos + b->pos) / 2;
1751 }
1753 if (a->subpath == b->subpath) {
1754 Inkscape::NodePath::SubPath *sp = a->subpath;
1755 sp_nodepath_subpath_close(sp);
1756 sp_node_moveto (sp->first, c);
1758 sp_nodepath_update_handles(sp->nodepath);
1759 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1760 return;
1761 }
1763 /* a and b are separate subpaths */
1764 Inkscape::NodePath::SubPath *sa = a->subpath;
1765 Inkscape::NodePath::SubPath *sb = b->subpath;
1766 NR::Point p;
1767 Inkscape::NodePath::Node *n;
1768 NRPathcode code;
1769 if (a == sa->first) {
1770 p = sa->first->n.pos;
1771 code = (NRPathcode)sa->first->n.other->code;
1772 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1773 n = sa->last;
1774 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1775 n = n->p.other;
1776 while (n) {
1777 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1778 n = n->p.other;
1779 if (n == sa->first) n = NULL;
1780 }
1781 sp_nodepath_subpath_destroy(sa);
1782 sa = t;
1783 } else if (a == sa->last) {
1784 p = sa->last->p.pos;
1785 code = (NRPathcode)sa->last->code;
1786 sp_nodepath_node_destroy(sa->last);
1787 } else {
1788 code = NR_END;
1789 g_assert_not_reached();
1790 }
1792 if (b == sb->first) {
1793 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1794 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1795 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1796 }
1797 } else if (b == sb->last) {
1798 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1799 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1800 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1801 }
1802 } else {
1803 g_assert_not_reached();
1804 }
1805 /* and now destroy sb */
1807 sp_nodepath_subpath_destroy(sb);
1809 sp_nodepath_update_handles(sa->nodepath);
1811 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1813 sp_nodepath_update_statusbar(nodepath);
1814 }
1816 /**
1817 * Join two nodes by adding a segment between them.
1818 */
1819 void sp_node_selected_join_segment()
1820 {
1821 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1822 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1824 if (g_list_length(nodepath->selected) != 2) {
1825 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1826 return;
1827 }
1829 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1830 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1832 g_assert(a != b);
1833 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1834 // someone tried to join an orphan node (i.e. a single-node subpath).
1835 // this is not worth an error message, just fail silently.
1836 return;
1837 }
1839 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1840 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1841 return;
1842 }
1844 if (a->subpath == b->subpath) {
1845 Inkscape::NodePath::SubPath *sp = a->subpath;
1847 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1848 sp->closed = TRUE;
1850 sp->first->p.other = sp->last;
1851 sp->last->n.other = sp->first;
1853 sp_node_handle_mirror_p_to_n(sp->last);
1854 sp_node_handle_mirror_n_to_p(sp->first);
1856 sp->first->code = sp->last->code;
1857 sp->first = sp->last;
1859 sp_nodepath_update_handles(sp->nodepath);
1861 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1863 return;
1864 }
1866 /* a and b are separate subpaths */
1867 Inkscape::NodePath::SubPath *sa = a->subpath;
1868 Inkscape::NodePath::SubPath *sb = b->subpath;
1870 Inkscape::NodePath::Node *n;
1871 NR::Point p;
1872 NRPathcode code;
1873 if (a == sa->first) {
1874 code = (NRPathcode) sa->first->n.other->code;
1875 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1876 n = sa->last;
1877 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1878 for (n = n->p.other; n != NULL; n = n->p.other) {
1879 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1880 }
1881 sp_nodepath_subpath_destroy(sa);
1882 sa = t;
1883 } else if (a == sa->last) {
1884 code = (NRPathcode)sa->last->code;
1885 } else {
1886 code = NR_END;
1887 g_assert_not_reached();
1888 }
1890 if (b == sb->first) {
1891 n = sb->first;
1892 sp_node_handle_mirror_p_to_n(sa->last);
1893 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1894 sp_node_handle_mirror_n_to_p(sa->last);
1895 for (n = n->n.other; n != NULL; n = n->n.other) {
1896 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1897 }
1898 } else if (b == sb->last) {
1899 n = sb->last;
1900 sp_node_handle_mirror_p_to_n(sa->last);
1901 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1902 sp_node_handle_mirror_n_to_p(sa->last);
1903 for (n = n->p.other; n != NULL; n = n->p.other) {
1904 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1905 }
1906 } else {
1907 g_assert_not_reached();
1908 }
1909 /* and now destroy sb */
1911 sp_nodepath_subpath_destroy(sb);
1913 sp_nodepath_update_handles(sa->nodepath);
1915 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1916 }
1918 /**
1919 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1920 */
1921 void sp_node_delete_preserve(GList *nodes_to_delete)
1922 {
1923 GSList *nodepaths = NULL;
1925 while (nodes_to_delete) {
1926 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1927 Inkscape::NodePath::SubPath *sp = node->subpath;
1928 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1929 Inkscape::NodePath::Node *sample_cursor = NULL;
1930 Inkscape::NodePath::Node *sample_end = NULL;
1931 Inkscape::NodePath::Node *delete_cursor = node;
1932 bool just_delete = false;
1934 //find the start of this contiguous selection
1935 //move left to the first node that is not selected
1936 //or the start of the non-closed path
1937 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1938 delete_cursor = curr;
1939 }
1941 //just delete at the beginning of an open path
1942 if (!delete_cursor->p.other) {
1943 sample_cursor = delete_cursor;
1944 just_delete = true;
1945 } else {
1946 sample_cursor = delete_cursor->p.other;
1947 }
1949 //calculate points for each segment
1950 int rate = 5;
1951 float period = 1.0 / rate;
1952 std::vector<NR::Point> data;
1953 if (!just_delete) {
1954 data.push_back(sample_cursor->pos);
1955 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1956 //just delete at the end of an open path
1957 if (!sp->closed && curr == sp->last) {
1958 just_delete = true;
1959 break;
1960 }
1962 //sample points on the contiguous selected segment
1963 NR::Point *bez;
1964 bez = new NR::Point [4];
1965 bez[0] = curr->pos;
1966 bez[1] = curr->n.pos;
1967 bez[2] = curr->n.other->p.pos;
1968 bez[3] = curr->n.other->pos;
1969 for (int i=1; i<rate; i++) {
1970 gdouble t = i * period;
1971 NR::Point p = bezier_pt(3, bez, t);
1972 data.push_back(p);
1973 }
1974 data.push_back(curr->n.other->pos);
1976 sample_end = curr->n.other;
1977 //break if we've come full circle or hit the end of the selection
1978 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1979 break;
1980 }
1981 }
1982 }
1984 if (!just_delete) {
1985 //calculate the best fitting single segment and adjust the endpoints
1986 NR::Point *adata;
1987 adata = new NR::Point [data.size()];
1988 copy(data.begin(), data.end(), adata);
1990 NR::Point *bez;
1991 bez = new NR::Point [4];
1992 //would decreasing error create a better fitting approximation?
1993 gdouble error = 1.0;
1994 gint ret;
1995 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1997 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1998 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1999 //the resulting nodes behave as expected.
2000 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2001 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2003 //adjust endpoints
2004 sample_cursor->n.pos = bez[1];
2005 sample_end->p.pos = bez[2];
2006 }
2008 //destroy this contiguous selection
2009 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2010 Inkscape::NodePath::Node *temp = delete_cursor;
2011 if (delete_cursor->n.other == delete_cursor) {
2012 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2013 delete_cursor = NULL;
2014 } else {
2015 delete_cursor = delete_cursor->n.other;
2016 }
2017 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2018 sp_nodepath_node_destroy(temp);
2019 }
2021 sp_nodepath_update_handles(nodepath);
2023 if (!g_slist_find(nodepaths, nodepath))
2024 nodepaths = g_slist_prepend (nodepaths, nodepath);
2025 }
2027 for (GSList *i = nodepaths; i; i = i->next) {
2028 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2029 // different nodepaths will give us one undo event per nodepath
2030 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2032 // if the entire nodepath is removed, delete the selected object.
2033 if (nodepath->subpaths == NULL ||
2034 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2035 //at least 2
2036 sp_nodepath_get_node_count(nodepath) < 2) {
2037 SPDocument *document = sp_desktop_document (nodepath->desktop);
2038 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2039 //delete this nodepath's object, not the entire selection! (though at this time, this
2040 //does not matter)
2041 sp_selection_delete();
2042 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2043 _("Delete nodes"));
2044 } else {
2045 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2046 sp_nodepath_update_statusbar(nodepath);
2047 }
2048 }
2050 g_slist_free (nodepaths);
2051 }
2053 /**
2054 * Delete one or more selected nodes.
2055 */
2056 void sp_node_selected_delete()
2057 {
2058 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2059 if (!nodepath) return;
2060 if (!nodepath->selected) return;
2062 /** \todo fixme: do it the right way */
2063 while (nodepath->selected) {
2064 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2065 sp_nodepath_node_destroy(node);
2066 }
2069 //clean up the nodepath (such as for trivial subpaths)
2070 sp_nodepath_cleanup(nodepath);
2072 sp_nodepath_update_handles(nodepath);
2074 // if the entire nodepath is removed, delete the selected object.
2075 if (nodepath->subpaths == NULL ||
2076 sp_nodepath_get_node_count(nodepath) < 2) {
2077 SPDocument *document = sp_desktop_document (nodepath->desktop);
2078 sp_selection_delete();
2079 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2080 _("Delete nodes"));
2081 return;
2082 }
2084 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2086 sp_nodepath_update_statusbar(nodepath);
2087 }
2089 /**
2090 * Delete one or more segments between two selected nodes.
2091 * This is the code for 'split'.
2092 */
2093 void
2094 sp_node_selected_delete_segment(void)
2095 {
2096 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2097 Inkscape::NodePath::Node *curr, *next; //Iterators
2099 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2100 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2102 if (g_list_length(nodepath->selected) != 2) {
2103 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2104 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2105 return;
2106 }
2108 //Selected nodes, not inclusive
2109 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2110 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2112 if ( ( a==b) || //same node
2113 (a->subpath != b->subpath ) || //not the same path
2114 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2115 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2116 {
2117 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2118 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2119 return;
2120 }
2122 //###########################################
2123 //# BEGIN EDITS
2124 //###########################################
2125 //##################################
2126 //# CLOSED PATH
2127 //##################################
2128 if (a->subpath->closed) {
2131 gboolean reversed = FALSE;
2133 //Since we can go in a circle, we need to find the shorter distance.
2134 // a->b or b->a
2135 start = end = NULL;
2136 int distance = 0;
2137 int minDistance = 0;
2138 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2139 if (curr==b) {
2140 //printf("a to b:%d\n", distance);
2141 start = a;//go from a to b
2142 end = b;
2143 minDistance = distance;
2144 //printf("A to B :\n");
2145 break;
2146 }
2147 distance++;
2148 }
2150 //try again, the other direction
2151 distance = 0;
2152 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2153 if (curr==a) {
2154 //printf("b to a:%d\n", distance);
2155 if (distance < minDistance) {
2156 start = b; //we go from b to a
2157 end = a;
2158 reversed = TRUE;
2159 //printf("B to A\n");
2160 }
2161 break;
2162 }
2163 distance++;
2164 }
2167 //Copy everything from 'end' to 'start' to a new subpath
2168 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2169 for (curr=end ; curr ; curr=curr->n.other) {
2170 NRPathcode code = (NRPathcode) curr->code;
2171 if (curr == end)
2172 code = NR_MOVETO;
2173 sp_nodepath_node_new(t, NULL,
2174 (Inkscape::NodePath::NodeType)curr->type, code,
2175 &curr->p.pos, &curr->pos, &curr->n.pos);
2176 if (curr == start)
2177 break;
2178 }
2179 sp_nodepath_subpath_destroy(a->subpath);
2182 }
2186 //##################################
2187 //# OPEN PATH
2188 //##################################
2189 else {
2191 //We need to get the direction of the list between A and B
2192 //Can we walk from a to b?
2193 start = end = NULL;
2194 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2195 if (curr==b) {
2196 start = a; //did it! we go from a to b
2197 end = b;
2198 //printf("A to B\n");
2199 break;
2200 }
2201 }
2202 if (!start) {//didn't work? let's try the other direction
2203 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2204 if (curr==a) {
2205 start = b; //did it! we go from b to a
2206 end = a;
2207 //printf("B to A\n");
2208 break;
2209 }
2210 }
2211 }
2212 if (!start) {
2213 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2214 _("Cannot find path between nodes."));
2215 return;
2216 }
2220 //Copy everything after 'end' to a new subpath
2221 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2222 for (curr=end ; curr ; curr=curr->n.other) {
2223 NRPathcode code = (NRPathcode) curr->code;
2224 if (curr == end)
2225 code = NR_MOVETO;
2226 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2227 &curr->p.pos, &curr->pos, &curr->n.pos);
2228 }
2230 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2231 for (curr = start->n.other ; curr ; curr=next) {
2232 next = curr->n.other;
2233 sp_nodepath_node_destroy(curr);
2234 }
2236 }
2237 //###########################################
2238 //# END EDITS
2239 //###########################################
2241 //clean up the nodepath (such as for trivial subpaths)
2242 sp_nodepath_cleanup(nodepath);
2244 sp_nodepath_update_handles(nodepath);
2246 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2248 sp_nodepath_update_statusbar(nodepath);
2249 }
2251 /**
2252 * Call sp_nodepath_set_line() for all selected segments.
2253 */
2254 void
2255 sp_node_selected_set_line_type(NRPathcode code)
2256 {
2257 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2258 if (nodepath == NULL) return;
2260 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2261 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2262 g_assert(n->selected);
2263 if (n->p.other && n->p.other->selected) {
2264 sp_nodepath_set_line_type(n, code);
2265 }
2266 }
2268 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2269 }
2271 /**
2272 * Call sp_nodepath_convert_node_type() for all selected nodes.
2273 */
2274 void
2275 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2276 {
2277 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2278 if (nodepath == NULL) return;
2280 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2281 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2282 }
2284 sp_nodepath_update_repr(nodepath, _("Change node type"));
2285 }
2287 /**
2288 * Change select status of node, update its own and neighbour handles.
2289 */
2290 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2291 {
2292 node->selected = selected;
2294 if (selected) {
2295 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2296 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2297 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2298 sp_knot_update_ctrl(node->knot);
2299 } else {
2300 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2301 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2302 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2303 sp_knot_update_ctrl(node->knot);
2304 }
2306 sp_node_update_handles(node);
2307 if (node->n.other) sp_node_update_handles(node->n.other);
2308 if (node->p.other) sp_node_update_handles(node->p.other);
2309 }
2311 /**
2312 \brief Select a node
2313 \param node The node to select
2314 \param incremental If true, add to selection, otherwise deselect others
2315 \param override If true, always select this node, otherwise toggle selected status
2316 */
2317 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2318 {
2319 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2321 if (incremental) {
2322 if (override) {
2323 if (!g_list_find(nodepath->selected, node)) {
2324 nodepath->selected = g_list_prepend(nodepath->selected, node);
2325 }
2326 sp_node_set_selected(node, TRUE);
2327 } else { // toggle
2328 if (node->selected) {
2329 g_assert(g_list_find(nodepath->selected, node));
2330 nodepath->selected = g_list_remove(nodepath->selected, node);
2331 } else {
2332 g_assert(!g_list_find(nodepath->selected, node));
2333 nodepath->selected = g_list_prepend(nodepath->selected, node);
2334 }
2335 sp_node_set_selected(node, !node->selected);
2336 }
2337 } else {
2338 sp_nodepath_deselect(nodepath);
2339 nodepath->selected = g_list_prepend(nodepath->selected, node);
2340 sp_node_set_selected(node, TRUE);
2341 }
2343 sp_nodepath_update_statusbar(nodepath);
2344 }
2347 /**
2348 \brief Deselect all nodes in the nodepath
2349 */
2350 void
2351 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2352 {
2353 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2355 while (nodepath->selected) {
2356 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2357 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2358 }
2359 sp_nodepath_update_statusbar(nodepath);
2360 }
2362 /**
2363 \brief Select or invert selection of all nodes in the nodepath
2364 */
2365 void
2366 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2367 {
2368 if (!nodepath) return;
2370 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2371 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2372 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2373 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2374 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2375 }
2376 }
2377 }
2379 /**
2380 * If nothing selected, does the same as sp_nodepath_select_all();
2381 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2382 * (i.e., similar to "select all in layer", with the "selected" subpaths
2383 * being treated as "layers" in the path).
2384 */
2385 void
2386 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2387 {
2388 if (!nodepath) return;
2390 if (g_list_length (nodepath->selected) == 0) {
2391 sp_nodepath_select_all (nodepath, invert);
2392 return;
2393 }
2395 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2396 GSList *subpaths = NULL;
2398 for (GList *l = copy; l != NULL; l = l->next) {
2399 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2400 Inkscape::NodePath::SubPath *subpath = n->subpath;
2401 if (!g_slist_find (subpaths, subpath))
2402 subpaths = g_slist_prepend (subpaths, subpath);
2403 }
2405 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2406 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2407 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2408 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2409 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2410 }
2411 }
2413 g_slist_free (subpaths);
2414 g_list_free (copy);
2415 }
2417 /**
2418 * \brief Select the node after the last selected; if none is selected,
2419 * select the first within path.
2420 */
2421 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2422 {
2423 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2425 Inkscape::NodePath::Node *last = NULL;
2426 if (nodepath->selected) {
2427 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2428 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2429 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2430 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2431 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2432 if (node->selected) {
2433 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2434 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2435 if (spl->next) { // there's a next subpath
2436 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2437 last = subpath_next->first;
2438 } else if (spl->prev) { // there's a previous subpath
2439 last = NULL; // to be set later to the first node of first subpath
2440 } else {
2441 last = node->n.other;
2442 }
2443 } else {
2444 last = node->n.other;
2445 }
2446 } else {
2447 if (node->n.other) {
2448 last = node->n.other;
2449 } else {
2450 if (spl->next) { // there's a next subpath
2451 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2452 last = subpath_next->first;
2453 } else if (spl->prev) { // there's a previous subpath
2454 last = NULL; // to be set later to the first node of first subpath
2455 } else {
2456 last = (Inkscape::NodePath::Node *) subpath->first;
2457 }
2458 }
2459 }
2460 }
2461 }
2462 }
2463 sp_nodepath_deselect(nodepath);
2464 }
2466 if (last) { // there's at least one more node after selected
2467 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2468 } else { // no more nodes, select the first one in first subpath
2469 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2470 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2471 }
2472 }
2474 /**
2475 * \brief Select the node before the first selected; if none is selected,
2476 * select the last within path
2477 */
2478 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2479 {
2480 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2482 Inkscape::NodePath::Node *last = NULL;
2483 if (nodepath->selected) {
2484 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2485 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2486 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2487 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2488 if (node->selected) {
2489 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2490 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2491 if (spl->prev) { // there's a prev subpath
2492 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2493 last = subpath_prev->last;
2494 } else if (spl->next) { // there's a next subpath
2495 last = NULL; // to be set later to the last node of last subpath
2496 } else {
2497 last = node->p.other;
2498 }
2499 } else {
2500 last = node->p.other;
2501 }
2502 } else {
2503 if (node->p.other) {
2504 last = node->p.other;
2505 } else {
2506 if (spl->prev) { // there's a prev subpath
2507 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2508 last = subpath_prev->last;
2509 } else if (spl->next) { // there's a next subpath
2510 last = NULL; // to be set later to the last node of last subpath
2511 } else {
2512 last = (Inkscape::NodePath::Node *) subpath->last;
2513 }
2514 }
2515 }
2516 }
2517 }
2518 }
2519 sp_nodepath_deselect(nodepath);
2520 }
2522 if (last) { // there's at least one more node before selected
2523 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2524 } else { // no more nodes, select the last one in last subpath
2525 GList *spl = g_list_last(nodepath->subpaths);
2526 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2527 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2528 }
2529 }
2531 /**
2532 * \brief Select all nodes that are within the rectangle.
2533 */
2534 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2535 {
2536 if (!incremental) {
2537 sp_nodepath_deselect(nodepath);
2538 }
2540 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2541 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2542 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2543 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2545 if (b.contains(node->pos)) {
2546 sp_nodepath_node_select(node, TRUE, TRUE);
2547 }
2548 }
2549 }
2550 }
2553 void
2554 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2555 {
2556 g_assert (n);
2557 g_assert (nodepath);
2558 g_assert (n->subpath->nodepath == nodepath);
2560 if (g_list_length (nodepath->selected) == 0) {
2561 if (grow > 0) {
2562 sp_nodepath_node_select(n, TRUE, TRUE);
2563 }
2564 return;
2565 }
2567 if (g_list_length (nodepath->selected) == 1) {
2568 if (grow < 0) {
2569 sp_nodepath_deselect (nodepath);
2570 return;
2571 }
2572 }
2574 double n_sel_range = 0, p_sel_range = 0;
2575 Inkscape::NodePath::Node *farthest_n_node = n;
2576 Inkscape::NodePath::Node *farthest_p_node = n;
2578 // Calculate ranges
2579 {
2580 double n_range = 0, p_range = 0;
2581 bool n_going = true, p_going = true;
2582 Inkscape::NodePath::Node *n_node = n;
2583 Inkscape::NodePath::Node *p_node = n;
2584 do {
2585 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2586 if (n_node && n_going)
2587 n_node = n_node->n.other;
2588 if (n_node == NULL) {
2589 n_going = false;
2590 } else {
2591 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2592 if (n_node->selected) {
2593 n_sel_range = n_range;
2594 farthest_n_node = n_node;
2595 }
2596 if (n_node == p_node) {
2597 n_going = false;
2598 p_going = false;
2599 }
2600 }
2601 if (p_node && p_going)
2602 p_node = p_node->p.other;
2603 if (p_node == NULL) {
2604 p_going = false;
2605 } else {
2606 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2607 if (p_node->selected) {
2608 p_sel_range = p_range;
2609 farthest_p_node = p_node;
2610 }
2611 if (p_node == n_node) {
2612 n_going = false;
2613 p_going = false;
2614 }
2615 }
2616 } while (n_going || p_going);
2617 }
2619 if (grow > 0) {
2620 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2621 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2622 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2623 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2624 }
2625 } else {
2626 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2627 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2628 } else if (farthest_p_node && farthest_p_node->selected) {
2629 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2630 }
2631 }
2632 }
2634 void
2635 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2636 {
2637 g_assert (n);
2638 g_assert (nodepath);
2639 g_assert (n->subpath->nodepath == nodepath);
2641 if (g_list_length (nodepath->selected) == 0) {
2642 if (grow > 0) {
2643 sp_nodepath_node_select(n, TRUE, TRUE);
2644 }
2645 return;
2646 }
2648 if (g_list_length (nodepath->selected) == 1) {
2649 if (grow < 0) {
2650 sp_nodepath_deselect (nodepath);
2651 return;
2652 }
2653 }
2655 Inkscape::NodePath::Node *farthest_selected = NULL;
2656 double farthest_dist = 0;
2658 Inkscape::NodePath::Node *closest_unselected = NULL;
2659 double closest_dist = NR_HUGE;
2661 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2662 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2663 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2664 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2665 if (node == n)
2666 continue;
2667 if (node->selected) {
2668 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2669 farthest_dist = NR::L2(node->pos - n->pos);
2670 farthest_selected = node;
2671 }
2672 } else {
2673 if (NR::L2(node->pos - n->pos) < closest_dist) {
2674 closest_dist = NR::L2(node->pos - n->pos);
2675 closest_unselected = node;
2676 }
2677 }
2678 }
2679 }
2681 if (grow > 0) {
2682 if (closest_unselected) {
2683 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2684 }
2685 } else {
2686 if (farthest_selected) {
2687 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2688 }
2689 }
2690 }
2693 /**
2694 \brief Saves all nodes' and handles' current positions in their origin members
2695 */
2696 void
2697 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2698 {
2699 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2700 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2701 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2702 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2703 n->origin = n->pos;
2704 n->p.origin = n->p.pos;
2705 n->n.origin = n->n.pos;
2706 }
2707 }
2708 }
2710 /**
2711 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2712 */
2713 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2714 {
2715 if (!nodepath->selected) {
2716 return NULL;
2717 }
2719 GList *r = NULL;
2720 guint i = 0;
2721 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2722 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2723 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2724 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2725 i++;
2726 if (node->selected) {
2727 r = g_list_append(r, GINT_TO_POINTER(i));
2728 }
2729 }
2730 }
2731 return r;
2732 }
2734 /**
2735 \brief Restores selection by selecting nodes whose positions are in the list
2736 */
2737 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2738 {
2739 sp_nodepath_deselect(nodepath);
2741 guint i = 0;
2742 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2743 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2744 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2745 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2746 i++;
2747 if (g_list_find(r, GINT_TO_POINTER(i))) {
2748 sp_nodepath_node_select(node, TRUE, TRUE);
2749 }
2750 }
2751 }
2753 }
2755 /**
2756 \brief Adjusts handle according to node type and line code.
2757 */
2758 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2759 {
2760 double len, otherlen, linelen;
2762 g_assert(node);
2764 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2765 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2767 /** \todo fixme: */
2768 if (me->other == NULL) return;
2769 if (other->other == NULL) return;
2771 /* I have line */
2773 NRPathcode mecode, ocode;
2774 if (which_adjust == 1) {
2775 mecode = (NRPathcode)me->other->code;
2776 ocode = (NRPathcode)node->code;
2777 } else {
2778 mecode = (NRPathcode)node->code;
2779 ocode = (NRPathcode)other->other->code;
2780 }
2782 if (mecode == NR_LINETO) return;
2784 /* I am curve */
2786 if (other->other == NULL) return;
2788 /* Other has line */
2790 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2792 NR::Point delta;
2793 if (ocode == NR_LINETO) {
2794 /* other is lineto, we are either smooth or symm */
2795 Inkscape::NodePath::Node *othernode = other->other;
2796 len = NR::L2(me->pos - node->pos);
2797 delta = node->pos - othernode->pos;
2798 linelen = NR::L2(delta);
2799 if (linelen < 1e-18)
2800 return;
2801 me->pos = node->pos + (len / linelen)*delta;
2802 return;
2803 }
2805 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2807 me->pos = 2 * node->pos - other->pos;
2808 return;
2809 }
2811 /* We are smooth */
2813 len = NR::L2(me->pos - node->pos);
2814 delta = other->pos - node->pos;
2815 otherlen = NR::L2(delta);
2816 if (otherlen < 1e-18) return;
2818 me->pos = node->pos - (len / otherlen) * delta;
2819 }
2821 /**
2822 \brief Adjusts both handles according to node type and line code
2823 */
2824 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2825 {
2826 g_assert(node);
2828 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2830 /* we are either smooth or symm */
2832 if (node->p.other == NULL) return;
2834 if (node->n.other == NULL) return;
2836 if (node->code == NR_LINETO) {
2837 if (node->n.other->code == NR_LINETO) return;
2838 sp_node_adjust_handle(node, 1);
2839 return;
2840 }
2842 if (node->n.other->code == NR_LINETO) {
2843 if (node->code == NR_LINETO) return;
2844 sp_node_adjust_handle(node, -1);
2845 return;
2846 }
2848 /* both are curves */
2849 NR::Point const delta( node->n.pos - node->p.pos );
2851 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2852 node->p.pos = node->pos - delta / 2;
2853 node->n.pos = node->pos + delta / 2;
2854 return;
2855 }
2857 /* We are smooth */
2858 double plen = NR::L2(node->p.pos - node->pos);
2859 if (plen < 1e-18) return;
2860 double nlen = NR::L2(node->n.pos - node->pos);
2861 if (nlen < 1e-18) return;
2862 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2863 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2864 }
2866 /**
2867 * Node event callback.
2868 */
2869 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2870 {
2871 gboolean ret = FALSE;
2872 switch (event->type) {
2873 case GDK_ENTER_NOTIFY:
2874 active_node = n;
2875 break;
2876 case GDK_LEAVE_NOTIFY:
2877 active_node = NULL;
2878 break;
2879 case GDK_SCROLL:
2880 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2881 switch (event->scroll.direction) {
2882 case GDK_SCROLL_UP:
2883 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2884 break;
2885 case GDK_SCROLL_DOWN:
2886 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2887 break;
2888 default:
2889 break;
2890 }
2891 ret = TRUE;
2892 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2893 switch (event->scroll.direction) {
2894 case GDK_SCROLL_UP:
2895 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2896 break;
2897 case GDK_SCROLL_DOWN:
2898 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2899 break;
2900 default:
2901 break;
2902 }
2903 ret = TRUE;
2904 }
2905 break;
2906 case GDK_KEY_PRESS:
2907 switch (get_group0_keyval (&event->key)) {
2908 case GDK_space:
2909 if (event->key.state & GDK_BUTTON1_MASK) {
2910 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2911 stamp_repr(nodepath);
2912 ret = TRUE;
2913 }
2914 break;
2915 case GDK_Page_Up:
2916 if (event->key.state & GDK_CONTROL_MASK) {
2917 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2918 } else {
2919 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2920 }
2921 break;
2922 case GDK_Page_Down:
2923 if (event->key.state & GDK_CONTROL_MASK) {
2924 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2925 } else {
2926 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2927 }
2928 break;
2929 default:
2930 break;
2931 }
2932 break;
2933 default:
2934 break;
2935 }
2937 return ret;
2938 }
2940 /**
2941 * Handle keypress on node; directly called.
2942 */
2943 gboolean node_key(GdkEvent *event)
2944 {
2945 Inkscape::NodePath::Path *np;
2947 // there is no way to verify nodes so set active_node to nil when deleting!!
2948 if (active_node == NULL) return FALSE;
2950 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2951 gint ret = FALSE;
2952 switch (get_group0_keyval (&event->key)) {
2953 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2954 case GDK_BackSpace:
2955 np = active_node->subpath->nodepath;
2956 sp_nodepath_node_destroy(active_node);
2957 sp_nodepath_update_repr(np, _("Delete node"));
2958 active_node = NULL;
2959 ret = TRUE;
2960 break;
2961 case GDK_c:
2962 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2963 ret = TRUE;
2964 break;
2965 case GDK_s:
2966 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2967 ret = TRUE;
2968 break;
2969 case GDK_y:
2970 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2971 ret = TRUE;
2972 break;
2973 case GDK_b:
2974 sp_nodepath_node_break(active_node);
2975 ret = TRUE;
2976 break;
2977 }
2978 return ret;
2979 }
2980 return FALSE;
2981 }
2983 /**
2984 * Mouseclick on node callback.
2985 */
2986 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2987 {
2988 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2990 if (state & GDK_CONTROL_MASK) {
2991 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2993 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2994 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2995 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2996 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2997 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2998 } else {
2999 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3000 }
3001 sp_nodepath_update_repr(nodepath, _("Change node type"));
3002 sp_nodepath_update_statusbar(nodepath);
3004 } else { //ctrl+alt+click: delete node
3005 GList *node_to_delete = NULL;
3006 node_to_delete = g_list_append(node_to_delete, n);
3007 sp_node_delete_preserve(node_to_delete);
3008 }
3010 } else {
3011 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3012 }
3013 }
3015 /**
3016 * Mouse grabbed node callback.
3017 */
3018 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3019 {
3020 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3022 if (!n->selected) {
3023 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3024 }
3026 n->is_dragging = true;
3027 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3029 sp_nodepath_remember_origins (n->subpath->nodepath);
3030 }
3032 /**
3033 * Mouse ungrabbed node callback.
3034 */
3035 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3036 {
3037 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3039 n->dragging_out = NULL;
3040 n->is_dragging = false;
3041 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3043 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3044 }
3046 /**
3047 * The point on a line, given by its angle, closest to the given point.
3048 * \param p A point.
3049 * \param a Angle of the line; it is assumed to go through coordinate origin.
3050 * \param closest Pointer to the point struct where the result is stored.
3051 * \todo FIXME: use dot product perhaps?
3052 */
3053 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3054 {
3055 if (a == HUGE_VAL) { // vertical
3056 *closest = NR::Point(0, (*p)[NR::Y]);
3057 } else {
3058 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3059 (*closest)[NR::Y] = a * (*closest)[NR::X];
3060 }
3061 }
3063 /**
3064 * Distance from the point to a line given by its angle.
3065 * \param p A point.
3066 * \param a Angle of the line; it is assumed to go through coordinate origin.
3067 */
3068 static double point_line_distance(NR::Point *p, double a)
3069 {
3070 NR::Point c;
3071 point_line_closest(p, a, &c);
3072 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]));
3073 }
3075 /**
3076 * Callback for node "request" signal.
3077 * \todo fixme: This goes to "moved" event? (lauris)
3078 */
3079 static gboolean
3080 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3081 {
3082 double yn, xn, yp, xp;
3083 double an, ap, na, pa;
3084 double d_an, d_ap, d_na, d_pa;
3085 gboolean collinear = FALSE;
3086 NR::Point c;
3087 NR::Point pr;
3089 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3091 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3092 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3094 NR::Point mouse = (*p);
3096 if (!n->dragging_out) {
3097 // This is the first drag-out event; find out which handle to drag out
3098 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3099 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3101 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3102 return FALSE;
3104 Inkscape::NodePath::NodeSide *opposite;
3105 if (appr_p > appr_n) { // closer to p
3106 n->dragging_out = &n->p;
3107 opposite = &n->n;
3108 n->code = NR_CURVETO;
3109 } else if (appr_p < appr_n) { // closer to n
3110 n->dragging_out = &n->n;
3111 opposite = &n->p;
3112 n->n.other->code = NR_CURVETO;
3113 } else { // p and n nodes are the same
3114 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3115 n->dragging_out = &n->p;
3116 opposite = &n->n;
3117 n->code = NR_CURVETO;
3118 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3119 n->dragging_out = &n->n;
3120 opposite = &n->p;
3121 n->n.other->code = NR_CURVETO;
3122 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3123 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);
3124 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);
3125 if (appr_other_p > appr_other_n) { // closer to other's p handle
3126 n->dragging_out = &n->n;
3127 opposite = &n->p;
3128 n->n.other->code = NR_CURVETO;
3129 } else { // closer to other's n handle
3130 n->dragging_out = &n->p;
3131 opposite = &n->n;
3132 n->code = NR_CURVETO;
3133 }
3134 }
3135 }
3137 // if there's another handle, make sure the one we drag out starts parallel to it
3138 if (opposite->pos != n->pos) {
3139 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3140 }
3142 // knots might not be created yet!
3143 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3144 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3145 }
3147 // pass this on to the handle-moved callback
3148 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3149 sp_node_update_handles(n);
3150 return TRUE;
3151 }
3153 if (state & GDK_CONTROL_MASK) { // constrained motion
3155 // calculate relative distances of handles
3156 // n handle:
3157 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3158 xn = n->n.pos[NR::X] - n->pos[NR::X];
3159 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3160 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3161 if (n->n.other) { // if there is the next point
3162 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3163 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3164 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3165 }
3166 }
3167 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3168 if (yn < 0) { xn = -xn; yn = -yn; }
3170 // p handle:
3171 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3172 xp = n->p.pos[NR::X] - n->pos[NR::X];
3173 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3174 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3175 if (n->p.other) {
3176 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3177 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3178 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3179 }
3180 }
3181 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3182 if (yp < 0) { xp = -xp; yp = -yp; }
3184 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3185 // sliding on handles, only if at least one of the handles is non-vertical
3186 // (otherwise it's the same as ctrl+drag anyway)
3188 // calculate angles of the handles
3189 if (xn == 0) {
3190 if (yn == 0) { // no handle, consider it the continuation of the other one
3191 an = 0;
3192 collinear = TRUE;
3193 }
3194 else an = 0; // vertical; set the angle to horizontal
3195 } else an = yn/xn;
3197 if (xp == 0) {
3198 if (yp == 0) { // no handle, consider it the continuation of the other one
3199 ap = an;
3200 }
3201 else ap = 0; // vertical; set the angle to horizontal
3202 } else ap = yp/xp;
3204 if (collinear) an = ap;
3206 // angles of the perpendiculars; HUGE_VAL means vertical
3207 if (an == 0) na = HUGE_VAL; else na = -1/an;
3208 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3210 // mouse point relative to the node's original pos
3211 pr = (*p) - n->origin;
3213 // distances to the four lines (two handles and two perpendiculars)
3214 d_an = point_line_distance(&pr, an);
3215 d_na = point_line_distance(&pr, na);
3216 d_ap = point_line_distance(&pr, ap);
3217 d_pa = point_line_distance(&pr, pa);
3219 // find out which line is the closest, save its closest point in c
3220 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3221 point_line_closest(&pr, an, &c);
3222 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3223 point_line_closest(&pr, ap, &c);
3224 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3225 point_line_closest(&pr, na, &c);
3226 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3227 point_line_closest(&pr, pa, &c);
3228 }
3230 // move the node to the closest point
3231 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3232 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3233 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3235 } else { // constraining to hor/vert
3237 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3238 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3239 } else { // snap to vert
3240 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3241 }
3242 }
3243 } else { // move freely
3244 if (n->is_dragging) {
3245 if (state & GDK_MOD1_MASK) { // sculpt
3246 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3247 } else {
3248 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3249 (*p)[NR::X] - n->pos[NR::X],
3250 (*p)[NR::Y] - n->pos[NR::Y],
3251 (state & GDK_SHIFT_MASK) == 0);
3252 }
3253 }
3254 }
3256 n->subpath->nodepath->desktop->scroll_to_point(p);
3258 return TRUE;
3259 }
3261 /**
3262 * Node handle clicked callback.
3263 */
3264 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3265 {
3266 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3268 if (state & GDK_CONTROL_MASK) { // "delete" handle
3269 if (n->p.knot == knot) {
3270 n->p.pos = n->pos;
3271 } else if (n->n.knot == knot) {
3272 n->n.pos = n->pos;
3273 }
3274 sp_node_update_handles(n);
3275 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3276 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3277 sp_nodepath_update_statusbar(nodepath);
3279 } else { // just select or add to selection, depending in Shift
3280 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3281 }
3282 }
3284 /**
3285 * Node handle grabbed callback.
3286 */
3287 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3288 {
3289 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3291 if (!n->selected) {
3292 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3293 }
3295 // remember the origin point of the handle
3296 if (n->p.knot == knot) {
3297 n->p.origin_radial = n->p.pos - n->pos;
3298 } else if (n->n.knot == knot) {
3299 n->n.origin_radial = n->n.pos - n->pos;
3300 } else {
3301 g_assert_not_reached();
3302 }
3304 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3305 }
3307 /**
3308 * Node handle ungrabbed callback.
3309 */
3310 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3311 {
3312 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3314 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3315 if (n->p.knot == knot) {
3316 n->p.origin_radial.a = 0;
3317 sp_knot_set_position(knot, &n->p.pos, state);
3318 } else if (n->n.knot == knot) {
3319 n->n.origin_radial.a = 0;
3320 sp_knot_set_position(knot, &n->n.pos, state);
3321 } else {
3322 g_assert_not_reached();
3323 }
3325 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3326 }
3328 /**
3329 * Node handle "request" signal callback.
3330 */
3331 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3332 {
3333 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3335 Inkscape::NodePath::NodeSide *me, *opposite;
3336 gint which;
3337 if (n->p.knot == knot) {
3338 me = &n->p;
3339 opposite = &n->n;
3340 which = -1;
3341 } else if (n->n.knot == knot) {
3342 me = &n->n;
3343 opposite = &n->p;
3344 which = 1;
3345 } else {
3346 me = opposite = NULL;
3347 which = 0;
3348 g_assert_not_reached();
3349 }
3351 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3353 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3355 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3356 /* We are smooth node adjacent with line */
3357 NR::Point const delta = *p - n->pos;
3358 NR::Coord const len = NR::L2(delta);
3359 Inkscape::NodePath::Node *othernode = opposite->other;
3360 NR::Point const ndelta = n->pos - othernode->pos;
3361 NR::Coord const linelen = NR::L2(ndelta);
3362 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3363 NR::Coord const scal = dot(delta, ndelta) / linelen;
3364 (*p) = n->pos + (scal / linelen) * ndelta;
3365 }
3366 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3367 } else {
3368 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3369 }
3371 sp_node_adjust_handle(n, -which);
3373 return FALSE;
3374 }
3376 /**
3377 * Node handle moved callback.
3378 */
3379 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3380 {
3381 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3383 Inkscape::NodePath::NodeSide *me;
3384 Inkscape::NodePath::NodeSide *other;
3385 if (n->p.knot == knot) {
3386 me = &n->p;
3387 other = &n->n;
3388 } else if (n->n.knot == knot) {
3389 me = &n->n;
3390 other = &n->p;
3391 } else {
3392 me = NULL;
3393 other = NULL;
3394 g_assert_not_reached();
3395 }
3397 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3398 Radial rme(me->pos - n->pos);
3399 Radial rother(other->pos - n->pos);
3400 Radial rnew(*p - n->pos);
3402 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3403 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3404 /* 0 interpreted as "no snapping". */
3406 // The closest PI/snaps angle, starting from zero.
3407 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3408 if (me->origin_radial.a == HUGE_VAL) {
3409 // ortho doesn't exist: original handle was zero length.
3410 rnew.a = a_snapped;
3411 } else {
3412 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3413 * its opposite and perpendiculars). */
3414 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3416 // Snap to the closest.
3417 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3418 ? a_snapped
3419 : a_ortho );
3420 }
3421 }
3423 if (state & GDK_MOD1_MASK) {
3424 // lock handle length
3425 rnew.r = me->origin_radial.r;
3426 }
3428 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3429 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3430 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3431 rother.a += rnew.a - rme.a;
3432 other->pos = NR::Point(rother) + n->pos;
3433 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3434 sp_knot_set_position(other->knot, &other->pos, 0);
3435 }
3437 me->pos = NR::Point(rnew) + n->pos;
3438 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3440 // this is what sp_knot_set_position does, but without emitting the signal:
3441 // we cannot emit a "moved" signal because we're now processing it
3442 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3444 knot->desktop->set_coordinate_status(me->pos);
3446 update_object(n->subpath->nodepath);
3448 /* status text */
3449 SPDesktop *desktop = n->subpath->nodepath->desktop;
3450 if (!desktop) return;
3451 SPEventContext *ec = desktop->event_context;
3452 if (!ec) return;
3453 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3454 if (!mc) return;
3456 double degrees = 180 / M_PI * rnew.a;
3457 if (degrees > 180) degrees -= 360;
3458 if (degrees < -180) degrees += 360;
3459 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3460 degrees = angle_to_compass (degrees);
3462 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3464 mc->setF(Inkscape::NORMAL_MESSAGE,
3465 _("<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);
3467 g_string_free(length, TRUE);
3468 }
3470 /**
3471 * Node handle event callback.
3472 */
3473 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3474 {
3475 gboolean ret = FALSE;
3476 switch (event->type) {
3477 case GDK_KEY_PRESS:
3478 switch (get_group0_keyval (&event->key)) {
3479 case GDK_space:
3480 if (event->key.state & GDK_BUTTON1_MASK) {
3481 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3482 stamp_repr(nodepath);
3483 ret = TRUE;
3484 }
3485 break;
3486 default:
3487 break;
3488 }
3489 break;
3490 default:
3491 break;
3492 }
3494 return ret;
3495 }
3497 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3498 Radial &rme, Radial &rother, gboolean const both)
3499 {
3500 rme.a += angle;
3501 if ( both
3502 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3503 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3504 {
3505 rother.a += angle;
3506 }
3507 }
3509 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3510 Radial &rme, Radial &rother, gboolean const both)
3511 {
3512 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3514 gdouble r;
3515 if ( both
3516 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3517 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3518 {
3519 r = MAX(rme.r, rother.r);
3520 } else {
3521 r = rme.r;
3522 }
3524 gdouble const weird_angle = atan2(norm_angle, r);
3525 /* Bulia says norm_angle is just the visible distance that the
3526 * object's end must travel on the screen. Left as 'angle' for want of
3527 * a better name.*/
3529 rme.a += weird_angle;
3530 if ( both
3531 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3532 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3533 {
3534 rother.a += weird_angle;
3535 }
3536 }
3538 /**
3539 * Rotate one node.
3540 */
3541 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3542 {
3543 Inkscape::NodePath::NodeSide *me, *other;
3544 bool both = false;
3546 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3547 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3549 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3550 me = &(n->p);
3551 other = &(n->n);
3552 } else if (!n->p.other) {
3553 me = &(n->n);
3554 other = &(n->p);
3555 } else {
3556 if (which > 0) { // right handle
3557 if (xn > xp) {
3558 me = &(n->n);
3559 other = &(n->p);
3560 } else {
3561 me = &(n->p);
3562 other = &(n->n);
3563 }
3564 } else if (which < 0){ // left handle
3565 if (xn <= xp) {
3566 me = &(n->n);
3567 other = &(n->p);
3568 } else {
3569 me = &(n->p);
3570 other = &(n->n);
3571 }
3572 } else { // both handles
3573 me = &(n->n);
3574 other = &(n->p);
3575 both = true;
3576 }
3577 }
3579 Radial rme(me->pos - n->pos);
3580 Radial rother(other->pos - n->pos);
3582 if (screen) {
3583 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3584 } else {
3585 node_rotate_one_internal (*n, angle, rme, rother, both);
3586 }
3588 me->pos = n->pos + NR::Point(rme);
3590 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3591 other->pos = n->pos + NR::Point(rother);
3592 }
3594 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3595 // so here we just move all the knots without emitting move signals, for speed
3596 sp_node_update_handles(n, false);
3597 }
3599 /**
3600 * Rotate selected nodes.
3601 */
3602 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3603 {
3604 if (!nodepath || !nodepath->selected) return;
3606 if (g_list_length(nodepath->selected) == 1) {
3607 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3608 node_rotate_one (n, angle, which, screen);
3609 } else {
3610 // rotate as an object:
3612 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3613 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3614 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3615 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3616 box.expandTo (n->pos); // contain all selected nodes
3617 }
3619 gdouble rot;
3620 if (screen) {
3621 gdouble const zoom = nodepath->desktop->current_zoom();
3622 gdouble const zmove = angle / zoom;
3623 gdouble const r = NR::L2(box.max() - box.midpoint());
3624 rot = atan2(zmove, r);
3625 } else {
3626 rot = angle;
3627 }
3629 NR::Matrix t =
3630 NR::Matrix (NR::translate(-box.midpoint())) *
3631 NR::Matrix (NR::rotate(rot)) *
3632 NR::Matrix (NR::translate(box.midpoint()));
3634 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3635 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3636 n->pos *= t;
3637 n->n.pos *= t;
3638 n->p.pos *= t;
3639 sp_node_update_handles(n, false);
3640 }
3641 }
3643 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3644 }
3646 /**
3647 * Scale one node.
3648 */
3649 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3650 {
3651 bool both = false;
3652 Inkscape::NodePath::NodeSide *me, *other;
3654 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3655 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3657 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3658 me = &(n->p);
3659 other = &(n->n);
3660 n->code = NR_CURVETO;
3661 } else if (!n->p.other) {
3662 me = &(n->n);
3663 other = &(n->p);
3664 if (n->n.other)
3665 n->n.other->code = NR_CURVETO;
3666 } else {
3667 if (which > 0) { // right handle
3668 if (xn > xp) {
3669 me = &(n->n);
3670 other = &(n->p);
3671 if (n->n.other)
3672 n->n.other->code = NR_CURVETO;
3673 } else {
3674 me = &(n->p);
3675 other = &(n->n);
3676 n->code = NR_CURVETO;
3677 }
3678 } else if (which < 0){ // left handle
3679 if (xn <= xp) {
3680 me = &(n->n);
3681 other = &(n->p);
3682 if (n->n.other)
3683 n->n.other->code = NR_CURVETO;
3684 } else {
3685 me = &(n->p);
3686 other = &(n->n);
3687 n->code = NR_CURVETO;
3688 }
3689 } else { // both handles
3690 me = &(n->n);
3691 other = &(n->p);
3692 both = true;
3693 n->code = NR_CURVETO;
3694 if (n->n.other)
3695 n->n.other->code = NR_CURVETO;
3696 }
3697 }
3699 Radial rme(me->pos - n->pos);
3700 Radial rother(other->pos - n->pos);
3702 rme.r += grow;
3703 if (rme.r < 0) rme.r = 0;
3704 if (rme.a == HUGE_VAL) {
3705 if (me->other) { // if direction is unknown, initialize it towards the next node
3706 Radial rme_next(me->other->pos - n->pos);
3707 rme.a = rme_next.a;
3708 } else { // if there's no next, initialize to 0
3709 rme.a = 0;
3710 }
3711 }
3712 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3713 rother.r += grow;
3714 if (rother.r < 0) rother.r = 0;
3715 if (rother.a == HUGE_VAL) {
3716 rother.a = rme.a + M_PI;
3717 }
3718 }
3720 me->pos = n->pos + NR::Point(rme);
3722 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3723 other->pos = n->pos + NR::Point(rother);
3724 }
3726 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3727 // so here we just move all the knots without emitting move signals, for speed
3728 sp_node_update_handles(n, false);
3729 }
3731 /**
3732 * Scale selected nodes.
3733 */
3734 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3735 {
3736 if (!nodepath || !nodepath->selected) return;
3738 if (g_list_length(nodepath->selected) == 1) {
3739 // scale handles of the single selected node
3740 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3741 node_scale_one (n, grow, which);
3742 } else {
3743 // scale nodes as an "object":
3745 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3746 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3747 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3748 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3749 box.expandTo (n->pos); // contain all selected nodes
3750 }
3752 double scale = (box.maxExtent() + grow)/box.maxExtent();
3754 NR::Matrix t =
3755 NR::Matrix (NR::translate(-box.midpoint())) *
3756 NR::Matrix (NR::scale(scale, scale)) *
3757 NR::Matrix (NR::translate(box.midpoint()));
3759 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3760 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3761 n->pos *= t;
3762 n->n.pos *= t;
3763 n->p.pos *= t;
3764 sp_node_update_handles(n, false);
3765 }
3766 }
3768 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3769 }
3771 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3772 {
3773 if (!nodepath) return;
3774 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3775 }
3777 /**
3778 * Flip selected nodes horizontally/vertically.
3779 */
3780 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3781 {
3782 if (!nodepath || !nodepath->selected) return;
3784 if (g_list_length(nodepath->selected) == 1) {
3785 // flip handles of the single selected node
3786 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3787 double temp = n->p.pos[axis];
3788 n->p.pos[axis] = n->n.pos[axis];
3789 n->n.pos[axis] = temp;
3790 sp_node_update_handles(n, false);
3791 } else {
3792 // scale nodes as an "object":
3794 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3795 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3796 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3797 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3798 box.expandTo (n->pos); // contain all selected nodes
3799 }
3801 NR::Matrix t =
3802 NR::Matrix (NR::translate(-box.midpoint())) *
3803 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3804 NR::Matrix (NR::translate(box.midpoint()));
3806 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3807 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3808 n->pos *= t;
3809 n->n.pos *= t;
3810 n->p.pos *= t;
3811 sp_node_update_handles(n, false);
3812 }
3813 }
3815 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3816 }
3818 //-----------------------------------------------
3819 /**
3820 * Return new subpath under given nodepath.
3821 */
3822 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3823 {
3824 g_assert(nodepath);
3825 g_assert(nodepath->desktop);
3827 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3829 s->nodepath = nodepath;
3830 s->closed = FALSE;
3831 s->nodes = NULL;
3832 s->first = NULL;
3833 s->last = NULL;
3835 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3836 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3837 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3839 return s;
3840 }
3842 /**
3843 * Destroy nodes in subpath, then subpath itself.
3844 */
3845 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3846 {
3847 g_assert(subpath);
3848 g_assert(subpath->nodepath);
3849 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3851 while (subpath->nodes) {
3852 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3853 }
3855 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3857 g_free(subpath);
3858 }
3860 /**
3861 * Link head to tail in subpath.
3862 */
3863 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3864 {
3865 g_assert(!sp->closed);
3866 g_assert(sp->last != sp->first);
3867 g_assert(sp->first->code == NR_MOVETO);
3869 sp->closed = TRUE;
3871 //Link the head to the tail
3872 sp->first->p.other = sp->last;
3873 sp->last->n.other = sp->first;
3874 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3875 sp->first = sp->last;
3877 //Remove the extra end node
3878 sp_nodepath_node_destroy(sp->last->n.other);
3879 }
3881 /**
3882 * Open closed (loopy) subpath at node.
3883 */
3884 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3885 {
3886 g_assert(sp->closed);
3887 g_assert(n->subpath == sp);
3888 g_assert(sp->first == sp->last);
3890 /* We create new startpoint, current node will become last one */
3892 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3893 &n->pos, &n->pos, &n->n.pos);
3896 sp->closed = FALSE;
3898 //Unlink to make a head and tail
3899 sp->first = new_path;
3900 sp->last = n;
3901 n->n.other = NULL;
3902 new_path->p.other = NULL;
3903 }
3905 /**
3906 * Returns area in triangle given by points; may be negative.
3907 */
3908 inline double
3909 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3910 {
3911 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]);
3912 }
3914 /**
3915 * Return new node in subpath with given properties.
3916 * \param pos Position of node.
3917 * \param ppos Handle position in previous direction
3918 * \param npos Handle position in previous direction
3919 */
3920 Inkscape::NodePath::Node *
3921 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)
3922 {
3923 g_assert(sp);
3924 g_assert(sp->nodepath);
3925 g_assert(sp->nodepath->desktop);
3927 if (nodechunk == NULL)
3928 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3930 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3932 n->subpath = sp;
3934 if (type != Inkscape::NodePath::NODE_NONE) {
3935 // use the type from sodipodi:nodetypes
3936 n->type = type;
3937 } else {
3938 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3939 // points are (almost) collinear
3940 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3941 // endnode, or a node with a retracted handle
3942 n->type = Inkscape::NodePath::NODE_CUSP;
3943 } else {
3944 n->type = Inkscape::NodePath::NODE_SMOOTH;
3945 }
3946 } else {
3947 n->type = Inkscape::NodePath::NODE_CUSP;
3948 }
3949 }
3951 n->code = code;
3952 n->selected = FALSE;
3953 n->pos = *pos;
3954 n->p.pos = *ppos;
3955 n->n.pos = *npos;
3957 n->dragging_out = NULL;
3959 Inkscape::NodePath::Node *prev;
3960 if (next) {
3961 //g_assert(g_list_find(sp->nodes, next));
3962 prev = next->p.other;
3963 } else {
3964 prev = sp->last;
3965 }
3967 if (prev)
3968 prev->n.other = n;
3969 else
3970 sp->first = n;
3972 if (next)
3973 next->p.other = n;
3974 else
3975 sp->last = n;
3977 n->p.other = prev;
3978 n->n.other = next;
3980 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"));
3981 sp_knot_set_position(n->knot, pos, 0);
3983 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3984 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3985 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3986 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3987 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3988 sp_knot_update_ctrl(n->knot);
3990 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3991 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3992 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3993 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3994 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3995 sp_knot_show(n->knot);
3997 // We only create handle knots and lines on demand
3998 n->p.knot = NULL;
3999 n->p.line = NULL;
4000 n->n.knot = NULL;
4001 n->n.line = NULL;
4003 sp->nodes = g_list_prepend(sp->nodes, n);
4005 return n;
4006 }
4008 /**
4009 * Destroy node and its knots, link neighbors in subpath.
4010 */
4011 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4012 {
4013 g_assert(node);
4014 g_assert(node->subpath);
4015 g_assert(SP_IS_KNOT(node->knot));
4017 Inkscape::NodePath::SubPath *sp = node->subpath;
4019 if (node->selected) { // first, deselect
4020 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4021 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4022 }
4024 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4026 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4027 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4028 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4029 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4030 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4031 g_object_unref(G_OBJECT(node->knot));
4033 if (node->p.knot) {
4034 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4035 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4036 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4037 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4038 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4039 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4040 g_object_unref(G_OBJECT(node->p.knot));
4041 node->p.knot = NULL;
4042 }
4044 if (node->n.knot) {
4045 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4046 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4047 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4048 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4049 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4050 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4051 g_object_unref(G_OBJECT(node->n.knot));
4052 node->n.knot = NULL;
4053 }
4055 if (node->p.line)
4056 gtk_object_destroy(GTK_OBJECT(node->p.line));
4057 if (node->n.line)
4058 gtk_object_destroy(GTK_OBJECT(node->n.line));
4060 if (sp->nodes) { // there are others nodes on the subpath
4061 if (sp->closed) {
4062 if (sp->first == node) {
4063 g_assert(sp->last == node);
4064 sp->first = node->n.other;
4065 sp->last = sp->first;
4066 }
4067 node->p.other->n.other = node->n.other;
4068 node->n.other->p.other = node->p.other;
4069 } else {
4070 if (sp->first == node) {
4071 sp->first = node->n.other;
4072 sp->first->code = NR_MOVETO;
4073 }
4074 if (sp->last == node) sp->last = node->p.other;
4075 if (node->p.other) node->p.other->n.other = node->n.other;
4076 if (node->n.other) node->n.other->p.other = node->p.other;
4077 }
4078 } else { // this was the last node on subpath
4079 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4080 }
4082 g_mem_chunk_free(nodechunk, node);
4083 }
4085 /**
4086 * Returns one of the node's two sides.
4087 * \param which Indicates which side.
4088 * \return Pointer to previous node side if which==-1, next if which==1.
4089 */
4090 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4091 {
4092 g_assert(node);
4094 switch (which) {
4095 case -1:
4096 return &node->p;
4097 case 1:
4098 return &node->n;
4099 default:
4100 break;
4101 }
4103 g_assert_not_reached();
4105 return NULL;
4106 }
4108 /**
4109 * Return the other side of the node, given one of its sides.
4110 */
4111 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4112 {
4113 g_assert(node);
4115 if (me == &node->p) return &node->n;
4116 if (me == &node->n) return &node->p;
4118 g_assert_not_reached();
4120 return NULL;
4121 }
4123 /**
4124 * Return NRPathcode on the given side of the node.
4125 */
4126 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4127 {
4128 g_assert(node);
4130 if (me == &node->p) {
4131 if (node->p.other) return (NRPathcode)node->code;
4132 return NR_MOVETO;
4133 }
4135 if (me == &node->n) {
4136 if (node->n.other) return (NRPathcode)node->n.other->code;
4137 return NR_MOVETO;
4138 }
4140 g_assert_not_reached();
4142 return NR_END;
4143 }
4145 /**
4146 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4147 */
4148 Inkscape::NodePath::Node *
4149 sp_nodepath_get_node_by_index(int index)
4150 {
4151 Inkscape::NodePath::Node *e = NULL;
4153 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4154 if (!nodepath) {
4155 return e;
4156 }
4158 //find segment
4159 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4161 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4162 int n = g_list_length(sp->nodes);
4163 if (sp->closed) {
4164 n++;
4165 }
4167 //if the piece belongs to this subpath grab it
4168 //otherwise move onto the next subpath
4169 if (index < n) {
4170 e = sp->first;
4171 for (int i = 0; i < index; ++i) {
4172 e = e->n.other;
4173 }
4174 break;
4175 } else {
4176 if (sp->closed) {
4177 index -= (n+1);
4178 } else {
4179 index -= n;
4180 }
4181 }
4182 }
4184 return e;
4185 }
4187 /**
4188 * Returns plain text meaning of node type.
4189 */
4190 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4191 {
4192 unsigned retracted = 0;
4193 bool endnode = false;
4195 for (int which = -1; which <= 1; which += 2) {
4196 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4197 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4198 retracted ++;
4199 if (!side->other)
4200 endnode = true;
4201 }
4203 if (retracted == 0) {
4204 if (endnode) {
4205 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4206 return _("end node");
4207 } else {
4208 switch (node->type) {
4209 case Inkscape::NodePath::NODE_CUSP:
4210 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4211 return _("cusp");
4212 case Inkscape::NodePath::NODE_SMOOTH:
4213 // TRANSLATORS: "smooth" is an adjective here
4214 return _("smooth");
4215 case Inkscape::NodePath::NODE_SYMM:
4216 return _("symmetric");
4217 }
4218 }
4219 } else if (retracted == 1) {
4220 if (endnode) {
4221 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4222 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4223 } else {
4224 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4225 }
4226 } else {
4227 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4228 }
4230 return NULL;
4231 }
4233 /**
4234 * Handles content of statusbar as long as node tool is active.
4235 */
4236 void
4237 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4238 {
4239 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");
4240 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4242 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4243 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4244 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4245 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4247 SPDesktop *desktop = NULL;
4248 if (nodepath) {
4249 desktop = nodepath->desktop;
4250 } else {
4251 desktop = SP_ACTIVE_DESKTOP;
4252 }
4254 SPEventContext *ec = desktop->event_context;
4255 if (!ec) return;
4256 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4257 if (!mc) return;
4259 if (selected_nodes == 0) {
4260 Inkscape::Selection *sel = desktop->selection;
4261 if (!sel || sel->isEmpty()) {
4262 mc->setF(Inkscape::NORMAL_MESSAGE,
4263 _("Select a single object to edit its nodes or handles."));
4264 } else {
4265 if (nodepath) {
4266 mc->setF(Inkscape::NORMAL_MESSAGE,
4267 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.",
4268 "<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.",
4269 total_nodes),
4270 total_nodes);
4271 } else {
4272 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4273 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4274 } else {
4275 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4276 }
4277 }
4278 }
4279 } else if (nodepath && selected_nodes == 1) {
4280 mc->setF(Inkscape::NORMAL_MESSAGE,
4281 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4282 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4283 total_nodes),
4284 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4285 } else {
4286 if (selected_subpaths > 1) {
4287 mc->setF(Inkscape::NORMAL_MESSAGE,
4288 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4289 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4290 total_nodes),
4291 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4292 } else {
4293 mc->setF(Inkscape::NORMAL_MESSAGE,
4294 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4295 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4296 total_nodes),
4297 selected_nodes, total_nodes, when_selected);
4298 }
4299 }
4300 }
4303 /*
4304 Local Variables:
4305 mode:c++
4306 c-file-style:"stroustrup"
4307 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4308 indent-tabs-mode:nil
4309 fill-column:99
4310 End:
4311 */
4312 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :