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(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1612 {
1613 //fixme: e and e->p can be NULL, so check for those before proceeding
1614 g_return_if_fail(e != NULL);
1615 g_return_if_fail(&e->p != NULL);
1617 /* feel good is an arbitrary parameter that distributes the delta between handles
1618 * if t of the drag point is less than 1/6 distance form the endpoint only
1619 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1620 */
1621 double feel_good;
1622 if (t <= 1.0 / 6.0)
1623 feel_good = 0;
1624 else if (t <= 0.5)
1625 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1626 else if (t <= 5.0 / 6.0)
1627 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1628 else
1629 feel_good = 1;
1631 //if we're dragging a line convert it to a curve
1632 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1633 sp_nodepath_set_line_type(e, NR_CURVETO);
1634 }
1636 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1637 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1638 e->p.other->n.pos += offsetcoord0;
1639 e->p.pos += offsetcoord1;
1641 // adjust handles of adjacent nodes where necessary
1642 sp_node_adjust_handle(e,1);
1643 sp_node_adjust_handle(e->p.other,-1);
1645 sp_nodepath_update_handles(e->subpath->nodepath);
1647 update_object(e->subpath->nodepath);
1649 sp_nodepath_update_statusbar(e->subpath->nodepath);
1650 }
1653 /**
1654 * Call sp_nodepath_break() for all selected segments.
1655 */
1656 void sp_node_selected_break()
1657 {
1658 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1659 if (!nodepath) return;
1661 GList *temp = NULL;
1662 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1663 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1664 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1665 if (nn == NULL) continue; // no break, no new node
1666 temp = g_list_prepend(temp, nn);
1667 }
1669 if (temp) {
1670 sp_nodepath_deselect(nodepath);
1671 }
1672 for (GList *l = temp; l != NULL; l = l->next) {
1673 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1674 }
1676 sp_nodepath_update_handles(nodepath);
1678 sp_nodepath_update_repr(nodepath, _("Break path"));
1679 }
1681 /**
1682 * Duplicate the selected node(s).
1683 */
1684 void sp_node_selected_duplicate()
1685 {
1686 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1687 if (!nodepath) {
1688 return;
1689 }
1691 GList *temp = NULL;
1692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1694 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1695 if (nn == NULL) continue; // could not duplicate
1696 temp = g_list_prepend(temp, nn);
1697 }
1699 if (temp) {
1700 sp_nodepath_deselect(nodepath);
1701 }
1702 for (GList *l = temp; l != NULL; l = l->next) {
1703 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1704 }
1706 sp_nodepath_update_handles(nodepath);
1708 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1709 }
1711 /**
1712 * Join two nodes by merging them into one.
1713 */
1714 void sp_node_selected_join()
1715 {
1716 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1717 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1719 if (g_list_length(nodepath->selected) != 2) {
1720 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1721 return;
1722 }
1724 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1725 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1727 g_assert(a != b);
1728 g_assert(a->p.other || a->n.other);
1729 g_assert(b->p.other || b->n.other);
1731 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1732 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1733 return;
1734 }
1736 /* a and b are endpoints */
1738 NR::Point c;
1739 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1740 c = a->pos;
1741 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1742 c = b->pos;
1743 } else {
1744 c = (a->pos + b->pos) / 2;
1745 }
1747 if (a->subpath == b->subpath) {
1748 Inkscape::NodePath::SubPath *sp = a->subpath;
1749 sp_nodepath_subpath_close(sp);
1750 sp_node_moveto (sp->first, c);
1752 sp_nodepath_update_handles(sp->nodepath);
1753 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1754 return;
1755 }
1757 /* a and b are separate subpaths */
1758 Inkscape::NodePath::SubPath *sa = a->subpath;
1759 Inkscape::NodePath::SubPath *sb = b->subpath;
1760 NR::Point p;
1761 Inkscape::NodePath::Node *n;
1762 NRPathcode code;
1763 if (a == sa->first) {
1764 p = sa->first->n.pos;
1765 code = (NRPathcode)sa->first->n.other->code;
1766 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1767 n = sa->last;
1768 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1769 n = n->p.other;
1770 while (n) {
1771 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1772 n = n->p.other;
1773 if (n == sa->first) n = NULL;
1774 }
1775 sp_nodepath_subpath_destroy(sa);
1776 sa = t;
1777 } else if (a == sa->last) {
1778 p = sa->last->p.pos;
1779 code = (NRPathcode)sa->last->code;
1780 sp_nodepath_node_destroy(sa->last);
1781 } else {
1782 code = NR_END;
1783 g_assert_not_reached();
1784 }
1786 if (b == sb->first) {
1787 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1788 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1789 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1790 }
1791 } else if (b == sb->last) {
1792 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1793 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1794 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1795 }
1796 } else {
1797 g_assert_not_reached();
1798 }
1799 /* and now destroy sb */
1801 sp_nodepath_subpath_destroy(sb);
1803 sp_nodepath_update_handles(sa->nodepath);
1805 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1807 sp_nodepath_update_statusbar(nodepath);
1808 }
1810 /**
1811 * Join two nodes by adding a segment between them.
1812 */
1813 void sp_node_selected_join_segment()
1814 {
1815 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1816 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1818 if (g_list_length(nodepath->selected) != 2) {
1819 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1820 return;
1821 }
1823 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1824 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1826 g_assert(a != b);
1827 g_assert(a->p.other || a->n.other);
1828 g_assert(b->p.other || b->n.other);
1830 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1831 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1832 return;
1833 }
1835 if (a->subpath == b->subpath) {
1836 Inkscape::NodePath::SubPath *sp = a->subpath;
1838 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1839 sp->closed = TRUE;
1841 sp->first->p.other = sp->last;
1842 sp->last->n.other = sp->first;
1844 sp_node_handle_mirror_p_to_n(sp->last);
1845 sp_node_handle_mirror_n_to_p(sp->first);
1847 sp->first->code = sp->last->code;
1848 sp->first = sp->last;
1850 sp_nodepath_update_handles(sp->nodepath);
1852 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1854 return;
1855 }
1857 /* a and b are separate subpaths */
1858 Inkscape::NodePath::SubPath *sa = a->subpath;
1859 Inkscape::NodePath::SubPath *sb = b->subpath;
1861 Inkscape::NodePath::Node *n;
1862 NR::Point p;
1863 NRPathcode code;
1864 if (a == sa->first) {
1865 code = (NRPathcode) sa->first->n.other->code;
1866 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1867 n = sa->last;
1868 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1869 for (n = n->p.other; n != NULL; n = n->p.other) {
1870 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1871 }
1872 sp_nodepath_subpath_destroy(sa);
1873 sa = t;
1874 } else if (a == sa->last) {
1875 code = (NRPathcode)sa->last->code;
1876 } else {
1877 code = NR_END;
1878 g_assert_not_reached();
1879 }
1881 if (b == sb->first) {
1882 n = sb->first;
1883 sp_node_handle_mirror_p_to_n(sa->last);
1884 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1885 sp_node_handle_mirror_n_to_p(sa->last);
1886 for (n = n->n.other; n != NULL; n = n->n.other) {
1887 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1888 }
1889 } else if (b == sb->last) {
1890 n = sb->last;
1891 sp_node_handle_mirror_p_to_n(sa->last);
1892 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1893 sp_node_handle_mirror_n_to_p(sa->last);
1894 for (n = n->p.other; n != NULL; n = n->p.other) {
1895 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1896 }
1897 } else {
1898 g_assert_not_reached();
1899 }
1900 /* and now destroy sb */
1902 sp_nodepath_subpath_destroy(sb);
1904 sp_nodepath_update_handles(sa->nodepath);
1906 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1907 }
1909 /**
1910 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1911 */
1912 void sp_node_delete_preserve(GList *nodes_to_delete)
1913 {
1914 GSList *nodepaths = NULL;
1916 while (nodes_to_delete) {
1917 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1918 Inkscape::NodePath::SubPath *sp = node->subpath;
1919 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1920 Inkscape::NodePath::Node *sample_cursor = NULL;
1921 Inkscape::NodePath::Node *sample_end = NULL;
1922 Inkscape::NodePath::Node *delete_cursor = node;
1923 bool just_delete = false;
1925 //find the start of this contiguous selection
1926 //move left to the first node that is not selected
1927 //or the start of the non-closed path
1928 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1929 delete_cursor = curr;
1930 }
1932 //just delete at the beginning of an open path
1933 if (!delete_cursor->p.other) {
1934 sample_cursor = delete_cursor;
1935 just_delete = true;
1936 } else {
1937 sample_cursor = delete_cursor->p.other;
1938 }
1940 //calculate points for each segment
1941 int rate = 5;
1942 float period = 1.0 / rate;
1943 std::vector<NR::Point> data;
1944 if (!just_delete) {
1945 data.push_back(sample_cursor->pos);
1946 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1947 //just delete at the end of an open path
1948 if (!sp->closed && curr == sp->last) {
1949 just_delete = true;
1950 break;
1951 }
1953 //sample points on the contiguous selected segment
1954 NR::Point *bez;
1955 bez = new NR::Point [4];
1956 bez[0] = curr->pos;
1957 bez[1] = curr->n.pos;
1958 bez[2] = curr->n.other->p.pos;
1959 bez[3] = curr->n.other->pos;
1960 for (int i=1; i<rate; i++) {
1961 gdouble t = i * period;
1962 NR::Point p = bezier_pt(3, bez, t);
1963 data.push_back(p);
1964 }
1965 data.push_back(curr->n.other->pos);
1967 sample_end = curr->n.other;
1968 //break if we've come full circle or hit the end of the selection
1969 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1970 break;
1971 }
1972 }
1973 }
1975 if (!just_delete) {
1976 //calculate the best fitting single segment and adjust the endpoints
1977 NR::Point *adata;
1978 adata = new NR::Point [data.size()];
1979 copy(data.begin(), data.end(), adata);
1981 NR::Point *bez;
1982 bez = new NR::Point [4];
1983 //would decreasing error create a better fitting approximation?
1984 gdouble error = 1.0;
1985 gint ret;
1986 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1988 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1989 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1990 //the resulting nodes behave as expected.
1991 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1992 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1994 //adjust endpoints
1995 sample_cursor->n.pos = bez[1];
1996 sample_end->p.pos = bez[2];
1997 }
1999 //destroy this contiguous selection
2000 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2001 Inkscape::NodePath::Node *temp = delete_cursor;
2002 if (delete_cursor->n.other == delete_cursor) {
2003 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2004 delete_cursor = NULL;
2005 } else {
2006 delete_cursor = delete_cursor->n.other;
2007 }
2008 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2009 sp_nodepath_node_destroy(temp);
2010 }
2012 sp_nodepath_update_handles(nodepath);
2014 if (!g_slist_find(nodepaths, nodepath))
2015 nodepaths = g_slist_prepend (nodepaths, nodepath);
2016 }
2018 for (GSList *i = nodepaths; i; i = i->next) {
2019 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2020 // different nodepaths will give us one undo event per nodepath
2021 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2023 // if the entire nodepath is removed, delete the selected object.
2024 if (nodepath->subpaths == NULL ||
2025 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2026 //at least 2
2027 sp_nodepath_get_node_count(nodepath) < 2) {
2028 SPDocument *document = sp_desktop_document (nodepath->desktop);
2029 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2030 //delete this nodepath's object, not the entire selection! (though at this time, this
2031 //does not matter)
2032 sp_selection_delete();
2033 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2034 _("Delete nodes"));
2035 } else {
2036 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2037 sp_nodepath_update_statusbar(nodepath);
2038 }
2039 }
2041 g_slist_free (nodepaths);
2042 }
2044 /**
2045 * Delete one or more selected nodes.
2046 */
2047 void sp_node_selected_delete()
2048 {
2049 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2050 if (!nodepath) return;
2051 if (!nodepath->selected) return;
2053 /** \todo fixme: do it the right way */
2054 while (nodepath->selected) {
2055 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2056 sp_nodepath_node_destroy(node);
2057 }
2060 //clean up the nodepath (such as for trivial subpaths)
2061 sp_nodepath_cleanup(nodepath);
2063 sp_nodepath_update_handles(nodepath);
2065 // if the entire nodepath is removed, delete the selected object.
2066 if (nodepath->subpaths == NULL ||
2067 sp_nodepath_get_node_count(nodepath) < 2) {
2068 SPDocument *document = sp_desktop_document (nodepath->desktop);
2069 sp_selection_delete();
2070 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2071 _("Delete nodes"));
2072 return;
2073 }
2075 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2077 sp_nodepath_update_statusbar(nodepath);
2078 }
2080 /**
2081 * Delete one or more segments between two selected nodes.
2082 * This is the code for 'split'.
2083 */
2084 void
2085 sp_node_selected_delete_segment(void)
2086 {
2087 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2088 Inkscape::NodePath::Node *curr, *next; //Iterators
2090 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2091 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2093 if (g_list_length(nodepath->selected) != 2) {
2094 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2095 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2096 return;
2097 }
2099 //Selected nodes, not inclusive
2100 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2101 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2103 if ( ( a==b) || //same node
2104 (a->subpath != b->subpath ) || //not the same path
2105 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2106 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2107 {
2108 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2109 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2110 return;
2111 }
2113 //###########################################
2114 //# BEGIN EDITS
2115 //###########################################
2116 //##################################
2117 //# CLOSED PATH
2118 //##################################
2119 if (a->subpath->closed) {
2122 gboolean reversed = FALSE;
2124 //Since we can go in a circle, we need to find the shorter distance.
2125 // a->b or b->a
2126 start = end = NULL;
2127 int distance = 0;
2128 int minDistance = 0;
2129 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2130 if (curr==b) {
2131 //printf("a to b:%d\n", distance);
2132 start = a;//go from a to b
2133 end = b;
2134 minDistance = distance;
2135 //printf("A to B :\n");
2136 break;
2137 }
2138 distance++;
2139 }
2141 //try again, the other direction
2142 distance = 0;
2143 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2144 if (curr==a) {
2145 //printf("b to a:%d\n", distance);
2146 if (distance < minDistance) {
2147 start = b; //we go from b to a
2148 end = a;
2149 reversed = TRUE;
2150 //printf("B to A\n");
2151 }
2152 break;
2153 }
2154 distance++;
2155 }
2158 //Copy everything from 'end' to 'start' to a new subpath
2159 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2160 for (curr=end ; curr ; curr=curr->n.other) {
2161 NRPathcode code = (NRPathcode) curr->code;
2162 if (curr == end)
2163 code = NR_MOVETO;
2164 sp_nodepath_node_new(t, NULL,
2165 (Inkscape::NodePath::NodeType)curr->type, code,
2166 &curr->p.pos, &curr->pos, &curr->n.pos);
2167 if (curr == start)
2168 break;
2169 }
2170 sp_nodepath_subpath_destroy(a->subpath);
2173 }
2177 //##################################
2178 //# OPEN PATH
2179 //##################################
2180 else {
2182 //We need to get the direction of the list between A and B
2183 //Can we walk from a to b?
2184 start = end = NULL;
2185 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2186 if (curr==b) {
2187 start = a; //did it! we go from a to b
2188 end = b;
2189 //printf("A to B\n");
2190 break;
2191 }
2192 }
2193 if (!start) {//didn't work? let's try the other direction
2194 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2195 if (curr==a) {
2196 start = b; //did it! we go from b to a
2197 end = a;
2198 //printf("B to A\n");
2199 break;
2200 }
2201 }
2202 }
2203 if (!start) {
2204 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2205 _("Cannot find path between nodes."));
2206 return;
2207 }
2211 //Copy everything after 'end' to a new subpath
2212 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2213 for (curr=end ; curr ; curr=curr->n.other) {
2214 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2215 &curr->p.pos, &curr->pos, &curr->n.pos);
2216 }
2218 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2219 for (curr = start->n.other ; curr ; curr=next) {
2220 next = curr->n.other;
2221 sp_nodepath_node_destroy(curr);
2222 }
2224 }
2225 //###########################################
2226 //# END EDITS
2227 //###########################################
2229 //clean up the nodepath (such as for trivial subpaths)
2230 sp_nodepath_cleanup(nodepath);
2232 sp_nodepath_update_handles(nodepath);
2234 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2236 sp_nodepath_update_statusbar(nodepath);
2237 }
2239 /**
2240 * Call sp_nodepath_set_line() for all selected segments.
2241 */
2242 void
2243 sp_node_selected_set_line_type(NRPathcode code)
2244 {
2245 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2246 if (nodepath == NULL) return;
2248 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2250 g_assert(n->selected);
2251 if (n->p.other && n->p.other->selected) {
2252 sp_nodepath_set_line_type(n, code);
2253 }
2254 }
2256 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2257 }
2259 /**
2260 * Call sp_nodepath_convert_node_type() for all selected nodes.
2261 */
2262 void
2263 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2264 {
2265 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2266 if (nodepath == NULL) return;
2268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2269 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2270 }
2272 sp_nodepath_update_repr(nodepath, _("Change node type"));
2273 }
2275 /**
2276 * Change select status of node, update its own and neighbour handles.
2277 */
2278 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2279 {
2280 node->selected = selected;
2282 if (selected) {
2283 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2284 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2285 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2286 sp_knot_update_ctrl(node->knot);
2287 } else {
2288 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2289 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2290 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2291 sp_knot_update_ctrl(node->knot);
2292 }
2294 sp_node_update_handles(node);
2295 if (node->n.other) sp_node_update_handles(node->n.other);
2296 if (node->p.other) sp_node_update_handles(node->p.other);
2297 }
2299 /**
2300 \brief Select a node
2301 \param node The node to select
2302 \param incremental If true, add to selection, otherwise deselect others
2303 \param override If true, always select this node, otherwise toggle selected status
2304 */
2305 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2306 {
2307 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2309 if (incremental) {
2310 if (override) {
2311 if (!g_list_find(nodepath->selected, node)) {
2312 nodepath->selected = g_list_prepend(nodepath->selected, node);
2313 }
2314 sp_node_set_selected(node, TRUE);
2315 } else { // toggle
2316 if (node->selected) {
2317 g_assert(g_list_find(nodepath->selected, node));
2318 nodepath->selected = g_list_remove(nodepath->selected, node);
2319 } else {
2320 g_assert(!g_list_find(nodepath->selected, node));
2321 nodepath->selected = g_list_prepend(nodepath->selected, node);
2322 }
2323 sp_node_set_selected(node, !node->selected);
2324 }
2325 } else {
2326 sp_nodepath_deselect(nodepath);
2327 nodepath->selected = g_list_prepend(nodepath->selected, node);
2328 sp_node_set_selected(node, TRUE);
2329 }
2331 sp_nodepath_update_statusbar(nodepath);
2332 }
2335 /**
2336 \brief Deselect all nodes in the nodepath
2337 */
2338 void
2339 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2340 {
2341 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2343 while (nodepath->selected) {
2344 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2345 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2346 }
2347 sp_nodepath_update_statusbar(nodepath);
2348 }
2350 /**
2351 \brief Select or invert selection of all nodes in the nodepath
2352 */
2353 void
2354 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2355 {
2356 if (!nodepath) return;
2358 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2359 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2360 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2361 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2362 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2363 }
2364 }
2365 }
2367 /**
2368 * If nothing selected, does the same as sp_nodepath_select_all();
2369 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2370 * (i.e., similar to "select all in layer", with the "selected" subpaths
2371 * being treated as "layers" in the path).
2372 */
2373 void
2374 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2375 {
2376 if (!nodepath) return;
2378 if (g_list_length (nodepath->selected) == 0) {
2379 sp_nodepath_select_all (nodepath, invert);
2380 return;
2381 }
2383 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2384 GSList *subpaths = NULL;
2386 for (GList *l = copy; l != NULL; l = l->next) {
2387 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2388 Inkscape::NodePath::SubPath *subpath = n->subpath;
2389 if (!g_slist_find (subpaths, subpath))
2390 subpaths = g_slist_prepend (subpaths, subpath);
2391 }
2393 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2394 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2395 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2396 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2397 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2398 }
2399 }
2401 g_slist_free (subpaths);
2402 g_list_free (copy);
2403 }
2405 /**
2406 * \brief Select the node after the last selected; if none is selected,
2407 * select the first within path.
2408 */
2409 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2410 {
2411 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2413 Inkscape::NodePath::Node *last = NULL;
2414 if (nodepath->selected) {
2415 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2416 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2417 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2418 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2419 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2420 if (node->selected) {
2421 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2422 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2423 if (spl->next) { // there's a next subpath
2424 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2425 last = subpath_next->first;
2426 } else if (spl->prev) { // there's a previous subpath
2427 last = NULL; // to be set later to the first node of first subpath
2428 } else {
2429 last = node->n.other;
2430 }
2431 } else {
2432 last = node->n.other;
2433 }
2434 } else {
2435 if (node->n.other) {
2436 last = node->n.other;
2437 } else {
2438 if (spl->next) { // there's a next subpath
2439 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2440 last = subpath_next->first;
2441 } else if (spl->prev) { // there's a previous subpath
2442 last = NULL; // to be set later to the first node of first subpath
2443 } else {
2444 last = (Inkscape::NodePath::Node *) subpath->first;
2445 }
2446 }
2447 }
2448 }
2449 }
2450 }
2451 sp_nodepath_deselect(nodepath);
2452 }
2454 if (last) { // there's at least one more node after selected
2455 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2456 } else { // no more nodes, select the first one in first subpath
2457 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2458 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2459 }
2460 }
2462 /**
2463 * \brief Select the node before the first selected; if none is selected,
2464 * select the last within path
2465 */
2466 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2467 {
2468 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2470 Inkscape::NodePath::Node *last = NULL;
2471 if (nodepath->selected) {
2472 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2473 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2474 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2475 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2476 if (node->selected) {
2477 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2478 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2479 if (spl->prev) { // there's a prev subpath
2480 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2481 last = subpath_prev->last;
2482 } else if (spl->next) { // there's a next subpath
2483 last = NULL; // to be set later to the last node of last subpath
2484 } else {
2485 last = node->p.other;
2486 }
2487 } else {
2488 last = node->p.other;
2489 }
2490 } else {
2491 if (node->p.other) {
2492 last = node->p.other;
2493 } else {
2494 if (spl->prev) { // there's a prev subpath
2495 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2496 last = subpath_prev->last;
2497 } else if (spl->next) { // there's a next subpath
2498 last = NULL; // to be set later to the last node of last subpath
2499 } else {
2500 last = (Inkscape::NodePath::Node *) subpath->last;
2501 }
2502 }
2503 }
2504 }
2505 }
2506 }
2507 sp_nodepath_deselect(nodepath);
2508 }
2510 if (last) { // there's at least one more node before selected
2511 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2512 } else { // no more nodes, select the last one in last subpath
2513 GList *spl = g_list_last(nodepath->subpaths);
2514 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2515 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2516 }
2517 }
2519 /**
2520 * \brief Select all nodes that are within the rectangle.
2521 */
2522 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2523 {
2524 if (!incremental) {
2525 sp_nodepath_deselect(nodepath);
2526 }
2528 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2529 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2530 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2531 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2533 if (b.contains(node->pos)) {
2534 sp_nodepath_node_select(node, TRUE, TRUE);
2535 }
2536 }
2537 }
2538 }
2541 void
2542 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2543 {
2544 g_assert (n);
2545 g_assert (nodepath);
2546 g_assert (n->subpath->nodepath == nodepath);
2548 if (g_list_length (nodepath->selected) == 0) {
2549 if (grow > 0) {
2550 sp_nodepath_node_select(n, TRUE, TRUE);
2551 }
2552 return;
2553 }
2555 if (g_list_length (nodepath->selected) == 1) {
2556 if (grow < 0) {
2557 sp_nodepath_deselect (nodepath);
2558 return;
2559 }
2560 }
2562 double n_sel_range = 0, p_sel_range = 0;
2563 Inkscape::NodePath::Node *farthest_n_node = n;
2564 Inkscape::NodePath::Node *farthest_p_node = n;
2566 // Calculate ranges
2567 {
2568 double n_range = 0, p_range = 0;
2569 bool n_going = true, p_going = true;
2570 Inkscape::NodePath::Node *n_node = n;
2571 Inkscape::NodePath::Node *p_node = n;
2572 do {
2573 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2574 if (n_node && n_going)
2575 n_node = n_node->n.other;
2576 if (n_node == NULL) {
2577 n_going = false;
2578 } else {
2579 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2580 if (n_node->selected) {
2581 n_sel_range = n_range;
2582 farthest_n_node = n_node;
2583 }
2584 if (n_node == p_node) {
2585 n_going = false;
2586 p_going = false;
2587 }
2588 }
2589 if (p_node && p_going)
2590 p_node = p_node->p.other;
2591 if (p_node == NULL) {
2592 p_going = false;
2593 } else {
2594 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2595 if (p_node->selected) {
2596 p_sel_range = p_range;
2597 farthest_p_node = p_node;
2598 }
2599 if (p_node == n_node) {
2600 n_going = false;
2601 p_going = false;
2602 }
2603 }
2604 } while (n_going || p_going);
2605 }
2607 if (grow > 0) {
2608 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2609 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2610 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2611 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2612 }
2613 } else {
2614 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2615 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2616 } else if (farthest_p_node && farthest_p_node->selected) {
2617 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2618 }
2619 }
2620 }
2622 void
2623 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2624 {
2625 g_assert (n);
2626 g_assert (nodepath);
2627 g_assert (n->subpath->nodepath == nodepath);
2629 if (g_list_length (nodepath->selected) == 0) {
2630 if (grow > 0) {
2631 sp_nodepath_node_select(n, TRUE, TRUE);
2632 }
2633 return;
2634 }
2636 if (g_list_length (nodepath->selected) == 1) {
2637 if (grow < 0) {
2638 sp_nodepath_deselect (nodepath);
2639 return;
2640 }
2641 }
2643 Inkscape::NodePath::Node *farthest_selected = NULL;
2644 double farthest_dist = 0;
2646 Inkscape::NodePath::Node *closest_unselected = NULL;
2647 double closest_dist = NR_HUGE;
2649 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2650 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2651 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2652 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2653 if (node == n)
2654 continue;
2655 if (node->selected) {
2656 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2657 farthest_dist = NR::L2(node->pos - n->pos);
2658 farthest_selected = node;
2659 }
2660 } else {
2661 if (NR::L2(node->pos - n->pos) < closest_dist) {
2662 closest_dist = NR::L2(node->pos - n->pos);
2663 closest_unselected = node;
2664 }
2665 }
2666 }
2667 }
2669 if (grow > 0) {
2670 if (closest_unselected) {
2671 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2672 }
2673 } else {
2674 if (farthest_selected) {
2675 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2676 }
2677 }
2678 }
2681 /**
2682 \brief Saves all nodes' and handles' current positions in their origin members
2683 */
2684 void
2685 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2686 {
2687 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2688 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2689 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2690 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2691 n->origin = n->pos;
2692 n->p.origin = n->p.pos;
2693 n->n.origin = n->n.pos;
2694 }
2695 }
2696 }
2698 /**
2699 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2700 */
2701 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2702 {
2703 if (!nodepath->selected) {
2704 return NULL;
2705 }
2707 GList *r = NULL;
2708 guint i = 0;
2709 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2710 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2711 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2712 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2713 i++;
2714 if (node->selected) {
2715 r = g_list_append(r, GINT_TO_POINTER(i));
2716 }
2717 }
2718 }
2719 return r;
2720 }
2722 /**
2723 \brief Restores selection by selecting nodes whose positions are in the list
2724 */
2725 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2726 {
2727 sp_nodepath_deselect(nodepath);
2729 guint i = 0;
2730 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2731 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2732 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2733 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2734 i++;
2735 if (g_list_find(r, GINT_TO_POINTER(i))) {
2736 sp_nodepath_node_select(node, TRUE, TRUE);
2737 }
2738 }
2739 }
2741 }
2743 /**
2744 \brief Adjusts handle according to node type and line code.
2745 */
2746 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2747 {
2748 double len, otherlen, linelen;
2750 g_assert(node);
2752 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2753 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2755 /** \todo fixme: */
2756 if (me->other == NULL) return;
2757 if (other->other == NULL) return;
2759 /* I have line */
2761 NRPathcode mecode, ocode;
2762 if (which_adjust == 1) {
2763 mecode = (NRPathcode)me->other->code;
2764 ocode = (NRPathcode)node->code;
2765 } else {
2766 mecode = (NRPathcode)node->code;
2767 ocode = (NRPathcode)other->other->code;
2768 }
2770 if (mecode == NR_LINETO) return;
2772 /* I am curve */
2774 if (other->other == NULL) return;
2776 /* Other has line */
2778 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2780 NR::Point delta;
2781 if (ocode == NR_LINETO) {
2782 /* other is lineto, we are either smooth or symm */
2783 Inkscape::NodePath::Node *othernode = other->other;
2784 len = NR::L2(me->pos - node->pos);
2785 delta = node->pos - othernode->pos;
2786 linelen = NR::L2(delta);
2787 if (linelen < 1e-18)
2788 return;
2789 me->pos = node->pos + (len / linelen)*delta;
2790 return;
2791 }
2793 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2795 me->pos = 2 * node->pos - other->pos;
2796 return;
2797 }
2799 /* We are smooth */
2801 len = NR::L2(me->pos - node->pos);
2802 delta = other->pos - node->pos;
2803 otherlen = NR::L2(delta);
2804 if (otherlen < 1e-18) return;
2806 me->pos = node->pos - (len / otherlen) * delta;
2807 }
2809 /**
2810 \brief Adjusts both handles according to node type and line code
2811 */
2812 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2813 {
2814 g_assert(node);
2816 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2818 /* we are either smooth or symm */
2820 if (node->p.other == NULL) return;
2822 if (node->n.other == NULL) return;
2824 if (node->code == NR_LINETO) {
2825 if (node->n.other->code == NR_LINETO) return;
2826 sp_node_adjust_handle(node, 1);
2827 return;
2828 }
2830 if (node->n.other->code == NR_LINETO) {
2831 if (node->code == NR_LINETO) return;
2832 sp_node_adjust_handle(node, -1);
2833 return;
2834 }
2836 /* both are curves */
2837 NR::Point const delta( node->n.pos - node->p.pos );
2839 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2840 node->p.pos = node->pos - delta / 2;
2841 node->n.pos = node->pos + delta / 2;
2842 return;
2843 }
2845 /* We are smooth */
2846 double plen = NR::L2(node->p.pos - node->pos);
2847 if (plen < 1e-18) return;
2848 double nlen = NR::L2(node->n.pos - node->pos);
2849 if (nlen < 1e-18) return;
2850 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2851 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2852 }
2854 /**
2855 * Node event callback.
2856 */
2857 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2858 {
2859 gboolean ret = FALSE;
2860 switch (event->type) {
2861 case GDK_ENTER_NOTIFY:
2862 active_node = n;
2863 break;
2864 case GDK_LEAVE_NOTIFY:
2865 active_node = NULL;
2866 break;
2867 case GDK_SCROLL:
2868 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2869 switch (event->scroll.direction) {
2870 case GDK_SCROLL_UP:
2871 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2872 break;
2873 case GDK_SCROLL_DOWN:
2874 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2875 break;
2876 default:
2877 break;
2878 }
2879 ret = TRUE;
2880 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2881 switch (event->scroll.direction) {
2882 case GDK_SCROLL_UP:
2883 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2884 break;
2885 case GDK_SCROLL_DOWN:
2886 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2887 break;
2888 default:
2889 break;
2890 }
2891 ret = TRUE;
2892 }
2893 break;
2894 case GDK_KEY_PRESS:
2895 switch (get_group0_keyval (&event->key)) {
2896 case GDK_space:
2897 if (event->key.state & GDK_BUTTON1_MASK) {
2898 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2899 stamp_repr(nodepath);
2900 ret = TRUE;
2901 }
2902 break;
2903 case GDK_Page_Up:
2904 if (event->key.state & GDK_CONTROL_MASK) {
2905 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2906 } else {
2907 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2908 }
2909 break;
2910 case GDK_Page_Down:
2911 if (event->key.state & GDK_CONTROL_MASK) {
2912 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2913 } else {
2914 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2915 }
2916 break;
2917 default:
2918 break;
2919 }
2920 break;
2921 default:
2922 break;
2923 }
2925 return ret;
2926 }
2928 /**
2929 * Handle keypress on node; directly called.
2930 */
2931 gboolean node_key(GdkEvent *event)
2932 {
2933 Inkscape::NodePath::Path *np;
2935 // there is no way to verify nodes so set active_node to nil when deleting!!
2936 if (active_node == NULL) return FALSE;
2938 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2939 gint ret = FALSE;
2940 switch (get_group0_keyval (&event->key)) {
2941 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2942 case GDK_BackSpace:
2943 np = active_node->subpath->nodepath;
2944 sp_nodepath_node_destroy(active_node);
2945 sp_nodepath_update_repr(np, _("Delete node"));
2946 active_node = NULL;
2947 ret = TRUE;
2948 break;
2949 case GDK_c:
2950 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2951 ret = TRUE;
2952 break;
2953 case GDK_s:
2954 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2955 ret = TRUE;
2956 break;
2957 case GDK_y:
2958 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2959 ret = TRUE;
2960 break;
2961 case GDK_b:
2962 sp_nodepath_node_break(active_node);
2963 ret = TRUE;
2964 break;
2965 }
2966 return ret;
2967 }
2968 return FALSE;
2969 }
2971 /**
2972 * Mouseclick on node callback.
2973 */
2974 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2975 {
2976 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2978 if (state & GDK_CONTROL_MASK) {
2979 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2981 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2982 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2983 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2984 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2985 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2986 } else {
2987 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2988 }
2989 sp_nodepath_update_repr(nodepath, _("Change node type"));
2990 sp_nodepath_update_statusbar(nodepath);
2992 } else { //ctrl+alt+click: delete node
2993 GList *node_to_delete = NULL;
2994 node_to_delete = g_list_append(node_to_delete, n);
2995 sp_node_delete_preserve(node_to_delete);
2996 }
2998 } else {
2999 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3000 }
3001 }
3003 /**
3004 * Mouse grabbed node callback.
3005 */
3006 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3007 {
3008 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3010 if (!n->selected) {
3011 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3012 }
3014 n->is_dragging = true;
3015 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3017 sp_nodepath_remember_origins (n->subpath->nodepath);
3018 }
3020 /**
3021 * Mouse ungrabbed node callback.
3022 */
3023 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3024 {
3025 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3027 n->dragging_out = NULL;
3028 n->is_dragging = false;
3029 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3031 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3032 }
3034 /**
3035 * The point on a line, given by its angle, closest to the given point.
3036 * \param p A point.
3037 * \param a Angle of the line; it is assumed to go through coordinate origin.
3038 * \param closest Pointer to the point struct where the result is stored.
3039 * \todo FIXME: use dot product perhaps?
3040 */
3041 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3042 {
3043 if (a == HUGE_VAL) { // vertical
3044 *closest = NR::Point(0, (*p)[NR::Y]);
3045 } else {
3046 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3047 (*closest)[NR::Y] = a * (*closest)[NR::X];
3048 }
3049 }
3051 /**
3052 * Distance from the point to a line given by its angle.
3053 * \param p A point.
3054 * \param a Angle of the line; it is assumed to go through coordinate origin.
3055 */
3056 static double point_line_distance(NR::Point *p, double a)
3057 {
3058 NR::Point c;
3059 point_line_closest(p, a, &c);
3060 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]));
3061 }
3063 /**
3064 * Callback for node "request" signal.
3065 * \todo fixme: This goes to "moved" event? (lauris)
3066 */
3067 static gboolean
3068 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3069 {
3070 double yn, xn, yp, xp;
3071 double an, ap, na, pa;
3072 double d_an, d_ap, d_na, d_pa;
3073 gboolean collinear = FALSE;
3074 NR::Point c;
3075 NR::Point pr;
3077 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3079 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3080 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3082 NR::Point mouse = (*p);
3084 if (!n->dragging_out) {
3085 // This is the first drag-out event; find out which handle to drag out
3086 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3087 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3089 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3090 return FALSE;
3092 Inkscape::NodePath::NodeSide *opposite;
3093 if (appr_p > appr_n) { // closer to p
3094 n->dragging_out = &n->p;
3095 opposite = &n->n;
3096 n->code = NR_CURVETO;
3097 } else if (appr_p < appr_n) { // closer to n
3098 n->dragging_out = &n->n;
3099 opposite = &n->p;
3100 n->n.other->code = NR_CURVETO;
3101 } else { // p and n nodes are the same
3102 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3103 n->dragging_out = &n->p;
3104 opposite = &n->n;
3105 n->code = NR_CURVETO;
3106 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3107 n->dragging_out = &n->n;
3108 opposite = &n->p;
3109 n->n.other->code = NR_CURVETO;
3110 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3111 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);
3112 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);
3113 if (appr_other_p > appr_other_n) { // closer to other's p handle
3114 n->dragging_out = &n->n;
3115 opposite = &n->p;
3116 n->n.other->code = NR_CURVETO;
3117 } else { // closer to other's n handle
3118 n->dragging_out = &n->p;
3119 opposite = &n->n;
3120 n->code = NR_CURVETO;
3121 }
3122 }
3123 }
3125 // if there's another handle, make sure the one we drag out starts parallel to it
3126 if (opposite->pos != n->pos) {
3127 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3128 }
3130 // knots might not be created yet!
3131 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3132 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3133 }
3135 // pass this on to the handle-moved callback
3136 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3137 sp_node_update_handles(n);
3138 return TRUE;
3139 }
3141 if (state & GDK_CONTROL_MASK) { // constrained motion
3143 // calculate relative distances of handles
3144 // n handle:
3145 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3146 xn = n->n.pos[NR::X] - n->pos[NR::X];
3147 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3148 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3149 if (n->n.other) { // if there is the next point
3150 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3151 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3152 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3153 }
3154 }
3155 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3156 if (yn < 0) { xn = -xn; yn = -yn; }
3158 // p handle:
3159 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3160 xp = n->p.pos[NR::X] - n->pos[NR::X];
3161 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3162 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3163 if (n->p.other) {
3164 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3165 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3166 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3167 }
3168 }
3169 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3170 if (yp < 0) { xp = -xp; yp = -yp; }
3172 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3173 // sliding on handles, only if at least one of the handles is non-vertical
3174 // (otherwise it's the same as ctrl+drag anyway)
3176 // calculate angles of the handles
3177 if (xn == 0) {
3178 if (yn == 0) { // no handle, consider it the continuation of the other one
3179 an = 0;
3180 collinear = TRUE;
3181 }
3182 else an = 0; // vertical; set the angle to horizontal
3183 } else an = yn/xn;
3185 if (xp == 0) {
3186 if (yp == 0) { // no handle, consider it the continuation of the other one
3187 ap = an;
3188 }
3189 else ap = 0; // vertical; set the angle to horizontal
3190 } else ap = yp/xp;
3192 if (collinear) an = ap;
3194 // angles of the perpendiculars; HUGE_VAL means vertical
3195 if (an == 0) na = HUGE_VAL; else na = -1/an;
3196 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3198 // mouse point relative to the node's original pos
3199 pr = (*p) - n->origin;
3201 // distances to the four lines (two handles and two perpendiculars)
3202 d_an = point_line_distance(&pr, an);
3203 d_na = point_line_distance(&pr, na);
3204 d_ap = point_line_distance(&pr, ap);
3205 d_pa = point_line_distance(&pr, pa);
3207 // find out which line is the closest, save its closest point in c
3208 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3209 point_line_closest(&pr, an, &c);
3210 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3211 point_line_closest(&pr, ap, &c);
3212 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3213 point_line_closest(&pr, na, &c);
3214 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3215 point_line_closest(&pr, pa, &c);
3216 }
3218 // move the node to the closest point
3219 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3220 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3221 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3223 } else { // constraining to hor/vert
3225 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3226 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3227 } else { // snap to vert
3228 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3229 }
3230 }
3231 } else { // move freely
3232 if (n->is_dragging) {
3233 if (state & GDK_MOD1_MASK) { // sculpt
3234 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3235 } else {
3236 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3237 (*p)[NR::X] - n->pos[NR::X],
3238 (*p)[NR::Y] - n->pos[NR::Y],
3239 (state & GDK_SHIFT_MASK) == 0);
3240 }
3241 }
3242 }
3244 n->subpath->nodepath->desktop->scroll_to_point(p);
3246 return TRUE;
3247 }
3249 /**
3250 * Node handle clicked callback.
3251 */
3252 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3253 {
3254 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3256 if (state & GDK_CONTROL_MASK) { // "delete" handle
3257 if (n->p.knot == knot) {
3258 n->p.pos = n->pos;
3259 } else if (n->n.knot == knot) {
3260 n->n.pos = n->pos;
3261 }
3262 sp_node_update_handles(n);
3263 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3264 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3265 sp_nodepath_update_statusbar(nodepath);
3267 } else { // just select or add to selection, depending in Shift
3268 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3269 }
3270 }
3272 /**
3273 * Node handle grabbed callback.
3274 */
3275 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3276 {
3277 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3279 if (!n->selected) {
3280 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3281 }
3283 // remember the origin point of the handle
3284 if (n->p.knot == knot) {
3285 n->p.origin_radial = n->p.pos - n->pos;
3286 } else if (n->n.knot == knot) {
3287 n->n.origin_radial = n->n.pos - n->pos;
3288 } else {
3289 g_assert_not_reached();
3290 }
3292 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3293 }
3295 /**
3296 * Node handle ungrabbed callback.
3297 */
3298 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3299 {
3300 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3302 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3303 if (n->p.knot == knot) {
3304 n->p.origin_radial.a = 0;
3305 sp_knot_set_position(knot, &n->p.pos, state);
3306 } else if (n->n.knot == knot) {
3307 n->n.origin_radial.a = 0;
3308 sp_knot_set_position(knot, &n->n.pos, state);
3309 } else {
3310 g_assert_not_reached();
3311 }
3313 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3314 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3315 }
3317 /**
3318 * Node handle "request" signal callback.
3319 */
3320 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3321 {
3322 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3324 Inkscape::NodePath::NodeSide *me, *opposite;
3325 gint which;
3326 if (n->p.knot == knot) {
3327 me = &n->p;
3328 opposite = &n->n;
3329 which = -1;
3330 } else if (n->n.knot == knot) {
3331 me = &n->n;
3332 opposite = &n->p;
3333 which = 1;
3334 } else {
3335 me = opposite = NULL;
3336 which = 0;
3337 g_assert_not_reached();
3338 }
3340 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3342 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3344 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3345 /* We are smooth node adjacent with line */
3346 NR::Point const delta = *p - n->pos;
3347 NR::Coord const len = NR::L2(delta);
3348 Inkscape::NodePath::Node *othernode = opposite->other;
3349 NR::Point const ndelta = n->pos - othernode->pos;
3350 NR::Coord const linelen = NR::L2(ndelta);
3351 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3352 NR::Coord const scal = dot(delta, ndelta) / linelen;
3353 (*p) = n->pos + (scal / linelen) * ndelta;
3354 }
3355 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3356 } else {
3357 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3358 }
3360 sp_node_adjust_handle(n, -which);
3362 return FALSE;
3363 }
3365 /**
3366 * Node handle moved callback.
3367 */
3368 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3369 {
3370 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3372 Inkscape::NodePath::NodeSide *me;
3373 Inkscape::NodePath::NodeSide *other;
3374 if (n->p.knot == knot) {
3375 me = &n->p;
3376 other = &n->n;
3377 } else if (n->n.knot == knot) {
3378 me = &n->n;
3379 other = &n->p;
3380 } else {
3381 me = NULL;
3382 other = NULL;
3383 g_assert_not_reached();
3384 }
3386 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3387 Radial rme(me->pos - n->pos);
3388 Radial rother(other->pos - n->pos);
3389 Radial rnew(*p - n->pos);
3391 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3392 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3393 /* 0 interpreted as "no snapping". */
3395 // The closest PI/snaps angle, starting from zero.
3396 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3397 if (me->origin_radial.a == HUGE_VAL) {
3398 // ortho doesn't exist: original handle was zero length.
3399 rnew.a = a_snapped;
3400 } else {
3401 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3402 * its opposite and perpendiculars). */
3403 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3405 // Snap to the closest.
3406 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3407 ? a_snapped
3408 : a_ortho );
3409 }
3410 }
3412 if (state & GDK_MOD1_MASK) {
3413 // lock handle length
3414 rnew.r = me->origin_radial.r;
3415 }
3417 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3418 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3419 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3420 rother.a += rnew.a - rme.a;
3421 other->pos = NR::Point(rother) + n->pos;
3422 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3423 sp_knot_set_position(other->knot, &other->pos, 0);
3424 }
3426 me->pos = NR::Point(rnew) + n->pos;
3427 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3429 // this is what sp_knot_set_position does, but without emitting the signal:
3430 // we cannot emit a "moved" signal because we're now processing it
3431 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3433 knot->desktop->set_coordinate_status(me->pos);
3435 update_object(n->subpath->nodepath);
3437 /* status text */
3438 SPDesktop *desktop = n->subpath->nodepath->desktop;
3439 if (!desktop) return;
3440 SPEventContext *ec = desktop->event_context;
3441 if (!ec) return;
3442 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3443 if (!mc) return;
3445 double degrees = 180 / M_PI * rnew.a;
3446 if (degrees > 180) degrees -= 360;
3447 if (degrees < -180) degrees += 360;
3448 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3449 degrees = angle_to_compass (degrees);
3451 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3453 mc->setF(Inkscape::NORMAL_MESSAGE,
3454 _("<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);
3456 g_string_free(length, TRUE);
3457 }
3459 /**
3460 * Node handle event callback.
3461 */
3462 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3463 {
3464 gboolean ret = FALSE;
3465 switch (event->type) {
3466 case GDK_KEY_PRESS:
3467 switch (get_group0_keyval (&event->key)) {
3468 case GDK_space:
3469 if (event->key.state & GDK_BUTTON1_MASK) {
3470 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3471 stamp_repr(nodepath);
3472 ret = TRUE;
3473 }
3474 break;
3475 default:
3476 break;
3477 }
3478 break;
3479 default:
3480 break;
3481 }
3483 return ret;
3484 }
3486 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3487 Radial &rme, Radial &rother, gboolean const both)
3488 {
3489 rme.a += angle;
3490 if ( both
3491 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3492 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3493 {
3494 rother.a += angle;
3495 }
3496 }
3498 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3499 Radial &rme, Radial &rother, gboolean const both)
3500 {
3501 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3503 gdouble r;
3504 if ( both
3505 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3506 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3507 {
3508 r = MAX(rme.r, rother.r);
3509 } else {
3510 r = rme.r;
3511 }
3513 gdouble const weird_angle = atan2(norm_angle, r);
3514 /* Bulia says norm_angle is just the visible distance that the
3515 * object's end must travel on the screen. Left as 'angle' for want of
3516 * a better name.*/
3518 rme.a += weird_angle;
3519 if ( both
3520 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3521 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3522 {
3523 rother.a += weird_angle;
3524 }
3525 }
3527 /**
3528 * Rotate one node.
3529 */
3530 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3531 {
3532 Inkscape::NodePath::NodeSide *me, *other;
3533 bool both = false;
3535 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3536 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3538 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3539 me = &(n->p);
3540 other = &(n->n);
3541 } else if (!n->p.other) {
3542 me = &(n->n);
3543 other = &(n->p);
3544 } else {
3545 if (which > 0) { // right handle
3546 if (xn > xp) {
3547 me = &(n->n);
3548 other = &(n->p);
3549 } else {
3550 me = &(n->p);
3551 other = &(n->n);
3552 }
3553 } else if (which < 0){ // left handle
3554 if (xn <= xp) {
3555 me = &(n->n);
3556 other = &(n->p);
3557 } else {
3558 me = &(n->p);
3559 other = &(n->n);
3560 }
3561 } else { // both handles
3562 me = &(n->n);
3563 other = &(n->p);
3564 both = true;
3565 }
3566 }
3568 Radial rme(me->pos - n->pos);
3569 Radial rother(other->pos - n->pos);
3571 if (screen) {
3572 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3573 } else {
3574 node_rotate_one_internal (*n, angle, rme, rother, both);
3575 }
3577 me->pos = n->pos + NR::Point(rme);
3579 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3580 other->pos = n->pos + NR::Point(rother);
3581 }
3583 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3584 // so here we just move all the knots without emitting move signals, for speed
3585 sp_node_update_handles(n, false);
3586 }
3588 /**
3589 * Rotate selected nodes.
3590 */
3591 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3592 {
3593 if (!nodepath || !nodepath->selected) return;
3595 if (g_list_length(nodepath->selected) == 1) {
3596 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3597 node_rotate_one (n, angle, which, screen);
3598 } else {
3599 // rotate as an object:
3601 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3602 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3603 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3604 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3605 box.expandTo (n->pos); // contain all selected nodes
3606 }
3608 gdouble rot;
3609 if (screen) {
3610 gdouble const zoom = nodepath->desktop->current_zoom();
3611 gdouble const zmove = angle / zoom;
3612 gdouble const r = NR::L2(box.max() - box.midpoint());
3613 rot = atan2(zmove, r);
3614 } else {
3615 rot = angle;
3616 }
3618 NR::Matrix t =
3619 NR::Matrix (NR::translate(-box.midpoint())) *
3620 NR::Matrix (NR::rotate(rot)) *
3621 NR::Matrix (NR::translate(box.midpoint()));
3623 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3624 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3625 n->pos *= t;
3626 n->n.pos *= t;
3627 n->p.pos *= t;
3628 sp_node_update_handles(n, false);
3629 }
3630 }
3632 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3633 }
3635 /**
3636 * Scale one node.
3637 */
3638 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3639 {
3640 bool both = false;
3641 Inkscape::NodePath::NodeSide *me, *other;
3643 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3644 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3646 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3647 me = &(n->p);
3648 other = &(n->n);
3649 n->code = NR_CURVETO;
3650 } else if (!n->p.other) {
3651 me = &(n->n);
3652 other = &(n->p);
3653 if (n->n.other)
3654 n->n.other->code = NR_CURVETO;
3655 } else {
3656 if (which > 0) { // right handle
3657 if (xn > xp) {
3658 me = &(n->n);
3659 other = &(n->p);
3660 if (n->n.other)
3661 n->n.other->code = NR_CURVETO;
3662 } else {
3663 me = &(n->p);
3664 other = &(n->n);
3665 n->code = NR_CURVETO;
3666 }
3667 } else if (which < 0){ // left handle
3668 if (xn <= xp) {
3669 me = &(n->n);
3670 other = &(n->p);
3671 if (n->n.other)
3672 n->n.other->code = NR_CURVETO;
3673 } else {
3674 me = &(n->p);
3675 other = &(n->n);
3676 n->code = NR_CURVETO;
3677 }
3678 } else { // both handles
3679 me = &(n->n);
3680 other = &(n->p);
3681 both = true;
3682 n->code = NR_CURVETO;
3683 if (n->n.other)
3684 n->n.other->code = NR_CURVETO;
3685 }
3686 }
3688 Radial rme(me->pos - n->pos);
3689 Radial rother(other->pos - n->pos);
3691 rme.r += grow;
3692 if (rme.r < 0) rme.r = 0;
3693 if (rme.a == HUGE_VAL) {
3694 if (me->other) { // if direction is unknown, initialize it towards the next node
3695 Radial rme_next(me->other->pos - n->pos);
3696 rme.a = rme_next.a;
3697 } else { // if there's no next, initialize to 0
3698 rme.a = 0;
3699 }
3700 }
3701 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3702 rother.r += grow;
3703 if (rother.r < 0) rother.r = 0;
3704 if (rother.a == HUGE_VAL) {
3705 rother.a = rme.a + M_PI;
3706 }
3707 }
3709 me->pos = n->pos + NR::Point(rme);
3711 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3712 other->pos = n->pos + NR::Point(rother);
3713 }
3715 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3716 // so here we just move all the knots without emitting move signals, for speed
3717 sp_node_update_handles(n, false);
3718 }
3720 /**
3721 * Scale selected nodes.
3722 */
3723 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3724 {
3725 if (!nodepath || !nodepath->selected) return;
3727 if (g_list_length(nodepath->selected) == 1) {
3728 // scale handles of the single selected node
3729 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3730 node_scale_one (n, grow, which);
3731 } else {
3732 // scale nodes as an "object":
3734 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3735 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3736 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3737 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3738 box.expandTo (n->pos); // contain all selected nodes
3739 }
3741 double scale = (box.maxExtent() + grow)/box.maxExtent();
3743 NR::Matrix t =
3744 NR::Matrix (NR::translate(-box.midpoint())) *
3745 NR::Matrix (NR::scale(scale, scale)) *
3746 NR::Matrix (NR::translate(box.midpoint()));
3748 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3749 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3750 n->pos *= t;
3751 n->n.pos *= t;
3752 n->p.pos *= t;
3753 sp_node_update_handles(n, false);
3754 }
3755 }
3757 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3758 }
3760 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3761 {
3762 if (!nodepath) return;
3763 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3764 }
3766 /**
3767 * Flip selected nodes horizontally/vertically.
3768 */
3769 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3770 {
3771 if (!nodepath || !nodepath->selected) return;
3773 if (g_list_length(nodepath->selected) == 1) {
3774 // flip handles of the single selected node
3775 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3776 double temp = n->p.pos[axis];
3777 n->p.pos[axis] = n->n.pos[axis];
3778 n->n.pos[axis] = temp;
3779 sp_node_update_handles(n, false);
3780 } else {
3781 // scale nodes as an "object":
3783 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3784 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3785 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3786 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3787 box.expandTo (n->pos); // contain all selected nodes
3788 }
3790 NR::Matrix t =
3791 NR::Matrix (NR::translate(-box.midpoint())) *
3792 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3793 NR::Matrix (NR::translate(box.midpoint()));
3795 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3796 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3797 n->pos *= t;
3798 n->n.pos *= t;
3799 n->p.pos *= t;
3800 sp_node_update_handles(n, false);
3801 }
3802 }
3804 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3805 }
3807 //-----------------------------------------------
3808 /**
3809 * Return new subpath under given nodepath.
3810 */
3811 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3812 {
3813 g_assert(nodepath);
3814 g_assert(nodepath->desktop);
3816 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3818 s->nodepath = nodepath;
3819 s->closed = FALSE;
3820 s->nodes = NULL;
3821 s->first = NULL;
3822 s->last = NULL;
3824 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3825 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3826 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3828 return s;
3829 }
3831 /**
3832 * Destroy nodes in subpath, then subpath itself.
3833 */
3834 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3835 {
3836 g_assert(subpath);
3837 g_assert(subpath->nodepath);
3838 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3840 while (subpath->nodes) {
3841 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3842 }
3844 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3846 g_free(subpath);
3847 }
3849 /**
3850 * Link head to tail in subpath.
3851 */
3852 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3853 {
3854 g_assert(!sp->closed);
3855 g_assert(sp->last != sp->first);
3856 g_assert(sp->first->code == NR_MOVETO);
3858 sp->closed = TRUE;
3860 //Link the head to the tail
3861 sp->first->p.other = sp->last;
3862 sp->last->n.other = sp->first;
3863 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3864 sp->first = sp->last;
3866 //Remove the extra end node
3867 sp_nodepath_node_destroy(sp->last->n.other);
3868 }
3870 /**
3871 * Open closed (loopy) subpath at node.
3872 */
3873 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3874 {
3875 g_assert(sp->closed);
3876 g_assert(n->subpath == sp);
3877 g_assert(sp->first == sp->last);
3879 /* We create new startpoint, current node will become last one */
3881 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3882 &n->pos, &n->pos, &n->n.pos);
3885 sp->closed = FALSE;
3887 //Unlink to make a head and tail
3888 sp->first = new_path;
3889 sp->last = n;
3890 n->n.other = NULL;
3891 new_path->p.other = NULL;
3892 }
3894 /**
3895 * Returns area in triangle given by points; may be negative.
3896 */
3897 inline double
3898 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3899 {
3900 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]);
3901 }
3903 /**
3904 * Return new node in subpath with given properties.
3905 * \param pos Position of node.
3906 * \param ppos Handle position in previous direction
3907 * \param npos Handle position in previous direction
3908 */
3909 Inkscape::NodePath::Node *
3910 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)
3911 {
3912 g_assert(sp);
3913 g_assert(sp->nodepath);
3914 g_assert(sp->nodepath->desktop);
3916 if (nodechunk == NULL)
3917 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3919 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3921 n->subpath = sp;
3923 if (type != Inkscape::NodePath::NODE_NONE) {
3924 // use the type from sodipodi:nodetypes
3925 n->type = type;
3926 } else {
3927 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3928 // points are (almost) collinear
3929 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3930 // endnode, or a node with a retracted handle
3931 n->type = Inkscape::NodePath::NODE_CUSP;
3932 } else {
3933 n->type = Inkscape::NodePath::NODE_SMOOTH;
3934 }
3935 } else {
3936 n->type = Inkscape::NodePath::NODE_CUSP;
3937 }
3938 }
3940 n->code = code;
3941 n->selected = FALSE;
3942 n->pos = *pos;
3943 n->p.pos = *ppos;
3944 n->n.pos = *npos;
3946 n->dragging_out = NULL;
3948 Inkscape::NodePath::Node *prev;
3949 if (next) {
3950 //g_assert(g_list_find(sp->nodes, next));
3951 prev = next->p.other;
3952 } else {
3953 prev = sp->last;
3954 }
3956 if (prev)
3957 prev->n.other = n;
3958 else
3959 sp->first = n;
3961 if (next)
3962 next->p.other = n;
3963 else
3964 sp->last = n;
3966 n->p.other = prev;
3967 n->n.other = next;
3969 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"));
3970 sp_knot_set_position(n->knot, pos, 0);
3972 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3973 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3974 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3975 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3976 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3977 sp_knot_update_ctrl(n->knot);
3979 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3980 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3981 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3982 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3983 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3984 sp_knot_show(n->knot);
3986 // We only create handle knots and lines on demand
3987 n->p.knot = NULL;
3988 n->p.line = NULL;
3989 n->n.knot = NULL;
3990 n->n.line = NULL;
3992 sp->nodes = g_list_prepend(sp->nodes, n);
3994 return n;
3995 }
3997 /**
3998 * Destroy node and its knots, link neighbors in subpath.
3999 */
4000 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4001 {
4002 g_assert(node);
4003 g_assert(node->subpath);
4004 g_assert(SP_IS_KNOT(node->knot));
4006 Inkscape::NodePath::SubPath *sp = node->subpath;
4008 if (node->selected) { // first, deselect
4009 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4010 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4011 }
4013 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4015 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4016 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4017 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4018 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4019 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4020 g_object_unref(G_OBJECT(node->knot));
4022 if (node->p.knot) {
4023 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4024 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4025 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4026 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4027 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4028 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4029 g_object_unref(G_OBJECT(node->p.knot));
4030 }
4032 if (node->n.knot) {
4033 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4034 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4035 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4036 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4037 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4038 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4039 g_object_unref(G_OBJECT(node->n.knot));
4040 }
4042 if (node->p.line)
4043 gtk_object_destroy(GTK_OBJECT(node->p.line));
4044 if (node->n.line)
4045 gtk_object_destroy(GTK_OBJECT(node->n.line));
4047 if (sp->nodes) { // there are others nodes on the subpath
4048 if (sp->closed) {
4049 if (sp->first == node) {
4050 g_assert(sp->last == node);
4051 sp->first = node->n.other;
4052 sp->last = sp->first;
4053 }
4054 node->p.other->n.other = node->n.other;
4055 node->n.other->p.other = node->p.other;
4056 } else {
4057 if (sp->first == node) {
4058 sp->first = node->n.other;
4059 sp->first->code = NR_MOVETO;
4060 }
4061 if (sp->last == node) sp->last = node->p.other;
4062 if (node->p.other) node->p.other->n.other = node->n.other;
4063 if (node->n.other) node->n.other->p.other = node->p.other;
4064 }
4065 } else { // this was the last node on subpath
4066 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4067 }
4069 g_mem_chunk_free(nodechunk, node);
4070 }
4072 /**
4073 * Returns one of the node's two sides.
4074 * \param which Indicates which side.
4075 * \return Pointer to previous node side if which==-1, next if which==1.
4076 */
4077 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4078 {
4079 g_assert(node);
4081 switch (which) {
4082 case -1:
4083 return &node->p;
4084 case 1:
4085 return &node->n;
4086 default:
4087 break;
4088 }
4090 g_assert_not_reached();
4092 return NULL;
4093 }
4095 /**
4096 * Return the other side of the node, given one of its sides.
4097 */
4098 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4099 {
4100 g_assert(node);
4102 if (me == &node->p) return &node->n;
4103 if (me == &node->n) return &node->p;
4105 g_assert_not_reached();
4107 return NULL;
4108 }
4110 /**
4111 * Return NRPathcode on the given side of the node.
4112 */
4113 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4114 {
4115 g_assert(node);
4117 if (me == &node->p) {
4118 if (node->p.other) return (NRPathcode)node->code;
4119 return NR_MOVETO;
4120 }
4122 if (me == &node->n) {
4123 if (node->n.other) return (NRPathcode)node->n.other->code;
4124 return NR_MOVETO;
4125 }
4127 g_assert_not_reached();
4129 return NR_END;
4130 }
4132 /**
4133 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4134 */
4135 Inkscape::NodePath::Node *
4136 sp_nodepath_get_node_by_index(int index)
4137 {
4138 Inkscape::NodePath::Node *e = NULL;
4140 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4141 if (!nodepath) {
4142 return e;
4143 }
4145 //find segment
4146 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4148 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4149 int n = g_list_length(sp->nodes);
4150 if (sp->closed) {
4151 n++;
4152 }
4154 //if the piece belongs to this subpath grab it
4155 //otherwise move onto the next subpath
4156 if (index < n) {
4157 e = sp->first;
4158 for (int i = 0; i < index; ++i) {
4159 e = e->n.other;
4160 }
4161 break;
4162 } else {
4163 if (sp->closed) {
4164 index -= (n+1);
4165 } else {
4166 index -= n;
4167 }
4168 }
4169 }
4171 return e;
4172 }
4174 /**
4175 * Returns plain text meaning of node type.
4176 */
4177 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4178 {
4179 unsigned retracted = 0;
4180 bool endnode = false;
4182 for (int which = -1; which <= 1; which += 2) {
4183 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4184 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4185 retracted ++;
4186 if (!side->other)
4187 endnode = true;
4188 }
4190 if (retracted == 0) {
4191 if (endnode) {
4192 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4193 return _("end node");
4194 } else {
4195 switch (node->type) {
4196 case Inkscape::NodePath::NODE_CUSP:
4197 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4198 return _("cusp");
4199 case Inkscape::NodePath::NODE_SMOOTH:
4200 // TRANSLATORS: "smooth" is an adjective here
4201 return _("smooth");
4202 case Inkscape::NodePath::NODE_SYMM:
4203 return _("symmetric");
4204 }
4205 }
4206 } else if (retracted == 1) {
4207 if (endnode) {
4208 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4209 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4210 } else {
4211 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4212 }
4213 } else {
4214 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4215 }
4217 return NULL;
4218 }
4220 /**
4221 * Handles content of statusbar as long as node tool is active.
4222 */
4223 void
4224 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4225 {
4226 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");
4227 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4229 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4230 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4231 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4232 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4234 SPDesktop *desktop = NULL;
4235 if (nodepath) {
4236 desktop = nodepath->desktop;
4237 } else {
4238 desktop = SP_ACTIVE_DESKTOP;
4239 }
4241 SPEventContext *ec = desktop->event_context;
4242 if (!ec) return;
4243 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4244 if (!mc) return;
4246 if (selected_nodes == 0) {
4247 Inkscape::Selection *sel = desktop->selection;
4248 if (!sel || sel->isEmpty()) {
4249 mc->setF(Inkscape::NORMAL_MESSAGE,
4250 _("Select a single object to edit its nodes or handles."));
4251 } else {
4252 if (nodepath) {
4253 mc->setF(Inkscape::NORMAL_MESSAGE,
4254 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.",
4255 "<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.",
4256 total_nodes),
4257 total_nodes);
4258 } else {
4259 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4260 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4261 } else {
4262 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4263 }
4264 }
4265 }
4266 } else if (nodepath && selected_nodes == 1) {
4267 mc->setF(Inkscape::NORMAL_MESSAGE,
4268 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4269 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4270 total_nodes),
4271 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4272 } else {
4273 if (selected_subpaths > 1) {
4274 mc->setF(Inkscape::NORMAL_MESSAGE,
4275 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4276 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4277 total_nodes),
4278 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4279 } else {
4280 mc->setF(Inkscape::NORMAL_MESSAGE,
4281 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4282 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4283 total_nodes),
4284 selected_nodes, total_nodes, when_selected);
4285 }
4286 }
4287 }
4290 /*
4291 Local Variables:
4292 mode:c++
4293 c-file-style:"stroustrup"
4294 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4295 indent-tabs-mode:nil
4296 fill-column:99
4297 End:
4298 */
4299 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :