8e6783f20d4209031cfc469399fc1a6dfc2cad78
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(old_repr->document());
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 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1553 if (!maybe_position) {
1554 return;
1555 }
1556 Path::cut_position position = *maybe_position;
1558 //find segment to segment
1559 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1561 //fixme: this can return NULL, so check before proceeding.
1562 g_return_if_fail(e != NULL);
1564 gboolean force = FALSE;
1565 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1566 force = TRUE;
1567 }
1568 sp_nodepath_node_select(e, (gboolean) toggle, force);
1569 if (e->p.other)
1570 sp_nodepath_node_select(e->p.other, TRUE, force);
1572 sp_nodepath_update_handles(nodepath);
1574 sp_nodepath_update_statusbar(nodepath);
1575 }
1577 /**
1578 * Add a node nearest to point
1579 */
1580 void
1581 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1582 {
1583 if (!nodepath) {
1584 return;
1585 }
1587 sp_nodepath_ensure_livarot_path(nodepath);
1588 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1589 if (!maybe_position) {
1590 return;
1591 }
1592 Path::cut_position position = *maybe_position;
1594 //find segment to split
1595 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1597 //don't know why but t seems to flip for lines
1598 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1599 position.t = 1.0 - position.t;
1600 }
1601 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1602 sp_nodepath_node_select(n, FALSE, TRUE);
1604 /* fixme: adjust ? */
1605 sp_nodepath_update_handles(nodepath);
1607 sp_nodepath_update_repr(nodepath, _("Add node"));
1609 sp_nodepath_update_statusbar(nodepath);
1610 }
1612 /*
1613 * Adjusts a segment so that t moves by a certain delta for dragging
1614 * converts lines to curves
1615 *
1616 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1617 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1618 */
1619 void
1620 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1621 {
1622 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1624 //fixme: e and e->p can be NULL, so check for those before proceeding
1625 g_return_if_fail(e != NULL);
1626 g_return_if_fail(&e->p != NULL);
1628 /* feel good is an arbitrary parameter that distributes the delta between handles
1629 * if t of the drag point is less than 1/6 distance form the endpoint only
1630 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1631 */
1632 double feel_good;
1633 if (t <= 1.0 / 6.0)
1634 feel_good = 0;
1635 else if (t <= 0.5)
1636 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1637 else if (t <= 5.0 / 6.0)
1638 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1639 else
1640 feel_good = 1;
1642 //if we're dragging a line convert it to a curve
1643 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1644 sp_nodepath_set_line_type(e, NR_CURVETO);
1645 }
1647 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1648 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1649 e->p.other->n.pos += offsetcoord0;
1650 e->p.pos += offsetcoord1;
1652 // adjust handles of adjacent nodes where necessary
1653 sp_node_adjust_handle(e,1);
1654 sp_node_adjust_handle(e->p.other,-1);
1656 sp_nodepath_update_handles(e->subpath->nodepath);
1658 update_object(e->subpath->nodepath);
1660 sp_nodepath_update_statusbar(e->subpath->nodepath);
1661 }
1664 /**
1665 * Call sp_nodepath_break() for all selected segments.
1666 */
1667 void sp_node_selected_break()
1668 {
1669 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1670 if (!nodepath) return;
1672 GList *temp = NULL;
1673 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1674 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1675 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1676 if (nn == NULL) continue; // no break, no new node
1677 temp = g_list_prepend(temp, nn);
1678 }
1680 if (temp) {
1681 sp_nodepath_deselect(nodepath);
1682 }
1683 for (GList *l = temp; l != NULL; l = l->next) {
1684 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1685 }
1687 sp_nodepath_update_handles(nodepath);
1689 sp_nodepath_update_repr(nodepath, _("Break path"));
1690 }
1692 /**
1693 * Duplicate the selected node(s).
1694 */
1695 void sp_node_selected_duplicate()
1696 {
1697 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1698 if (!nodepath) {
1699 return;
1700 }
1702 GList *temp = NULL;
1703 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1704 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1705 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1706 if (nn == NULL) continue; // could not duplicate
1707 temp = g_list_prepend(temp, nn);
1708 }
1710 if (temp) {
1711 sp_nodepath_deselect(nodepath);
1712 }
1713 for (GList *l = temp; l != NULL; l = l->next) {
1714 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1715 }
1717 sp_nodepath_update_handles(nodepath);
1719 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1720 }
1722 /**
1723 * Join two nodes by merging them into one.
1724 */
1725 void sp_node_selected_join()
1726 {
1727 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1728 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1730 if (g_list_length(nodepath->selected) != 2) {
1731 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1732 return;
1733 }
1735 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1736 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1738 g_assert(a != b);
1739 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1740 // someone tried to join an orphan node (i.e. a single-node subpath).
1741 // this is not worth an error message, just fail silently.
1742 return;
1743 }
1745 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1746 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1747 return;
1748 }
1750 /* a and b are endpoints */
1752 NR::Point c;
1753 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1754 c = a->pos;
1755 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1756 c = b->pos;
1757 } else {
1758 c = (a->pos + b->pos) / 2;
1759 }
1761 if (a->subpath == b->subpath) {
1762 Inkscape::NodePath::SubPath *sp = a->subpath;
1763 sp_nodepath_subpath_close(sp);
1764 sp_node_moveto (sp->first, c);
1766 sp_nodepath_update_handles(sp->nodepath);
1767 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1768 return;
1769 }
1771 /* a and b are separate subpaths */
1772 Inkscape::NodePath::SubPath *sa = a->subpath;
1773 Inkscape::NodePath::SubPath *sb = b->subpath;
1774 NR::Point p;
1775 Inkscape::NodePath::Node *n;
1776 NRPathcode code;
1777 if (a == sa->first) {
1778 p = sa->first->n.pos;
1779 code = (NRPathcode)sa->first->n.other->code;
1780 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1781 n = sa->last;
1782 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1783 n = n->p.other;
1784 while (n) {
1785 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1786 n = n->p.other;
1787 if (n == sa->first) n = NULL;
1788 }
1789 sp_nodepath_subpath_destroy(sa);
1790 sa = t;
1791 } else if (a == sa->last) {
1792 p = sa->last->p.pos;
1793 code = (NRPathcode)sa->last->code;
1794 sp_nodepath_node_destroy(sa->last);
1795 } else {
1796 code = NR_END;
1797 g_assert_not_reached();
1798 }
1800 if (b == sb->first) {
1801 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1802 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1803 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1804 }
1805 } else if (b == sb->last) {
1806 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1807 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1808 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1809 }
1810 } else {
1811 g_assert_not_reached();
1812 }
1813 /* and now destroy sb */
1815 sp_nodepath_subpath_destroy(sb);
1817 sp_nodepath_update_handles(sa->nodepath);
1819 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1821 sp_nodepath_update_statusbar(nodepath);
1822 }
1824 /**
1825 * Join two nodes by adding a segment between them.
1826 */
1827 void sp_node_selected_join_segment()
1828 {
1829 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1830 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1832 if (g_list_length(nodepath->selected) != 2) {
1833 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1834 return;
1835 }
1837 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1838 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1840 g_assert(a != b);
1841 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1842 // someone tried to join an orphan node (i.e. a single-node subpath).
1843 // this is not worth an error message, just fail silently.
1844 return;
1845 }
1847 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1848 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1849 return;
1850 }
1852 if (a->subpath == b->subpath) {
1853 Inkscape::NodePath::SubPath *sp = a->subpath;
1855 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1856 sp->closed = TRUE;
1858 sp->first->p.other = sp->last;
1859 sp->last->n.other = sp->first;
1861 sp_node_handle_mirror_p_to_n(sp->last);
1862 sp_node_handle_mirror_n_to_p(sp->first);
1864 sp->first->code = sp->last->code;
1865 sp->first = sp->last;
1867 sp_nodepath_update_handles(sp->nodepath);
1869 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1871 return;
1872 }
1874 /* a and b are separate subpaths */
1875 Inkscape::NodePath::SubPath *sa = a->subpath;
1876 Inkscape::NodePath::SubPath *sb = b->subpath;
1878 Inkscape::NodePath::Node *n;
1879 NR::Point p;
1880 NRPathcode code;
1881 if (a == sa->first) {
1882 code = (NRPathcode) sa->first->n.other->code;
1883 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1884 n = sa->last;
1885 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1886 for (n = n->p.other; n != NULL; n = n->p.other) {
1887 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1888 }
1889 sp_nodepath_subpath_destroy(sa);
1890 sa = t;
1891 } else if (a == sa->last) {
1892 code = (NRPathcode)sa->last->code;
1893 } else {
1894 code = NR_END;
1895 g_assert_not_reached();
1896 }
1898 if (b == sb->first) {
1899 n = sb->first;
1900 sp_node_handle_mirror_p_to_n(sa->last);
1901 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1902 sp_node_handle_mirror_n_to_p(sa->last);
1903 for (n = n->n.other; n != NULL; n = n->n.other) {
1904 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1905 }
1906 } else if (b == sb->last) {
1907 n = sb->last;
1908 sp_node_handle_mirror_p_to_n(sa->last);
1909 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1910 sp_node_handle_mirror_n_to_p(sa->last);
1911 for (n = n->p.other; n != NULL; n = n->p.other) {
1912 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1913 }
1914 } else {
1915 g_assert_not_reached();
1916 }
1917 /* and now destroy sb */
1919 sp_nodepath_subpath_destroy(sb);
1921 sp_nodepath_update_handles(sa->nodepath);
1923 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1924 }
1926 /**
1927 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1928 */
1929 void sp_node_delete_preserve(GList *nodes_to_delete)
1930 {
1931 GSList *nodepaths = NULL;
1933 while (nodes_to_delete) {
1934 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1935 Inkscape::NodePath::SubPath *sp = node->subpath;
1936 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1937 Inkscape::NodePath::Node *sample_cursor = NULL;
1938 Inkscape::NodePath::Node *sample_end = NULL;
1939 Inkscape::NodePath::Node *delete_cursor = node;
1940 bool just_delete = false;
1942 //find the start of this contiguous selection
1943 //move left to the first node that is not selected
1944 //or the start of the non-closed path
1945 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1946 delete_cursor = curr;
1947 }
1949 //just delete at the beginning of an open path
1950 if (!delete_cursor->p.other) {
1951 sample_cursor = delete_cursor;
1952 just_delete = true;
1953 } else {
1954 sample_cursor = delete_cursor->p.other;
1955 }
1957 //calculate points for each segment
1958 int rate = 5;
1959 float period = 1.0 / rate;
1960 std::vector<NR::Point> data;
1961 if (!just_delete) {
1962 data.push_back(sample_cursor->pos);
1963 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1964 //just delete at the end of an open path
1965 if (!sp->closed && curr == sp->last) {
1966 just_delete = true;
1967 break;
1968 }
1970 //sample points on the contiguous selected segment
1971 NR::Point *bez;
1972 bez = new NR::Point [4];
1973 bez[0] = curr->pos;
1974 bez[1] = curr->n.pos;
1975 bez[2] = curr->n.other->p.pos;
1976 bez[3] = curr->n.other->pos;
1977 for (int i=1; i<rate; i++) {
1978 gdouble t = i * period;
1979 NR::Point p = bezier_pt(3, bez, t);
1980 data.push_back(p);
1981 }
1982 data.push_back(curr->n.other->pos);
1984 sample_end = curr->n.other;
1985 //break if we've come full circle or hit the end of the selection
1986 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1987 break;
1988 }
1989 }
1990 }
1992 if (!just_delete) {
1993 //calculate the best fitting single segment and adjust the endpoints
1994 NR::Point *adata;
1995 adata = new NR::Point [data.size()];
1996 copy(data.begin(), data.end(), adata);
1998 NR::Point *bez;
1999 bez = new NR::Point [4];
2000 //would decreasing error create a better fitting approximation?
2001 gdouble error = 1.0;
2002 gint ret;
2003 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2005 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2006 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2007 //the resulting nodes behave as expected.
2008 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2009 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2011 //adjust endpoints
2012 sample_cursor->n.pos = bez[1];
2013 sample_end->p.pos = bez[2];
2014 }
2016 //destroy this contiguous selection
2017 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2018 Inkscape::NodePath::Node *temp = delete_cursor;
2019 if (delete_cursor->n.other == delete_cursor) {
2020 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2021 delete_cursor = NULL;
2022 } else {
2023 delete_cursor = delete_cursor->n.other;
2024 }
2025 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2026 sp_nodepath_node_destroy(temp);
2027 }
2029 sp_nodepath_update_handles(nodepath);
2031 if (!g_slist_find(nodepaths, nodepath))
2032 nodepaths = g_slist_prepend (nodepaths, nodepath);
2033 }
2035 for (GSList *i = nodepaths; i; i = i->next) {
2036 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2037 // different nodepaths will give us one undo event per nodepath
2038 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2040 // if the entire nodepath is removed, delete the selected object.
2041 if (nodepath->subpaths == NULL ||
2042 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2043 //at least 2
2044 sp_nodepath_get_node_count(nodepath) < 2) {
2045 SPDocument *document = sp_desktop_document (nodepath->desktop);
2046 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2047 //delete this nodepath's object, not the entire selection! (though at this time, this
2048 //does not matter)
2049 sp_selection_delete();
2050 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2051 _("Delete nodes"));
2052 } else {
2053 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2054 sp_nodepath_update_statusbar(nodepath);
2055 }
2056 }
2058 g_slist_free (nodepaths);
2059 }
2061 /**
2062 * Delete one or more selected nodes.
2063 */
2064 void sp_node_selected_delete()
2065 {
2066 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2067 if (!nodepath) return;
2068 if (!nodepath->selected) return;
2070 /** \todo fixme: do it the right way */
2071 while (nodepath->selected) {
2072 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2073 sp_nodepath_node_destroy(node);
2074 }
2077 //clean up the nodepath (such as for trivial subpaths)
2078 sp_nodepath_cleanup(nodepath);
2080 sp_nodepath_update_handles(nodepath);
2082 // if the entire nodepath is removed, delete the selected object.
2083 if (nodepath->subpaths == NULL ||
2084 sp_nodepath_get_node_count(nodepath) < 2) {
2085 SPDocument *document = sp_desktop_document (nodepath->desktop);
2086 sp_selection_delete();
2087 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2088 _("Delete nodes"));
2089 return;
2090 }
2092 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2094 sp_nodepath_update_statusbar(nodepath);
2095 }
2097 /**
2098 * Delete one or more segments between two selected nodes.
2099 * This is the code for 'split'.
2100 */
2101 void
2102 sp_node_selected_delete_segment(void)
2103 {
2104 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2105 Inkscape::NodePath::Node *curr, *next; //Iterators
2107 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2108 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2110 if (g_list_length(nodepath->selected) != 2) {
2111 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2112 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2113 return;
2114 }
2116 //Selected nodes, not inclusive
2117 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2118 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2120 if ( ( a==b) || //same node
2121 (a->subpath != b->subpath ) || //not the same path
2122 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2123 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2124 {
2125 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2126 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2127 return;
2128 }
2130 //###########################################
2131 //# BEGIN EDITS
2132 //###########################################
2133 //##################################
2134 //# CLOSED PATH
2135 //##################################
2136 if (a->subpath->closed) {
2139 gboolean reversed = FALSE;
2141 //Since we can go in a circle, we need to find the shorter distance.
2142 // a->b or b->a
2143 start = end = NULL;
2144 int distance = 0;
2145 int minDistance = 0;
2146 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2147 if (curr==b) {
2148 //printf("a to b:%d\n", distance);
2149 start = a;//go from a to b
2150 end = b;
2151 minDistance = distance;
2152 //printf("A to B :\n");
2153 break;
2154 }
2155 distance++;
2156 }
2158 //try again, the other direction
2159 distance = 0;
2160 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2161 if (curr==a) {
2162 //printf("b to a:%d\n", distance);
2163 if (distance < minDistance) {
2164 start = b; //we go from b to a
2165 end = a;
2166 reversed = TRUE;
2167 //printf("B to A\n");
2168 }
2169 break;
2170 }
2171 distance++;
2172 }
2175 //Copy everything from 'end' to 'start' to a new subpath
2176 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2177 for (curr=end ; curr ; curr=curr->n.other) {
2178 NRPathcode code = (NRPathcode) curr->code;
2179 if (curr == end)
2180 code = NR_MOVETO;
2181 sp_nodepath_node_new(t, NULL,
2182 (Inkscape::NodePath::NodeType)curr->type, code,
2183 &curr->p.pos, &curr->pos, &curr->n.pos);
2184 if (curr == start)
2185 break;
2186 }
2187 sp_nodepath_subpath_destroy(a->subpath);
2190 }
2194 //##################################
2195 //# OPEN PATH
2196 //##################################
2197 else {
2199 //We need to get the direction of the list between A and B
2200 //Can we walk from a to b?
2201 start = end = NULL;
2202 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2203 if (curr==b) {
2204 start = a; //did it! we go from a to b
2205 end = b;
2206 //printf("A to B\n");
2207 break;
2208 }
2209 }
2210 if (!start) {//didn't work? let's try the other direction
2211 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2212 if (curr==a) {
2213 start = b; //did it! we go from b to a
2214 end = a;
2215 //printf("B to A\n");
2216 break;
2217 }
2218 }
2219 }
2220 if (!start) {
2221 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2222 _("Cannot find path between nodes."));
2223 return;
2224 }
2228 //Copy everything after 'end' to a new subpath
2229 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2230 for (curr=end ; curr ; curr=curr->n.other) {
2231 NRPathcode code = (NRPathcode) curr->code;
2232 if (curr == end)
2233 code = NR_MOVETO;
2234 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2235 &curr->p.pos, &curr->pos, &curr->n.pos);
2236 }
2238 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2239 for (curr = start->n.other ; curr ; curr=next) {
2240 next = curr->n.other;
2241 sp_nodepath_node_destroy(curr);
2242 }
2244 }
2245 //###########################################
2246 //# END EDITS
2247 //###########################################
2249 //clean up the nodepath (such as for trivial subpaths)
2250 sp_nodepath_cleanup(nodepath);
2252 sp_nodepath_update_handles(nodepath);
2254 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2256 sp_nodepath_update_statusbar(nodepath);
2257 }
2259 /**
2260 * Call sp_nodepath_set_line() for all selected segments.
2261 */
2262 void
2263 sp_node_selected_set_line_type(NRPathcode code)
2264 {
2265 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2266 if (nodepath == NULL) return;
2268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2269 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2270 g_assert(n->selected);
2271 if (n->p.other && n->p.other->selected) {
2272 sp_nodepath_set_line_type(n, code);
2273 }
2274 }
2276 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2277 }
2279 /**
2280 * Call sp_nodepath_convert_node_type() for all selected nodes.
2281 */
2282 void
2283 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2284 {
2285 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2286 if (nodepath == NULL) return;
2288 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2289 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2290 }
2292 sp_nodepath_update_repr(nodepath, _("Change node type"));
2293 }
2295 /**
2296 * Change select status of node, update its own and neighbour handles.
2297 */
2298 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2299 {
2300 node->selected = selected;
2302 if (selected) {
2303 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2304 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2305 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2306 sp_knot_update_ctrl(node->knot);
2307 } else {
2308 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2309 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2310 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2311 sp_knot_update_ctrl(node->knot);
2312 }
2314 sp_node_update_handles(node);
2315 if (node->n.other) sp_node_update_handles(node->n.other);
2316 if (node->p.other) sp_node_update_handles(node->p.other);
2317 }
2319 /**
2320 \brief Select a node
2321 \param node The node to select
2322 \param incremental If true, add to selection, otherwise deselect others
2323 \param override If true, always select this node, otherwise toggle selected status
2324 */
2325 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2326 {
2327 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2329 if (incremental) {
2330 if (override) {
2331 if (!g_list_find(nodepath->selected, node)) {
2332 nodepath->selected = g_list_prepend(nodepath->selected, node);
2333 }
2334 sp_node_set_selected(node, TRUE);
2335 } else { // toggle
2336 if (node->selected) {
2337 g_assert(g_list_find(nodepath->selected, node));
2338 nodepath->selected = g_list_remove(nodepath->selected, node);
2339 } else {
2340 g_assert(!g_list_find(nodepath->selected, node));
2341 nodepath->selected = g_list_prepend(nodepath->selected, node);
2342 }
2343 sp_node_set_selected(node, !node->selected);
2344 }
2345 } else {
2346 sp_nodepath_deselect(nodepath);
2347 nodepath->selected = g_list_prepend(nodepath->selected, node);
2348 sp_node_set_selected(node, TRUE);
2349 }
2351 sp_nodepath_update_statusbar(nodepath);
2352 }
2355 /**
2356 \brief Deselect all nodes in the nodepath
2357 */
2358 void
2359 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2360 {
2361 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2363 while (nodepath->selected) {
2364 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2365 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2366 }
2367 sp_nodepath_update_statusbar(nodepath);
2368 }
2370 /**
2371 \brief Select or invert selection of all nodes in the nodepath
2372 */
2373 void
2374 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2375 {
2376 if (!nodepath) return;
2378 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2379 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2380 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2381 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2382 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2383 }
2384 }
2385 }
2387 /**
2388 * If nothing selected, does the same as sp_nodepath_select_all();
2389 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2390 * (i.e., similar to "select all in layer", with the "selected" subpaths
2391 * being treated as "layers" in the path).
2392 */
2393 void
2394 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2395 {
2396 if (!nodepath) return;
2398 if (g_list_length (nodepath->selected) == 0) {
2399 sp_nodepath_select_all (nodepath, invert);
2400 return;
2401 }
2403 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2404 GSList *subpaths = NULL;
2406 for (GList *l = copy; l != NULL; l = l->next) {
2407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2408 Inkscape::NodePath::SubPath *subpath = n->subpath;
2409 if (!g_slist_find (subpaths, subpath))
2410 subpaths = g_slist_prepend (subpaths, subpath);
2411 }
2413 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2414 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2415 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2416 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2417 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2418 }
2419 }
2421 g_slist_free (subpaths);
2422 g_list_free (copy);
2423 }
2425 /**
2426 * \brief Select the node after the last selected; if none is selected,
2427 * select the first within path.
2428 */
2429 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2430 {
2431 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2433 Inkscape::NodePath::Node *last = NULL;
2434 if (nodepath->selected) {
2435 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2436 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2437 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2438 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2439 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2440 if (node->selected) {
2441 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2442 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2443 if (spl->next) { // there's a next subpath
2444 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2445 last = subpath_next->first;
2446 } else if (spl->prev) { // there's a previous subpath
2447 last = NULL; // to be set later to the first node of first subpath
2448 } else {
2449 last = node->n.other;
2450 }
2451 } else {
2452 last = node->n.other;
2453 }
2454 } else {
2455 if (node->n.other) {
2456 last = node->n.other;
2457 } else {
2458 if (spl->next) { // there's a next subpath
2459 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2460 last = subpath_next->first;
2461 } else if (spl->prev) { // there's a previous subpath
2462 last = NULL; // to be set later to the first node of first subpath
2463 } else {
2464 last = (Inkscape::NodePath::Node *) subpath->first;
2465 }
2466 }
2467 }
2468 }
2469 }
2470 }
2471 sp_nodepath_deselect(nodepath);
2472 }
2474 if (last) { // there's at least one more node after selected
2475 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2476 } else { // no more nodes, select the first one in first subpath
2477 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2478 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2479 }
2480 }
2482 /**
2483 * \brief Select the node before the first selected; if none is selected,
2484 * select the last within path
2485 */
2486 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2487 {
2488 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2490 Inkscape::NodePath::Node *last = NULL;
2491 if (nodepath->selected) {
2492 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2493 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2494 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2495 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2496 if (node->selected) {
2497 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2498 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2499 if (spl->prev) { // there's a prev subpath
2500 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2501 last = subpath_prev->last;
2502 } else if (spl->next) { // there's a next subpath
2503 last = NULL; // to be set later to the last node of last subpath
2504 } else {
2505 last = node->p.other;
2506 }
2507 } else {
2508 last = node->p.other;
2509 }
2510 } else {
2511 if (node->p.other) {
2512 last = node->p.other;
2513 } else {
2514 if (spl->prev) { // there's a prev subpath
2515 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2516 last = subpath_prev->last;
2517 } else if (spl->next) { // there's a next subpath
2518 last = NULL; // to be set later to the last node of last subpath
2519 } else {
2520 last = (Inkscape::NodePath::Node *) subpath->last;
2521 }
2522 }
2523 }
2524 }
2525 }
2526 }
2527 sp_nodepath_deselect(nodepath);
2528 }
2530 if (last) { // there's at least one more node before selected
2531 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2532 } else { // no more nodes, select the last one in last subpath
2533 GList *spl = g_list_last(nodepath->subpaths);
2534 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2535 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2536 }
2537 }
2539 /**
2540 * \brief Select all nodes that are within the rectangle.
2541 */
2542 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2543 {
2544 if (!incremental) {
2545 sp_nodepath_deselect(nodepath);
2546 }
2548 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2549 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2550 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2551 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2553 if (b.contains(node->pos)) {
2554 sp_nodepath_node_select(node, TRUE, TRUE);
2555 }
2556 }
2557 }
2558 }
2561 void
2562 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2563 {
2564 g_assert (n);
2565 g_assert (nodepath);
2566 g_assert (n->subpath->nodepath == nodepath);
2568 if (g_list_length (nodepath->selected) == 0) {
2569 if (grow > 0) {
2570 sp_nodepath_node_select(n, TRUE, TRUE);
2571 }
2572 return;
2573 }
2575 if (g_list_length (nodepath->selected) == 1) {
2576 if (grow < 0) {
2577 sp_nodepath_deselect (nodepath);
2578 return;
2579 }
2580 }
2582 double n_sel_range = 0, p_sel_range = 0;
2583 Inkscape::NodePath::Node *farthest_n_node = n;
2584 Inkscape::NodePath::Node *farthest_p_node = n;
2586 // Calculate ranges
2587 {
2588 double n_range = 0, p_range = 0;
2589 bool n_going = true, p_going = true;
2590 Inkscape::NodePath::Node *n_node = n;
2591 Inkscape::NodePath::Node *p_node = n;
2592 do {
2593 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2594 if (n_node && n_going)
2595 n_node = n_node->n.other;
2596 if (n_node == NULL) {
2597 n_going = false;
2598 } else {
2599 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2600 if (n_node->selected) {
2601 n_sel_range = n_range;
2602 farthest_n_node = n_node;
2603 }
2604 if (n_node == p_node) {
2605 n_going = false;
2606 p_going = false;
2607 }
2608 }
2609 if (p_node && p_going)
2610 p_node = p_node->p.other;
2611 if (p_node == NULL) {
2612 p_going = false;
2613 } else {
2614 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2615 if (p_node->selected) {
2616 p_sel_range = p_range;
2617 farthest_p_node = p_node;
2618 }
2619 if (p_node == n_node) {
2620 n_going = false;
2621 p_going = false;
2622 }
2623 }
2624 } while (n_going || p_going);
2625 }
2627 if (grow > 0) {
2628 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2629 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2630 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2631 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2632 }
2633 } else {
2634 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2635 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2636 } else if (farthest_p_node && farthest_p_node->selected) {
2637 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2638 }
2639 }
2640 }
2642 void
2643 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2644 {
2645 g_assert (n);
2646 g_assert (nodepath);
2647 g_assert (n->subpath->nodepath == nodepath);
2649 if (g_list_length (nodepath->selected) == 0) {
2650 if (grow > 0) {
2651 sp_nodepath_node_select(n, TRUE, TRUE);
2652 }
2653 return;
2654 }
2656 if (g_list_length (nodepath->selected) == 1) {
2657 if (grow < 0) {
2658 sp_nodepath_deselect (nodepath);
2659 return;
2660 }
2661 }
2663 Inkscape::NodePath::Node *farthest_selected = NULL;
2664 double farthest_dist = 0;
2666 Inkscape::NodePath::Node *closest_unselected = NULL;
2667 double closest_dist = NR_HUGE;
2669 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2670 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2671 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2672 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2673 if (node == n)
2674 continue;
2675 if (node->selected) {
2676 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2677 farthest_dist = NR::L2(node->pos - n->pos);
2678 farthest_selected = node;
2679 }
2680 } else {
2681 if (NR::L2(node->pos - n->pos) < closest_dist) {
2682 closest_dist = NR::L2(node->pos - n->pos);
2683 closest_unselected = node;
2684 }
2685 }
2686 }
2687 }
2689 if (grow > 0) {
2690 if (closest_unselected) {
2691 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2692 }
2693 } else {
2694 if (farthest_selected) {
2695 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2696 }
2697 }
2698 }
2701 /**
2702 \brief Saves all nodes' and handles' current positions in their origin members
2703 */
2704 void
2705 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2706 {
2707 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2708 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2709 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2710 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2711 n->origin = n->pos;
2712 n->p.origin = n->p.pos;
2713 n->n.origin = n->n.pos;
2714 }
2715 }
2716 }
2718 /**
2719 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2720 */
2721 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2722 {
2723 if (!nodepath->selected) {
2724 return NULL;
2725 }
2727 GList *r = NULL;
2728 guint i = 0;
2729 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2730 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2731 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2732 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2733 i++;
2734 if (node->selected) {
2735 r = g_list_append(r, GINT_TO_POINTER(i));
2736 }
2737 }
2738 }
2739 return r;
2740 }
2742 /**
2743 \brief Restores selection by selecting nodes whose positions are in the list
2744 */
2745 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2746 {
2747 sp_nodepath_deselect(nodepath);
2749 guint i = 0;
2750 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2751 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2752 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2753 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2754 i++;
2755 if (g_list_find(r, GINT_TO_POINTER(i))) {
2756 sp_nodepath_node_select(node, TRUE, TRUE);
2757 }
2758 }
2759 }
2761 }
2763 /**
2764 \brief Adjusts handle according to node type and line code.
2765 */
2766 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2767 {
2768 double len, otherlen, linelen;
2770 g_assert(node);
2772 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2773 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2775 /** \todo fixme: */
2776 if (me->other == NULL) return;
2777 if (other->other == NULL) return;
2779 /* I have line */
2781 NRPathcode mecode, ocode;
2782 if (which_adjust == 1) {
2783 mecode = (NRPathcode)me->other->code;
2784 ocode = (NRPathcode)node->code;
2785 } else {
2786 mecode = (NRPathcode)node->code;
2787 ocode = (NRPathcode)other->other->code;
2788 }
2790 if (mecode == NR_LINETO) return;
2792 /* I am curve */
2794 if (other->other == NULL) return;
2796 /* Other has line */
2798 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2800 NR::Point delta;
2801 if (ocode == NR_LINETO) {
2802 /* other is lineto, we are either smooth or symm */
2803 Inkscape::NodePath::Node *othernode = other->other;
2804 len = NR::L2(me->pos - node->pos);
2805 delta = node->pos - othernode->pos;
2806 linelen = NR::L2(delta);
2807 if (linelen < 1e-18)
2808 return;
2809 me->pos = node->pos + (len / linelen)*delta;
2810 return;
2811 }
2813 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2815 me->pos = 2 * node->pos - other->pos;
2816 return;
2817 }
2819 /* We are smooth */
2821 len = NR::L2(me->pos - node->pos);
2822 delta = other->pos - node->pos;
2823 otherlen = NR::L2(delta);
2824 if (otherlen < 1e-18) return;
2826 me->pos = node->pos - (len / otherlen) * delta;
2827 }
2829 /**
2830 \brief Adjusts both handles according to node type and line code
2831 */
2832 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2833 {
2834 g_assert(node);
2836 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2838 /* we are either smooth or symm */
2840 if (node->p.other == NULL) return;
2842 if (node->n.other == NULL) return;
2844 if (node->code == NR_LINETO) {
2845 if (node->n.other->code == NR_LINETO) return;
2846 sp_node_adjust_handle(node, 1);
2847 return;
2848 }
2850 if (node->n.other->code == NR_LINETO) {
2851 if (node->code == NR_LINETO) return;
2852 sp_node_adjust_handle(node, -1);
2853 return;
2854 }
2856 /* both are curves */
2857 NR::Point const delta( node->n.pos - node->p.pos );
2859 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2860 node->p.pos = node->pos - delta / 2;
2861 node->n.pos = node->pos + delta / 2;
2862 return;
2863 }
2865 /* We are smooth */
2866 double plen = NR::L2(node->p.pos - node->pos);
2867 if (plen < 1e-18) return;
2868 double nlen = NR::L2(node->n.pos - node->pos);
2869 if (nlen < 1e-18) return;
2870 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2871 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2872 }
2874 /**
2875 * Node event callback.
2876 */
2877 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2878 {
2879 gboolean ret = FALSE;
2880 switch (event->type) {
2881 case GDK_ENTER_NOTIFY:
2882 active_node = n;
2883 break;
2884 case GDK_LEAVE_NOTIFY:
2885 active_node = NULL;
2886 break;
2887 case GDK_SCROLL:
2888 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2889 switch (event->scroll.direction) {
2890 case GDK_SCROLL_UP:
2891 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2892 break;
2893 case GDK_SCROLL_DOWN:
2894 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2895 break;
2896 default:
2897 break;
2898 }
2899 ret = TRUE;
2900 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2901 switch (event->scroll.direction) {
2902 case GDK_SCROLL_UP:
2903 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2904 break;
2905 case GDK_SCROLL_DOWN:
2906 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2907 break;
2908 default:
2909 break;
2910 }
2911 ret = TRUE;
2912 }
2913 break;
2914 case GDK_KEY_PRESS:
2915 switch (get_group0_keyval (&event->key)) {
2916 case GDK_space:
2917 if (event->key.state & GDK_BUTTON1_MASK) {
2918 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2919 stamp_repr(nodepath);
2920 ret = TRUE;
2921 }
2922 break;
2923 case GDK_Page_Up:
2924 if (event->key.state & GDK_CONTROL_MASK) {
2925 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2926 } else {
2927 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2928 }
2929 break;
2930 case GDK_Page_Down:
2931 if (event->key.state & GDK_CONTROL_MASK) {
2932 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2933 } else {
2934 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2935 }
2936 break;
2937 default:
2938 break;
2939 }
2940 break;
2941 default:
2942 break;
2943 }
2945 return ret;
2946 }
2948 /**
2949 * Handle keypress on node; directly called.
2950 */
2951 gboolean node_key(GdkEvent *event)
2952 {
2953 Inkscape::NodePath::Path *np;
2955 // there is no way to verify nodes so set active_node to nil when deleting!!
2956 if (active_node == NULL) return FALSE;
2958 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2959 gint ret = FALSE;
2960 switch (get_group0_keyval (&event->key)) {
2961 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2962 case GDK_BackSpace:
2963 np = active_node->subpath->nodepath;
2964 sp_nodepath_node_destroy(active_node);
2965 sp_nodepath_update_repr(np, _("Delete node"));
2966 active_node = NULL;
2967 ret = TRUE;
2968 break;
2969 case GDK_c:
2970 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2971 ret = TRUE;
2972 break;
2973 case GDK_s:
2974 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2975 ret = TRUE;
2976 break;
2977 case GDK_y:
2978 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2979 ret = TRUE;
2980 break;
2981 case GDK_b:
2982 sp_nodepath_node_break(active_node);
2983 ret = TRUE;
2984 break;
2985 }
2986 return ret;
2987 }
2988 return FALSE;
2989 }
2991 /**
2992 * Mouseclick on node callback.
2993 */
2994 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2995 {
2996 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2998 if (state & GDK_CONTROL_MASK) {
2999 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3001 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3002 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3003 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3004 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3005 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3006 } else {
3007 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3008 }
3009 sp_nodepath_update_repr(nodepath, _("Change node type"));
3010 sp_nodepath_update_statusbar(nodepath);
3012 } else { //ctrl+alt+click: delete node
3013 GList *node_to_delete = NULL;
3014 node_to_delete = g_list_append(node_to_delete, n);
3015 sp_node_delete_preserve(node_to_delete);
3016 }
3018 } else {
3019 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3020 }
3021 }
3023 /**
3024 * Mouse grabbed node callback.
3025 */
3026 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3027 {
3028 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3030 if (!n->selected) {
3031 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3032 }
3034 n->is_dragging = true;
3035 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3037 sp_nodepath_remember_origins (n->subpath->nodepath);
3038 }
3040 /**
3041 * Mouse ungrabbed node callback.
3042 */
3043 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3044 {
3045 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3047 n->dragging_out = NULL;
3048 n->is_dragging = false;
3049 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3051 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3052 }
3054 /**
3055 * The point on a line, given by its angle, closest to the given point.
3056 * \param p A point.
3057 * \param a Angle of the line; it is assumed to go through coordinate origin.
3058 * \param closest Pointer to the point struct where the result is stored.
3059 * \todo FIXME: use dot product perhaps?
3060 */
3061 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3062 {
3063 if (a == HUGE_VAL) { // vertical
3064 *closest = NR::Point(0, (*p)[NR::Y]);
3065 } else {
3066 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3067 (*closest)[NR::Y] = a * (*closest)[NR::X];
3068 }
3069 }
3071 /**
3072 * Distance from the point to a line given by its angle.
3073 * \param p A point.
3074 * \param a Angle of the line; it is assumed to go through coordinate origin.
3075 */
3076 static double point_line_distance(NR::Point *p, double a)
3077 {
3078 NR::Point c;
3079 point_line_closest(p, a, &c);
3080 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]));
3081 }
3083 /**
3084 * Callback for node "request" signal.
3085 * \todo fixme: This goes to "moved" event? (lauris)
3086 */
3087 static gboolean
3088 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3089 {
3090 double yn, xn, yp, xp;
3091 double an, ap, na, pa;
3092 double d_an, d_ap, d_na, d_pa;
3093 gboolean collinear = FALSE;
3094 NR::Point c;
3095 NR::Point pr;
3097 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3099 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3100 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3102 NR::Point mouse = (*p);
3104 if (!n->dragging_out) {
3105 // This is the first drag-out event; find out which handle to drag out
3106 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3107 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3109 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3110 return FALSE;
3112 Inkscape::NodePath::NodeSide *opposite;
3113 if (appr_p > appr_n) { // closer to p
3114 n->dragging_out = &n->p;
3115 opposite = &n->n;
3116 n->code = NR_CURVETO;
3117 } else if (appr_p < appr_n) { // closer to n
3118 n->dragging_out = &n->n;
3119 opposite = &n->p;
3120 n->n.other->code = NR_CURVETO;
3121 } else { // p and n nodes are the same
3122 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3123 n->dragging_out = &n->p;
3124 opposite = &n->n;
3125 n->code = NR_CURVETO;
3126 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3127 n->dragging_out = &n->n;
3128 opposite = &n->p;
3129 n->n.other->code = NR_CURVETO;
3130 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3131 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);
3132 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);
3133 if (appr_other_p > appr_other_n) { // closer to other's p handle
3134 n->dragging_out = &n->n;
3135 opposite = &n->p;
3136 n->n.other->code = NR_CURVETO;
3137 } else { // closer to other's n handle
3138 n->dragging_out = &n->p;
3139 opposite = &n->n;
3140 n->code = NR_CURVETO;
3141 }
3142 }
3143 }
3145 // if there's another handle, make sure the one we drag out starts parallel to it
3146 if (opposite->pos != n->pos) {
3147 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3148 }
3150 // knots might not be created yet!
3151 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3152 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3153 }
3155 // pass this on to the handle-moved callback
3156 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3157 sp_node_update_handles(n);
3158 return TRUE;
3159 }
3161 if (state & GDK_CONTROL_MASK) { // constrained motion
3163 // calculate relative distances of handles
3164 // n handle:
3165 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3166 xn = n->n.pos[NR::X] - n->pos[NR::X];
3167 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3168 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3169 if (n->n.other) { // if there is the next point
3170 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3171 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3172 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3173 }
3174 }
3175 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3176 if (yn < 0) { xn = -xn; yn = -yn; }
3178 // p handle:
3179 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3180 xp = n->p.pos[NR::X] - n->pos[NR::X];
3181 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3182 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3183 if (n->p.other) {
3184 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3185 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3186 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3187 }
3188 }
3189 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3190 if (yp < 0) { xp = -xp; yp = -yp; }
3192 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3193 // sliding on handles, only if at least one of the handles is non-vertical
3194 // (otherwise it's the same as ctrl+drag anyway)
3196 // calculate angles of the handles
3197 if (xn == 0) {
3198 if (yn == 0) { // no handle, consider it the continuation of the other one
3199 an = 0;
3200 collinear = TRUE;
3201 }
3202 else an = 0; // vertical; set the angle to horizontal
3203 } else an = yn/xn;
3205 if (xp == 0) {
3206 if (yp == 0) { // no handle, consider it the continuation of the other one
3207 ap = an;
3208 }
3209 else ap = 0; // vertical; set the angle to horizontal
3210 } else ap = yp/xp;
3212 if (collinear) an = ap;
3214 // angles of the perpendiculars; HUGE_VAL means vertical
3215 if (an == 0) na = HUGE_VAL; else na = -1/an;
3216 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3218 // mouse point relative to the node's original pos
3219 pr = (*p) - n->origin;
3221 // distances to the four lines (two handles and two perpendiculars)
3222 d_an = point_line_distance(&pr, an);
3223 d_na = point_line_distance(&pr, na);
3224 d_ap = point_line_distance(&pr, ap);
3225 d_pa = point_line_distance(&pr, pa);
3227 // find out which line is the closest, save its closest point in c
3228 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3229 point_line_closest(&pr, an, &c);
3230 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3231 point_line_closest(&pr, ap, &c);
3232 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3233 point_line_closest(&pr, na, &c);
3234 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3235 point_line_closest(&pr, pa, &c);
3236 }
3238 // move the node to the closest point
3239 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3240 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3241 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3243 } else { // constraining to hor/vert
3245 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3246 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3247 } else { // snap to vert
3248 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3249 }
3250 }
3251 } else { // move freely
3252 if (n->is_dragging) {
3253 if (state & GDK_MOD1_MASK) { // sculpt
3254 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3255 } else {
3256 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3257 (*p)[NR::X] - n->pos[NR::X],
3258 (*p)[NR::Y] - n->pos[NR::Y],
3259 (state & GDK_SHIFT_MASK) == 0);
3260 }
3261 }
3262 }
3264 n->subpath->nodepath->desktop->scroll_to_point(p);
3266 return TRUE;
3267 }
3269 /**
3270 * Node handle clicked callback.
3271 */
3272 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3273 {
3274 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3276 if (state & GDK_CONTROL_MASK) { // "delete" handle
3277 if (n->p.knot == knot) {
3278 n->p.pos = n->pos;
3279 } else if (n->n.knot == knot) {
3280 n->n.pos = n->pos;
3281 }
3282 sp_node_update_handles(n);
3283 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3284 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3285 sp_nodepath_update_statusbar(nodepath);
3287 } else { // just select or add to selection, depending in Shift
3288 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3289 }
3290 }
3292 /**
3293 * Node handle grabbed callback.
3294 */
3295 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3296 {
3297 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3299 if (!n->selected) {
3300 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3301 }
3303 // remember the origin point of the handle
3304 if (n->p.knot == knot) {
3305 n->p.origin_radial = n->p.pos - n->pos;
3306 } else if (n->n.knot == knot) {
3307 n->n.origin_radial = n->n.pos - n->pos;
3308 } else {
3309 g_assert_not_reached();
3310 }
3312 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3313 }
3315 /**
3316 * Node handle ungrabbed callback.
3317 */
3318 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3319 {
3320 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3322 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3323 if (n->p.knot == knot) {
3324 n->p.origin_radial.a = 0;
3325 sp_knot_set_position(knot, &n->p.pos, state);
3326 } else if (n->n.knot == knot) {
3327 n->n.origin_radial.a = 0;
3328 sp_knot_set_position(knot, &n->n.pos, state);
3329 } else {
3330 g_assert_not_reached();
3331 }
3333 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3334 }
3336 /**
3337 * Node handle "request" signal callback.
3338 */
3339 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3340 {
3341 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3343 Inkscape::NodePath::NodeSide *me, *opposite;
3344 gint which;
3345 if (n->p.knot == knot) {
3346 me = &n->p;
3347 opposite = &n->n;
3348 which = -1;
3349 } else if (n->n.knot == knot) {
3350 me = &n->n;
3351 opposite = &n->p;
3352 which = 1;
3353 } else {
3354 me = opposite = NULL;
3355 which = 0;
3356 g_assert_not_reached();
3357 }
3359 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3361 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3363 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3364 /* We are smooth node adjacent with line */
3365 NR::Point const delta = *p - n->pos;
3366 NR::Coord const len = NR::L2(delta);
3367 Inkscape::NodePath::Node *othernode = opposite->other;
3368 NR::Point const ndelta = n->pos - othernode->pos;
3369 NR::Coord const linelen = NR::L2(ndelta);
3370 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3371 NR::Coord const scal = dot(delta, ndelta) / linelen;
3372 (*p) = n->pos + (scal / linelen) * ndelta;
3373 }
3374 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3375 } else {
3376 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3377 }
3379 sp_node_adjust_handle(n, -which);
3381 return FALSE;
3382 }
3384 /**
3385 * Node handle moved callback.
3386 */
3387 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3388 {
3389 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3391 Inkscape::NodePath::NodeSide *me;
3392 Inkscape::NodePath::NodeSide *other;
3393 if (n->p.knot == knot) {
3394 me = &n->p;
3395 other = &n->n;
3396 } else if (n->n.knot == knot) {
3397 me = &n->n;
3398 other = &n->p;
3399 } else {
3400 me = NULL;
3401 other = NULL;
3402 g_assert_not_reached();
3403 }
3405 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3406 Radial rme(me->pos - n->pos);
3407 Radial rother(other->pos - n->pos);
3408 Radial rnew(*p - n->pos);
3410 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3411 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3412 /* 0 interpreted as "no snapping". */
3414 // The closest PI/snaps angle, starting from zero.
3415 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3416 if (me->origin_radial.a == HUGE_VAL) {
3417 // ortho doesn't exist: original handle was zero length.
3418 rnew.a = a_snapped;
3419 } else {
3420 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3421 * its opposite and perpendiculars). */
3422 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3424 // Snap to the closest.
3425 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3426 ? a_snapped
3427 : a_ortho );
3428 }
3429 }
3431 if (state & GDK_MOD1_MASK) {
3432 // lock handle length
3433 rnew.r = me->origin_radial.r;
3434 }
3436 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3437 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3438 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3439 rother.a += rnew.a - rme.a;
3440 other->pos = NR::Point(rother) + n->pos;
3441 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3442 sp_knot_set_position(other->knot, &other->pos, 0);
3443 }
3445 me->pos = NR::Point(rnew) + n->pos;
3446 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3448 // move knot, but without emitting the signal:
3449 // we cannot emit a "moved" signal because we're now processing it
3450 sp_knot_moveto(me->knot, &(me->pos));
3452 update_object(n->subpath->nodepath);
3454 /* status text */
3455 SPDesktop *desktop = n->subpath->nodepath->desktop;
3456 if (!desktop) return;
3457 SPEventContext *ec = desktop->event_context;
3458 if (!ec) return;
3459 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3460 if (!mc) return;
3462 double degrees = 180 / M_PI * rnew.a;
3463 if (degrees > 180) degrees -= 360;
3464 if (degrees < -180) degrees += 360;
3465 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3466 degrees = angle_to_compass (degrees);
3468 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3470 mc->setF(Inkscape::NORMAL_MESSAGE,
3471 _("<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);
3473 g_string_free(length, TRUE);
3474 }
3476 /**
3477 * Node handle event callback.
3478 */
3479 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3480 {
3481 gboolean ret = FALSE;
3482 switch (event->type) {
3483 case GDK_KEY_PRESS:
3484 switch (get_group0_keyval (&event->key)) {
3485 case GDK_space:
3486 if (event->key.state & GDK_BUTTON1_MASK) {
3487 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3488 stamp_repr(nodepath);
3489 ret = TRUE;
3490 }
3491 break;
3492 default:
3493 break;
3494 }
3495 break;
3496 default:
3497 break;
3498 }
3500 return ret;
3501 }
3503 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3504 Radial &rme, Radial &rother, gboolean const both)
3505 {
3506 rme.a += angle;
3507 if ( both
3508 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3509 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3510 {
3511 rother.a += angle;
3512 }
3513 }
3515 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3516 Radial &rme, Radial &rother, gboolean const both)
3517 {
3518 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3520 gdouble r;
3521 if ( both
3522 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3523 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3524 {
3525 r = MAX(rme.r, rother.r);
3526 } else {
3527 r = rme.r;
3528 }
3530 gdouble const weird_angle = atan2(norm_angle, r);
3531 /* Bulia says norm_angle is just the visible distance that the
3532 * object's end must travel on the screen. Left as 'angle' for want of
3533 * a better name.*/
3535 rme.a += weird_angle;
3536 if ( both
3537 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3538 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3539 {
3540 rother.a += weird_angle;
3541 }
3542 }
3544 /**
3545 * Rotate one node.
3546 */
3547 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3548 {
3549 Inkscape::NodePath::NodeSide *me, *other;
3550 bool both = false;
3552 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3553 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3555 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3556 me = &(n->p);
3557 other = &(n->n);
3558 } else if (!n->p.other) {
3559 me = &(n->n);
3560 other = &(n->p);
3561 } else {
3562 if (which > 0) { // right handle
3563 if (xn > xp) {
3564 me = &(n->n);
3565 other = &(n->p);
3566 } else {
3567 me = &(n->p);
3568 other = &(n->n);
3569 }
3570 } else if (which < 0){ // left handle
3571 if (xn <= xp) {
3572 me = &(n->n);
3573 other = &(n->p);
3574 } else {
3575 me = &(n->p);
3576 other = &(n->n);
3577 }
3578 } else { // both handles
3579 me = &(n->n);
3580 other = &(n->p);
3581 both = true;
3582 }
3583 }
3585 Radial rme(me->pos - n->pos);
3586 Radial rother(other->pos - n->pos);
3588 if (screen) {
3589 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3590 } else {
3591 node_rotate_one_internal (*n, angle, rme, rother, both);
3592 }
3594 me->pos = n->pos + NR::Point(rme);
3596 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3597 other->pos = n->pos + NR::Point(rother);
3598 }
3600 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3601 // so here we just move all the knots without emitting move signals, for speed
3602 sp_node_update_handles(n, false);
3603 }
3605 /**
3606 * Rotate selected nodes.
3607 */
3608 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3609 {
3610 if (!nodepath || !nodepath->selected) return;
3612 if (g_list_length(nodepath->selected) == 1) {
3613 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3614 node_rotate_one (n, angle, which, screen);
3615 } else {
3616 // rotate as an object:
3618 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3619 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3620 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3621 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3622 box.expandTo (n->pos); // contain all selected nodes
3623 }
3625 gdouble rot;
3626 if (screen) {
3627 gdouble const zoom = nodepath->desktop->current_zoom();
3628 gdouble const zmove = angle / zoom;
3629 gdouble const r = NR::L2(box.max() - box.midpoint());
3630 rot = atan2(zmove, r);
3631 } else {
3632 rot = angle;
3633 }
3635 NR::Matrix t =
3636 NR::Matrix (NR::translate(-box.midpoint())) *
3637 NR::Matrix (NR::rotate(rot)) *
3638 NR::Matrix (NR::translate(box.midpoint()));
3640 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3641 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3642 n->pos *= t;
3643 n->n.pos *= t;
3644 n->p.pos *= t;
3645 sp_node_update_handles(n, false);
3646 }
3647 }
3649 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3650 }
3652 /**
3653 * Scale one node.
3654 */
3655 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3656 {
3657 bool both = false;
3658 Inkscape::NodePath::NodeSide *me, *other;
3660 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3661 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3663 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3664 me = &(n->p);
3665 other = &(n->n);
3666 n->code = NR_CURVETO;
3667 } else if (!n->p.other) {
3668 me = &(n->n);
3669 other = &(n->p);
3670 if (n->n.other)
3671 n->n.other->code = NR_CURVETO;
3672 } else {
3673 if (which > 0) { // right handle
3674 if (xn > xp) {
3675 me = &(n->n);
3676 other = &(n->p);
3677 if (n->n.other)
3678 n->n.other->code = NR_CURVETO;
3679 } else {
3680 me = &(n->p);
3681 other = &(n->n);
3682 n->code = NR_CURVETO;
3683 }
3684 } else if (which < 0){ // left handle
3685 if (xn <= xp) {
3686 me = &(n->n);
3687 other = &(n->p);
3688 if (n->n.other)
3689 n->n.other->code = NR_CURVETO;
3690 } else {
3691 me = &(n->p);
3692 other = &(n->n);
3693 n->code = NR_CURVETO;
3694 }
3695 } else { // both handles
3696 me = &(n->n);
3697 other = &(n->p);
3698 both = true;
3699 n->code = NR_CURVETO;
3700 if (n->n.other)
3701 n->n.other->code = NR_CURVETO;
3702 }
3703 }
3705 Radial rme(me->pos - n->pos);
3706 Radial rother(other->pos - n->pos);
3708 rme.r += grow;
3709 if (rme.r < 0) rme.r = 0;
3710 if (rme.a == HUGE_VAL) {
3711 if (me->other) { // if direction is unknown, initialize it towards the next node
3712 Radial rme_next(me->other->pos - n->pos);
3713 rme.a = rme_next.a;
3714 } else { // if there's no next, initialize to 0
3715 rme.a = 0;
3716 }
3717 }
3718 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3719 rother.r += grow;
3720 if (rother.r < 0) rother.r = 0;
3721 if (rother.a == HUGE_VAL) {
3722 rother.a = rme.a + M_PI;
3723 }
3724 }
3726 me->pos = n->pos + NR::Point(rme);
3728 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3729 other->pos = n->pos + NR::Point(rother);
3730 }
3732 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3733 // so here we just move all the knots without emitting move signals, for speed
3734 sp_node_update_handles(n, false);
3735 }
3737 /**
3738 * Scale selected nodes.
3739 */
3740 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3741 {
3742 if (!nodepath || !nodepath->selected) return;
3744 if (g_list_length(nodepath->selected) == 1) {
3745 // scale handles of the single selected node
3746 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3747 node_scale_one (n, grow, which);
3748 } else {
3749 // scale nodes as an "object":
3751 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3752 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3753 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3754 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3755 box.expandTo (n->pos); // contain all selected nodes
3756 }
3758 double scale = (box.maxExtent() + grow)/box.maxExtent();
3760 NR::Matrix t =
3761 NR::Matrix (NR::translate(-box.midpoint())) *
3762 NR::Matrix (NR::scale(scale, scale)) *
3763 NR::Matrix (NR::translate(box.midpoint()));
3765 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3766 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3767 n->pos *= t;
3768 n->n.pos *= t;
3769 n->p.pos *= t;
3770 sp_node_update_handles(n, false);
3771 }
3772 }
3774 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3775 }
3777 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3778 {
3779 if (!nodepath) return;
3780 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3781 }
3783 /**
3784 * Flip selected nodes horizontally/vertically.
3785 */
3786 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3787 {
3788 if (!nodepath || !nodepath->selected) return;
3790 if (g_list_length(nodepath->selected) == 1) {
3791 // flip handles of the single selected node
3792 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3793 double temp = n->p.pos[axis];
3794 n->p.pos[axis] = n->n.pos[axis];
3795 n->n.pos[axis] = temp;
3796 sp_node_update_handles(n, false);
3797 } else {
3798 // scale nodes as an "object":
3800 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3801 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3802 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3803 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3804 box.expandTo (n->pos); // contain all selected nodes
3805 }
3807 NR::Matrix t =
3808 NR::Matrix (NR::translate(-box.midpoint())) *
3809 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3810 NR::Matrix (NR::translate(box.midpoint()));
3812 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3813 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3814 n->pos *= t;
3815 n->n.pos *= t;
3816 n->p.pos *= t;
3817 sp_node_update_handles(n, false);
3818 }
3819 }
3821 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3822 }
3824 //-----------------------------------------------
3825 /**
3826 * Return new subpath under given nodepath.
3827 */
3828 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3829 {
3830 g_assert(nodepath);
3831 g_assert(nodepath->desktop);
3833 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3835 s->nodepath = nodepath;
3836 s->closed = FALSE;
3837 s->nodes = NULL;
3838 s->first = NULL;
3839 s->last = NULL;
3841 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3842 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3843 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3845 return s;
3846 }
3848 /**
3849 * Destroy nodes in subpath, then subpath itself.
3850 */
3851 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3852 {
3853 g_assert(subpath);
3854 g_assert(subpath->nodepath);
3855 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3857 while (subpath->nodes) {
3858 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3859 }
3861 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3863 g_free(subpath);
3864 }
3866 /**
3867 * Link head to tail in subpath.
3868 */
3869 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3870 {
3871 g_assert(!sp->closed);
3872 g_assert(sp->last != sp->first);
3873 g_assert(sp->first->code == NR_MOVETO);
3875 sp->closed = TRUE;
3877 //Link the head to the tail
3878 sp->first->p.other = sp->last;
3879 sp->last->n.other = sp->first;
3880 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3881 sp->first = sp->last;
3883 //Remove the extra end node
3884 sp_nodepath_node_destroy(sp->last->n.other);
3885 }
3887 /**
3888 * Open closed (loopy) subpath at node.
3889 */
3890 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3891 {
3892 g_assert(sp->closed);
3893 g_assert(n->subpath == sp);
3894 g_assert(sp->first == sp->last);
3896 /* We create new startpoint, current node will become last one */
3898 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3899 &n->pos, &n->pos, &n->n.pos);
3902 sp->closed = FALSE;
3904 //Unlink to make a head and tail
3905 sp->first = new_path;
3906 sp->last = n;
3907 n->n.other = NULL;
3908 new_path->p.other = NULL;
3909 }
3911 /**
3912 * Returns area in triangle given by points; may be negative.
3913 */
3914 inline double
3915 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3916 {
3917 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]);
3918 }
3920 /**
3921 * Return new node in subpath with given properties.
3922 * \param pos Position of node.
3923 * \param ppos Handle position in previous direction
3924 * \param npos Handle position in previous direction
3925 */
3926 Inkscape::NodePath::Node *
3927 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)
3928 {
3929 g_assert(sp);
3930 g_assert(sp->nodepath);
3931 g_assert(sp->nodepath->desktop);
3933 if (nodechunk == NULL)
3934 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3936 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3938 n->subpath = sp;
3940 if (type != Inkscape::NodePath::NODE_NONE) {
3941 // use the type from sodipodi:nodetypes
3942 n->type = type;
3943 } else {
3944 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3945 // points are (almost) collinear
3946 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3947 // endnode, or a node with a retracted handle
3948 n->type = Inkscape::NodePath::NODE_CUSP;
3949 } else {
3950 n->type = Inkscape::NodePath::NODE_SMOOTH;
3951 }
3952 } else {
3953 n->type = Inkscape::NodePath::NODE_CUSP;
3954 }
3955 }
3957 n->code = code;
3958 n->selected = FALSE;
3959 n->pos = *pos;
3960 n->p.pos = *ppos;
3961 n->n.pos = *npos;
3963 n->dragging_out = NULL;
3965 Inkscape::NodePath::Node *prev;
3966 if (next) {
3967 //g_assert(g_list_find(sp->nodes, next));
3968 prev = next->p.other;
3969 } else {
3970 prev = sp->last;
3971 }
3973 if (prev)
3974 prev->n.other = n;
3975 else
3976 sp->first = n;
3978 if (next)
3979 next->p.other = n;
3980 else
3981 sp->last = n;
3983 n->p.other = prev;
3984 n->n.other = next;
3986 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"));
3987 sp_knot_set_position(n->knot, pos, 0);
3989 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3990 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3991 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3992 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3993 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3994 sp_knot_update_ctrl(n->knot);
3996 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3997 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3998 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3999 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4000 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4001 sp_knot_show(n->knot);
4003 // We only create handle knots and lines on demand
4004 n->p.knot = NULL;
4005 n->p.line = NULL;
4006 n->n.knot = NULL;
4007 n->n.line = NULL;
4009 sp->nodes = g_list_prepend(sp->nodes, n);
4011 return n;
4012 }
4014 /**
4015 * Destroy node and its knots, link neighbors in subpath.
4016 */
4017 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4018 {
4019 g_assert(node);
4020 g_assert(node->subpath);
4021 g_assert(SP_IS_KNOT(node->knot));
4023 Inkscape::NodePath::SubPath *sp = node->subpath;
4025 if (node->selected) { // first, deselect
4026 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4027 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4028 }
4030 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4032 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4033 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4034 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4035 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4036 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4037 g_object_unref(G_OBJECT(node->knot));
4039 if (node->p.knot) {
4040 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4041 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4042 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4043 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4044 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4045 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4046 g_object_unref(G_OBJECT(node->p.knot));
4047 node->p.knot = NULL;
4048 }
4050 if (node->n.knot) {
4051 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4052 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4053 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4054 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4055 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4056 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4057 g_object_unref(G_OBJECT(node->n.knot));
4058 node->n.knot = NULL;
4059 }
4061 if (node->p.line)
4062 gtk_object_destroy(GTK_OBJECT(node->p.line));
4063 if (node->n.line)
4064 gtk_object_destroy(GTK_OBJECT(node->n.line));
4066 if (sp->nodes) { // there are others nodes on the subpath
4067 if (sp->closed) {
4068 if (sp->first == node) {
4069 g_assert(sp->last == node);
4070 sp->first = node->n.other;
4071 sp->last = sp->first;
4072 }
4073 node->p.other->n.other = node->n.other;
4074 node->n.other->p.other = node->p.other;
4075 } else {
4076 if (sp->first == node) {
4077 sp->first = node->n.other;
4078 sp->first->code = NR_MOVETO;
4079 }
4080 if (sp->last == node) sp->last = node->p.other;
4081 if (node->p.other) node->p.other->n.other = node->n.other;
4082 if (node->n.other) node->n.other->p.other = node->p.other;
4083 }
4084 } else { // this was the last node on subpath
4085 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4086 }
4088 g_mem_chunk_free(nodechunk, node);
4089 }
4091 /**
4092 * Returns one of the node's two sides.
4093 * \param which Indicates which side.
4094 * \return Pointer to previous node side if which==-1, next if which==1.
4095 */
4096 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4097 {
4098 g_assert(node);
4100 switch (which) {
4101 case -1:
4102 return &node->p;
4103 case 1:
4104 return &node->n;
4105 default:
4106 break;
4107 }
4109 g_assert_not_reached();
4111 return NULL;
4112 }
4114 /**
4115 * Return the other side of the node, given one of its sides.
4116 */
4117 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4118 {
4119 g_assert(node);
4121 if (me == &node->p) return &node->n;
4122 if (me == &node->n) return &node->p;
4124 g_assert_not_reached();
4126 return NULL;
4127 }
4129 /**
4130 * Return NRPathcode on the given side of the node.
4131 */
4132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4133 {
4134 g_assert(node);
4136 if (me == &node->p) {
4137 if (node->p.other) return (NRPathcode)node->code;
4138 return NR_MOVETO;
4139 }
4141 if (me == &node->n) {
4142 if (node->n.other) return (NRPathcode)node->n.other->code;
4143 return NR_MOVETO;
4144 }
4146 g_assert_not_reached();
4148 return NR_END;
4149 }
4151 /**
4152 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4153 */
4154 Inkscape::NodePath::Node *
4155 sp_nodepath_get_node_by_index(int index)
4156 {
4157 Inkscape::NodePath::Node *e = NULL;
4159 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4160 if (!nodepath) {
4161 return e;
4162 }
4164 //find segment
4165 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4167 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4168 int n = g_list_length(sp->nodes);
4169 if (sp->closed) {
4170 n++;
4171 }
4173 //if the piece belongs to this subpath grab it
4174 //otherwise move onto the next subpath
4175 if (index < n) {
4176 e = sp->first;
4177 for (int i = 0; i < index; ++i) {
4178 e = e->n.other;
4179 }
4180 break;
4181 } else {
4182 if (sp->closed) {
4183 index -= (n+1);
4184 } else {
4185 index -= n;
4186 }
4187 }
4188 }
4190 return e;
4191 }
4193 /**
4194 * Returns plain text meaning of node type.
4195 */
4196 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4197 {
4198 unsigned retracted = 0;
4199 bool endnode = false;
4201 for (int which = -1; which <= 1; which += 2) {
4202 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4203 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4204 retracted ++;
4205 if (!side->other)
4206 endnode = true;
4207 }
4209 if (retracted == 0) {
4210 if (endnode) {
4211 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4212 return _("end node");
4213 } else {
4214 switch (node->type) {
4215 case Inkscape::NodePath::NODE_CUSP:
4216 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4217 return _("cusp");
4218 case Inkscape::NodePath::NODE_SMOOTH:
4219 // TRANSLATORS: "smooth" is an adjective here
4220 return _("smooth");
4221 case Inkscape::NodePath::NODE_SYMM:
4222 return _("symmetric");
4223 }
4224 }
4225 } else if (retracted == 1) {
4226 if (endnode) {
4227 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4228 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4229 } else {
4230 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4231 }
4232 } else {
4233 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4234 }
4236 return NULL;
4237 }
4239 /**
4240 * Handles content of statusbar as long as node tool is active.
4241 */
4242 void
4243 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4244 {
4245 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");
4246 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4248 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4249 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4250 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4251 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4253 SPDesktop *desktop = NULL;
4254 if (nodepath) {
4255 desktop = nodepath->desktop;
4256 } else {
4257 desktop = SP_ACTIVE_DESKTOP;
4258 }
4260 SPEventContext *ec = desktop->event_context;
4261 if (!ec) return;
4262 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4263 if (!mc) return;
4265 if (selected_nodes == 0) {
4266 Inkscape::Selection *sel = desktop->selection;
4267 if (!sel || sel->isEmpty()) {
4268 mc->setF(Inkscape::NORMAL_MESSAGE,
4269 _("Select a single object to edit its nodes or handles."));
4270 } else {
4271 if (nodepath) {
4272 mc->setF(Inkscape::NORMAL_MESSAGE,
4273 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.",
4274 "<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.",
4275 total_nodes),
4276 total_nodes);
4277 } else {
4278 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4279 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4280 } else {
4281 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4282 }
4283 }
4284 }
4285 } else if (nodepath && selected_nodes == 1) {
4286 mc->setF(Inkscape::NORMAL_MESSAGE,
4287 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4288 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4289 total_nodes),
4290 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4291 } else {
4292 if (selected_subpaths > 1) {
4293 mc->setF(Inkscape::NORMAL_MESSAGE,
4294 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4295 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4296 total_nodes),
4297 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4298 } else {
4299 mc->setF(Inkscape::NORMAL_MESSAGE,
4300 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4301 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4302 total_nodes),
4303 selected_nodes, total_nodes, when_selected);
4304 }
4305 }
4306 }
4309 /*
4310 Local Variables:
4311 mode:c++
4312 c-file-style:"stroustrup"
4313 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4314 indent-tabs-mode:nil
4315 fill-column:99
4316 End:
4317 */
4318 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :