8371c8241825d00a153c1305b2de858b44c75a0a
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 g_assert(a->p.other || a->n.other);
1731 g_assert(b->p.other || b->n.other);
1733 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1734 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1735 return;
1736 }
1738 /* a and b are endpoints */
1740 NR::Point c;
1741 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1742 c = a->pos;
1743 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1744 c = b->pos;
1745 } else {
1746 c = (a->pos + b->pos) / 2;
1747 }
1749 if (a->subpath == b->subpath) {
1750 Inkscape::NodePath::SubPath *sp = a->subpath;
1751 sp_nodepath_subpath_close(sp);
1752 sp_node_moveto (sp->first, c);
1754 sp_nodepath_update_handles(sp->nodepath);
1755 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1756 return;
1757 }
1759 /* a and b are separate subpaths */
1760 Inkscape::NodePath::SubPath *sa = a->subpath;
1761 Inkscape::NodePath::SubPath *sb = b->subpath;
1762 NR::Point p;
1763 Inkscape::NodePath::Node *n;
1764 NRPathcode code;
1765 if (a == sa->first) {
1766 p = sa->first->n.pos;
1767 code = (NRPathcode)sa->first->n.other->code;
1768 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1769 n = sa->last;
1770 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1771 n = n->p.other;
1772 while (n) {
1773 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1774 n = n->p.other;
1775 if (n == sa->first) n = NULL;
1776 }
1777 sp_nodepath_subpath_destroy(sa);
1778 sa = t;
1779 } else if (a == sa->last) {
1780 p = sa->last->p.pos;
1781 code = (NRPathcode)sa->last->code;
1782 sp_nodepath_node_destroy(sa->last);
1783 } else {
1784 code = NR_END;
1785 g_assert_not_reached();
1786 }
1788 if (b == sb->first) {
1789 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1790 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1791 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1792 }
1793 } else if (b == sb->last) {
1794 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1795 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1796 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1797 }
1798 } else {
1799 g_assert_not_reached();
1800 }
1801 /* and now destroy sb */
1803 sp_nodepath_subpath_destroy(sb);
1805 sp_nodepath_update_handles(sa->nodepath);
1807 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1809 sp_nodepath_update_statusbar(nodepath);
1810 }
1812 /**
1813 * Join two nodes by adding a segment between them.
1814 */
1815 void sp_node_selected_join_segment()
1816 {
1817 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1818 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1820 if (g_list_length(nodepath->selected) != 2) {
1821 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1822 return;
1823 }
1825 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1826 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1828 g_assert(a != b);
1829 g_assert(a->p.other || a->n.other);
1830 g_assert(b->p.other || b->n.other);
1832 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1833 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1834 return;
1835 }
1837 if (a->subpath == b->subpath) {
1838 Inkscape::NodePath::SubPath *sp = a->subpath;
1840 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1841 sp->closed = TRUE;
1843 sp->first->p.other = sp->last;
1844 sp->last->n.other = sp->first;
1846 sp_node_handle_mirror_p_to_n(sp->last);
1847 sp_node_handle_mirror_n_to_p(sp->first);
1849 sp->first->code = sp->last->code;
1850 sp->first = sp->last;
1852 sp_nodepath_update_handles(sp->nodepath);
1854 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1856 return;
1857 }
1859 /* a and b are separate subpaths */
1860 Inkscape::NodePath::SubPath *sa = a->subpath;
1861 Inkscape::NodePath::SubPath *sb = b->subpath;
1863 Inkscape::NodePath::Node *n;
1864 NR::Point p;
1865 NRPathcode code;
1866 if (a == sa->first) {
1867 code = (NRPathcode) sa->first->n.other->code;
1868 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1869 n = sa->last;
1870 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1871 for (n = n->p.other; n != NULL; n = n->p.other) {
1872 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1873 }
1874 sp_nodepath_subpath_destroy(sa);
1875 sa = t;
1876 } else if (a == sa->last) {
1877 code = (NRPathcode)sa->last->code;
1878 } else {
1879 code = NR_END;
1880 g_assert_not_reached();
1881 }
1883 if (b == sb->first) {
1884 n = sb->first;
1885 sp_node_handle_mirror_p_to_n(sa->last);
1886 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1887 sp_node_handle_mirror_n_to_p(sa->last);
1888 for (n = n->n.other; n != NULL; n = n->n.other) {
1889 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1890 }
1891 } else if (b == sb->last) {
1892 n = sb->last;
1893 sp_node_handle_mirror_p_to_n(sa->last);
1894 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1895 sp_node_handle_mirror_n_to_p(sa->last);
1896 for (n = n->p.other; n != NULL; n = n->p.other) {
1897 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1898 }
1899 } else {
1900 g_assert_not_reached();
1901 }
1902 /* and now destroy sb */
1904 sp_nodepath_subpath_destroy(sb);
1906 sp_nodepath_update_handles(sa->nodepath);
1908 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1909 }
1911 /**
1912 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1913 */
1914 void sp_node_delete_preserve(GList *nodes_to_delete)
1915 {
1916 GSList *nodepaths = NULL;
1918 while (nodes_to_delete) {
1919 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1920 Inkscape::NodePath::SubPath *sp = node->subpath;
1921 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1922 Inkscape::NodePath::Node *sample_cursor = NULL;
1923 Inkscape::NodePath::Node *sample_end = NULL;
1924 Inkscape::NodePath::Node *delete_cursor = node;
1925 bool just_delete = false;
1927 //find the start of this contiguous selection
1928 //move left to the first node that is not selected
1929 //or the start of the non-closed path
1930 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1931 delete_cursor = curr;
1932 }
1934 //just delete at the beginning of an open path
1935 if (!delete_cursor->p.other) {
1936 sample_cursor = delete_cursor;
1937 just_delete = true;
1938 } else {
1939 sample_cursor = delete_cursor->p.other;
1940 }
1942 //calculate points for each segment
1943 int rate = 5;
1944 float period = 1.0 / rate;
1945 std::vector<NR::Point> data;
1946 if (!just_delete) {
1947 data.push_back(sample_cursor->pos);
1948 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1949 //just delete at the end of an open path
1950 if (!sp->closed && curr == sp->last) {
1951 just_delete = true;
1952 break;
1953 }
1955 //sample points on the contiguous selected segment
1956 NR::Point *bez;
1957 bez = new NR::Point [4];
1958 bez[0] = curr->pos;
1959 bez[1] = curr->n.pos;
1960 bez[2] = curr->n.other->p.pos;
1961 bez[3] = curr->n.other->pos;
1962 for (int i=1; i<rate; i++) {
1963 gdouble t = i * period;
1964 NR::Point p = bezier_pt(3, bez, t);
1965 data.push_back(p);
1966 }
1967 data.push_back(curr->n.other->pos);
1969 sample_end = curr->n.other;
1970 //break if we've come full circle or hit the end of the selection
1971 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1972 break;
1973 }
1974 }
1975 }
1977 if (!just_delete) {
1978 //calculate the best fitting single segment and adjust the endpoints
1979 NR::Point *adata;
1980 adata = new NR::Point [data.size()];
1981 copy(data.begin(), data.end(), adata);
1983 NR::Point *bez;
1984 bez = new NR::Point [4];
1985 //would decreasing error create a better fitting approximation?
1986 gdouble error = 1.0;
1987 gint ret;
1988 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1990 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1991 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1992 //the resulting nodes behave as expected.
1993 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1994 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1996 //adjust endpoints
1997 sample_cursor->n.pos = bez[1];
1998 sample_end->p.pos = bez[2];
1999 }
2001 //destroy this contiguous selection
2002 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2003 Inkscape::NodePath::Node *temp = delete_cursor;
2004 if (delete_cursor->n.other == delete_cursor) {
2005 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2006 delete_cursor = NULL;
2007 } else {
2008 delete_cursor = delete_cursor->n.other;
2009 }
2010 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2011 sp_nodepath_node_destroy(temp);
2012 }
2014 sp_nodepath_update_handles(nodepath);
2016 if (!g_slist_find(nodepaths, nodepath))
2017 nodepaths = g_slist_prepend (nodepaths, nodepath);
2018 }
2020 for (GSList *i = nodepaths; i; i = i->next) {
2021 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2022 // different nodepaths will give us one undo event per nodepath
2023 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2025 // if the entire nodepath is removed, delete the selected object.
2026 if (nodepath->subpaths == NULL ||
2027 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2028 //at least 2
2029 sp_nodepath_get_node_count(nodepath) < 2) {
2030 SPDocument *document = sp_desktop_document (nodepath->desktop);
2031 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2032 //delete this nodepath's object, not the entire selection! (though at this time, this
2033 //does not matter)
2034 sp_selection_delete();
2035 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2036 _("Delete nodes"));
2037 } else {
2038 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2039 sp_nodepath_update_statusbar(nodepath);
2040 }
2041 }
2043 g_slist_free (nodepaths);
2044 }
2046 /**
2047 * Delete one or more selected nodes.
2048 */
2049 void sp_node_selected_delete()
2050 {
2051 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2052 if (!nodepath) return;
2053 if (!nodepath->selected) return;
2055 /** \todo fixme: do it the right way */
2056 while (nodepath->selected) {
2057 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2058 sp_nodepath_node_destroy(node);
2059 }
2062 //clean up the nodepath (such as for trivial subpaths)
2063 sp_nodepath_cleanup(nodepath);
2065 sp_nodepath_update_handles(nodepath);
2067 // if the entire nodepath is removed, delete the selected object.
2068 if (nodepath->subpaths == NULL ||
2069 sp_nodepath_get_node_count(nodepath) < 2) {
2070 SPDocument *document = sp_desktop_document (nodepath->desktop);
2071 sp_selection_delete();
2072 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2073 _("Delete nodes"));
2074 return;
2075 }
2077 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2079 sp_nodepath_update_statusbar(nodepath);
2080 }
2082 /**
2083 * Delete one or more segments between two selected nodes.
2084 * This is the code for 'split'.
2085 */
2086 void
2087 sp_node_selected_delete_segment(void)
2088 {
2089 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2090 Inkscape::NodePath::Node *curr, *next; //Iterators
2092 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2093 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2095 if (g_list_length(nodepath->selected) != 2) {
2096 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2097 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2098 return;
2099 }
2101 //Selected nodes, not inclusive
2102 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2103 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2105 if ( ( a==b) || //same node
2106 (a->subpath != b->subpath ) || //not the same path
2107 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2108 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2109 {
2110 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2111 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2112 return;
2113 }
2115 //###########################################
2116 //# BEGIN EDITS
2117 //###########################################
2118 //##################################
2119 //# CLOSED PATH
2120 //##################################
2121 if (a->subpath->closed) {
2124 gboolean reversed = FALSE;
2126 //Since we can go in a circle, we need to find the shorter distance.
2127 // a->b or b->a
2128 start = end = NULL;
2129 int distance = 0;
2130 int minDistance = 0;
2131 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2132 if (curr==b) {
2133 //printf("a to b:%d\n", distance);
2134 start = a;//go from a to b
2135 end = b;
2136 minDistance = distance;
2137 //printf("A to B :\n");
2138 break;
2139 }
2140 distance++;
2141 }
2143 //try again, the other direction
2144 distance = 0;
2145 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2146 if (curr==a) {
2147 //printf("b to a:%d\n", distance);
2148 if (distance < minDistance) {
2149 start = b; //we go from b to a
2150 end = a;
2151 reversed = TRUE;
2152 //printf("B to A\n");
2153 }
2154 break;
2155 }
2156 distance++;
2157 }
2160 //Copy everything from 'end' to 'start' to a new subpath
2161 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2162 for (curr=end ; curr ; curr=curr->n.other) {
2163 NRPathcode code = (NRPathcode) curr->code;
2164 if (curr == end)
2165 code = NR_MOVETO;
2166 sp_nodepath_node_new(t, NULL,
2167 (Inkscape::NodePath::NodeType)curr->type, code,
2168 &curr->p.pos, &curr->pos, &curr->n.pos);
2169 if (curr == start)
2170 break;
2171 }
2172 sp_nodepath_subpath_destroy(a->subpath);
2175 }
2179 //##################################
2180 //# OPEN PATH
2181 //##################################
2182 else {
2184 //We need to get the direction of the list between A and B
2185 //Can we walk from a to b?
2186 start = end = NULL;
2187 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2188 if (curr==b) {
2189 start = a; //did it! we go from a to b
2190 end = b;
2191 //printf("A to B\n");
2192 break;
2193 }
2194 }
2195 if (!start) {//didn't work? let's try the other direction
2196 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2197 if (curr==a) {
2198 start = b; //did it! we go from b to a
2199 end = a;
2200 //printf("B to A\n");
2201 break;
2202 }
2203 }
2204 }
2205 if (!start) {
2206 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2207 _("Cannot find path between nodes."));
2208 return;
2209 }
2213 //Copy everything after 'end' to a new subpath
2214 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2215 for (curr=end ; curr ; curr=curr->n.other) {
2216 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2217 &curr->p.pos, &curr->pos, &curr->n.pos);
2218 }
2220 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2221 for (curr = start->n.other ; curr ; curr=next) {
2222 next = curr->n.other;
2223 sp_nodepath_node_destroy(curr);
2224 }
2226 }
2227 //###########################################
2228 //# END EDITS
2229 //###########################################
2231 //clean up the nodepath (such as for trivial subpaths)
2232 sp_nodepath_cleanup(nodepath);
2234 sp_nodepath_update_handles(nodepath);
2236 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2238 sp_nodepath_update_statusbar(nodepath);
2239 }
2241 /**
2242 * Call sp_nodepath_set_line() for all selected segments.
2243 */
2244 void
2245 sp_node_selected_set_line_type(NRPathcode code)
2246 {
2247 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2248 if (nodepath == NULL) return;
2250 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2251 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2252 g_assert(n->selected);
2253 if (n->p.other && n->p.other->selected) {
2254 sp_nodepath_set_line_type(n, code);
2255 }
2256 }
2258 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2259 }
2261 /**
2262 * Call sp_nodepath_convert_node_type() for all selected nodes.
2263 */
2264 void
2265 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2266 {
2267 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2268 if (nodepath == NULL) return;
2270 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2271 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2272 }
2274 sp_nodepath_update_repr(nodepath, _("Change node type"));
2275 }
2277 /**
2278 * Change select status of node, update its own and neighbour handles.
2279 */
2280 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2281 {
2282 node->selected = selected;
2284 if (selected) {
2285 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2286 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2287 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2288 sp_knot_update_ctrl(node->knot);
2289 } else {
2290 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2291 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2292 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2293 sp_knot_update_ctrl(node->knot);
2294 }
2296 sp_node_update_handles(node);
2297 if (node->n.other) sp_node_update_handles(node->n.other);
2298 if (node->p.other) sp_node_update_handles(node->p.other);
2299 }
2301 /**
2302 \brief Select a node
2303 \param node The node to select
2304 \param incremental If true, add to selection, otherwise deselect others
2305 \param override If true, always select this node, otherwise toggle selected status
2306 */
2307 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2308 {
2309 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2311 if (incremental) {
2312 if (override) {
2313 if (!g_list_find(nodepath->selected, node)) {
2314 nodepath->selected = g_list_prepend(nodepath->selected, node);
2315 }
2316 sp_node_set_selected(node, TRUE);
2317 } else { // toggle
2318 if (node->selected) {
2319 g_assert(g_list_find(nodepath->selected, node));
2320 nodepath->selected = g_list_remove(nodepath->selected, node);
2321 } else {
2322 g_assert(!g_list_find(nodepath->selected, node));
2323 nodepath->selected = g_list_prepend(nodepath->selected, node);
2324 }
2325 sp_node_set_selected(node, !node->selected);
2326 }
2327 } else {
2328 sp_nodepath_deselect(nodepath);
2329 nodepath->selected = g_list_prepend(nodepath->selected, node);
2330 sp_node_set_selected(node, TRUE);
2331 }
2333 sp_nodepath_update_statusbar(nodepath);
2334 }
2337 /**
2338 \brief Deselect all nodes in the nodepath
2339 */
2340 void
2341 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2342 {
2343 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2345 while (nodepath->selected) {
2346 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2347 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2348 }
2349 sp_nodepath_update_statusbar(nodepath);
2350 }
2352 /**
2353 \brief Select or invert selection of all nodes in the nodepath
2354 */
2355 void
2356 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2357 {
2358 if (!nodepath) return;
2360 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2361 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2362 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2363 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2364 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2365 }
2366 }
2367 }
2369 /**
2370 * If nothing selected, does the same as sp_nodepath_select_all();
2371 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2372 * (i.e., similar to "select all in layer", with the "selected" subpaths
2373 * being treated as "layers" in the path).
2374 */
2375 void
2376 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2377 {
2378 if (!nodepath) return;
2380 if (g_list_length (nodepath->selected) == 0) {
2381 sp_nodepath_select_all (nodepath, invert);
2382 return;
2383 }
2385 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2386 GSList *subpaths = NULL;
2388 for (GList *l = copy; l != NULL; l = l->next) {
2389 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2390 Inkscape::NodePath::SubPath *subpath = n->subpath;
2391 if (!g_slist_find (subpaths, subpath))
2392 subpaths = g_slist_prepend (subpaths, subpath);
2393 }
2395 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2396 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2397 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2398 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2399 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2400 }
2401 }
2403 g_slist_free (subpaths);
2404 g_list_free (copy);
2405 }
2407 /**
2408 * \brief Select the node after the last selected; if none is selected,
2409 * select the first within path.
2410 */
2411 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2412 {
2413 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2415 Inkscape::NodePath::Node *last = NULL;
2416 if (nodepath->selected) {
2417 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2418 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2419 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2420 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2421 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2422 if (node->selected) {
2423 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2424 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2425 if (spl->next) { // there's a next subpath
2426 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2427 last = subpath_next->first;
2428 } else if (spl->prev) { // there's a previous subpath
2429 last = NULL; // to be set later to the first node of first subpath
2430 } else {
2431 last = node->n.other;
2432 }
2433 } else {
2434 last = node->n.other;
2435 }
2436 } else {
2437 if (node->n.other) {
2438 last = node->n.other;
2439 } else {
2440 if (spl->next) { // there's a next subpath
2441 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2442 last = subpath_next->first;
2443 } else if (spl->prev) { // there's a previous subpath
2444 last = NULL; // to be set later to the first node of first subpath
2445 } else {
2446 last = (Inkscape::NodePath::Node *) subpath->first;
2447 }
2448 }
2449 }
2450 }
2451 }
2452 }
2453 sp_nodepath_deselect(nodepath);
2454 }
2456 if (last) { // there's at least one more node after selected
2457 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2458 } else { // no more nodes, select the first one in first subpath
2459 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2460 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2461 }
2462 }
2464 /**
2465 * \brief Select the node before the first selected; if none is selected,
2466 * select the last within path
2467 */
2468 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2469 {
2470 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2472 Inkscape::NodePath::Node *last = NULL;
2473 if (nodepath->selected) {
2474 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2475 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2476 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2477 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2478 if (node->selected) {
2479 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2480 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2481 if (spl->prev) { // there's a prev subpath
2482 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2483 last = subpath_prev->last;
2484 } else if (spl->next) { // there's a next subpath
2485 last = NULL; // to be set later to the last node of last subpath
2486 } else {
2487 last = node->p.other;
2488 }
2489 } else {
2490 last = node->p.other;
2491 }
2492 } else {
2493 if (node->p.other) {
2494 last = node->p.other;
2495 } else {
2496 if (spl->prev) { // there's a prev subpath
2497 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2498 last = subpath_prev->last;
2499 } else if (spl->next) { // there's a next subpath
2500 last = NULL; // to be set later to the last node of last subpath
2501 } else {
2502 last = (Inkscape::NodePath::Node *) subpath->last;
2503 }
2504 }
2505 }
2506 }
2507 }
2508 }
2509 sp_nodepath_deselect(nodepath);
2510 }
2512 if (last) { // there's at least one more node before selected
2513 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2514 } else { // no more nodes, select the last one in last subpath
2515 GList *spl = g_list_last(nodepath->subpaths);
2516 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2517 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2518 }
2519 }
2521 /**
2522 * \brief Select all nodes that are within the rectangle.
2523 */
2524 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2525 {
2526 if (!incremental) {
2527 sp_nodepath_deselect(nodepath);
2528 }
2530 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2531 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2532 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2533 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2535 if (b.contains(node->pos)) {
2536 sp_nodepath_node_select(node, TRUE, TRUE);
2537 }
2538 }
2539 }
2540 }
2543 void
2544 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2545 {
2546 g_assert (n);
2547 g_assert (nodepath);
2548 g_assert (n->subpath->nodepath == nodepath);
2550 if (g_list_length (nodepath->selected) == 0) {
2551 if (grow > 0) {
2552 sp_nodepath_node_select(n, TRUE, TRUE);
2553 }
2554 return;
2555 }
2557 if (g_list_length (nodepath->selected) == 1) {
2558 if (grow < 0) {
2559 sp_nodepath_deselect (nodepath);
2560 return;
2561 }
2562 }
2564 double n_sel_range = 0, p_sel_range = 0;
2565 Inkscape::NodePath::Node *farthest_n_node = n;
2566 Inkscape::NodePath::Node *farthest_p_node = n;
2568 // Calculate ranges
2569 {
2570 double n_range = 0, p_range = 0;
2571 bool n_going = true, p_going = true;
2572 Inkscape::NodePath::Node *n_node = n;
2573 Inkscape::NodePath::Node *p_node = n;
2574 do {
2575 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2576 if (n_node && n_going)
2577 n_node = n_node->n.other;
2578 if (n_node == NULL) {
2579 n_going = false;
2580 } else {
2581 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2582 if (n_node->selected) {
2583 n_sel_range = n_range;
2584 farthest_n_node = n_node;
2585 }
2586 if (n_node == p_node) {
2587 n_going = false;
2588 p_going = false;
2589 }
2590 }
2591 if (p_node && p_going)
2592 p_node = p_node->p.other;
2593 if (p_node == NULL) {
2594 p_going = false;
2595 } else {
2596 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2597 if (p_node->selected) {
2598 p_sel_range = p_range;
2599 farthest_p_node = p_node;
2600 }
2601 if (p_node == n_node) {
2602 n_going = false;
2603 p_going = false;
2604 }
2605 }
2606 } while (n_going || p_going);
2607 }
2609 if (grow > 0) {
2610 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2611 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2612 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2613 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2614 }
2615 } else {
2616 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2617 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2618 } else if (farthest_p_node && farthest_p_node->selected) {
2619 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2620 }
2621 }
2622 }
2624 void
2625 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2626 {
2627 g_assert (n);
2628 g_assert (nodepath);
2629 g_assert (n->subpath->nodepath == nodepath);
2631 if (g_list_length (nodepath->selected) == 0) {
2632 if (grow > 0) {
2633 sp_nodepath_node_select(n, TRUE, TRUE);
2634 }
2635 return;
2636 }
2638 if (g_list_length (nodepath->selected) == 1) {
2639 if (grow < 0) {
2640 sp_nodepath_deselect (nodepath);
2641 return;
2642 }
2643 }
2645 Inkscape::NodePath::Node *farthest_selected = NULL;
2646 double farthest_dist = 0;
2648 Inkscape::NodePath::Node *closest_unselected = NULL;
2649 double closest_dist = NR_HUGE;
2651 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2652 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2653 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2654 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2655 if (node == n)
2656 continue;
2657 if (node->selected) {
2658 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2659 farthest_dist = NR::L2(node->pos - n->pos);
2660 farthest_selected = node;
2661 }
2662 } else {
2663 if (NR::L2(node->pos - n->pos) < closest_dist) {
2664 closest_dist = NR::L2(node->pos - n->pos);
2665 closest_unselected = node;
2666 }
2667 }
2668 }
2669 }
2671 if (grow > 0) {
2672 if (closest_unselected) {
2673 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2674 }
2675 } else {
2676 if (farthest_selected) {
2677 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2678 }
2679 }
2680 }
2683 /**
2684 \brief Saves all nodes' and handles' current positions in their origin members
2685 */
2686 void
2687 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2688 {
2689 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2690 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2691 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2692 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2693 n->origin = n->pos;
2694 n->p.origin = n->p.pos;
2695 n->n.origin = n->n.pos;
2696 }
2697 }
2698 }
2700 /**
2701 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2702 */
2703 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2704 {
2705 if (!nodepath->selected) {
2706 return NULL;
2707 }
2709 GList *r = NULL;
2710 guint i = 0;
2711 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2712 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2713 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2714 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2715 i++;
2716 if (node->selected) {
2717 r = g_list_append(r, GINT_TO_POINTER(i));
2718 }
2719 }
2720 }
2721 return r;
2722 }
2724 /**
2725 \brief Restores selection by selecting nodes whose positions are in the list
2726 */
2727 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2728 {
2729 sp_nodepath_deselect(nodepath);
2731 guint i = 0;
2732 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2733 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2734 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2735 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2736 i++;
2737 if (g_list_find(r, GINT_TO_POINTER(i))) {
2738 sp_nodepath_node_select(node, TRUE, TRUE);
2739 }
2740 }
2741 }
2743 }
2745 /**
2746 \brief Adjusts handle according to node type and line code.
2747 */
2748 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2749 {
2750 double len, otherlen, linelen;
2752 g_assert(node);
2754 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2755 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2757 /** \todo fixme: */
2758 if (me->other == NULL) return;
2759 if (other->other == NULL) return;
2761 /* I have line */
2763 NRPathcode mecode, ocode;
2764 if (which_adjust == 1) {
2765 mecode = (NRPathcode)me->other->code;
2766 ocode = (NRPathcode)node->code;
2767 } else {
2768 mecode = (NRPathcode)node->code;
2769 ocode = (NRPathcode)other->other->code;
2770 }
2772 if (mecode == NR_LINETO) return;
2774 /* I am curve */
2776 if (other->other == NULL) return;
2778 /* Other has line */
2780 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2782 NR::Point delta;
2783 if (ocode == NR_LINETO) {
2784 /* other is lineto, we are either smooth or symm */
2785 Inkscape::NodePath::Node *othernode = other->other;
2786 len = NR::L2(me->pos - node->pos);
2787 delta = node->pos - othernode->pos;
2788 linelen = NR::L2(delta);
2789 if (linelen < 1e-18)
2790 return;
2791 me->pos = node->pos + (len / linelen)*delta;
2792 return;
2793 }
2795 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2797 me->pos = 2 * node->pos - other->pos;
2798 return;
2799 }
2801 /* We are smooth */
2803 len = NR::L2(me->pos - node->pos);
2804 delta = other->pos - node->pos;
2805 otherlen = NR::L2(delta);
2806 if (otherlen < 1e-18) return;
2808 me->pos = node->pos - (len / otherlen) * delta;
2809 }
2811 /**
2812 \brief Adjusts both handles according to node type and line code
2813 */
2814 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2815 {
2816 g_assert(node);
2818 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2820 /* we are either smooth or symm */
2822 if (node->p.other == NULL) return;
2824 if (node->n.other == NULL) return;
2826 if (node->code == NR_LINETO) {
2827 if (node->n.other->code == NR_LINETO) return;
2828 sp_node_adjust_handle(node, 1);
2829 return;
2830 }
2832 if (node->n.other->code == NR_LINETO) {
2833 if (node->code == NR_LINETO) return;
2834 sp_node_adjust_handle(node, -1);
2835 return;
2836 }
2838 /* both are curves */
2839 NR::Point const delta( node->n.pos - node->p.pos );
2841 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2842 node->p.pos = node->pos - delta / 2;
2843 node->n.pos = node->pos + delta / 2;
2844 return;
2845 }
2847 /* We are smooth */
2848 double plen = NR::L2(node->p.pos - node->pos);
2849 if (plen < 1e-18) return;
2850 double nlen = NR::L2(node->n.pos - node->pos);
2851 if (nlen < 1e-18) return;
2852 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2853 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2854 }
2856 /**
2857 * Node event callback.
2858 */
2859 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2860 {
2861 gboolean ret = FALSE;
2862 switch (event->type) {
2863 case GDK_ENTER_NOTIFY:
2864 active_node = n;
2865 break;
2866 case GDK_LEAVE_NOTIFY:
2867 active_node = NULL;
2868 break;
2869 case GDK_SCROLL:
2870 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2871 switch (event->scroll.direction) {
2872 case GDK_SCROLL_UP:
2873 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2874 break;
2875 case GDK_SCROLL_DOWN:
2876 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2877 break;
2878 default:
2879 break;
2880 }
2881 ret = TRUE;
2882 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2883 switch (event->scroll.direction) {
2884 case GDK_SCROLL_UP:
2885 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2886 break;
2887 case GDK_SCROLL_DOWN:
2888 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2889 break;
2890 default:
2891 break;
2892 }
2893 ret = TRUE;
2894 }
2895 break;
2896 case GDK_KEY_PRESS:
2897 switch (get_group0_keyval (&event->key)) {
2898 case GDK_space:
2899 if (event->key.state & GDK_BUTTON1_MASK) {
2900 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2901 stamp_repr(nodepath);
2902 ret = TRUE;
2903 }
2904 break;
2905 case GDK_Page_Up:
2906 if (event->key.state & GDK_CONTROL_MASK) {
2907 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2908 } else {
2909 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2910 }
2911 break;
2912 case GDK_Page_Down:
2913 if (event->key.state & GDK_CONTROL_MASK) {
2914 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2915 } else {
2916 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2917 }
2918 break;
2919 default:
2920 break;
2921 }
2922 break;
2923 default:
2924 break;
2925 }
2927 return ret;
2928 }
2930 /**
2931 * Handle keypress on node; directly called.
2932 */
2933 gboolean node_key(GdkEvent *event)
2934 {
2935 Inkscape::NodePath::Path *np;
2937 // there is no way to verify nodes so set active_node to nil when deleting!!
2938 if (active_node == NULL) return FALSE;
2940 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2941 gint ret = FALSE;
2942 switch (get_group0_keyval (&event->key)) {
2943 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2944 case GDK_BackSpace:
2945 np = active_node->subpath->nodepath;
2946 sp_nodepath_node_destroy(active_node);
2947 sp_nodepath_update_repr(np, _("Delete node"));
2948 active_node = NULL;
2949 ret = TRUE;
2950 break;
2951 case GDK_c:
2952 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2953 ret = TRUE;
2954 break;
2955 case GDK_s:
2956 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2957 ret = TRUE;
2958 break;
2959 case GDK_y:
2960 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2961 ret = TRUE;
2962 break;
2963 case GDK_b:
2964 sp_nodepath_node_break(active_node);
2965 ret = TRUE;
2966 break;
2967 }
2968 return ret;
2969 }
2970 return FALSE;
2971 }
2973 /**
2974 * Mouseclick on node callback.
2975 */
2976 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2977 {
2978 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2980 if (state & GDK_CONTROL_MASK) {
2981 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2983 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2984 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2985 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2986 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2987 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2988 } else {
2989 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2990 }
2991 sp_nodepath_update_repr(nodepath, _("Change node type"));
2992 sp_nodepath_update_statusbar(nodepath);
2994 } else { //ctrl+alt+click: delete node
2995 GList *node_to_delete = NULL;
2996 node_to_delete = g_list_append(node_to_delete, n);
2997 sp_node_delete_preserve(node_to_delete);
2998 }
3000 } else {
3001 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3002 }
3003 }
3005 /**
3006 * Mouse grabbed node callback.
3007 */
3008 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3009 {
3010 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3012 if (!n->selected) {
3013 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3014 }
3016 n->is_dragging = true;
3017 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3019 sp_nodepath_remember_origins (n->subpath->nodepath);
3020 }
3022 /**
3023 * Mouse ungrabbed node callback.
3024 */
3025 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3026 {
3027 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3029 n->dragging_out = NULL;
3030 n->is_dragging = false;
3031 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3033 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3034 }
3036 /**
3037 * The point on a line, given by its angle, closest to the given point.
3038 * \param p A point.
3039 * \param a Angle of the line; it is assumed to go through coordinate origin.
3040 * \param closest Pointer to the point struct where the result is stored.
3041 * \todo FIXME: use dot product perhaps?
3042 */
3043 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3044 {
3045 if (a == HUGE_VAL) { // vertical
3046 *closest = NR::Point(0, (*p)[NR::Y]);
3047 } else {
3048 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3049 (*closest)[NR::Y] = a * (*closest)[NR::X];
3050 }
3051 }
3053 /**
3054 * Distance from the point to a line given by its angle.
3055 * \param p A point.
3056 * \param a Angle of the line; it is assumed to go through coordinate origin.
3057 */
3058 static double point_line_distance(NR::Point *p, double a)
3059 {
3060 NR::Point c;
3061 point_line_closest(p, a, &c);
3062 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]));
3063 }
3065 /**
3066 * Callback for node "request" signal.
3067 * \todo fixme: This goes to "moved" event? (lauris)
3068 */
3069 static gboolean
3070 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3071 {
3072 double yn, xn, yp, xp;
3073 double an, ap, na, pa;
3074 double d_an, d_ap, d_na, d_pa;
3075 gboolean collinear = FALSE;
3076 NR::Point c;
3077 NR::Point pr;
3079 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3081 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3082 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3084 NR::Point mouse = (*p);
3086 if (!n->dragging_out) {
3087 // This is the first drag-out event; find out which handle to drag out
3088 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3089 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3091 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3092 return FALSE;
3094 Inkscape::NodePath::NodeSide *opposite;
3095 if (appr_p > appr_n) { // closer to p
3096 n->dragging_out = &n->p;
3097 opposite = &n->n;
3098 n->code = NR_CURVETO;
3099 } else if (appr_p < appr_n) { // closer to n
3100 n->dragging_out = &n->n;
3101 opposite = &n->p;
3102 n->n.other->code = NR_CURVETO;
3103 } else { // p and n nodes are the same
3104 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3105 n->dragging_out = &n->p;
3106 opposite = &n->n;
3107 n->code = NR_CURVETO;
3108 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3109 n->dragging_out = &n->n;
3110 opposite = &n->p;
3111 n->n.other->code = NR_CURVETO;
3112 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3113 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);
3114 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);
3115 if (appr_other_p > appr_other_n) { // closer to other's p handle
3116 n->dragging_out = &n->n;
3117 opposite = &n->p;
3118 n->n.other->code = NR_CURVETO;
3119 } else { // closer to other's n handle
3120 n->dragging_out = &n->p;
3121 opposite = &n->n;
3122 n->code = NR_CURVETO;
3123 }
3124 }
3125 }
3127 // if there's another handle, make sure the one we drag out starts parallel to it
3128 if (opposite->pos != n->pos) {
3129 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3130 }
3132 // knots might not be created yet!
3133 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3134 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3135 }
3137 // pass this on to the handle-moved callback
3138 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3139 sp_node_update_handles(n);
3140 return TRUE;
3141 }
3143 if (state & GDK_CONTROL_MASK) { // constrained motion
3145 // calculate relative distances of handles
3146 // n handle:
3147 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3148 xn = n->n.pos[NR::X] - n->pos[NR::X];
3149 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3150 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3151 if (n->n.other) { // if there is the next point
3152 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3153 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3154 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3155 }
3156 }
3157 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3158 if (yn < 0) { xn = -xn; yn = -yn; }
3160 // p handle:
3161 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3162 xp = n->p.pos[NR::X] - n->pos[NR::X];
3163 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3164 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3165 if (n->p.other) {
3166 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3167 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3168 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3169 }
3170 }
3171 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3172 if (yp < 0) { xp = -xp; yp = -yp; }
3174 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3175 // sliding on handles, only if at least one of the handles is non-vertical
3176 // (otherwise it's the same as ctrl+drag anyway)
3178 // calculate angles of the handles
3179 if (xn == 0) {
3180 if (yn == 0) { // no handle, consider it the continuation of the other one
3181 an = 0;
3182 collinear = TRUE;
3183 }
3184 else an = 0; // vertical; set the angle to horizontal
3185 } else an = yn/xn;
3187 if (xp == 0) {
3188 if (yp == 0) { // no handle, consider it the continuation of the other one
3189 ap = an;
3190 }
3191 else ap = 0; // vertical; set the angle to horizontal
3192 } else ap = yp/xp;
3194 if (collinear) an = ap;
3196 // angles of the perpendiculars; HUGE_VAL means vertical
3197 if (an == 0) na = HUGE_VAL; else na = -1/an;
3198 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3200 // mouse point relative to the node's original pos
3201 pr = (*p) - n->origin;
3203 // distances to the four lines (two handles and two perpendiculars)
3204 d_an = point_line_distance(&pr, an);
3205 d_na = point_line_distance(&pr, na);
3206 d_ap = point_line_distance(&pr, ap);
3207 d_pa = point_line_distance(&pr, pa);
3209 // find out which line is the closest, save its closest point in c
3210 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3211 point_line_closest(&pr, an, &c);
3212 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3213 point_line_closest(&pr, ap, &c);
3214 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3215 point_line_closest(&pr, na, &c);
3216 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3217 point_line_closest(&pr, pa, &c);
3218 }
3220 // move the node to the closest point
3221 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3222 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3223 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3225 } else { // constraining to hor/vert
3227 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3228 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3229 } else { // snap to vert
3230 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3231 }
3232 }
3233 } else { // move freely
3234 if (n->is_dragging) {
3235 if (state & GDK_MOD1_MASK) { // sculpt
3236 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3237 } else {
3238 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3239 (*p)[NR::X] - n->pos[NR::X],
3240 (*p)[NR::Y] - n->pos[NR::Y],
3241 (state & GDK_SHIFT_MASK) == 0);
3242 }
3243 }
3244 }
3246 n->subpath->nodepath->desktop->scroll_to_point(p);
3248 return TRUE;
3249 }
3251 /**
3252 * Node handle clicked callback.
3253 */
3254 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3255 {
3256 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3258 if (state & GDK_CONTROL_MASK) { // "delete" handle
3259 if (n->p.knot == knot) {
3260 n->p.pos = n->pos;
3261 } else if (n->n.knot == knot) {
3262 n->n.pos = n->pos;
3263 }
3264 sp_node_update_handles(n);
3265 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3266 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3267 sp_nodepath_update_statusbar(nodepath);
3269 } else { // just select or add to selection, depending in Shift
3270 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3271 }
3272 }
3274 /**
3275 * Node handle grabbed callback.
3276 */
3277 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3278 {
3279 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3281 if (!n->selected) {
3282 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3283 }
3285 // remember the origin point of the handle
3286 if (n->p.knot == knot) {
3287 n->p.origin_radial = n->p.pos - n->pos;
3288 } else if (n->n.knot == knot) {
3289 n->n.origin_radial = n->n.pos - n->pos;
3290 } else {
3291 g_assert_not_reached();
3292 }
3294 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3295 }
3297 /**
3298 * Node handle ungrabbed callback.
3299 */
3300 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3301 {
3302 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3304 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3305 if (n->p.knot == knot) {
3306 n->p.origin_radial.a = 0;
3307 sp_knot_set_position(knot, &n->p.pos, state);
3308 } else if (n->n.knot == knot) {
3309 n->n.origin_radial.a = 0;
3310 sp_knot_set_position(knot, &n->n.pos, state);
3311 } else {
3312 g_assert_not_reached();
3313 }
3315 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3316 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3317 }
3319 /**
3320 * Node handle "request" signal callback.
3321 */
3322 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3323 {
3324 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3326 Inkscape::NodePath::NodeSide *me, *opposite;
3327 gint which;
3328 if (n->p.knot == knot) {
3329 me = &n->p;
3330 opposite = &n->n;
3331 which = -1;
3332 } else if (n->n.knot == knot) {
3333 me = &n->n;
3334 opposite = &n->p;
3335 which = 1;
3336 } else {
3337 me = opposite = NULL;
3338 which = 0;
3339 g_assert_not_reached();
3340 }
3342 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3344 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3346 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3347 /* We are smooth node adjacent with line */
3348 NR::Point const delta = *p - n->pos;
3349 NR::Coord const len = NR::L2(delta);
3350 Inkscape::NodePath::Node *othernode = opposite->other;
3351 NR::Point const ndelta = n->pos - othernode->pos;
3352 NR::Coord const linelen = NR::L2(ndelta);
3353 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3354 NR::Coord const scal = dot(delta, ndelta) / linelen;
3355 (*p) = n->pos + (scal / linelen) * ndelta;
3356 }
3357 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3358 } else {
3359 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3360 }
3362 sp_node_adjust_handle(n, -which);
3364 return FALSE;
3365 }
3367 /**
3368 * Node handle moved callback.
3369 */
3370 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3371 {
3372 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3374 Inkscape::NodePath::NodeSide *me;
3375 Inkscape::NodePath::NodeSide *other;
3376 if (n->p.knot == knot) {
3377 me = &n->p;
3378 other = &n->n;
3379 } else if (n->n.knot == knot) {
3380 me = &n->n;
3381 other = &n->p;
3382 } else {
3383 me = NULL;
3384 other = NULL;
3385 g_assert_not_reached();
3386 }
3388 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3389 Radial rme(me->pos - n->pos);
3390 Radial rother(other->pos - n->pos);
3391 Radial rnew(*p - n->pos);
3393 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3394 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3395 /* 0 interpreted as "no snapping". */
3397 // The closest PI/snaps angle, starting from zero.
3398 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3399 if (me->origin_radial.a == HUGE_VAL) {
3400 // ortho doesn't exist: original handle was zero length.
3401 rnew.a = a_snapped;
3402 } else {
3403 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3404 * its opposite and perpendiculars). */
3405 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3407 // Snap to the closest.
3408 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3409 ? a_snapped
3410 : a_ortho );
3411 }
3412 }
3414 if (state & GDK_MOD1_MASK) {
3415 // lock handle length
3416 rnew.r = me->origin_radial.r;
3417 }
3419 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3420 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3421 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3422 rother.a += rnew.a - rme.a;
3423 other->pos = NR::Point(rother) + n->pos;
3424 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3425 sp_knot_set_position(other->knot, &other->pos, 0);
3426 }
3428 me->pos = NR::Point(rnew) + n->pos;
3429 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3431 // this is what sp_knot_set_position does, but without emitting the signal:
3432 // we cannot emit a "moved" signal because we're now processing it
3433 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3435 knot->desktop->set_coordinate_status(me->pos);
3437 update_object(n->subpath->nodepath);
3439 /* status text */
3440 SPDesktop *desktop = n->subpath->nodepath->desktop;
3441 if (!desktop) return;
3442 SPEventContext *ec = desktop->event_context;
3443 if (!ec) return;
3444 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3445 if (!mc) return;
3447 double degrees = 180 / M_PI * rnew.a;
3448 if (degrees > 180) degrees -= 360;
3449 if (degrees < -180) degrees += 360;
3450 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3451 degrees = angle_to_compass (degrees);
3453 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3455 mc->setF(Inkscape::NORMAL_MESSAGE,
3456 _("<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);
3458 g_string_free(length, TRUE);
3459 }
3461 /**
3462 * Node handle event callback.
3463 */
3464 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3465 {
3466 gboolean ret = FALSE;
3467 switch (event->type) {
3468 case GDK_KEY_PRESS:
3469 switch (get_group0_keyval (&event->key)) {
3470 case GDK_space:
3471 if (event->key.state & GDK_BUTTON1_MASK) {
3472 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3473 stamp_repr(nodepath);
3474 ret = TRUE;
3475 }
3476 break;
3477 default:
3478 break;
3479 }
3480 break;
3481 default:
3482 break;
3483 }
3485 return ret;
3486 }
3488 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3489 Radial &rme, Radial &rother, gboolean const both)
3490 {
3491 rme.a += angle;
3492 if ( both
3493 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3494 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3495 {
3496 rother.a += angle;
3497 }
3498 }
3500 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3501 Radial &rme, Radial &rother, gboolean const both)
3502 {
3503 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3505 gdouble r;
3506 if ( both
3507 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3508 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3509 {
3510 r = MAX(rme.r, rother.r);
3511 } else {
3512 r = rme.r;
3513 }
3515 gdouble const weird_angle = atan2(norm_angle, r);
3516 /* Bulia says norm_angle is just the visible distance that the
3517 * object's end must travel on the screen. Left as 'angle' for want of
3518 * a better name.*/
3520 rme.a += weird_angle;
3521 if ( both
3522 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3523 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3524 {
3525 rother.a += weird_angle;
3526 }
3527 }
3529 /**
3530 * Rotate one node.
3531 */
3532 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3533 {
3534 Inkscape::NodePath::NodeSide *me, *other;
3535 bool both = false;
3537 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3538 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3540 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3541 me = &(n->p);
3542 other = &(n->n);
3543 } else if (!n->p.other) {
3544 me = &(n->n);
3545 other = &(n->p);
3546 } else {
3547 if (which > 0) { // right handle
3548 if (xn > xp) {
3549 me = &(n->n);
3550 other = &(n->p);
3551 } else {
3552 me = &(n->p);
3553 other = &(n->n);
3554 }
3555 } else if (which < 0){ // left handle
3556 if (xn <= xp) {
3557 me = &(n->n);
3558 other = &(n->p);
3559 } else {
3560 me = &(n->p);
3561 other = &(n->n);
3562 }
3563 } else { // both handles
3564 me = &(n->n);
3565 other = &(n->p);
3566 both = true;
3567 }
3568 }
3570 Radial rme(me->pos - n->pos);
3571 Radial rother(other->pos - n->pos);
3573 if (screen) {
3574 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3575 } else {
3576 node_rotate_one_internal (*n, angle, rme, rother, both);
3577 }
3579 me->pos = n->pos + NR::Point(rme);
3581 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3582 other->pos = n->pos + NR::Point(rother);
3583 }
3585 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3586 // so here we just move all the knots without emitting move signals, for speed
3587 sp_node_update_handles(n, false);
3588 }
3590 /**
3591 * Rotate selected nodes.
3592 */
3593 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3594 {
3595 if (!nodepath || !nodepath->selected) return;
3597 if (g_list_length(nodepath->selected) == 1) {
3598 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3599 node_rotate_one (n, angle, which, screen);
3600 } else {
3601 // rotate as an object:
3603 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3604 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3605 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3606 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3607 box.expandTo (n->pos); // contain all selected nodes
3608 }
3610 gdouble rot;
3611 if (screen) {
3612 gdouble const zoom = nodepath->desktop->current_zoom();
3613 gdouble const zmove = angle / zoom;
3614 gdouble const r = NR::L2(box.max() - box.midpoint());
3615 rot = atan2(zmove, r);
3616 } else {
3617 rot = angle;
3618 }
3620 NR::Matrix t =
3621 NR::Matrix (NR::translate(-box.midpoint())) *
3622 NR::Matrix (NR::rotate(rot)) *
3623 NR::Matrix (NR::translate(box.midpoint()));
3625 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3626 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3627 n->pos *= t;
3628 n->n.pos *= t;
3629 n->p.pos *= t;
3630 sp_node_update_handles(n, false);
3631 }
3632 }
3634 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3635 }
3637 /**
3638 * Scale one node.
3639 */
3640 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3641 {
3642 bool both = false;
3643 Inkscape::NodePath::NodeSide *me, *other;
3645 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3646 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3648 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3649 me = &(n->p);
3650 other = &(n->n);
3651 n->code = NR_CURVETO;
3652 } else if (!n->p.other) {
3653 me = &(n->n);
3654 other = &(n->p);
3655 if (n->n.other)
3656 n->n.other->code = NR_CURVETO;
3657 } else {
3658 if (which > 0) { // right handle
3659 if (xn > xp) {
3660 me = &(n->n);
3661 other = &(n->p);
3662 if (n->n.other)
3663 n->n.other->code = NR_CURVETO;
3664 } else {
3665 me = &(n->p);
3666 other = &(n->n);
3667 n->code = NR_CURVETO;
3668 }
3669 } else if (which < 0){ // left handle
3670 if (xn <= xp) {
3671 me = &(n->n);
3672 other = &(n->p);
3673 if (n->n.other)
3674 n->n.other->code = NR_CURVETO;
3675 } else {
3676 me = &(n->p);
3677 other = &(n->n);
3678 n->code = NR_CURVETO;
3679 }
3680 } else { // both handles
3681 me = &(n->n);
3682 other = &(n->p);
3683 both = true;
3684 n->code = NR_CURVETO;
3685 if (n->n.other)
3686 n->n.other->code = NR_CURVETO;
3687 }
3688 }
3690 Radial rme(me->pos - n->pos);
3691 Radial rother(other->pos - n->pos);
3693 rme.r += grow;
3694 if (rme.r < 0) rme.r = 0;
3695 if (rme.a == HUGE_VAL) {
3696 if (me->other) { // if direction is unknown, initialize it towards the next node
3697 Radial rme_next(me->other->pos - n->pos);
3698 rme.a = rme_next.a;
3699 } else { // if there's no next, initialize to 0
3700 rme.a = 0;
3701 }
3702 }
3703 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3704 rother.r += grow;
3705 if (rother.r < 0) rother.r = 0;
3706 if (rother.a == HUGE_VAL) {
3707 rother.a = rme.a + M_PI;
3708 }
3709 }
3711 me->pos = n->pos + NR::Point(rme);
3713 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3714 other->pos = n->pos + NR::Point(rother);
3715 }
3717 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3718 // so here we just move all the knots without emitting move signals, for speed
3719 sp_node_update_handles(n, false);
3720 }
3722 /**
3723 * Scale selected nodes.
3724 */
3725 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3726 {
3727 if (!nodepath || !nodepath->selected) return;
3729 if (g_list_length(nodepath->selected) == 1) {
3730 // scale handles of the single selected node
3731 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3732 node_scale_one (n, grow, which);
3733 } else {
3734 // scale nodes as an "object":
3736 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3737 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3738 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3739 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3740 box.expandTo (n->pos); // contain all selected nodes
3741 }
3743 double scale = (box.maxExtent() + grow)/box.maxExtent();
3745 NR::Matrix t =
3746 NR::Matrix (NR::translate(-box.midpoint())) *
3747 NR::Matrix (NR::scale(scale, scale)) *
3748 NR::Matrix (NR::translate(box.midpoint()));
3750 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3751 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3752 n->pos *= t;
3753 n->n.pos *= t;
3754 n->p.pos *= t;
3755 sp_node_update_handles(n, false);
3756 }
3757 }
3759 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3760 }
3762 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3763 {
3764 if (!nodepath) return;
3765 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3766 }
3768 /**
3769 * Flip selected nodes horizontally/vertically.
3770 */
3771 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3772 {
3773 if (!nodepath || !nodepath->selected) return;
3775 if (g_list_length(nodepath->selected) == 1) {
3776 // flip handles of the single selected node
3777 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3778 double temp = n->p.pos[axis];
3779 n->p.pos[axis] = n->n.pos[axis];
3780 n->n.pos[axis] = temp;
3781 sp_node_update_handles(n, false);
3782 } else {
3783 // scale nodes as an "object":
3785 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3786 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3787 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3788 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3789 box.expandTo (n->pos); // contain all selected nodes
3790 }
3792 NR::Matrix t =
3793 NR::Matrix (NR::translate(-box.midpoint())) *
3794 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3795 NR::Matrix (NR::translate(box.midpoint()));
3797 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3798 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3799 n->pos *= t;
3800 n->n.pos *= t;
3801 n->p.pos *= t;
3802 sp_node_update_handles(n, false);
3803 }
3804 }
3806 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3807 }
3809 //-----------------------------------------------
3810 /**
3811 * Return new subpath under given nodepath.
3812 */
3813 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3814 {
3815 g_assert(nodepath);
3816 g_assert(nodepath->desktop);
3818 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3820 s->nodepath = nodepath;
3821 s->closed = FALSE;
3822 s->nodes = NULL;
3823 s->first = NULL;
3824 s->last = NULL;
3826 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3827 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3828 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3830 return s;
3831 }
3833 /**
3834 * Destroy nodes in subpath, then subpath itself.
3835 */
3836 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3837 {
3838 g_assert(subpath);
3839 g_assert(subpath->nodepath);
3840 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3842 while (subpath->nodes) {
3843 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3844 }
3846 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3848 g_free(subpath);
3849 }
3851 /**
3852 * Link head to tail in subpath.
3853 */
3854 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3855 {
3856 g_assert(!sp->closed);
3857 g_assert(sp->last != sp->first);
3858 g_assert(sp->first->code == NR_MOVETO);
3860 sp->closed = TRUE;
3862 //Link the head to the tail
3863 sp->first->p.other = sp->last;
3864 sp->last->n.other = sp->first;
3865 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3866 sp->first = sp->last;
3868 //Remove the extra end node
3869 sp_nodepath_node_destroy(sp->last->n.other);
3870 }
3872 /**
3873 * Open closed (loopy) subpath at node.
3874 */
3875 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3876 {
3877 g_assert(sp->closed);
3878 g_assert(n->subpath == sp);
3879 g_assert(sp->first == sp->last);
3881 /* We create new startpoint, current node will become last one */
3883 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3884 &n->pos, &n->pos, &n->n.pos);
3887 sp->closed = FALSE;
3889 //Unlink to make a head and tail
3890 sp->first = new_path;
3891 sp->last = n;
3892 n->n.other = NULL;
3893 new_path->p.other = NULL;
3894 }
3896 /**
3897 * Returns area in triangle given by points; may be negative.
3898 */
3899 inline double
3900 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3901 {
3902 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]);
3903 }
3905 /**
3906 * Return new node in subpath with given properties.
3907 * \param pos Position of node.
3908 * \param ppos Handle position in previous direction
3909 * \param npos Handle position in previous direction
3910 */
3911 Inkscape::NodePath::Node *
3912 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)
3913 {
3914 g_assert(sp);
3915 g_assert(sp->nodepath);
3916 g_assert(sp->nodepath->desktop);
3918 if (nodechunk == NULL)
3919 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3921 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3923 n->subpath = sp;
3925 if (type != Inkscape::NodePath::NODE_NONE) {
3926 // use the type from sodipodi:nodetypes
3927 n->type = type;
3928 } else {
3929 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3930 // points are (almost) collinear
3931 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3932 // endnode, or a node with a retracted handle
3933 n->type = Inkscape::NodePath::NODE_CUSP;
3934 } else {
3935 n->type = Inkscape::NodePath::NODE_SMOOTH;
3936 }
3937 } else {
3938 n->type = Inkscape::NodePath::NODE_CUSP;
3939 }
3940 }
3942 n->code = code;
3943 n->selected = FALSE;
3944 n->pos = *pos;
3945 n->p.pos = *ppos;
3946 n->n.pos = *npos;
3948 n->dragging_out = NULL;
3950 Inkscape::NodePath::Node *prev;
3951 if (next) {
3952 //g_assert(g_list_find(sp->nodes, next));
3953 prev = next->p.other;
3954 } else {
3955 prev = sp->last;
3956 }
3958 if (prev)
3959 prev->n.other = n;
3960 else
3961 sp->first = n;
3963 if (next)
3964 next->p.other = n;
3965 else
3966 sp->last = n;
3968 n->p.other = prev;
3969 n->n.other = next;
3971 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"));
3972 sp_knot_set_position(n->knot, pos, 0);
3974 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3975 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3976 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3977 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3978 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3979 sp_knot_update_ctrl(n->knot);
3981 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3982 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3983 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3984 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3985 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3986 sp_knot_show(n->knot);
3988 // We only create handle knots and lines on demand
3989 n->p.knot = NULL;
3990 n->p.line = NULL;
3991 n->n.knot = NULL;
3992 n->n.line = NULL;
3994 sp->nodes = g_list_prepend(sp->nodes, n);
3996 return n;
3997 }
3999 /**
4000 * Destroy node and its knots, link neighbors in subpath.
4001 */
4002 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4003 {
4004 g_assert(node);
4005 g_assert(node->subpath);
4006 g_assert(SP_IS_KNOT(node->knot));
4008 Inkscape::NodePath::SubPath *sp = node->subpath;
4010 if (node->selected) { // first, deselect
4011 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4012 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4013 }
4015 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4017 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4018 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4019 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4020 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4021 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4022 g_object_unref(G_OBJECT(node->knot));
4024 if (node->p.knot) {
4025 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4026 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4027 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4028 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4029 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4030 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4031 g_object_unref(G_OBJECT(node->p.knot));
4032 }
4034 if (node->n.knot) {
4035 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4036 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4037 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4038 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4039 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4040 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4041 g_object_unref(G_OBJECT(node->n.knot));
4042 }
4044 if (node->p.line)
4045 gtk_object_destroy(GTK_OBJECT(node->p.line));
4046 if (node->n.line)
4047 gtk_object_destroy(GTK_OBJECT(node->n.line));
4049 if (sp->nodes) { // there are others nodes on the subpath
4050 if (sp->closed) {
4051 if (sp->first == node) {
4052 g_assert(sp->last == node);
4053 sp->first = node->n.other;
4054 sp->last = sp->first;
4055 }
4056 node->p.other->n.other = node->n.other;
4057 node->n.other->p.other = node->p.other;
4058 } else {
4059 if (sp->first == node) {
4060 sp->first = node->n.other;
4061 sp->first->code = NR_MOVETO;
4062 }
4063 if (sp->last == node) sp->last = node->p.other;
4064 if (node->p.other) node->p.other->n.other = node->n.other;
4065 if (node->n.other) node->n.other->p.other = node->p.other;
4066 }
4067 } else { // this was the last node on subpath
4068 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4069 }
4071 g_mem_chunk_free(nodechunk, node);
4072 }
4074 /**
4075 * Returns one of the node's two sides.
4076 * \param which Indicates which side.
4077 * \return Pointer to previous node side if which==-1, next if which==1.
4078 */
4079 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4080 {
4081 g_assert(node);
4083 switch (which) {
4084 case -1:
4085 return &node->p;
4086 case 1:
4087 return &node->n;
4088 default:
4089 break;
4090 }
4092 g_assert_not_reached();
4094 return NULL;
4095 }
4097 /**
4098 * Return the other side of the node, given one of its sides.
4099 */
4100 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4101 {
4102 g_assert(node);
4104 if (me == &node->p) return &node->n;
4105 if (me == &node->n) return &node->p;
4107 g_assert_not_reached();
4109 return NULL;
4110 }
4112 /**
4113 * Return NRPathcode on the given side of the node.
4114 */
4115 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4116 {
4117 g_assert(node);
4119 if (me == &node->p) {
4120 if (node->p.other) return (NRPathcode)node->code;
4121 return NR_MOVETO;
4122 }
4124 if (me == &node->n) {
4125 if (node->n.other) return (NRPathcode)node->n.other->code;
4126 return NR_MOVETO;
4127 }
4129 g_assert_not_reached();
4131 return NR_END;
4132 }
4134 /**
4135 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4136 */
4137 Inkscape::NodePath::Node *
4138 sp_nodepath_get_node_by_index(int index)
4139 {
4140 Inkscape::NodePath::Node *e = NULL;
4142 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4143 if (!nodepath) {
4144 return e;
4145 }
4147 //find segment
4148 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4150 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4151 int n = g_list_length(sp->nodes);
4152 if (sp->closed) {
4153 n++;
4154 }
4156 //if the piece belongs to this subpath grab it
4157 //otherwise move onto the next subpath
4158 if (index < n) {
4159 e = sp->first;
4160 for (int i = 0; i < index; ++i) {
4161 e = e->n.other;
4162 }
4163 break;
4164 } else {
4165 if (sp->closed) {
4166 index -= (n+1);
4167 } else {
4168 index -= n;
4169 }
4170 }
4171 }
4173 return e;
4174 }
4176 /**
4177 * Returns plain text meaning of node type.
4178 */
4179 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4180 {
4181 unsigned retracted = 0;
4182 bool endnode = false;
4184 for (int which = -1; which <= 1; which += 2) {
4185 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4186 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4187 retracted ++;
4188 if (!side->other)
4189 endnode = true;
4190 }
4192 if (retracted == 0) {
4193 if (endnode) {
4194 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4195 return _("end node");
4196 } else {
4197 switch (node->type) {
4198 case Inkscape::NodePath::NODE_CUSP:
4199 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4200 return _("cusp");
4201 case Inkscape::NodePath::NODE_SMOOTH:
4202 // TRANSLATORS: "smooth" is an adjective here
4203 return _("smooth");
4204 case Inkscape::NodePath::NODE_SYMM:
4205 return _("symmetric");
4206 }
4207 }
4208 } else if (retracted == 1) {
4209 if (endnode) {
4210 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4211 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4212 } else {
4213 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4214 }
4215 } else {
4216 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4217 }
4219 return NULL;
4220 }
4222 /**
4223 * Handles content of statusbar as long as node tool is active.
4224 */
4225 void
4226 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4227 {
4228 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");
4229 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4231 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4232 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4233 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4234 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4236 SPDesktop *desktop = NULL;
4237 if (nodepath) {
4238 desktop = nodepath->desktop;
4239 } else {
4240 desktop = SP_ACTIVE_DESKTOP;
4241 }
4243 SPEventContext *ec = desktop->event_context;
4244 if (!ec) return;
4245 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4246 if (!mc) return;
4248 if (selected_nodes == 0) {
4249 Inkscape::Selection *sel = desktop->selection;
4250 if (!sel || sel->isEmpty()) {
4251 mc->setF(Inkscape::NORMAL_MESSAGE,
4252 _("Select a single object to edit its nodes or handles."));
4253 } else {
4254 if (nodepath) {
4255 mc->setF(Inkscape::NORMAL_MESSAGE,
4256 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.",
4257 "<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.",
4258 total_nodes),
4259 total_nodes);
4260 } else {
4261 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4262 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4263 } else {
4264 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4265 }
4266 }
4267 }
4268 } else if (nodepath && selected_nodes == 1) {
4269 mc->setF(Inkscape::NORMAL_MESSAGE,
4270 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4271 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4272 total_nodes),
4273 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4274 } else {
4275 if (selected_subpaths > 1) {
4276 mc->setF(Inkscape::NORMAL_MESSAGE,
4277 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4278 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4279 total_nodes),
4280 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4281 } else {
4282 mc->setF(Inkscape::NORMAL_MESSAGE,
4283 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4284 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4285 total_nodes),
4286 selected_nodes, total_nodes, when_selected);
4287 }
4288 }
4289 }
4292 /*
4293 Local Variables:
4294 mode:c++
4295 c-file-style:"stroustrup"
4296 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4297 indent-tabs-mode:nil
4298 fill-column:99
4299 End:
4300 */
4301 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :