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 "display/bezier-utils.h"
44 #include <vector>
45 #include <algorithm>
47 class NR::Matrix;
49 /// \todo
50 /// evil evil evil. FIXME: conflict of two different Path classes!
51 /// There is a conflict in the namespace between two classes named Path.
52 /// #include "sp-flowtext.h"
53 /// #include "sp-flowregion.h"
55 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
56 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
57 GType sp_flowregion_get_type (void);
58 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
59 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
60 GType sp_flowtext_get_type (void);
61 // end evil workaround
63 #include "helper/stlport.h"
66 /// \todo fixme: Implement these via preferences */
68 #define NODE_FILL 0xbfbfbf00
69 #define NODE_STROKE 0x000000ff
70 #define NODE_FILL_HI 0xff000000
71 #define NODE_STROKE_HI 0x000000ff
72 #define NODE_FILL_SEL 0x0000ffff
73 #define NODE_STROKE_SEL 0x000000ff
74 #define NODE_FILL_SEL_HI 0xff000000
75 #define NODE_STROKE_SEL_HI 0x000000ff
76 #define KNOT_FILL 0xffffffff
77 #define KNOT_STROKE 0x000000ff
78 #define KNOT_FILL_HI 0xff000000
79 #define KNOT_STROKE_HI 0x000000ff
81 static GMemChunk *nodechunk = NULL;
83 /* Creation from object */
85 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
86 static gchar *parse_nodetypes(gchar const *types, gint length);
88 /* Object updating */
90 static void stamp_repr(Inkscape::NodePath::Path *np);
91 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
92 static gchar *create_typestr(Inkscape::NodePath::Path *np);
94 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
96 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
98 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
100 /* Adjust handle placement, if the node or the other handle is moved */
101 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
102 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
104 /* Node event callbacks */
105 static void node_clicked(SPKnot *knot, guint state, gpointer data);
106 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
107 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
108 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
110 /* Handle event callbacks */
111 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
112 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
114 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
115 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
118 /* Constructors and destructors */
120 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
121 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
122 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
123 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
124 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
125 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
126 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
128 /* Helpers */
130 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
131 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
132 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
134 // active_node indicates mouseover node
135 static Inkscape::NodePath::Node *active_node = NULL;
137 /**
138 * \brief Creates new nodepath from item
139 */
140 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
141 {
142 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
144 /** \todo
145 * FIXME: remove this. We don't want to edit paths inside flowtext.
146 * Instead we will build our flowtext with cloned paths, so that the
147 * real paths are outside the flowtext and thus editable as usual.
148 */
149 if (SP_IS_FLOWTEXT(item)) {
150 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
151 if SP_IS_FLOWREGION(child) {
152 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
153 if (grandchild && SP_IS_PATH(grandchild)) {
154 item = SP_ITEM(grandchild);
155 break;
156 }
157 }
158 }
159 }
161 if (!SP_IS_PATH(item))
162 return NULL;
163 SPPath *path = SP_PATH(item);
164 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
165 if (curve == NULL)
166 return NULL;
168 NArtBpath *bpath = sp_curve_first_bpath(curve);
169 gint length = curve->end;
170 if (length == 0)
171 return NULL; // prevent crash for one-node paths
173 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
174 gchar *typestr = parse_nodetypes(nodetypes, length);
176 //Create new nodepath
177 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
178 if (!np)
179 return NULL;
181 // Set defaults
182 np->desktop = desktop;
183 np->path = path;
184 np->subpaths = NULL;
185 np->selected = NULL;
186 np->nodeContext = NULL; //Let the context that makes this set it
187 np->livarot_path = NULL;
188 np->local_change = 0;
189 np->show_handles = show_handles;
191 // we need to update item's transform from the repr here,
192 // because they may be out of sync when we respond
193 // to a change in repr by regenerating nodepath --bb
194 sp_object_read_attr(SP_OBJECT(item), "transform");
196 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
197 np->d2i = np->i2d.inverse();
198 np->repr = repr;
200 // create the subpath(s) from the bpath
201 NArtBpath *b = bpath;
202 while (b->code != NR_END) {
203 b = subpath_from_bpath(np, b, typestr + (b - bpath));
204 }
206 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
207 np->subpaths = g_list_reverse(np->subpaths);
209 g_free(typestr);
210 sp_curve_unref(curve);
212 // create the livarot representation from the same item
213 np->livarot_path = Path_for_item(item, true, true);
214 if (np->livarot_path)
215 np->livarot_path->ConvertWithBackData(0.01);
217 return np;
218 }
220 /**
221 * Destroys nodepath's subpaths, then itself, also tell context about it.
222 */
223 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
225 if (!np) //soft fail, like delete
226 return;
228 while (np->subpaths) {
229 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
230 }
232 //Inform the context that made me, if any, that I am gone.
233 if (np->nodeContext)
234 np->nodeContext->nodepath = NULL;
236 g_assert(!np->selected);
238 if (np->livarot_path) {
239 delete np->livarot_path;
240 np->livarot_path = NULL;
241 }
243 np->desktop = NULL;
245 g_free(np);
246 }
249 /**
250 * Return the node count of a given NodeSubPath.
251 */
252 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
253 {
254 if (!subpath)
255 return 0;
256 gint nodeCount = g_list_length(subpath->nodes);
257 return nodeCount;
258 }
260 /**
261 * Return the node count of a given NodePath.
262 */
263 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
264 {
265 if (!np)
266 return 0;
267 gint nodeCount = 0;
268 for (GList *item = np->subpaths ; item ; item=item->next) {
269 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
270 nodeCount += g_list_length(subpath->nodes);
271 }
272 return nodeCount;
273 }
275 /**
276 * Return the subpath count of a given NodePath.
277 */
278 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
279 {
280 if (!np)
281 return 0;
282 return g_list_length (np->subpaths);
283 }
285 /**
286 * Return the selected node count of a given NodePath.
287 */
288 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
289 {
290 if (!np)
291 return 0;
292 return g_list_length (np->selected);
293 }
295 /**
296 * Return the number of subpaths where nodes are selected in a given NodePath.
297 */
298 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
299 {
300 if (!np)
301 return 0;
302 if (!np->selected)
303 return 0;
304 if (!np->selected->next)
305 return 1;
306 gint count = 0;
307 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
308 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
309 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
310 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
311 if (node->selected) {
312 count ++;
313 break;
314 }
315 }
316 }
317 return count;
318 }
320 /**
321 * Clean up a nodepath after editing.
322 *
323 * Currently we are deleting trivial subpaths.
324 */
325 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
326 {
327 GList *badSubPaths = NULL;
329 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
330 for (GList *l = nodepath->subpaths; l ; l=l->next) {
331 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
332 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
333 badSubPaths = g_list_append(badSubPaths, sp);
334 }
336 //Delete them. This second step is because sp_nodepath_subpath_destroy()
337 //also removes the subpath from nodepath->subpaths
338 for (GList *l = badSubPaths; l ; l=l->next) {
339 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
340 sp_nodepath_subpath_destroy(sp);
341 }
343 g_list_free(badSubPaths);
344 }
346 /**
347 * Create new nodepath from b, make it subpath of np.
348 * \param t The node type.
349 * \todo Fixme: t should be a proper type, rather than gchar
350 */
351 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
352 {
353 NR::Point ppos, pos, npos;
355 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
357 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
358 bool const closed = (b->code == NR_MOVETO);
360 pos = NR::Point(b->x3, b->y3) * np->i2d;
361 if (b[1].code == NR_CURVETO) {
362 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
363 } else {
364 npos = pos;
365 }
366 Inkscape::NodePath::Node *n;
367 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
368 g_assert(sp->first == n);
369 g_assert(sp->last == n);
371 b++;
372 t++;
373 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
374 pos = NR::Point(b->x3, b->y3) * np->i2d;
375 if (b->code == NR_CURVETO) {
376 ppos = NR::Point(b->x2, b->y2) * np->i2d;
377 } else {
378 ppos = pos;
379 }
380 if (b[1].code == NR_CURVETO) {
381 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
382 } else {
383 npos = pos;
384 }
385 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
386 b++;
387 t++;
388 }
390 if (closed) sp_nodepath_subpath_close(sp);
392 return b;
393 }
395 /**
396 * Convert from sodipodi:nodetypes to new style type string.
397 */
398 static gchar *parse_nodetypes(gchar const *types, gint length)
399 {
400 g_assert(length > 0);
402 gchar *typestr = g_new(gchar, length + 1);
404 gint pos = 0;
406 if (types) {
407 for (gint i = 0; types[i] && ( i < length ); i++) {
408 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
409 if (types[i] != '\0') {
410 switch (types[i]) {
411 case 's':
412 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
413 break;
414 case 'z':
415 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
416 break;
417 case 'c':
418 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
419 break;
420 default:
421 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
422 break;
423 }
424 }
425 }
426 }
428 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
430 return typestr;
431 }
433 /**
434 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
435 * updated but repr is not (for speed). Used during curve and node drag.
436 */
437 static void update_object(Inkscape::NodePath::Path *np)
438 {
439 g_assert(np);
441 SPCurve *curve = create_curve(np);
443 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
445 sp_curve_unref(curve);
446 }
448 /**
449 * Update XML path node with data from path object.
450 */
451 static void update_repr_internal(Inkscape::NodePath::Path *np)
452 {
453 g_assert(np);
455 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
457 SPCurve *curve = create_curve(np);
458 gchar *typestr = create_typestr(np);
459 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
461 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
462 np->local_change++;
463 repr->setAttribute("d", svgpath);
464 }
466 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
467 np->local_change++;
468 repr->setAttribute("sodipodi:nodetypes", typestr);
469 }
471 g_free(svgpath);
472 g_free(typestr);
473 sp_curve_unref(curve);
474 }
476 /**
477 * Update XML path node with data from path object, commit changes forever.
478 */
479 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np)
480 {
481 update_repr_internal(np);
482 sp_document_done(sp_desktop_document(np->desktop));
484 if (np->livarot_path) {
485 delete np->livarot_path;
486 np->livarot_path = NULL;
487 }
489 if (np->path && SP_IS_ITEM(np->path)) {
490 np->livarot_path = Path_for_item (np->path, true, true);
491 if (np->livarot_path)
492 np->livarot_path->ConvertWithBackData(0.01);
493 }
494 }
496 /**
497 * Update XML path node with data from path object, commit changes with undo.
498 */
499 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key)
500 {
501 update_repr_internal(np);
502 sp_document_maybe_done(sp_desktop_document(np->desktop), key);
504 if (np->livarot_path) {
505 delete np->livarot_path;
506 np->livarot_path = NULL;
507 }
509 if (np->path && SP_IS_ITEM(np->path)) {
510 np->livarot_path = Path_for_item (np->path, true, true);
511 if (np->livarot_path)
512 np->livarot_path->ConvertWithBackData(0.01);
513 }
514 }
516 /**
517 * Make duplicate of path, replace corresponding XML node in tree, commit.
518 */
519 static void stamp_repr(Inkscape::NodePath::Path *np)
520 {
521 g_assert(np);
523 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
524 Inkscape::XML::Node *new_repr = old_repr->duplicate();
526 // remember the position of the item
527 gint pos = old_repr->position();
528 // remember parent
529 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
531 SPCurve *curve = create_curve(np);
532 gchar *typestr = create_typestr(np);
534 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
536 new_repr->setAttribute("d", svgpath);
537 new_repr->setAttribute("sodipodi:nodetypes", typestr);
539 // add the new repr to the parent
540 parent->appendChild(new_repr);
541 // move to the saved position
542 new_repr->setPosition(pos > 0 ? pos : 0);
544 sp_document_done(sp_desktop_document(np->desktop));
546 Inkscape::GC::release(new_repr);
547 g_free(svgpath);
548 g_free(typestr);
549 sp_curve_unref(curve);
550 }
552 /**
553 * Create curve from path.
554 */
555 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
556 {
557 SPCurve *curve = sp_curve_new();
559 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
560 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
561 sp_curve_moveto(curve,
562 sp->first->pos * np->d2i);
563 Inkscape::NodePath::Node *n = sp->first->n.other;
564 while (n) {
565 NR::Point const end_pt = n->pos * np->d2i;
566 switch (n->code) {
567 case NR_LINETO:
568 sp_curve_lineto(curve, end_pt);
569 break;
570 case NR_CURVETO:
571 sp_curve_curveto(curve,
572 n->p.other->n.pos * np->d2i,
573 n->p.pos * np->d2i,
574 end_pt);
575 break;
576 default:
577 g_assert_not_reached();
578 break;
579 }
580 if (n != sp->last) {
581 n = n->n.other;
582 } else {
583 n = NULL;
584 }
585 }
586 if (sp->closed) {
587 sp_curve_closepath(curve);
588 }
589 }
591 return curve;
592 }
594 /**
595 * Convert path type string to sodipodi:nodetypes style.
596 */
597 static gchar *create_typestr(Inkscape::NodePath::Path *np)
598 {
599 gchar *typestr = g_new(gchar, 32);
600 gint len = 32;
601 gint pos = 0;
603 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
604 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
606 if (pos >= len) {
607 typestr = g_renew(gchar, typestr, len + 32);
608 len += 32;
609 }
611 typestr[pos++] = 'c';
613 Inkscape::NodePath::Node *n;
614 n = sp->first->n.other;
615 while (n) {
616 gchar code;
618 switch (n->type) {
619 case Inkscape::NodePath::NODE_CUSP:
620 code = 'c';
621 break;
622 case Inkscape::NodePath::NODE_SMOOTH:
623 code = 's';
624 break;
625 case Inkscape::NodePath::NODE_SYMM:
626 code = 'z';
627 break;
628 default:
629 g_assert_not_reached();
630 code = '\0';
631 break;
632 }
634 if (pos >= len) {
635 typestr = g_renew(gchar, typestr, len + 32);
636 len += 32;
637 }
639 typestr[pos++] = code;
641 if (n != sp->last) {
642 n = n->n.other;
643 } else {
644 n = NULL;
645 }
646 }
647 }
649 if (pos >= len) {
650 typestr = g_renew(gchar, typestr, len + 1);
651 len += 1;
652 }
654 typestr[pos++] = '\0';
656 return typestr;
657 }
659 /**
660 * Returns current path in context.
661 */
662 static Inkscape::NodePath::Path *sp_nodepath_current()
663 {
664 if (!SP_ACTIVE_DESKTOP) {
665 return NULL;
666 }
668 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
670 if (!SP_IS_NODE_CONTEXT(event_context)) {
671 return NULL;
672 }
674 return SP_NODE_CONTEXT(event_context)->nodepath;
675 }
679 /**
680 \brief Fills node and handle positions for three nodes, splitting line
681 marked by end at distance t.
682 */
683 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
684 {
685 g_assert(new_path != NULL);
686 g_assert(end != NULL);
688 g_assert(end->p.other == new_path);
689 Inkscape::NodePath::Node *start = new_path->p.other;
690 g_assert(start);
692 if (end->code == NR_LINETO) {
693 new_path->type =Inkscape::NodePath::NODE_CUSP;
694 new_path->code = NR_LINETO;
695 new_path->pos = (t * start->pos + (1 - t) * end->pos);
696 } else {
697 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
698 new_path->code = NR_CURVETO;
699 gdouble s = 1 - t;
700 for (int dim = 0; dim < 2; dim++) {
701 NR::Coord const f000 = start->pos[dim];
702 NR::Coord const f001 = start->n.pos[dim];
703 NR::Coord const f011 = end->p.pos[dim];
704 NR::Coord const f111 = end->pos[dim];
705 NR::Coord const f00t = s * f000 + t * f001;
706 NR::Coord const f01t = s * f001 + t * f011;
707 NR::Coord const f11t = s * f011 + t * f111;
708 NR::Coord const f0tt = s * f00t + t * f01t;
709 NR::Coord const f1tt = s * f01t + t * f11t;
710 NR::Coord const fttt = s * f0tt + t * f1tt;
711 start->n.pos[dim] = f00t;
712 new_path->p.pos[dim] = f0tt;
713 new_path->pos[dim] = fttt;
714 new_path->n.pos[dim] = f1tt;
715 end->p.pos[dim] = f11t;
716 }
717 }
718 }
720 /**
721 * Adds new node on direct line between two nodes, activates handles of all
722 * three nodes.
723 */
724 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
725 {
726 g_assert(end);
727 g_assert(end->subpath);
728 g_assert(g_list_find(end->subpath->nodes, end));
730 Inkscape::NodePath::Node *start = end->p.other;
731 g_assert( start->n.other == end );
732 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
733 end,
734 Inkscape::NodePath::NODE_SMOOTH,
735 (NRPathcode)end->code,
736 &start->pos, &start->pos, &start->n.pos);
737 sp_nodepath_line_midpoint(newnode, end, t);
739 sp_node_update_handles(start);
740 sp_node_update_handles(newnode);
741 sp_node_update_handles(end);
743 return newnode;
744 }
746 /**
747 \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
748 */
749 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
750 {
751 g_assert(node);
752 g_assert(node->subpath);
753 g_assert(g_list_find(node->subpath->nodes, node));
755 Inkscape::NodePath::SubPath *sp = node->subpath;
756 Inkscape::NodePath::Path *np = sp->nodepath;
758 if (sp->closed) {
759 sp_nodepath_subpath_open(sp, node);
760 return sp->first;
761 } else {
762 // no break for end nodes
763 if (node == sp->first) return NULL;
764 if (node == sp->last ) return NULL;
766 // create a new subpath
767 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
769 // duplicate the break node as start of the new subpath
770 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
772 while (node->n.other) { // copy the remaining nodes into the new subpath
773 Inkscape::NodePath::Node *n = node->n.other;
774 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);
775 if (n->selected) {
776 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
777 }
778 sp_nodepath_node_destroy(n); // remove the point on the original subpath
779 }
781 return newnode;
782 }
783 }
785 /**
786 * Duplicate node and connect to neighbours.
787 */
788 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
789 {
790 g_assert(node);
791 g_assert(node->subpath);
792 g_assert(g_list_find(node->subpath->nodes, node));
794 Inkscape::NodePath::SubPath *sp = node->subpath;
796 NRPathcode code = (NRPathcode) node->code;
797 if (code == NR_MOVETO) { // if node is the endnode,
798 node->code = NR_LINETO; // new one is inserted before it, so change that to line
799 }
801 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
803 if (!node->n.other || !node->p.other) // if node is an endnode, select it
804 return node;
805 else
806 return newnode; // otherwise select the newly created node
807 }
809 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
810 {
811 node->p.pos = (node->pos + (node->pos - node->n.pos));
812 }
814 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
815 {
816 node->n.pos = (node->pos + (node->pos - node->p.pos));
817 }
819 /**
820 * Change line type at node, with side effects on neighbours.
821 */
822 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
823 {
824 g_assert(end);
825 g_assert(end->subpath);
826 g_assert(end->p.other);
828 if (end->code == static_cast< guint > ( code ) )
829 return;
831 Inkscape::NodePath::Node *start = end->p.other;
833 end->code = code;
835 if (code == NR_LINETO) {
836 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
837 if (end->n.other) {
838 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
839 }
840 sp_node_adjust_handle(start, -1);
841 sp_node_adjust_handle(end, 1);
842 } else {
843 NR::Point delta = end->pos - start->pos;
844 start->n.pos = start->pos + delta / 3;
845 end->p.pos = end->pos - delta / 3;
846 sp_node_adjust_handle(start, 1);
847 sp_node_adjust_handle(end, -1);
848 }
850 sp_node_update_handles(start);
851 sp_node_update_handles(end);
852 }
854 /**
855 * Change node type, and its handles accordingly.
856 */
857 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
858 {
859 g_assert(node);
860 g_assert(node->subpath);
862 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
863 return node;
865 if ((node->p.other != NULL) && (node->n.other != NULL)) {
866 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
867 type =Inkscape::NodePath::NODE_CUSP;
868 }
869 }
871 node->type = type;
873 if (node->type == Inkscape::NodePath::NODE_CUSP) {
874 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
875 node->knot->setSize (node->selected? 11 : 9);
876 sp_knot_update_ctrl(node->knot);
877 } else {
878 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
879 node->knot->setSize (node->selected? 9 : 7);
880 sp_knot_update_ctrl(node->knot);
881 }
883 // if one of handles is mouseovered, preserve its position
884 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
885 sp_node_adjust_handle(node, 1);
886 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
887 sp_node_adjust_handle(node, -1);
888 } else {
889 sp_node_adjust_handles(node);
890 }
892 sp_node_update_handles(node);
894 sp_nodepath_update_statusbar(node->subpath->nodepath);
896 return node;
897 }
899 /**
900 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
901 * adjacent segments from lines to curves.
902 */
903 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
904 {
905 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
906 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
907 // convert adjacent segment BEFORE to curve
908 node->code = NR_CURVETO;
909 NR::Point delta;
910 if (node->n.other != NULL)
911 delta = node->n.other->pos - node->p.other->pos;
912 else
913 delta = node->pos - node->p.other->pos;
914 node->p.pos = node->pos - delta / 4;
915 sp_node_update_handles(node);
916 }
918 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
919 // convert adjacent segment AFTER to curve
920 node->n.other->code = NR_CURVETO;
921 NR::Point delta;
922 if (node->p.other != NULL)
923 delta = node->p.other->pos - node->n.other->pos;
924 else
925 delta = node->pos - node->n.other->pos;
926 node->n.pos = node->pos - delta / 4;
927 sp_node_update_handles(node);
928 }
929 }
931 sp_nodepath_set_node_type (node, type);
932 }
934 /**
935 * Move node to point, and adjust its and neighbouring handles.
936 */
937 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
938 {
939 NR::Point delta = p - node->pos;
940 node->pos = p;
942 node->p.pos += delta;
943 node->n.pos += delta;
945 if (node->p.other) {
946 if (node->code == NR_LINETO) {
947 sp_node_adjust_handle(node, 1);
948 sp_node_adjust_handle(node->p.other, -1);
949 }
950 }
951 if (node->n.other) {
952 if (node->n.other->code == NR_LINETO) {
953 sp_node_adjust_handle(node, -1);
954 sp_node_adjust_handle(node->n.other, 1);
955 }
956 }
958 // this function is only called from batch movers that will update display at the end
959 // themselves, so here we just move all the knots without emitting move signals, for speed
960 sp_node_update_handles(node, false);
961 }
963 /**
964 * Call sp_node_moveto() for node selection and handle possible snapping.
965 */
966 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
967 bool const snap = true)
968 {
969 NR::Coord best = NR_HUGE;
970 NR::Point delta(dx, dy);
971 NR::Point best_pt = delta;
973 if (snap) {
974 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
976 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
977 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
978 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
979 if (s.getDistance() < best) {
980 best = s.getDistance();
981 best_pt = s.getPoint() - n->pos;
982 }
983 }
984 }
986 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
987 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
988 sp_node_moveto(n, n->pos + best_pt);
989 }
991 // do not update repr here so that node dragging is acceptably fast
992 update_object(nodepath);
993 }
995 /**
996 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
997 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
998 near x = 0.
999 */
1000 double
1001 sculpt_profile (double x, double alpha, guint profile)
1002 {
1003 if (x >= 1)
1004 return 0;
1005 if (x <= 0)
1006 return 1;
1008 switch (profile) {
1009 case SCULPT_PROFILE_LINEAR:
1010 return 1 - x;
1011 case SCULPT_PROFILE_BELL:
1012 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1013 case SCULPT_PROFILE_ELLIPTIC:
1014 return sqrt(1 - x*x);
1015 }
1017 return 1;
1018 }
1020 double
1021 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1022 {
1023 // extremely primitive for now, don't have time to look for the real one
1024 double lower = NR::L2(b - a);
1025 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1026 return (lower + upper)/2;
1027 }
1029 void
1030 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1031 {
1032 n->pos = n->origin + delta;
1033 n->n.pos = n->n.origin + delta_n;
1034 n->p.pos = n->p.origin + delta_p;
1035 sp_node_adjust_handles(n);
1036 sp_node_update_handles(n, false);
1037 }
1039 /**
1040 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1041 * on how far they are from the dragged node n.
1042 */
1043 static void
1044 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1045 {
1046 g_assert (n);
1047 g_assert (nodepath);
1048 g_assert (n->subpath->nodepath == nodepath);
1050 double pressure = n->knot->pressure;
1051 if (pressure == 0)
1052 pressure = 0.5; // default
1053 pressure = CLAMP (pressure, 0.2, 0.8);
1055 // map pressure to alpha = 1/5 ... 5
1056 double alpha = 1 - 2 * fabs(pressure - 0.5);
1057 if (pressure > 0.5)
1058 alpha = 1/alpha;
1060 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1062 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1063 // Only one subpath has selected nodes:
1064 // use linear mode, where the distance from n to node being dragged is calculated along the path
1066 double n_sel_range = 0, p_sel_range = 0;
1067 guint n_nodes = 0, p_nodes = 0;
1068 guint n_sel_nodes = 0, p_sel_nodes = 0;
1070 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1071 {
1072 double n_range = 0, p_range = 0;
1073 bool n_going = true, p_going = true;
1074 Inkscape::NodePath::Node *n_node = n;
1075 Inkscape::NodePath::Node *p_node = n;
1076 do {
1077 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1078 if (n_node && n_going)
1079 n_node = n_node->n.other;
1080 if (n_node == NULL) {
1081 n_going = false;
1082 } else {
1083 n_nodes ++;
1084 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1085 if (n_node->selected) {
1086 n_sel_nodes ++;
1087 n_sel_range = n_range;
1088 }
1089 if (n_node == p_node) {
1090 n_going = false;
1091 p_going = false;
1092 }
1093 }
1094 if (p_node && p_going)
1095 p_node = p_node->p.other;
1096 if (p_node == NULL) {
1097 p_going = false;
1098 } else {
1099 p_nodes ++;
1100 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1101 if (p_node->selected) {
1102 p_sel_nodes ++;
1103 p_sel_range = p_range;
1104 }
1105 if (p_node == n_node) {
1106 n_going = false;
1107 p_going = false;
1108 }
1109 }
1110 } while (n_going || p_going);
1111 }
1113 // Second pass: actually move nodes in this subpath
1114 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1115 {
1116 double n_range = 0, p_range = 0;
1117 bool n_going = true, p_going = true;
1118 Inkscape::NodePath::Node *n_node = n;
1119 Inkscape::NodePath::Node *p_node = n;
1120 do {
1121 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1122 if (n_node && n_going)
1123 n_node = n_node->n.other;
1124 if (n_node == NULL) {
1125 n_going = false;
1126 } else {
1127 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1128 if (n_node->selected) {
1129 sp_nodepath_move_node_and_handles (n_node,
1130 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1131 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1132 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1133 }
1134 if (n_node == p_node) {
1135 n_going = false;
1136 p_going = false;
1137 }
1138 }
1139 if (p_node && p_going)
1140 p_node = p_node->p.other;
1141 if (p_node == NULL) {
1142 p_going = false;
1143 } else {
1144 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1145 if (p_node->selected) {
1146 sp_nodepath_move_node_and_handles (p_node,
1147 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1148 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1149 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1150 }
1151 if (p_node == n_node) {
1152 n_going = false;
1153 p_going = false;
1154 }
1155 }
1156 } while (n_going || p_going);
1157 }
1159 } else {
1160 // Multiple subpaths have selected nodes:
1161 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1162 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1163 // fix the pear-like shape when sculpting e.g. a ring
1165 // First pass: calculate range
1166 gdouble direct_range = 0;
1167 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1168 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1169 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1170 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1171 if (node->selected) {
1172 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1173 }
1174 }
1175 }
1177 // Second pass: actually move nodes
1178 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1179 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1180 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1181 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1182 if (node->selected) {
1183 if (direct_range > 1e-6) {
1184 sp_nodepath_move_node_and_handles (node,
1185 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1186 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1187 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1188 } else {
1189 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1190 }
1192 }
1193 }
1194 }
1195 }
1197 // do not update repr here so that node dragging is acceptably fast
1198 update_object(nodepath);
1199 }
1202 /**
1203 * Move node selection to point, adjust its and neighbouring handles,
1204 * handle possible snapping, and commit the change with possible undo.
1205 */
1206 void
1207 sp_node_selected_move(gdouble dx, gdouble dy)
1208 {
1209 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1210 if (!nodepath) return;
1212 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1214 if (dx == 0) {
1215 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1216 } else if (dy == 0) {
1217 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1218 } else {
1219 sp_nodepath_update_repr(nodepath);
1220 }
1221 }
1223 /**
1224 * Move node selection off screen and commit the change.
1225 */
1226 void
1227 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1228 {
1229 // borrowed from sp_selection_move_screen in selection-chemistry.c
1230 // we find out the current zoom factor and divide deltas by it
1231 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1233 gdouble zoom = desktop->current_zoom();
1234 gdouble zdx = dx / zoom;
1235 gdouble zdy = dy / zoom;
1237 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1238 if (!nodepath) return;
1240 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1242 if (dx == 0) {
1243 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1244 } else if (dy == 0) {
1245 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1246 } else {
1247 sp_nodepath_update_repr(nodepath);
1248 }
1249 }
1251 /** If they don't yet exist, creates knot and line for the given side of the node */
1252 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1253 {
1254 if (!side->knot) {
1255 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"));
1257 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1258 side->knot->setSize (7);
1259 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1260 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1261 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1262 sp_knot_update_ctrl(side->knot);
1264 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1265 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1266 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1267 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1268 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1269 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1270 }
1272 if (!side->line) {
1273 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1274 SP_TYPE_CTRLLINE, NULL);
1275 }
1276 }
1278 /**
1279 * Ensure the given handle of the node is visible/invisible, update its screen position
1280 */
1281 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1282 {
1283 g_assert(node != NULL);
1285 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1286 NRPathcode code = sp_node_path_code_from_side(node, side);
1288 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1290 if (show_handle) {
1291 if (!side->knot) { // No handle knot at all
1292 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1293 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1294 side->knot->pos = side->pos;
1295 if (side->knot->item)
1296 SP_CTRL(side->knot->item)->moveto(side->pos);
1297 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1298 sp_knot_show(side->knot);
1299 } else {
1300 if (side->knot->pos != side->pos) { // only if it's really moved
1301 if (fire_move_signals) {
1302 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1303 } else {
1304 sp_knot_moveto(side->knot, &side->pos);
1305 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1306 }
1307 }
1308 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1309 sp_knot_show(side->knot);
1310 }
1311 }
1312 sp_canvas_item_show(side->line);
1313 } else {
1314 if (side->knot) {
1315 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1316 sp_knot_hide(side->knot);
1317 }
1318 }
1319 if (side->line) {
1320 sp_canvas_item_hide(side->line);
1321 }
1322 }
1323 }
1325 /**
1326 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1327 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1328 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1329 * updated; otherwise, just move the knots silently (used in batch moves).
1330 */
1331 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1332 {
1333 g_assert(node != NULL);
1335 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1336 sp_knot_show(node->knot);
1337 }
1339 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1340 if (fire_move_signals)
1341 sp_knot_set_position(node->knot, &node->pos, 0);
1342 else
1343 sp_knot_moveto(node->knot, &node->pos);
1344 }
1346 gboolean show_handles = node->selected;
1347 if (node->p.other != NULL) {
1348 if (node->p.other->selected) show_handles = TRUE;
1349 }
1350 if (node->n.other != NULL) {
1351 if (node->n.other->selected) show_handles = TRUE;
1352 }
1354 if (node->subpath->nodepath->show_handles == false)
1355 show_handles = FALSE;
1357 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1358 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1359 }
1361 /**
1362 * Call sp_node_update_handles() for all nodes on subpath.
1363 */
1364 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1365 {
1366 g_assert(subpath != NULL);
1368 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1369 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1370 }
1371 }
1373 /**
1374 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1375 */
1376 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1377 {
1378 g_assert(nodepath != NULL);
1380 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1381 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1382 }
1383 }
1385 void
1386 sp_nodepath_show_handles(bool show)
1387 {
1388 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1389 if (nodepath == NULL) return;
1391 nodepath->show_handles = show;
1392 sp_nodepath_update_handles(nodepath);
1393 }
1395 /**
1396 * Adds all selected nodes in nodepath to list.
1397 */
1398 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1399 {
1400 StlConv<Node *>::list(l, selected);
1401 /// \todo this adds a copying, rework when the selection becomes a stl list
1402 }
1404 /**
1405 * Align selected nodes on the specified axis.
1406 */
1407 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1408 {
1409 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1410 return;
1411 }
1413 if ( !nodepath->selected->next ) { // only one node selected
1414 return;
1415 }
1416 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1417 NR::Point dest(pNode->pos);
1418 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1419 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1420 if (pNode) {
1421 dest[axis] = pNode->pos[axis];
1422 sp_node_moveto(pNode, dest);
1423 }
1424 }
1426 sp_nodepath_update_repr(nodepath);
1427 }
1429 /// Helper struct.
1430 struct NodeSort
1431 {
1432 Inkscape::NodePath::Node *_node;
1433 NR::Coord _coord;
1434 /// \todo use vectorof pointers instead of calling copy ctor
1435 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1436 _node(node), _coord(node->pos[axis])
1437 {}
1439 };
1441 static bool operator<(NodeSort const &a, NodeSort const &b)
1442 {
1443 return (a._coord < b._coord);
1444 }
1446 /**
1447 * Distribute selected nodes on the specified axis.
1448 */
1449 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1450 {
1451 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1452 return;
1453 }
1455 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1456 return;
1457 }
1459 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1460 std::vector<NodeSort> sorted;
1461 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1462 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1463 if (pNode) {
1464 NodeSort n(pNode, axis);
1465 sorted.push_back(n);
1466 //dest[axis] = pNode->pos[axis];
1467 //sp_node_moveto(pNode, dest);
1468 }
1469 }
1470 std::sort(sorted.begin(), sorted.end());
1471 unsigned int len = sorted.size();
1472 //overall bboxes span
1473 float dist = (sorted.back()._coord -
1474 sorted.front()._coord);
1475 //new distance between each bbox
1476 float step = (dist) / (len - 1);
1477 float pos = sorted.front()._coord;
1478 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1479 it < sorted.end();
1480 it ++ )
1481 {
1482 NR::Point dest((*it)._node->pos);
1483 dest[axis] = pos;
1484 sp_node_moveto((*it)._node, dest);
1485 pos += step;
1486 }
1488 sp_nodepath_update_repr(nodepath);
1489 }
1492 /**
1493 * Call sp_nodepath_line_add_node() for all selected segments.
1494 */
1495 void
1496 sp_node_selected_add_node(void)
1497 {
1498 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1499 if (!nodepath) {
1500 return;
1501 }
1503 GList *nl = NULL;
1505 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1506 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1507 g_assert(t->selected);
1508 if (t->p.other && t->p.other->selected) {
1509 nl = g_list_prepend(nl, t);
1510 }
1511 }
1513 while (nl) {
1514 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1515 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1516 sp_nodepath_node_select(n, TRUE, FALSE);
1517 nl = g_list_remove(nl, t);
1518 }
1520 /** \todo fixme: adjust ? */
1521 sp_nodepath_update_handles(nodepath);
1523 sp_nodepath_update_repr(nodepath);
1525 sp_nodepath_update_statusbar(nodepath);
1526 }
1528 /**
1529 * Select segment nearest to point
1530 */
1531 void
1532 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1533 {
1534 if (!nodepath) {
1535 return;
1536 }
1538 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1540 //find segment to segment
1541 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1543 gboolean force = FALSE;
1544 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1545 force = TRUE;
1546 }
1547 sp_nodepath_node_select(e, (gboolean) toggle, force);
1548 if (e->p.other)
1549 sp_nodepath_node_select(e->p.other, TRUE, force);
1551 sp_nodepath_update_handles(nodepath);
1553 sp_nodepath_update_statusbar(nodepath);
1554 }
1556 /**
1557 * Add a node nearest to point
1558 */
1559 void
1560 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1561 {
1562 if (!nodepath) {
1563 return;
1564 }
1566 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1568 //find segment to split
1569 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1571 //don't know why but t seems to flip for lines
1572 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1573 position.t = 1.0 - position.t;
1574 }
1575 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1576 sp_nodepath_node_select(n, FALSE, TRUE);
1578 /* fixme: adjust ? */
1579 sp_nodepath_update_handles(nodepath);
1581 sp_nodepath_update_repr(nodepath);
1583 sp_nodepath_update_statusbar(nodepath);
1584 }
1586 /*
1587 * Adjusts a segment so that t moves by a certain delta for dragging
1588 * converts lines to curves
1589 *
1590 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1591 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1592 */
1593 void
1594 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1595 {
1596 /* feel good is an arbitrary parameter that distributes the delta between handles
1597 * if t of the drag point is less than 1/6 distance form the endpoint only
1598 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1599 */
1600 double feel_good;
1601 if (t <= 1.0 / 6.0)
1602 feel_good = 0;
1603 else if (t <= 0.5)
1604 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1605 else if (t <= 5.0 / 6.0)
1606 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1607 else
1608 feel_good = 1;
1610 //if we're dragging a line convert it to a curve
1611 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1612 sp_nodepath_set_line_type(e, NR_CURVETO);
1613 }
1615 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1616 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1617 e->p.other->n.pos += offsetcoord0;
1618 e->p.pos += offsetcoord1;
1620 // adjust handles of adjacent nodes where necessary
1621 sp_node_adjust_handle(e,1);
1622 sp_node_adjust_handle(e->p.other,-1);
1624 sp_nodepath_update_handles(e->subpath->nodepath);
1626 update_object(e->subpath->nodepath);
1628 sp_nodepath_update_statusbar(e->subpath->nodepath);
1629 }
1632 /**
1633 * Call sp_nodepath_break() for all selected segments.
1634 */
1635 void sp_node_selected_break()
1636 {
1637 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1638 if (!nodepath) return;
1640 GList *temp = NULL;
1641 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1642 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1643 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1644 if (nn == NULL) continue; // no break, no new node
1645 temp = g_list_prepend(temp, nn);
1646 }
1648 if (temp) {
1649 sp_nodepath_deselect(nodepath);
1650 }
1651 for (GList *l = temp; l != NULL; l = l->next) {
1652 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1653 }
1655 sp_nodepath_update_handles(nodepath);
1657 sp_nodepath_update_repr(nodepath);
1658 }
1660 /**
1661 * Duplicate the selected node(s).
1662 */
1663 void sp_node_selected_duplicate()
1664 {
1665 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1666 if (!nodepath) {
1667 return;
1668 }
1670 GList *temp = NULL;
1671 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1672 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1673 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1674 if (nn == NULL) continue; // could not duplicate
1675 temp = g_list_prepend(temp, nn);
1676 }
1678 if (temp) {
1679 sp_nodepath_deselect(nodepath);
1680 }
1681 for (GList *l = temp; l != NULL; l = l->next) {
1682 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1683 }
1685 sp_nodepath_update_handles(nodepath);
1687 sp_nodepath_update_repr(nodepath);
1688 }
1690 /**
1691 * Join two nodes by merging them into one.
1692 */
1693 void sp_node_selected_join()
1694 {
1695 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1696 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1698 if (g_list_length(nodepath->selected) != 2) {
1699 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1700 return;
1701 }
1703 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1704 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1706 g_assert(a != b);
1707 g_assert(a->p.other || a->n.other);
1708 g_assert(b->p.other || b->n.other);
1710 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1711 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1712 return;
1713 }
1715 /* a and b are endpoints */
1717 NR::Point c;
1718 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1719 c = a->pos;
1720 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1721 c = b->pos;
1722 } else {
1723 c = (a->pos + b->pos) / 2;
1724 }
1726 if (a->subpath == b->subpath) {
1727 Inkscape::NodePath::SubPath *sp = a->subpath;
1728 sp_nodepath_subpath_close(sp);
1729 sp_node_moveto (sp->first, c);
1731 sp_nodepath_update_handles(sp->nodepath);
1732 sp_nodepath_update_repr(nodepath);
1733 return;
1734 }
1736 /* a and b are separate subpaths */
1737 Inkscape::NodePath::SubPath *sa = a->subpath;
1738 Inkscape::NodePath::SubPath *sb = b->subpath;
1739 NR::Point p;
1740 Inkscape::NodePath::Node *n;
1741 NRPathcode code;
1742 if (a == sa->first) {
1743 p = sa->first->n.pos;
1744 code = (NRPathcode)sa->first->n.other->code;
1745 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1746 n = sa->last;
1747 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1748 n = n->p.other;
1749 while (n) {
1750 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1751 n = n->p.other;
1752 if (n == sa->first) n = NULL;
1753 }
1754 sp_nodepath_subpath_destroy(sa);
1755 sa = t;
1756 } else if (a == sa->last) {
1757 p = sa->last->p.pos;
1758 code = (NRPathcode)sa->last->code;
1759 sp_nodepath_node_destroy(sa->last);
1760 } else {
1761 code = NR_END;
1762 g_assert_not_reached();
1763 }
1765 if (b == sb->first) {
1766 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1767 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1768 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1769 }
1770 } else if (b == sb->last) {
1771 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1772 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1773 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1774 }
1775 } else {
1776 g_assert_not_reached();
1777 }
1778 /* and now destroy sb */
1780 sp_nodepath_subpath_destroy(sb);
1782 sp_nodepath_update_handles(sa->nodepath);
1784 sp_nodepath_update_repr(nodepath);
1786 sp_nodepath_update_statusbar(nodepath);
1787 }
1789 /**
1790 * Join two nodes by adding a segment between them.
1791 */
1792 void sp_node_selected_join_segment()
1793 {
1794 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1795 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1797 if (g_list_length(nodepath->selected) != 2) {
1798 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1799 return;
1800 }
1802 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1803 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1805 g_assert(a != b);
1806 g_assert(a->p.other || a->n.other);
1807 g_assert(b->p.other || b->n.other);
1809 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1810 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1811 return;
1812 }
1814 if (a->subpath == b->subpath) {
1815 Inkscape::NodePath::SubPath *sp = a->subpath;
1817 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1818 sp->closed = TRUE;
1820 sp->first->p.other = sp->last;
1821 sp->last->n.other = sp->first;
1823 sp_node_handle_mirror_p_to_n(sp->last);
1824 sp_node_handle_mirror_n_to_p(sp->first);
1826 sp->first->code = sp->last->code;
1827 sp->first = sp->last;
1829 sp_nodepath_update_handles(sp->nodepath);
1831 sp_nodepath_update_repr(nodepath);
1833 return;
1834 }
1836 /* a and b are separate subpaths */
1837 Inkscape::NodePath::SubPath *sa = a->subpath;
1838 Inkscape::NodePath::SubPath *sb = b->subpath;
1840 Inkscape::NodePath::Node *n;
1841 NR::Point p;
1842 NRPathcode code;
1843 if (a == sa->first) {
1844 code = (NRPathcode) sa->first->n.other->code;
1845 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1846 n = sa->last;
1847 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1848 for (n = n->p.other; n != NULL; n = n->p.other) {
1849 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1850 }
1851 sp_nodepath_subpath_destroy(sa);
1852 sa = t;
1853 } else if (a == sa->last) {
1854 code = (NRPathcode)sa->last->code;
1855 } else {
1856 code = NR_END;
1857 g_assert_not_reached();
1858 }
1860 if (b == sb->first) {
1861 n = sb->first;
1862 sp_node_handle_mirror_p_to_n(sa->last);
1863 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1864 sp_node_handle_mirror_n_to_p(sa->last);
1865 for (n = n->n.other; n != NULL; n = n->n.other) {
1866 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1867 }
1868 } else if (b == sb->last) {
1869 n = sb->last;
1870 sp_node_handle_mirror_p_to_n(sa->last);
1871 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1872 sp_node_handle_mirror_n_to_p(sa->last);
1873 for (n = n->p.other; n != NULL; n = n->p.other) {
1874 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1875 }
1876 } else {
1877 g_assert_not_reached();
1878 }
1879 /* and now destroy sb */
1881 sp_nodepath_subpath_destroy(sb);
1883 sp_nodepath_update_handles(sa->nodepath);
1885 sp_nodepath_update_repr(nodepath);
1886 }
1888 /**
1889 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1890 */
1891 void sp_node_delete_preserve(GList *nodes_to_delete)
1892 {
1893 GSList *nodepaths = NULL;
1895 while (nodes_to_delete) {
1896 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1897 Inkscape::NodePath::SubPath *sp = node->subpath;
1898 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1899 Inkscape::NodePath::Node *sample_cursor = NULL;
1900 Inkscape::NodePath::Node *sample_end = NULL;
1901 Inkscape::NodePath::Node *delete_cursor = node;
1902 bool just_delete = false;
1904 //find the start of this contiguous selection
1905 //move left to the first node that is not selected
1906 //or the start of the non-closed path
1907 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1908 delete_cursor = curr;
1909 }
1911 //just delete at the beginning of an open path
1912 if (!delete_cursor->p.other) {
1913 sample_cursor = delete_cursor;
1914 just_delete = true;
1915 } else {
1916 sample_cursor = delete_cursor->p.other;
1917 }
1919 //calculate points for each segment
1920 int rate = 5;
1921 float period = 1.0 / rate;
1922 std::vector<NR::Point> data;
1923 if (!just_delete) {
1924 data.push_back(sample_cursor->pos);
1925 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1926 //just delete at the end of an open path
1927 if (!sp->closed && curr->n.other == sp->last) {
1928 just_delete = true;
1929 break;
1930 }
1932 //sample points on the contiguous selected segment
1933 NR::Point *bez;
1934 bez = new NR::Point [4];
1935 bez[0] = curr->pos;
1936 bez[1] = curr->n.pos;
1937 bez[2] = curr->n.other->p.pos;
1938 bez[3] = curr->n.other->pos;
1939 for (int i=1; i<rate; i++) {
1940 gdouble t = i * period;
1941 NR::Point p = bezier_pt(3, bez, t);
1942 data.push_back(p);
1943 }
1944 data.push_back(curr->n.other->pos);
1946 sample_end = curr->n.other;
1947 //break if we've come full circle or hit the end of the selection
1948 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1949 break;
1950 }
1951 }
1952 }
1954 if (!just_delete) {
1955 //calculate the best fitting single segment and adjust the endpoints
1956 NR::Point *adata;
1957 adata = new NR::Point [data.size()];
1958 copy(data.begin(), data.end(), adata);
1960 NR::Point *bez;
1961 bez = new NR::Point [4];
1962 //would decreasing error create a better fitting approximation?
1963 gdouble error = 1.0;
1964 gint ret;
1965 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1967 //adjust endpoints
1968 sample_cursor->n.pos = bez[1];
1969 sample_end->p.pos = bez[2];
1970 }
1972 //destroy this contiguous selection
1973 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1974 Inkscape::NodePath::Node *temp = delete_cursor;
1975 if (delete_cursor->n.other == delete_cursor) {
1976 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1977 delete_cursor = NULL;
1978 } else {
1979 delete_cursor = delete_cursor->n.other;
1980 }
1981 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1982 sp_nodepath_node_destroy(temp);
1983 }
1985 sp_nodepath_update_handles(nodepath);
1987 if (!g_slist_find(nodepaths, nodepath))
1988 nodepaths = g_slist_prepend (nodepaths, nodepath);
1989 }
1991 for (GSList *i = nodepaths; i; i = i->next) {
1992 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1993 // different nodepaths will give us one undo event per nodepath
1994 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1996 // if the entire nodepath is removed, delete the selected object.
1997 if (nodepath->subpaths == NULL ||
1998 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1999 //at least 2
2000 sp_nodepath_get_node_count(nodepath) < 2) {
2001 SPDocument *document = sp_desktop_document (nodepath->desktop);
2002 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2003 //delete this nodepath's object, not the entire selection! (though at this time, this
2004 //does not matter)
2005 sp_selection_delete();
2006 sp_document_done (document);
2007 } else {
2008 sp_nodepath_update_repr(nodepath);
2009 sp_nodepath_update_statusbar(nodepath);
2010 }
2011 }
2013 g_slist_free (nodepaths);
2014 }
2016 /**
2017 * Delete one or more selected nodes.
2018 */
2019 void sp_node_selected_delete()
2020 {
2021 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2022 if (!nodepath) return;
2023 if (!nodepath->selected) return;
2025 /** \todo fixme: do it the right way */
2026 while (nodepath->selected) {
2027 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2028 sp_nodepath_node_destroy(node);
2029 }
2032 //clean up the nodepath (such as for trivial subpaths)
2033 sp_nodepath_cleanup(nodepath);
2035 sp_nodepath_update_handles(nodepath);
2037 // if the entire nodepath is removed, delete the selected object.
2038 if (nodepath->subpaths == NULL ||
2039 sp_nodepath_get_node_count(nodepath) < 2) {
2040 SPDocument *document = sp_desktop_document (nodepath->desktop);
2041 sp_selection_delete();
2042 sp_document_done (document);
2043 return;
2044 }
2046 sp_nodepath_update_repr(nodepath);
2048 sp_nodepath_update_statusbar(nodepath);
2049 }
2051 /**
2052 * Delete one or more segments between two selected nodes.
2053 * This is the code for 'split'.
2054 */
2055 void
2056 sp_node_selected_delete_segment(void)
2057 {
2058 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2059 Inkscape::NodePath::Node *curr, *next; //Iterators
2061 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2062 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2064 if (g_list_length(nodepath->selected) != 2) {
2065 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2066 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2067 return;
2068 }
2070 //Selected nodes, not inclusive
2071 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2072 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2074 if ( ( a==b) || //same node
2075 (a->subpath != b->subpath ) || //not the same path
2076 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2077 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2078 {
2079 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2080 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2081 return;
2082 }
2084 //###########################################
2085 //# BEGIN EDITS
2086 //###########################################
2087 //##################################
2088 //# CLOSED PATH
2089 //##################################
2090 if (a->subpath->closed) {
2093 gboolean reversed = FALSE;
2095 //Since we can go in a circle, we need to find the shorter distance.
2096 // a->b or b->a
2097 start = end = NULL;
2098 int distance = 0;
2099 int minDistance = 0;
2100 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2101 if (curr==b) {
2102 //printf("a to b:%d\n", distance);
2103 start = a;//go from a to b
2104 end = b;
2105 minDistance = distance;
2106 //printf("A to B :\n");
2107 break;
2108 }
2109 distance++;
2110 }
2112 //try again, the other direction
2113 distance = 0;
2114 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2115 if (curr==a) {
2116 //printf("b to a:%d\n", distance);
2117 if (distance < minDistance) {
2118 start = b; //we go from b to a
2119 end = a;
2120 reversed = TRUE;
2121 //printf("B to A\n");
2122 }
2123 break;
2124 }
2125 distance++;
2126 }
2129 //Copy everything from 'end' to 'start' to a new subpath
2130 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2131 for (curr=end ; curr ; curr=curr->n.other) {
2132 NRPathcode code = (NRPathcode) curr->code;
2133 if (curr == end)
2134 code = NR_MOVETO;
2135 sp_nodepath_node_new(t, NULL,
2136 (Inkscape::NodePath::NodeType)curr->type, code,
2137 &curr->p.pos, &curr->pos, &curr->n.pos);
2138 if (curr == start)
2139 break;
2140 }
2141 sp_nodepath_subpath_destroy(a->subpath);
2144 }
2148 //##################################
2149 //# OPEN PATH
2150 //##################################
2151 else {
2153 //We need to get the direction of the list between A and B
2154 //Can we walk from a to b?
2155 start = end = NULL;
2156 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2157 if (curr==b) {
2158 start = a; //did it! we go from a to b
2159 end = b;
2160 //printf("A to B\n");
2161 break;
2162 }
2163 }
2164 if (!start) {//didn't work? let's try the other direction
2165 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2166 if (curr==a) {
2167 start = b; //did it! we go from b to a
2168 end = a;
2169 //printf("B to A\n");
2170 break;
2171 }
2172 }
2173 }
2174 if (!start) {
2175 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2176 _("Cannot find path between nodes."));
2177 return;
2178 }
2182 //Copy everything after 'end' to a new subpath
2183 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2184 for (curr=end ; curr ; curr=curr->n.other) {
2185 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2186 &curr->p.pos, &curr->pos, &curr->n.pos);
2187 }
2189 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2190 for (curr = start->n.other ; curr ; curr=next) {
2191 next = curr->n.other;
2192 sp_nodepath_node_destroy(curr);
2193 }
2195 }
2196 //###########################################
2197 //# END EDITS
2198 //###########################################
2200 //clean up the nodepath (such as for trivial subpaths)
2201 sp_nodepath_cleanup(nodepath);
2203 sp_nodepath_update_handles(nodepath);
2205 sp_nodepath_update_repr(nodepath);
2207 sp_nodepath_update_statusbar(nodepath);
2208 }
2210 /**
2211 * Call sp_nodepath_set_line() for all selected segments.
2212 */
2213 void
2214 sp_node_selected_set_line_type(NRPathcode code)
2215 {
2216 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2217 if (nodepath == NULL) return;
2219 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2220 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2221 g_assert(n->selected);
2222 if (n->p.other && n->p.other->selected) {
2223 sp_nodepath_set_line_type(n, code);
2224 }
2225 }
2227 sp_nodepath_update_repr(nodepath);
2228 }
2230 /**
2231 * Call sp_nodepath_convert_node_type() for all selected nodes.
2232 */
2233 void
2234 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2235 {
2236 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2237 if (nodepath == NULL) return;
2239 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2240 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2241 }
2243 sp_nodepath_update_repr(nodepath);
2244 }
2246 /**
2247 * Change select status of node, update its own and neighbour handles.
2248 */
2249 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2250 {
2251 node->selected = selected;
2253 if (selected) {
2254 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2255 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2256 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2257 sp_knot_update_ctrl(node->knot);
2258 } else {
2259 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2260 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2261 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2262 sp_knot_update_ctrl(node->knot);
2263 }
2265 sp_node_update_handles(node);
2266 if (node->n.other) sp_node_update_handles(node->n.other);
2267 if (node->p.other) sp_node_update_handles(node->p.other);
2268 }
2270 /**
2271 \brief Select a node
2272 \param node The node to select
2273 \param incremental If true, add to selection, otherwise deselect others
2274 \param override If true, always select this node, otherwise toggle selected status
2275 */
2276 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2277 {
2278 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2280 if (incremental) {
2281 if (override) {
2282 if (!g_list_find(nodepath->selected, node)) {
2283 nodepath->selected = g_list_prepend(nodepath->selected, node);
2284 }
2285 sp_node_set_selected(node, TRUE);
2286 } else { // toggle
2287 if (node->selected) {
2288 g_assert(g_list_find(nodepath->selected, node));
2289 nodepath->selected = g_list_remove(nodepath->selected, node);
2290 } else {
2291 g_assert(!g_list_find(nodepath->selected, node));
2292 nodepath->selected = g_list_prepend(nodepath->selected, node);
2293 }
2294 sp_node_set_selected(node, !node->selected);
2295 }
2296 } else {
2297 sp_nodepath_deselect(nodepath);
2298 nodepath->selected = g_list_prepend(nodepath->selected, node);
2299 sp_node_set_selected(node, TRUE);
2300 }
2302 sp_nodepath_update_statusbar(nodepath);
2303 }
2306 /**
2307 \brief Deselect all nodes in the nodepath
2308 */
2309 void
2310 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2311 {
2312 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2314 while (nodepath->selected) {
2315 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2316 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2317 }
2318 sp_nodepath_update_statusbar(nodepath);
2319 }
2321 /**
2322 \brief Select or invert selection of all nodes in the nodepath
2323 */
2324 void
2325 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2326 {
2327 if (!nodepath) return;
2329 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2330 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2331 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2332 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2333 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2334 }
2335 }
2336 }
2338 /**
2339 * If nothing selected, does the same as sp_nodepath_select_all();
2340 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2341 * (i.e., similar to "select all in layer", with the "selected" subpaths
2342 * being treated as "layers" in the path).
2343 */
2344 void
2345 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2346 {
2347 if (!nodepath) return;
2349 if (g_list_length (nodepath->selected) == 0) {
2350 sp_nodepath_select_all (nodepath, invert);
2351 return;
2352 }
2354 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2355 GSList *subpaths = NULL;
2357 for (GList *l = copy; l != NULL; l = l->next) {
2358 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2359 Inkscape::NodePath::SubPath *subpath = n->subpath;
2360 if (!g_slist_find (subpaths, subpath))
2361 subpaths = g_slist_prepend (subpaths, subpath);
2362 }
2364 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2365 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2366 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2367 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2368 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2369 }
2370 }
2372 g_slist_free (subpaths);
2373 g_list_free (copy);
2374 }
2376 /**
2377 * \brief Select the node after the last selected; if none is selected,
2378 * select the first within path.
2379 */
2380 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2381 {
2382 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2384 Inkscape::NodePath::Node *last = NULL;
2385 if (nodepath->selected) {
2386 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2387 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2388 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2389 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2390 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2391 if (node->selected) {
2392 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2393 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2394 if (spl->next) { // there's a next subpath
2395 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2396 last = subpath_next->first;
2397 } else if (spl->prev) { // there's a previous subpath
2398 last = NULL; // to be set later to the first node of first subpath
2399 } else {
2400 last = node->n.other;
2401 }
2402 } else {
2403 last = node->n.other;
2404 }
2405 } else {
2406 if (node->n.other) {
2407 last = node->n.other;
2408 } else {
2409 if (spl->next) { // there's a next subpath
2410 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2411 last = subpath_next->first;
2412 } else if (spl->prev) { // there's a previous subpath
2413 last = NULL; // to be set later to the first node of first subpath
2414 } else {
2415 last = (Inkscape::NodePath::Node *) subpath->first;
2416 }
2417 }
2418 }
2419 }
2420 }
2421 }
2422 sp_nodepath_deselect(nodepath);
2423 }
2425 if (last) { // there's at least one more node after selected
2426 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2427 } else { // no more nodes, select the first one in first subpath
2428 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2429 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2430 }
2431 }
2433 /**
2434 * \brief Select the node before the first selected; if none is selected,
2435 * select the last within path
2436 */
2437 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2438 {
2439 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2441 Inkscape::NodePath::Node *last = NULL;
2442 if (nodepath->selected) {
2443 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2444 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2445 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2446 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2447 if (node->selected) {
2448 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2449 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2450 if (spl->prev) { // there's a prev subpath
2451 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2452 last = subpath_prev->last;
2453 } else if (spl->next) { // there's a next subpath
2454 last = NULL; // to be set later to the last node of last subpath
2455 } else {
2456 last = node->p.other;
2457 }
2458 } else {
2459 last = node->p.other;
2460 }
2461 } else {
2462 if (node->p.other) {
2463 last = node->p.other;
2464 } else {
2465 if (spl->prev) { // there's a prev subpath
2466 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2467 last = subpath_prev->last;
2468 } else if (spl->next) { // there's a next subpath
2469 last = NULL; // to be set later to the last node of last subpath
2470 } else {
2471 last = (Inkscape::NodePath::Node *) subpath->last;
2472 }
2473 }
2474 }
2475 }
2476 }
2477 }
2478 sp_nodepath_deselect(nodepath);
2479 }
2481 if (last) { // there's at least one more node before selected
2482 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2483 } else { // no more nodes, select the last one in last subpath
2484 GList *spl = g_list_last(nodepath->subpaths);
2485 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2486 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2487 }
2488 }
2490 /**
2491 * \brief Select all nodes that are within the rectangle.
2492 */
2493 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2494 {
2495 if (!incremental) {
2496 sp_nodepath_deselect(nodepath);
2497 }
2499 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2500 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2501 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2502 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2504 if (b.contains(node->pos)) {
2505 sp_nodepath_node_select(node, TRUE, TRUE);
2506 }
2507 }
2508 }
2509 }
2512 void
2513 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2514 {
2515 g_assert (n);
2516 g_assert (nodepath);
2517 g_assert (n->subpath->nodepath == nodepath);
2519 if (g_list_length (nodepath->selected) == 0) {
2520 if (grow > 0) {
2521 sp_nodepath_node_select(n, TRUE, TRUE);
2522 }
2523 return;
2524 }
2526 if (g_list_length (nodepath->selected) == 1) {
2527 if (grow < 0) {
2528 sp_nodepath_deselect (nodepath);
2529 return;
2530 }
2531 }
2533 double n_sel_range = 0, p_sel_range = 0;
2534 Inkscape::NodePath::Node *farthest_n_node = n;
2535 Inkscape::NodePath::Node *farthest_p_node = n;
2537 // Calculate ranges
2538 {
2539 double n_range = 0, p_range = 0;
2540 bool n_going = true, p_going = true;
2541 Inkscape::NodePath::Node *n_node = n;
2542 Inkscape::NodePath::Node *p_node = n;
2543 do {
2544 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2545 if (n_node && n_going)
2546 n_node = n_node->n.other;
2547 if (n_node == NULL) {
2548 n_going = false;
2549 } else {
2550 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2551 if (n_node->selected) {
2552 n_sel_range = n_range;
2553 farthest_n_node = n_node;
2554 }
2555 if (n_node == p_node) {
2556 n_going = false;
2557 p_going = false;
2558 }
2559 }
2560 if (p_node && p_going)
2561 p_node = p_node->p.other;
2562 if (p_node == NULL) {
2563 p_going = false;
2564 } else {
2565 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2566 if (p_node->selected) {
2567 p_sel_range = p_range;
2568 farthest_p_node = p_node;
2569 }
2570 if (p_node == n_node) {
2571 n_going = false;
2572 p_going = false;
2573 }
2574 }
2575 } while (n_going || p_going);
2576 }
2578 if (grow > 0) {
2579 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2580 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2581 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2582 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2583 }
2584 } else {
2585 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2586 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2587 } else if (farthest_p_node && farthest_p_node->selected) {
2588 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2589 }
2590 }
2591 }
2593 void
2594 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2595 {
2596 g_assert (n);
2597 g_assert (nodepath);
2598 g_assert (n->subpath->nodepath == nodepath);
2600 if (g_list_length (nodepath->selected) == 0) {
2601 if (grow > 0) {
2602 sp_nodepath_node_select(n, TRUE, TRUE);
2603 }
2604 return;
2605 }
2607 if (g_list_length (nodepath->selected) == 1) {
2608 if (grow < 0) {
2609 sp_nodepath_deselect (nodepath);
2610 return;
2611 }
2612 }
2614 Inkscape::NodePath::Node *farthest_selected = NULL;
2615 double farthest_dist = 0;
2617 Inkscape::NodePath::Node *closest_unselected = NULL;
2618 double closest_dist = NR_HUGE;
2620 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2621 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2622 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2623 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2624 if (node == n)
2625 continue;
2626 if (node->selected) {
2627 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2628 farthest_dist = NR::L2(node->pos - n->pos);
2629 farthest_selected = node;
2630 }
2631 } else {
2632 if (NR::L2(node->pos - n->pos) < closest_dist) {
2633 closest_dist = NR::L2(node->pos - n->pos);
2634 closest_unselected = node;
2635 }
2636 }
2637 }
2638 }
2640 if (grow > 0) {
2641 if (closest_unselected) {
2642 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2643 }
2644 } else {
2645 if (farthest_selected) {
2646 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2647 }
2648 }
2649 }
2652 /**
2653 \brief Saves all nodes' and handles' current positions in their origin members
2654 */
2655 void
2656 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2657 {
2658 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2659 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2660 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2661 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2662 n->origin = n->pos;
2663 n->p.origin = n->p.pos;
2664 n->n.origin = n->n.pos;
2665 }
2666 }
2667 }
2669 /**
2670 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2671 */
2672 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2673 {
2674 if (!nodepath->selected) {
2675 return NULL;
2676 }
2678 GList *r = NULL;
2679 guint i = 0;
2680 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2681 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2682 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2683 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2684 i++;
2685 if (node->selected) {
2686 r = g_list_append(r, GINT_TO_POINTER(i));
2687 }
2688 }
2689 }
2690 return r;
2691 }
2693 /**
2694 \brief Restores selection by selecting nodes whose positions are in the list
2695 */
2696 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2697 {
2698 sp_nodepath_deselect(nodepath);
2700 guint i = 0;
2701 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2702 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2703 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2704 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2705 i++;
2706 if (g_list_find(r, GINT_TO_POINTER(i))) {
2707 sp_nodepath_node_select(node, TRUE, TRUE);
2708 }
2709 }
2710 }
2712 }
2714 /**
2715 \brief Adjusts handle according to node type and line code.
2716 */
2717 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2718 {
2719 double len, otherlen, linelen;
2721 g_assert(node);
2723 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2724 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2726 /** \todo fixme: */
2727 if (me->other == NULL) return;
2728 if (other->other == NULL) return;
2730 /* I have line */
2732 NRPathcode mecode, ocode;
2733 if (which_adjust == 1) {
2734 mecode = (NRPathcode)me->other->code;
2735 ocode = (NRPathcode)node->code;
2736 } else {
2737 mecode = (NRPathcode)node->code;
2738 ocode = (NRPathcode)other->other->code;
2739 }
2741 if (mecode == NR_LINETO) return;
2743 /* I am curve */
2745 if (other->other == NULL) return;
2747 /* Other has line */
2749 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2751 NR::Point delta;
2752 if (ocode == NR_LINETO) {
2753 /* other is lineto, we are either smooth or symm */
2754 Inkscape::NodePath::Node *othernode = other->other;
2755 len = NR::L2(me->pos - node->pos);
2756 delta = node->pos - othernode->pos;
2757 linelen = NR::L2(delta);
2758 if (linelen < 1e-18)
2759 return;
2760 me->pos = node->pos + (len / linelen)*delta;
2761 return;
2762 }
2764 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2766 me->pos = 2 * node->pos - other->pos;
2767 return;
2768 }
2770 /* We are smooth */
2772 len = NR::L2(me->pos - node->pos);
2773 delta = other->pos - node->pos;
2774 otherlen = NR::L2(delta);
2775 if (otherlen < 1e-18) return;
2777 me->pos = node->pos - (len / otherlen) * delta;
2778 }
2780 /**
2781 \brief Adjusts both handles according to node type and line code
2782 */
2783 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2784 {
2785 g_assert(node);
2787 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2789 /* we are either smooth or symm */
2791 if (node->p.other == NULL) return;
2793 if (node->n.other == NULL) return;
2795 if (node->code == NR_LINETO) {
2796 if (node->n.other->code == NR_LINETO) return;
2797 sp_node_adjust_handle(node, 1);
2798 return;
2799 }
2801 if (node->n.other->code == NR_LINETO) {
2802 if (node->code == NR_LINETO) return;
2803 sp_node_adjust_handle(node, -1);
2804 return;
2805 }
2807 /* both are curves */
2808 NR::Point const delta( node->n.pos - node->p.pos );
2810 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2811 node->p.pos = node->pos - delta / 2;
2812 node->n.pos = node->pos + delta / 2;
2813 return;
2814 }
2816 /* We are smooth */
2817 double plen = NR::L2(node->p.pos - node->pos);
2818 if (plen < 1e-18) return;
2819 double nlen = NR::L2(node->n.pos - node->pos);
2820 if (nlen < 1e-18) return;
2821 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2822 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2823 }
2825 /**
2826 * Node event callback.
2827 */
2828 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2829 {
2830 gboolean ret = FALSE;
2831 switch (event->type) {
2832 case GDK_ENTER_NOTIFY:
2833 active_node = n;
2834 break;
2835 case GDK_LEAVE_NOTIFY:
2836 active_node = NULL;
2837 break;
2838 case GDK_KEY_PRESS:
2839 switch (get_group0_keyval (&event->key)) {
2840 case GDK_space:
2841 if (event->key.state & GDK_BUTTON1_MASK) {
2842 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2843 stamp_repr(nodepath);
2844 ret = TRUE;
2845 }
2846 break;
2847 case GDK_Page_Up:
2848 if (event->key.state & GDK_CONTROL_MASK) {
2849 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2850 } else {
2851 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2852 }
2853 break;
2854 case GDK_Page_Down:
2855 if (event->key.state & GDK_CONTROL_MASK) {
2856 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2857 } else {
2858 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2859 }
2860 break;
2861 default:
2862 break;
2863 }
2864 break;
2865 default:
2866 break;
2867 }
2869 return ret;
2870 }
2872 /**
2873 * Handle keypress on node; directly called.
2874 */
2875 gboolean node_key(GdkEvent *event)
2876 {
2877 Inkscape::NodePath::Path *np;
2879 // there is no way to verify nodes so set active_node to nil when deleting!!
2880 if (active_node == NULL) return FALSE;
2882 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2883 gint ret = FALSE;
2884 switch (get_group0_keyval (&event->key)) {
2885 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2886 case GDK_BackSpace:
2887 np = active_node->subpath->nodepath;
2888 sp_nodepath_node_destroy(active_node);
2889 sp_nodepath_update_repr(np);
2890 active_node = NULL;
2891 ret = TRUE;
2892 break;
2893 case GDK_c:
2894 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2895 ret = TRUE;
2896 break;
2897 case GDK_s:
2898 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2899 ret = TRUE;
2900 break;
2901 case GDK_y:
2902 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2903 ret = TRUE;
2904 break;
2905 case GDK_b:
2906 sp_nodepath_node_break(active_node);
2907 ret = TRUE;
2908 break;
2909 }
2910 return ret;
2911 }
2912 return FALSE;
2913 }
2915 /**
2916 * Mouseclick on node callback.
2917 */
2918 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2919 {
2920 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2922 if (state & GDK_CONTROL_MASK) {
2923 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2925 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2926 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2927 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2928 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2929 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2930 } else {
2931 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2932 }
2933 sp_nodepath_update_repr(nodepath);
2934 sp_nodepath_update_statusbar(nodepath);
2936 } else { //ctrl+alt+click: delete node
2937 GList *node_to_delete = NULL;
2938 node_to_delete = g_list_append(node_to_delete, n);
2939 sp_node_delete_preserve(node_to_delete);
2940 }
2942 } else {
2943 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2944 }
2945 }
2947 /**
2948 * Mouse grabbed node callback.
2949 */
2950 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2951 {
2952 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2954 if (!n->selected) {
2955 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2956 }
2958 sp_nodepath_remember_origins (n->subpath->nodepath);
2959 }
2961 /**
2962 * Mouse ungrabbed node callback.
2963 */
2964 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2965 {
2966 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2968 n->dragging_out = NULL;
2970 sp_nodepath_update_repr(n->subpath->nodepath);
2971 }
2973 /**
2974 * The point on a line, given by its angle, closest to the given point.
2975 * \param p A point.
2976 * \param a Angle of the line; it is assumed to go through coordinate origin.
2977 * \param closest Pointer to the point struct where the result is stored.
2978 * \todo FIXME: use dot product perhaps?
2979 */
2980 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2981 {
2982 if (a == HUGE_VAL) { // vertical
2983 *closest = NR::Point(0, (*p)[NR::Y]);
2984 } else {
2985 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2986 (*closest)[NR::Y] = a * (*closest)[NR::X];
2987 }
2988 }
2990 /**
2991 * Distance from the point to a line given by its angle.
2992 * \param p A point.
2993 * \param a Angle of the line; it is assumed to go through coordinate origin.
2994 */
2995 static double point_line_distance(NR::Point *p, double a)
2996 {
2997 NR::Point c;
2998 point_line_closest(p, a, &c);
2999 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]));
3000 }
3002 /**
3003 * Callback for node "request" signal.
3004 * \todo fixme: This goes to "moved" event? (lauris)
3005 */
3006 static gboolean
3007 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3008 {
3009 double yn, xn, yp, xp;
3010 double an, ap, na, pa;
3011 double d_an, d_ap, d_na, d_pa;
3012 gboolean collinear = FALSE;
3013 NR::Point c;
3014 NR::Point pr;
3016 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3018 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3019 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3021 NR::Point mouse = (*p);
3023 if (!n->dragging_out) {
3024 // This is the first drag-out event; find out which handle to drag out
3025 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3026 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3028 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3029 return FALSE;
3031 Inkscape::NodePath::NodeSide *opposite;
3032 if (appr_p > appr_n) { // closer to p
3033 n->dragging_out = &n->p;
3034 opposite = &n->n;
3035 n->code = NR_CURVETO;
3036 } else if (appr_p < appr_n) { // closer to n
3037 n->dragging_out = &n->n;
3038 opposite = &n->p;
3039 n->n.other->code = NR_CURVETO;
3040 } else { // p and n nodes are the same
3041 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3042 n->dragging_out = &n->p;
3043 opposite = &n->n;
3044 n->code = NR_CURVETO;
3045 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3046 n->dragging_out = &n->n;
3047 opposite = &n->p;
3048 n->n.other->code = NR_CURVETO;
3049 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3050 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);
3051 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);
3052 if (appr_other_p > appr_other_n) { // closer to other's p handle
3053 n->dragging_out = &n->n;
3054 opposite = &n->p;
3055 n->n.other->code = NR_CURVETO;
3056 } else { // closer to other's n handle
3057 n->dragging_out = &n->p;
3058 opposite = &n->n;
3059 n->code = NR_CURVETO;
3060 }
3061 }
3062 }
3064 // if there's another handle, make sure the one we drag out starts parallel to it
3065 if (opposite->pos != n->pos) {
3066 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3067 }
3069 // knots might not be created yet!
3070 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3071 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3072 }
3074 // pass this on to the handle-moved callback
3075 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3076 sp_node_update_handles(n);
3077 return TRUE;
3078 }
3080 if (state & GDK_CONTROL_MASK) { // constrained motion
3082 // calculate relative distances of handles
3083 // n handle:
3084 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3085 xn = n->n.pos[NR::X] - n->pos[NR::X];
3086 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3087 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3088 if (n->n.other) { // if there is the next point
3089 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3090 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3091 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3092 }
3093 }
3094 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3095 if (yn < 0) { xn = -xn; yn = -yn; }
3097 // p handle:
3098 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3099 xp = n->p.pos[NR::X] - n->pos[NR::X];
3100 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3101 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3102 if (n->p.other) {
3103 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3104 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3105 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3106 }
3107 }
3108 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3109 if (yp < 0) { xp = -xp; yp = -yp; }
3111 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3112 // sliding on handles, only if at least one of the handles is non-vertical
3113 // (otherwise it's the same as ctrl+drag anyway)
3115 // calculate angles of the handles
3116 if (xn == 0) {
3117 if (yn == 0) { // no handle, consider it the continuation of the other one
3118 an = 0;
3119 collinear = TRUE;
3120 }
3121 else an = 0; // vertical; set the angle to horizontal
3122 } else an = yn/xn;
3124 if (xp == 0) {
3125 if (yp == 0) { // no handle, consider it the continuation of the other one
3126 ap = an;
3127 }
3128 else ap = 0; // vertical; set the angle to horizontal
3129 } else ap = yp/xp;
3131 if (collinear) an = ap;
3133 // angles of the perpendiculars; HUGE_VAL means vertical
3134 if (an == 0) na = HUGE_VAL; else na = -1/an;
3135 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3137 // mouse point relative to the node's original pos
3138 pr = (*p) - n->origin;
3140 // distances to the four lines (two handles and two perpendiculars)
3141 d_an = point_line_distance(&pr, an);
3142 d_na = point_line_distance(&pr, na);
3143 d_ap = point_line_distance(&pr, ap);
3144 d_pa = point_line_distance(&pr, pa);
3146 // find out which line is the closest, save its closest point in c
3147 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3148 point_line_closest(&pr, an, &c);
3149 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3150 point_line_closest(&pr, ap, &c);
3151 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3152 point_line_closest(&pr, na, &c);
3153 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3154 point_line_closest(&pr, pa, &c);
3155 }
3157 // move the node to the closest point
3158 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3159 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3160 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3162 } else { // constraining to hor/vert
3164 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3165 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3166 } else { // snap to vert
3167 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3168 }
3169 }
3170 } else { // move freely
3171 if (state & GDK_MOD1_MASK) { // sculpt
3172 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3173 } else {
3174 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3175 (*p)[NR::X] - n->pos[NR::X],
3176 (*p)[NR::Y] - n->pos[NR::Y],
3177 (state & GDK_SHIFT_MASK) == 0);
3178 }
3179 }
3181 n->subpath->nodepath->desktop->scroll_to_point(p);
3183 return TRUE;
3184 }
3186 /**
3187 * Node handle clicked callback.
3188 */
3189 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3190 {
3191 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3193 if (state & GDK_CONTROL_MASK) { // "delete" handle
3194 if (n->p.knot == knot) {
3195 n->p.pos = n->pos;
3196 } else if (n->n.knot == knot) {
3197 n->n.pos = n->pos;
3198 }
3199 sp_node_update_handles(n);
3200 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3201 sp_nodepath_update_repr(nodepath);
3202 sp_nodepath_update_statusbar(nodepath);
3204 } else { // just select or add to selection, depending in Shift
3205 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3206 }
3207 }
3209 /**
3210 * Node handle grabbed callback.
3211 */
3212 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3213 {
3214 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3216 if (!n->selected) {
3217 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3218 }
3220 // remember the origin point of the handle
3221 if (n->p.knot == knot) {
3222 n->p.origin_radial = n->p.pos - n->pos;
3223 } else if (n->n.knot == knot) {
3224 n->n.origin_radial = n->n.pos - n->pos;
3225 } else {
3226 g_assert_not_reached();
3227 }
3229 }
3231 /**
3232 * Node handle ungrabbed callback.
3233 */
3234 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3235 {
3236 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3238 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3239 if (n->p.knot == knot) {
3240 n->p.origin_radial.a = 0;
3241 sp_knot_set_position(knot, &n->p.pos, state);
3242 } else if (n->n.knot == knot) {
3243 n->n.origin_radial.a = 0;
3244 sp_knot_set_position(knot, &n->n.pos, state);
3245 } else {
3246 g_assert_not_reached();
3247 }
3249 sp_nodepath_update_repr(n->subpath->nodepath);
3250 }
3252 /**
3253 * Node handle "request" signal callback.
3254 */
3255 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3256 {
3257 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3259 Inkscape::NodePath::NodeSide *me, *opposite;
3260 gint which;
3261 if (n->p.knot == knot) {
3262 me = &n->p;
3263 opposite = &n->n;
3264 which = -1;
3265 } else if (n->n.knot == knot) {
3266 me = &n->n;
3267 opposite = &n->p;
3268 which = 1;
3269 } else {
3270 me = opposite = NULL;
3271 which = 0;
3272 g_assert_not_reached();
3273 }
3275 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3277 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3279 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3280 /* We are smooth node adjacent with line */
3281 NR::Point const delta = *p - n->pos;
3282 NR::Coord const len = NR::L2(delta);
3283 Inkscape::NodePath::Node *othernode = opposite->other;
3284 NR::Point const ndelta = n->pos - othernode->pos;
3285 NR::Coord const linelen = NR::L2(ndelta);
3286 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3287 NR::Coord const scal = dot(delta, ndelta) / linelen;
3288 (*p) = n->pos + (scal / linelen) * ndelta;
3289 }
3290 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3291 } else {
3292 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3293 }
3295 sp_node_adjust_handle(n, -which);
3297 return FALSE;
3298 }
3300 /**
3301 * Node handle moved callback.
3302 */
3303 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3304 {
3305 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3307 Inkscape::NodePath::NodeSide *me;
3308 Inkscape::NodePath::NodeSide *other;
3309 if (n->p.knot == knot) {
3310 me = &n->p;
3311 other = &n->n;
3312 } else if (n->n.knot == knot) {
3313 me = &n->n;
3314 other = &n->p;
3315 } else {
3316 me = NULL;
3317 other = NULL;
3318 g_assert_not_reached();
3319 }
3321 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3322 Radial rme(me->pos - n->pos);
3323 Radial rother(other->pos - n->pos);
3324 Radial rnew(*p - n->pos);
3326 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3327 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3328 /* 0 interpreted as "no snapping". */
3330 // The closest PI/snaps angle, starting from zero.
3331 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3332 if (me->origin_radial.a == HUGE_VAL) {
3333 // ortho doesn't exist: original handle was zero length.
3334 rnew.a = a_snapped;
3335 } else {
3336 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3337 * its opposite and perpendiculars). */
3338 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3340 // Snap to the closest.
3341 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3342 ? a_snapped
3343 : a_ortho );
3344 }
3345 }
3347 if (state & GDK_MOD1_MASK) {
3348 // lock handle length
3349 rnew.r = me->origin_radial.r;
3350 }
3352 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3353 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3354 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3355 rother.a += rnew.a - rme.a;
3356 other->pos = NR::Point(rother) + n->pos;
3357 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3358 sp_knot_set_position(other->knot, &other->pos, 0);
3359 }
3361 me->pos = NR::Point(rnew) + n->pos;
3362 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3364 // this is what sp_knot_set_position does, but without emitting the signal:
3365 // we cannot emit a "moved" signal because we're now processing it
3366 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3368 knot->desktop->set_coordinate_status(me->pos);
3370 update_object(n->subpath->nodepath);
3372 /* status text */
3373 SPDesktop *desktop = n->subpath->nodepath->desktop;
3374 if (!desktop) return;
3375 SPEventContext *ec = desktop->event_context;
3376 if (!ec) return;
3377 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3378 if (!mc) return;
3380 double degrees = 180 / M_PI * rnew.a;
3381 if (degrees > 180) degrees -= 360;
3382 if (degrees < -180) degrees += 360;
3383 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3384 degrees = angle_to_compass (degrees);
3386 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3388 mc->setF(Inkscape::NORMAL_MESSAGE,
3389 _("<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);
3391 g_string_free(length, TRUE);
3392 }
3394 /**
3395 * Node handle event callback.
3396 */
3397 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3398 {
3399 gboolean ret = FALSE;
3400 switch (event->type) {
3401 case GDK_KEY_PRESS:
3402 switch (get_group0_keyval (&event->key)) {
3403 case GDK_space:
3404 if (event->key.state & GDK_BUTTON1_MASK) {
3405 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3406 stamp_repr(nodepath);
3407 ret = TRUE;
3408 }
3409 break;
3410 default:
3411 break;
3412 }
3413 break;
3414 default:
3415 break;
3416 }
3418 return ret;
3419 }
3421 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3422 Radial &rme, Radial &rother, gboolean const both)
3423 {
3424 rme.a += angle;
3425 if ( both
3426 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3427 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3428 {
3429 rother.a += angle;
3430 }
3431 }
3433 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3434 Radial &rme, Radial &rother, gboolean const both)
3435 {
3436 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3438 gdouble r;
3439 if ( both
3440 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3441 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3442 {
3443 r = MAX(rme.r, rother.r);
3444 } else {
3445 r = rme.r;
3446 }
3448 gdouble const weird_angle = atan2(norm_angle, r);
3449 /* Bulia says norm_angle is just the visible distance that the
3450 * object's end must travel on the screen. Left as 'angle' for want of
3451 * a better name.*/
3453 rme.a += weird_angle;
3454 if ( both
3455 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3456 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3457 {
3458 rother.a += weird_angle;
3459 }
3460 }
3462 /**
3463 * Rotate one node.
3464 */
3465 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3466 {
3467 Inkscape::NodePath::NodeSide *me, *other;
3468 bool both = false;
3470 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3471 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3473 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3474 me = &(n->p);
3475 other = &(n->n);
3476 } else if (!n->p.other) {
3477 me = &(n->n);
3478 other = &(n->p);
3479 } else {
3480 if (which > 0) { // right handle
3481 if (xn > xp) {
3482 me = &(n->n);
3483 other = &(n->p);
3484 } else {
3485 me = &(n->p);
3486 other = &(n->n);
3487 }
3488 } else if (which < 0){ // left handle
3489 if (xn <= xp) {
3490 me = &(n->n);
3491 other = &(n->p);
3492 } else {
3493 me = &(n->p);
3494 other = &(n->n);
3495 }
3496 } else { // both handles
3497 me = &(n->n);
3498 other = &(n->p);
3499 both = true;
3500 }
3501 }
3503 Radial rme(me->pos - n->pos);
3504 Radial rother(other->pos - n->pos);
3506 if (screen) {
3507 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3508 } else {
3509 node_rotate_one_internal (*n, angle, rme, rother, both);
3510 }
3512 me->pos = n->pos + NR::Point(rme);
3514 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3515 other->pos = n->pos + NR::Point(rother);
3516 }
3518 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3519 // so here we just move all the knots without emitting move signals, for speed
3520 sp_node_update_handles(n, false);
3521 }
3523 /**
3524 * Rotate selected nodes.
3525 */
3526 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3527 {
3528 if (!nodepath || !nodepath->selected) return;
3530 if (g_list_length(nodepath->selected) == 1) {
3531 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3532 node_rotate_one (n, angle, which, screen);
3533 } else {
3534 // rotate as an object:
3536 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3537 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3538 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3539 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3540 box.expandTo (n->pos); // contain all selected nodes
3541 }
3543 gdouble rot;
3544 if (screen) {
3545 gdouble const zoom = nodepath->desktop->current_zoom();
3546 gdouble const zmove = angle / zoom;
3547 gdouble const r = NR::L2(box.max() - box.midpoint());
3548 rot = atan2(zmove, r);
3549 } else {
3550 rot = angle;
3551 }
3553 NR::Matrix t =
3554 NR::Matrix (NR::translate(-box.midpoint())) *
3555 NR::Matrix (NR::rotate(rot)) *
3556 NR::Matrix (NR::translate(box.midpoint()));
3558 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3559 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3560 n->pos *= t;
3561 n->n.pos *= t;
3562 n->p.pos *= t;
3563 sp_node_update_handles(n, false);
3564 }
3565 }
3567 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3568 }
3570 /**
3571 * Scale one node.
3572 */
3573 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3574 {
3575 bool both = false;
3576 Inkscape::NodePath::NodeSide *me, *other;
3578 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3579 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3581 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3582 me = &(n->p);
3583 other = &(n->n);
3584 n->code = NR_CURVETO;
3585 } else if (!n->p.other) {
3586 me = &(n->n);
3587 other = &(n->p);
3588 if (n->n.other)
3589 n->n.other->code = NR_CURVETO;
3590 } else {
3591 if (which > 0) { // right handle
3592 if (xn > xp) {
3593 me = &(n->n);
3594 other = &(n->p);
3595 if (n->n.other)
3596 n->n.other->code = NR_CURVETO;
3597 } else {
3598 me = &(n->p);
3599 other = &(n->n);
3600 n->code = NR_CURVETO;
3601 }
3602 } else if (which < 0){ // left handle
3603 if (xn <= xp) {
3604 me = &(n->n);
3605 other = &(n->p);
3606 if (n->n.other)
3607 n->n.other->code = NR_CURVETO;
3608 } else {
3609 me = &(n->p);
3610 other = &(n->n);
3611 n->code = NR_CURVETO;
3612 }
3613 } else { // both handles
3614 me = &(n->n);
3615 other = &(n->p);
3616 both = true;
3617 n->code = NR_CURVETO;
3618 if (n->n.other)
3619 n->n.other->code = NR_CURVETO;
3620 }
3621 }
3623 Radial rme(me->pos - n->pos);
3624 Radial rother(other->pos - n->pos);
3626 rme.r += grow;
3627 if (rme.r < 0) rme.r = 0;
3628 if (rme.a == HUGE_VAL) {
3629 if (me->other) { // if direction is unknown, initialize it towards the next node
3630 Radial rme_next(me->other->pos - n->pos);
3631 rme.a = rme_next.a;
3632 } else { // if there's no next, initialize to 0
3633 rme.a = 0;
3634 }
3635 }
3636 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3637 rother.r += grow;
3638 if (rother.r < 0) rother.r = 0;
3639 if (rother.a == HUGE_VAL) {
3640 rother.a = rme.a + M_PI;
3641 }
3642 }
3644 me->pos = n->pos + NR::Point(rme);
3646 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3647 other->pos = n->pos + NR::Point(rother);
3648 }
3650 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3651 // so here we just move all the knots without emitting move signals, for speed
3652 sp_node_update_handles(n, false);
3653 }
3655 /**
3656 * Scale selected nodes.
3657 */
3658 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3659 {
3660 if (!nodepath || !nodepath->selected) return;
3662 if (g_list_length(nodepath->selected) == 1) {
3663 // scale handles of the single selected node
3664 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3665 node_scale_one (n, grow, which);
3666 } else {
3667 // scale nodes as an "object":
3669 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3670 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3671 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3672 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3673 box.expandTo (n->pos); // contain all selected nodes
3674 }
3676 double scale = (box.maxExtent() + grow)/box.maxExtent();
3678 NR::Matrix t =
3679 NR::Matrix (NR::translate(-box.midpoint())) *
3680 NR::Matrix (NR::scale(scale, scale)) *
3681 NR::Matrix (NR::translate(box.midpoint()));
3683 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3684 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3685 n->pos *= t;
3686 n->n.pos *= t;
3687 n->p.pos *= t;
3688 sp_node_update_handles(n, false);
3689 }
3690 }
3692 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3693 }
3695 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3696 {
3697 if (!nodepath) return;
3698 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3699 }
3701 /**
3702 * Flip selected nodes horizontally/vertically.
3703 */
3704 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3705 {
3706 if (!nodepath || !nodepath->selected) return;
3708 if (g_list_length(nodepath->selected) == 1) {
3709 // flip handles of the single selected node
3710 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3711 double temp = n->p.pos[axis];
3712 n->p.pos[axis] = n->n.pos[axis];
3713 n->n.pos[axis] = temp;
3714 sp_node_update_handles(n, false);
3715 } else {
3716 // scale nodes as an "object":
3718 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3719 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3720 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3721 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3722 box.expandTo (n->pos); // contain all selected nodes
3723 }
3725 NR::Matrix t =
3726 NR::Matrix (NR::translate(-box.midpoint())) *
3727 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3728 NR::Matrix (NR::translate(box.midpoint()));
3730 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3731 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3732 n->pos *= t;
3733 n->n.pos *= t;
3734 n->p.pos *= t;
3735 sp_node_update_handles(n, false);
3736 }
3737 }
3739 sp_nodepath_update_repr(nodepath);
3740 }
3742 //-----------------------------------------------
3743 /**
3744 * Return new subpath under given nodepath.
3745 */
3746 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3747 {
3748 g_assert(nodepath);
3749 g_assert(nodepath->desktop);
3751 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3753 s->nodepath = nodepath;
3754 s->closed = FALSE;
3755 s->nodes = NULL;
3756 s->first = NULL;
3757 s->last = NULL;
3759 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3760 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3761 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3763 return s;
3764 }
3766 /**
3767 * Destroy nodes in subpath, then subpath itself.
3768 */
3769 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3770 {
3771 g_assert(subpath);
3772 g_assert(subpath->nodepath);
3773 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3775 while (subpath->nodes) {
3776 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3777 }
3779 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3781 g_free(subpath);
3782 }
3784 /**
3785 * Link head to tail in subpath.
3786 */
3787 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3788 {
3789 g_assert(!sp->closed);
3790 g_assert(sp->last != sp->first);
3791 g_assert(sp->first->code == NR_MOVETO);
3793 sp->closed = TRUE;
3795 //Link the head to the tail
3796 sp->first->p.other = sp->last;
3797 sp->last->n.other = sp->first;
3798 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3799 sp->first = sp->last;
3801 //Remove the extra end node
3802 sp_nodepath_node_destroy(sp->last->n.other);
3803 }
3805 /**
3806 * Open closed (loopy) subpath at node.
3807 */
3808 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3809 {
3810 g_assert(sp->closed);
3811 g_assert(n->subpath == sp);
3812 g_assert(sp->first == sp->last);
3814 /* We create new startpoint, current node will become last one */
3816 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3817 &n->pos, &n->pos, &n->n.pos);
3820 sp->closed = FALSE;
3822 //Unlink to make a head and tail
3823 sp->first = new_path;
3824 sp->last = n;
3825 n->n.other = NULL;
3826 new_path->p.other = NULL;
3827 }
3829 /**
3830 * Returns area in triangle given by points; may be negative.
3831 */
3832 inline double
3833 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3834 {
3835 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]);
3836 }
3838 /**
3839 * Return new node in subpath with given properties.
3840 * \param pos Position of node.
3841 * \param ppos Handle position in previous direction
3842 * \param npos Handle position in previous direction
3843 */
3844 Inkscape::NodePath::Node *
3845 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)
3846 {
3847 g_assert(sp);
3848 g_assert(sp->nodepath);
3849 g_assert(sp->nodepath->desktop);
3851 if (nodechunk == NULL)
3852 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3854 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3856 n->subpath = sp;
3858 if (type != Inkscape::NodePath::NODE_NONE) {
3859 // use the type from sodipodi:nodetypes
3860 n->type = type;
3861 } else {
3862 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3863 // points are (almost) collinear
3864 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3865 // endnode, or a node with a retracted handle
3866 n->type = Inkscape::NodePath::NODE_CUSP;
3867 } else {
3868 n->type = Inkscape::NodePath::NODE_SMOOTH;
3869 }
3870 } else {
3871 n->type = Inkscape::NodePath::NODE_CUSP;
3872 }
3873 }
3875 n->code = code;
3876 n->selected = FALSE;
3877 n->pos = *pos;
3878 n->p.pos = *ppos;
3879 n->n.pos = *npos;
3881 n->dragging_out = NULL;
3883 Inkscape::NodePath::Node *prev;
3884 if (next) {
3885 //g_assert(g_list_find(sp->nodes, next));
3886 prev = next->p.other;
3887 } else {
3888 prev = sp->last;
3889 }
3891 if (prev)
3892 prev->n.other = n;
3893 else
3894 sp->first = n;
3896 if (next)
3897 next->p.other = n;
3898 else
3899 sp->last = n;
3901 n->p.other = prev;
3902 n->n.other = next;
3904 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"));
3905 sp_knot_set_position(n->knot, pos, 0);
3907 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3908 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3909 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3910 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3911 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3912 sp_knot_update_ctrl(n->knot);
3914 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3915 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3916 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3917 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3918 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3919 sp_knot_show(n->knot);
3921 // We only create handle knots and lines on demand
3922 n->p.knot = NULL;
3923 n->p.line = NULL;
3924 n->n.knot = NULL;
3925 n->n.line = NULL;
3927 sp->nodes = g_list_prepend(sp->nodes, n);
3929 return n;
3930 }
3932 /**
3933 * Destroy node and its knots, link neighbors in subpath.
3934 */
3935 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3936 {
3937 g_assert(node);
3938 g_assert(node->subpath);
3939 g_assert(SP_IS_KNOT(node->knot));
3941 Inkscape::NodePath::SubPath *sp = node->subpath;
3943 if (node->selected) { // first, deselect
3944 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3945 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3946 }
3948 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3950 g_object_unref(G_OBJECT(node->knot));
3951 if (node->p.knot)
3952 g_object_unref(G_OBJECT(node->p.knot));
3953 if (node->n.knot)
3954 g_object_unref(G_OBJECT(node->n.knot));
3956 if (node->p.line)
3957 gtk_object_destroy(GTK_OBJECT(node->p.line));
3958 if (node->n.line)
3959 gtk_object_destroy(GTK_OBJECT(node->n.line));
3961 if (sp->nodes) { // there are others nodes on the subpath
3962 if (sp->closed) {
3963 if (sp->first == node) {
3964 g_assert(sp->last == node);
3965 sp->first = node->n.other;
3966 sp->last = sp->first;
3967 }
3968 node->p.other->n.other = node->n.other;
3969 node->n.other->p.other = node->p.other;
3970 } else {
3971 if (sp->first == node) {
3972 sp->first = node->n.other;
3973 sp->first->code = NR_MOVETO;
3974 }
3975 if (sp->last == node) sp->last = node->p.other;
3976 if (node->p.other) node->p.other->n.other = node->n.other;
3977 if (node->n.other) node->n.other->p.other = node->p.other;
3978 }
3979 } else { // this was the last node on subpath
3980 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3981 }
3983 g_mem_chunk_free(nodechunk, node);
3984 }
3986 /**
3987 * Returns one of the node's two sides.
3988 * \param which Indicates which side.
3989 * \return Pointer to previous node side if which==-1, next if which==1.
3990 */
3991 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3992 {
3993 g_assert(node);
3995 switch (which) {
3996 case -1:
3997 return &node->p;
3998 case 1:
3999 return &node->n;
4000 default:
4001 break;
4002 }
4004 g_assert_not_reached();
4006 return NULL;
4007 }
4009 /**
4010 * Return the other side of the node, given one of its sides.
4011 */
4012 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4013 {
4014 g_assert(node);
4016 if (me == &node->p) return &node->n;
4017 if (me == &node->n) return &node->p;
4019 g_assert_not_reached();
4021 return NULL;
4022 }
4024 /**
4025 * Return NRPathcode on the given side of the node.
4026 */
4027 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4028 {
4029 g_assert(node);
4031 if (me == &node->p) {
4032 if (node->p.other) return (NRPathcode)node->code;
4033 return NR_MOVETO;
4034 }
4036 if (me == &node->n) {
4037 if (node->n.other) return (NRPathcode)node->n.other->code;
4038 return NR_MOVETO;
4039 }
4041 g_assert_not_reached();
4043 return NR_END;
4044 }
4046 /**
4047 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4048 */
4049 Inkscape::NodePath::Node *
4050 sp_nodepath_get_node_by_index(int index)
4051 {
4052 Inkscape::NodePath::Node *e = NULL;
4054 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4055 if (!nodepath) {
4056 return e;
4057 }
4059 //find segment
4060 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4062 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4063 int n = g_list_length(sp->nodes);
4064 if (sp->closed) {
4065 n++;
4066 }
4068 //if the piece belongs to this subpath grab it
4069 //otherwise move onto the next subpath
4070 if (index < n) {
4071 e = sp->first;
4072 for (int i = 0; i < index; ++i) {
4073 e = e->n.other;
4074 }
4075 break;
4076 } else {
4077 if (sp->closed) {
4078 index -= (n+1);
4079 } else {
4080 index -= n;
4081 }
4082 }
4083 }
4085 return e;
4086 }
4088 /**
4089 * Returns plain text meaning of node type.
4090 */
4091 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4092 {
4093 unsigned retracted = 0;
4094 bool endnode = false;
4096 for (int which = -1; which <= 1; which += 2) {
4097 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4098 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4099 retracted ++;
4100 if (!side->other)
4101 endnode = true;
4102 }
4104 if (retracted == 0) {
4105 if (endnode) {
4106 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4107 return _("end node");
4108 } else {
4109 switch (node->type) {
4110 case Inkscape::NodePath::NODE_CUSP:
4111 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4112 return _("cusp");
4113 case Inkscape::NodePath::NODE_SMOOTH:
4114 // TRANSLATORS: "smooth" is an adjective here
4115 return _("smooth");
4116 case Inkscape::NodePath::NODE_SYMM:
4117 return _("symmetric");
4118 }
4119 }
4120 } else if (retracted == 1) {
4121 if (endnode) {
4122 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4123 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4124 } else {
4125 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4126 }
4127 } else {
4128 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4129 }
4131 return NULL;
4132 }
4134 /**
4135 * Handles content of statusbar as long as node tool is active.
4136 */
4137 void
4138 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4139 {
4140 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");
4141 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4143 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4144 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4145 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4146 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4148 SPDesktop *desktop = NULL;
4149 if (nodepath) {
4150 desktop = nodepath->desktop;
4151 } else {
4152 desktop = SP_ACTIVE_DESKTOP;
4153 }
4155 SPEventContext *ec = desktop->event_context;
4156 if (!ec) return;
4157 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4158 if (!mc) return;
4160 if (selected_nodes == 0) {
4161 Inkscape::Selection *sel = desktop->selection;
4162 if (!sel || sel->isEmpty()) {
4163 mc->setF(Inkscape::NORMAL_MESSAGE,
4164 _("Select a single object to edit its nodes or handles."));
4165 } else {
4166 if (nodepath) {
4167 mc->setF(Inkscape::NORMAL_MESSAGE,
4168 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.",
4169 "<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.",
4170 total_nodes),
4171 total_nodes);
4172 } else {
4173 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4174 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4175 } else {
4176 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4177 }
4178 }
4179 }
4180 } else if (nodepath && selected_nodes == 1) {
4181 mc->setF(Inkscape::NORMAL_MESSAGE,
4182 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4183 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4184 total_nodes),
4185 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4186 } else {
4187 if (selected_subpaths > 1) {
4188 mc->setF(Inkscape::NORMAL_MESSAGE,
4189 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4190 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4191 total_nodes),
4192 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4193 } else {
4194 mc->setF(Inkscape::NORMAL_MESSAGE,
4195 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4196 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4197 total_nodes),
4198 selected_nodes, total_nodes, when_selected);
4199 }
4200 }
4201 }
4204 /*
4205 Local Variables:
4206 mode:c++
4207 c-file-style:"stroustrup"
4208 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4209 indent-tabs-mode:nil
4210 fill-column:99
4211 End:
4212 */
4213 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :