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