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