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 }
1016 }
1018 double
1019 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1020 {
1021 // extremely primitive for now, don't have time to look for the real one
1022 double lower = NR::L2(b - a);
1023 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1024 return (lower + upper)/2;
1025 }
1027 void
1028 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1029 {
1030 n->pos = n->origin + delta;
1031 n->n.pos = n->n.origin + delta_n;
1032 n->p.pos = n->p.origin + delta_p;
1033 sp_node_adjust_handles(n);
1034 sp_node_update_handles(n, false);
1035 }
1037 /**
1038 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1039 * on how far they are from the dragged node n.
1040 */
1041 static void
1042 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1043 {
1044 g_assert (n);
1045 g_assert (nodepath);
1046 g_assert (n->subpath->nodepath == nodepath);
1048 double pressure = n->knot->pressure;
1049 if (pressure == 0)
1050 pressure = 0.5; // default
1051 pressure = CLAMP (pressure, 0.2, 0.8);
1053 // map pressure to alpha = 1/5 ... 5
1054 double alpha = 1 - 2 * fabs(pressure - 0.5);
1055 if (pressure > 0.5)
1056 alpha = 1/alpha;
1058 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1060 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1061 // Only one subpath has selected nodes:
1062 // use linear mode, where the distance from n to node being dragged is calculated along the path
1064 double n_sel_range = 0, p_sel_range = 0;
1065 guint n_nodes = 0, p_nodes = 0;
1066 guint n_sel_nodes = 0, p_sel_nodes = 0;
1068 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1069 {
1070 double n_range = 0, p_range = 0;
1071 bool n_going = true, p_going = true;
1072 Inkscape::NodePath::Node *n_node = n;
1073 Inkscape::NodePath::Node *p_node = n;
1074 do {
1075 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1076 if (n_node && n_going)
1077 n_node = n_node->n.other;
1078 if (n_node == NULL) {
1079 n_going = false;
1080 } else {
1081 n_nodes ++;
1082 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1083 if (n_node->selected) {
1084 n_sel_nodes ++;
1085 n_sel_range = n_range;
1086 }
1087 if (n_node == p_node) {
1088 n_going = false;
1089 p_going = false;
1090 }
1091 }
1092 if (p_node && p_going)
1093 p_node = p_node->p.other;
1094 if (p_node == NULL) {
1095 p_going = false;
1096 } else {
1097 p_nodes ++;
1098 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1099 if (p_node->selected) {
1100 p_sel_nodes ++;
1101 p_sel_range = p_range;
1102 }
1103 if (p_node == n_node) {
1104 n_going = false;
1105 p_going = false;
1106 }
1107 }
1108 } while (n_going || p_going);
1109 }
1111 // Second pass: actually move nodes in this subpath
1112 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1113 {
1114 double n_range = 0, p_range = 0;
1115 bool n_going = true, p_going = true;
1116 Inkscape::NodePath::Node *n_node = n;
1117 Inkscape::NodePath::Node *p_node = n;
1118 do {
1119 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1120 if (n_node && n_going)
1121 n_node = n_node->n.other;
1122 if (n_node == NULL) {
1123 n_going = false;
1124 } else {
1125 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1126 if (n_node->selected) {
1127 sp_nodepath_move_node_and_handles (n_node,
1128 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1129 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1130 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1131 }
1132 if (n_node == p_node) {
1133 n_going = false;
1134 p_going = false;
1135 }
1136 }
1137 if (p_node && p_going)
1138 p_node = p_node->p.other;
1139 if (p_node == NULL) {
1140 p_going = false;
1141 } else {
1142 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1143 if (p_node->selected) {
1144 sp_nodepath_move_node_and_handles (p_node,
1145 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1146 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1147 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1148 }
1149 if (p_node == n_node) {
1150 n_going = false;
1151 p_going = false;
1152 }
1153 }
1154 } while (n_going || p_going);
1155 }
1157 } else {
1158 // Multiple subpaths have selected nodes:
1159 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1160 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1161 // fix the pear-like shape when sculpting e.g. a ring
1163 // First pass: calculate range
1164 gdouble direct_range = 0;
1165 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1166 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1167 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1168 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1169 if (node->selected) {
1170 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1171 }
1172 }
1173 }
1175 // Second pass: actually move nodes
1176 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1177 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1178 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1179 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1180 if (node->selected) {
1181 if (direct_range > 1e-6) {
1182 sp_nodepath_move_node_and_handles (node,
1183 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1184 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1185 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1186 } else {
1187 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1188 }
1190 }
1191 }
1192 }
1193 }
1195 // do not update repr here so that node dragging is acceptably fast
1196 update_object(nodepath);
1197 }
1200 /**
1201 * Move node selection to point, adjust its and neighbouring handles,
1202 * handle possible snapping, and commit the change with possible undo.
1203 */
1204 void
1205 sp_node_selected_move(gdouble dx, gdouble dy)
1206 {
1207 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1208 if (!nodepath) return;
1210 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1212 if (dx == 0) {
1213 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1214 } else if (dy == 0) {
1215 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1216 } else {
1217 sp_nodepath_update_repr(nodepath);
1218 }
1219 }
1221 /**
1222 * Move node selection off screen and commit the change.
1223 */
1224 void
1225 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1226 {
1227 // borrowed from sp_selection_move_screen in selection-chemistry.c
1228 // we find out the current zoom factor and divide deltas by it
1229 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1231 gdouble zoom = desktop->current_zoom();
1232 gdouble zdx = dx / zoom;
1233 gdouble zdy = dy / zoom;
1235 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1236 if (!nodepath) return;
1238 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1240 if (dx == 0) {
1241 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical");
1242 } else if (dy == 0) {
1243 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal");
1244 } else {
1245 sp_nodepath_update_repr(nodepath);
1246 }
1247 }
1249 /** If they don't yet exist, creates knot and line for the given side of the node */
1250 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1251 {
1252 if (!side->knot) {
1253 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"));
1255 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1256 side->knot->setSize (7);
1257 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1258 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1259 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1260 sp_knot_update_ctrl(side->knot);
1262 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1263 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1264 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1265 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1266 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1267 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1268 }
1270 if (!side->line) {
1271 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1272 SP_TYPE_CTRLLINE, NULL);
1273 }
1274 }
1276 /**
1277 * Ensure the given handle of the node is visible/invisible, update its screen position
1278 */
1279 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1280 {
1281 g_assert(node != NULL);
1283 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1284 NRPathcode code = sp_node_path_code_from_side(node, side);
1286 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1288 if (show_handle) {
1289 if (!side->knot) { // No handle knot at all
1290 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1291 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1292 side->knot->pos = side->pos;
1293 if (side->knot->item)
1294 SP_CTRL(side->knot->item)->moveto(side->pos);
1295 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1296 sp_knot_show(side->knot);
1297 } else {
1298 if (side->knot->pos != side->pos) { // only if it's really moved
1299 if (fire_move_signals) {
1300 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1301 } else {
1302 sp_knot_moveto(side->knot, &side->pos);
1303 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1304 }
1305 }
1306 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1307 sp_knot_show(side->knot);
1308 }
1309 }
1310 sp_canvas_item_show(side->line);
1311 } else {
1312 if (side->knot) {
1313 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1314 sp_knot_hide(side->knot);
1315 }
1316 }
1317 if (side->line) {
1318 sp_canvas_item_hide(side->line);
1319 }
1320 }
1321 }
1323 /**
1324 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1325 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1326 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1327 * updated; otherwise, just move the knots silently (used in batch moves).
1328 */
1329 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1330 {
1331 g_assert(node != NULL);
1333 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1334 sp_knot_show(node->knot);
1335 }
1337 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1338 if (fire_move_signals)
1339 sp_knot_set_position(node->knot, &node->pos, 0);
1340 else
1341 sp_knot_moveto(node->knot, &node->pos);
1342 }
1344 gboolean show_handles = node->selected;
1345 if (node->p.other != NULL) {
1346 if (node->p.other->selected) show_handles = TRUE;
1347 }
1348 if (node->n.other != NULL) {
1349 if (node->n.other->selected) show_handles = TRUE;
1350 }
1352 if (node->subpath->nodepath->show_handles == false)
1353 show_handles = FALSE;
1355 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1356 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1357 }
1359 /**
1360 * Call sp_node_update_handles() for all nodes on subpath.
1361 */
1362 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1363 {
1364 g_assert(subpath != NULL);
1366 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1367 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1368 }
1369 }
1371 /**
1372 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1373 */
1374 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1375 {
1376 g_assert(nodepath != NULL);
1378 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1379 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1380 }
1381 }
1383 void
1384 sp_nodepath_show_handles(bool show)
1385 {
1386 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1387 if (nodepath == NULL) return;
1389 nodepath->show_handles = show;
1390 sp_nodepath_update_handles(nodepath);
1391 }
1393 /**
1394 * Adds all selected nodes in nodepath to list.
1395 */
1396 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1397 {
1398 StlConv<Node *>::list(l, selected);
1399 /// \todo this adds a copying, rework when the selection becomes a stl list
1400 }
1402 /**
1403 * Align selected nodes on the specified axis.
1404 */
1405 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1406 {
1407 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1408 return;
1409 }
1411 if ( !nodepath->selected->next ) { // only one node selected
1412 return;
1413 }
1414 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1415 NR::Point dest(pNode->pos);
1416 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1417 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1418 if (pNode) {
1419 dest[axis] = pNode->pos[axis];
1420 sp_node_moveto(pNode, dest);
1421 }
1422 }
1424 sp_nodepath_update_repr(nodepath);
1425 }
1427 /// Helper struct.
1428 struct NodeSort
1429 {
1430 Inkscape::NodePath::Node *_node;
1431 NR::Coord _coord;
1432 /// \todo use vectorof pointers instead of calling copy ctor
1433 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1434 _node(node), _coord(node->pos[axis])
1435 {}
1437 };
1439 static bool operator<(NodeSort const &a, NodeSort const &b)
1440 {
1441 return (a._coord < b._coord);
1442 }
1444 /**
1445 * Distribute selected nodes on the specified axis.
1446 */
1447 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1448 {
1449 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1450 return;
1451 }
1453 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1454 return;
1455 }
1457 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1458 std::vector<NodeSort> sorted;
1459 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1460 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1461 if (pNode) {
1462 NodeSort n(pNode, axis);
1463 sorted.push_back(n);
1464 //dest[axis] = pNode->pos[axis];
1465 //sp_node_moveto(pNode, dest);
1466 }
1467 }
1468 std::sort(sorted.begin(), sorted.end());
1469 unsigned int len = sorted.size();
1470 //overall bboxes span
1471 float dist = (sorted.back()._coord -
1472 sorted.front()._coord);
1473 //new distance between each bbox
1474 float step = (dist) / (len - 1);
1475 float pos = sorted.front()._coord;
1476 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1477 it < sorted.end();
1478 it ++ )
1479 {
1480 NR::Point dest((*it)._node->pos);
1481 dest[axis] = pos;
1482 sp_node_moveto((*it)._node, dest);
1483 pos += step;
1484 }
1486 sp_nodepath_update_repr(nodepath);
1487 }
1490 /**
1491 * Call sp_nodepath_line_add_node() for all selected segments.
1492 */
1493 void
1494 sp_node_selected_add_node(void)
1495 {
1496 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1497 if (!nodepath) {
1498 return;
1499 }
1501 GList *nl = NULL;
1503 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1504 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1505 g_assert(t->selected);
1506 if (t->p.other && t->p.other->selected) {
1507 nl = g_list_prepend(nl, t);
1508 }
1509 }
1511 while (nl) {
1512 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1513 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1514 sp_nodepath_node_select(n, TRUE, FALSE);
1515 nl = g_list_remove(nl, t);
1516 }
1518 /** \todo fixme: adjust ? */
1519 sp_nodepath_update_handles(nodepath);
1521 sp_nodepath_update_repr(nodepath);
1523 sp_nodepath_update_statusbar(nodepath);
1524 }
1526 /**
1527 * Select segment nearest to point
1528 */
1529 void
1530 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1531 {
1532 if (!nodepath) {
1533 return;
1534 }
1536 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1538 //find segment to segment
1539 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1541 gboolean force = FALSE;
1542 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1543 force = TRUE;
1544 }
1545 sp_nodepath_node_select(e, (gboolean) toggle, force);
1546 if (e->p.other)
1547 sp_nodepath_node_select(e->p.other, TRUE, force);
1549 sp_nodepath_update_handles(nodepath);
1551 sp_nodepath_update_statusbar(nodepath);
1552 }
1554 /**
1555 * Add a node nearest to point
1556 */
1557 void
1558 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1559 {
1560 if (!nodepath) {
1561 return;
1562 }
1564 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1566 //find segment to split
1567 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1569 //don't know why but t seems to flip for lines
1570 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1571 position.t = 1.0 - position.t;
1572 }
1573 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1574 sp_nodepath_node_select(n, FALSE, TRUE);
1576 /* fixme: adjust ? */
1577 sp_nodepath_update_handles(nodepath);
1579 sp_nodepath_update_repr(nodepath);
1581 sp_nodepath_update_statusbar(nodepath);
1582 }
1584 /*
1585 * Adjusts a segment so that t moves by a certain delta for dragging
1586 * converts lines to curves
1587 *
1588 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1589 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1590 */
1591 void
1592 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1593 {
1594 /* feel good is an arbitrary parameter that distributes the delta between handles
1595 * if t of the drag point is less than 1/6 distance form the endpoint only
1596 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1597 */
1598 double feel_good;
1599 if (t <= 1.0 / 6.0)
1600 feel_good = 0;
1601 else if (t <= 0.5)
1602 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1603 else if (t <= 5.0 / 6.0)
1604 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1605 else
1606 feel_good = 1;
1608 //if we're dragging a line convert it to a curve
1609 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1610 sp_nodepath_set_line_type(e, NR_CURVETO);
1611 }
1613 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1614 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1615 e->p.other->n.pos += offsetcoord0;
1616 e->p.pos += offsetcoord1;
1618 // adjust handles of adjacent nodes where necessary
1619 sp_node_adjust_handle(e,1);
1620 sp_node_adjust_handle(e->p.other,-1);
1622 sp_nodepath_update_handles(e->subpath->nodepath);
1624 update_object(e->subpath->nodepath);
1626 sp_nodepath_update_statusbar(e->subpath->nodepath);
1627 }
1630 /**
1631 * Call sp_nodepath_break() for all selected segments.
1632 */
1633 void sp_node_selected_break()
1634 {
1635 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1636 if (!nodepath) return;
1638 GList *temp = NULL;
1639 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1640 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1641 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1642 if (nn == NULL) continue; // no break, no new node
1643 temp = g_list_prepend(temp, nn);
1644 }
1646 if (temp) {
1647 sp_nodepath_deselect(nodepath);
1648 }
1649 for (GList *l = temp; l != NULL; l = l->next) {
1650 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1651 }
1653 sp_nodepath_update_handles(nodepath);
1655 sp_nodepath_update_repr(nodepath);
1656 }
1658 /**
1659 * Duplicate the selected node(s).
1660 */
1661 void sp_node_selected_duplicate()
1662 {
1663 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1664 if (!nodepath) {
1665 return;
1666 }
1668 GList *temp = NULL;
1669 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1670 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1671 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1672 if (nn == NULL) continue; // could not duplicate
1673 temp = g_list_prepend(temp, nn);
1674 }
1676 if (temp) {
1677 sp_nodepath_deselect(nodepath);
1678 }
1679 for (GList *l = temp; l != NULL; l = l->next) {
1680 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1681 }
1683 sp_nodepath_update_handles(nodepath);
1685 sp_nodepath_update_repr(nodepath);
1686 }
1688 /**
1689 * Join two nodes by merging them into one.
1690 */
1691 void sp_node_selected_join()
1692 {
1693 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1694 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1696 if (g_list_length(nodepath->selected) != 2) {
1697 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1698 return;
1699 }
1701 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1702 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1704 g_assert(a != b);
1705 g_assert(a->p.other || a->n.other);
1706 g_assert(b->p.other || b->n.other);
1708 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1709 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1710 return;
1711 }
1713 /* a and b are endpoints */
1715 NR::Point c;
1716 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1717 c = a->pos;
1718 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1719 c = b->pos;
1720 } else {
1721 c = (a->pos + b->pos) / 2;
1722 }
1724 if (a->subpath == b->subpath) {
1725 Inkscape::NodePath::SubPath *sp = a->subpath;
1726 sp_nodepath_subpath_close(sp);
1727 sp_node_moveto (sp->first, c);
1729 sp_nodepath_update_handles(sp->nodepath);
1730 sp_nodepath_update_repr(nodepath);
1731 return;
1732 }
1734 /* a and b are separate subpaths */
1735 Inkscape::NodePath::SubPath *sa = a->subpath;
1736 Inkscape::NodePath::SubPath *sb = b->subpath;
1737 NR::Point p;
1738 Inkscape::NodePath::Node *n;
1739 NRPathcode code;
1740 if (a == sa->first) {
1741 p = sa->first->n.pos;
1742 code = (NRPathcode)sa->first->n.other->code;
1743 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1744 n = sa->last;
1745 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1746 n = n->p.other;
1747 while (n) {
1748 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1749 n = n->p.other;
1750 if (n == sa->first) n = NULL;
1751 }
1752 sp_nodepath_subpath_destroy(sa);
1753 sa = t;
1754 } else if (a == sa->last) {
1755 p = sa->last->p.pos;
1756 code = (NRPathcode)sa->last->code;
1757 sp_nodepath_node_destroy(sa->last);
1758 } else {
1759 code = NR_END;
1760 g_assert_not_reached();
1761 }
1763 if (b == sb->first) {
1764 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1765 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1766 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1767 }
1768 } else if (b == sb->last) {
1769 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1770 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1771 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1772 }
1773 } else {
1774 g_assert_not_reached();
1775 }
1776 /* and now destroy sb */
1778 sp_nodepath_subpath_destroy(sb);
1780 sp_nodepath_update_handles(sa->nodepath);
1782 sp_nodepath_update_repr(nodepath);
1784 sp_nodepath_update_statusbar(nodepath);
1785 }
1787 /**
1788 * Join two nodes by adding a segment between them.
1789 */
1790 void sp_node_selected_join_segment()
1791 {
1792 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1793 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1795 if (g_list_length(nodepath->selected) != 2) {
1796 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1797 return;
1798 }
1800 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1801 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1803 g_assert(a != b);
1804 g_assert(a->p.other || a->n.other);
1805 g_assert(b->p.other || b->n.other);
1807 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1808 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1809 return;
1810 }
1812 if (a->subpath == b->subpath) {
1813 Inkscape::NodePath::SubPath *sp = a->subpath;
1815 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1816 sp->closed = TRUE;
1818 sp->first->p.other = sp->last;
1819 sp->last->n.other = sp->first;
1821 sp_node_handle_mirror_p_to_n(sp->last);
1822 sp_node_handle_mirror_n_to_p(sp->first);
1824 sp->first->code = sp->last->code;
1825 sp->first = sp->last;
1827 sp_nodepath_update_handles(sp->nodepath);
1829 sp_nodepath_update_repr(nodepath);
1831 return;
1832 }
1834 /* a and b are separate subpaths */
1835 Inkscape::NodePath::SubPath *sa = a->subpath;
1836 Inkscape::NodePath::SubPath *sb = b->subpath;
1838 Inkscape::NodePath::Node *n;
1839 NR::Point p;
1840 NRPathcode code;
1841 if (a == sa->first) {
1842 code = (NRPathcode) sa->first->n.other->code;
1843 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1844 n = sa->last;
1845 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1846 for (n = n->p.other; n != NULL; n = n->p.other) {
1847 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1848 }
1849 sp_nodepath_subpath_destroy(sa);
1850 sa = t;
1851 } else if (a == sa->last) {
1852 code = (NRPathcode)sa->last->code;
1853 } else {
1854 code = NR_END;
1855 g_assert_not_reached();
1856 }
1858 if (b == sb->first) {
1859 n = sb->first;
1860 sp_node_handle_mirror_p_to_n(sa->last);
1861 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1862 sp_node_handle_mirror_n_to_p(sa->last);
1863 for (n = n->n.other; n != NULL; n = n->n.other) {
1864 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1865 }
1866 } else if (b == sb->last) {
1867 n = sb->last;
1868 sp_node_handle_mirror_p_to_n(sa->last);
1869 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1870 sp_node_handle_mirror_n_to_p(sa->last);
1871 for (n = n->p.other; n != NULL; n = n->p.other) {
1872 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1873 }
1874 } else {
1875 g_assert_not_reached();
1876 }
1877 /* and now destroy sb */
1879 sp_nodepath_subpath_destroy(sb);
1881 sp_nodepath_update_handles(sa->nodepath);
1883 sp_nodepath_update_repr(nodepath);
1884 }
1886 /**
1887 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1888 */
1889 void sp_node_delete_preserve(GList *nodes_to_delete)
1890 {
1891 GSList *nodepaths = NULL;
1893 while (nodes_to_delete) {
1894 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1895 Inkscape::NodePath::SubPath *sp = node->subpath;
1896 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1897 Inkscape::NodePath::Node *sample_cursor = NULL;
1898 Inkscape::NodePath::Node *sample_end = NULL;
1899 Inkscape::NodePath::Node *delete_cursor = node;
1900 bool just_delete = false;
1902 //find the start of this contiguous selection
1903 //move left to the first node that is not selected
1904 //or the start of the non-closed path
1905 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1906 delete_cursor = curr;
1907 }
1909 //just delete at the beginning of an open path
1910 if (!delete_cursor->p.other) {
1911 sample_cursor = delete_cursor;
1912 just_delete = true;
1913 } else {
1914 sample_cursor = delete_cursor->p.other;
1915 }
1917 //calculate points for each segment
1918 int rate = 5;
1919 float period = 1.0 / rate;
1920 std::vector<NR::Point> data;
1921 if (!just_delete) {
1922 data.push_back(sample_cursor->pos);
1923 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1924 //just delete at the end of an open path
1925 if (!sp->closed && curr->n.other == sp->last) {
1926 just_delete = true;
1927 break;
1928 }
1930 //sample points on the contiguous selected segment
1931 NR::Point *bez;
1932 bez = new NR::Point [4];
1933 bez[0] = curr->pos;
1934 bez[1] = curr->n.pos;
1935 bez[2] = curr->n.other->p.pos;
1936 bez[3] = curr->n.other->pos;
1937 for (int i=1; i<rate; i++) {
1938 gdouble t = i * period;
1939 NR::Point p = bezier_pt(3, bez, t);
1940 data.push_back(p);
1941 }
1942 data.push_back(curr->n.other->pos);
1944 sample_end = curr->n.other;
1945 //break if we've come full circle or hit the end of the selection
1946 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1947 break;
1948 }
1949 }
1950 }
1952 if (!just_delete) {
1953 //calculate the best fitting single segment and adjust the endpoints
1954 NR::Point *adata;
1955 adata = new NR::Point [data.size()];
1956 copy(data.begin(), data.end(), adata);
1958 NR::Point *bez;
1959 bez = new NR::Point [4];
1960 //would decreasing error create a better fitting approximation?
1961 gdouble error = 1.0;
1962 gint ret;
1963 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1965 //adjust endpoints
1966 sample_cursor->n.pos = bez[1];
1967 sample_end->p.pos = bez[2];
1968 }
1970 //destroy this contiguous selection
1971 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
1972 Inkscape::NodePath::Node *temp = delete_cursor;
1973 if (delete_cursor->n.other == delete_cursor) {
1974 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
1975 delete_cursor = NULL;
1976 } else {
1977 delete_cursor = delete_cursor->n.other;
1978 }
1979 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
1980 sp_nodepath_node_destroy(temp);
1981 }
1983 sp_nodepath_update_handles(nodepath);
1985 if (!g_slist_find(nodepaths, nodepath))
1986 nodepaths = g_slist_prepend (nodepaths, nodepath);
1987 }
1989 for (GSList *i = nodepaths; i; i = i->next) {
1990 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
1991 // different nodepaths will give us one undo event per nodepath
1992 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
1994 // if the entire nodepath is removed, delete the selected object.
1995 if (nodepath->subpaths == NULL ||
1996 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
1997 //at least 2
1998 sp_nodepath_get_node_count(nodepath) < 2) {
1999 SPDocument *document = sp_desktop_document (nodepath->desktop);
2000 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2001 //delete this nodepath's object, not the entire selection! (though at this time, this
2002 //does not matter)
2003 sp_selection_delete();
2004 sp_document_done (document);
2005 } else {
2006 sp_nodepath_update_repr(nodepath);
2007 sp_nodepath_update_statusbar(nodepath);
2008 }
2009 }
2011 g_slist_free (nodepaths);
2012 }
2014 /**
2015 * Delete one or more selected nodes.
2016 */
2017 void sp_node_selected_delete()
2018 {
2019 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2020 if (!nodepath) return;
2021 if (!nodepath->selected) return;
2023 /** \todo fixme: do it the right way */
2024 while (nodepath->selected) {
2025 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2026 sp_nodepath_node_destroy(node);
2027 }
2030 //clean up the nodepath (such as for trivial subpaths)
2031 sp_nodepath_cleanup(nodepath);
2033 sp_nodepath_update_handles(nodepath);
2035 // if the entire nodepath is removed, delete the selected object.
2036 if (nodepath->subpaths == NULL ||
2037 sp_nodepath_get_node_count(nodepath) < 2) {
2038 SPDocument *document = sp_desktop_document (nodepath->desktop);
2039 sp_selection_delete();
2040 sp_document_done (document);
2041 return;
2042 }
2044 sp_nodepath_update_repr(nodepath);
2046 sp_nodepath_update_statusbar(nodepath);
2047 }
2049 /**
2050 * Delete one or more segments between two selected nodes.
2051 * This is the code for 'split'.
2052 */
2053 void
2054 sp_node_selected_delete_segment(void)
2055 {
2056 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2057 Inkscape::NodePath::Node *curr, *next; //Iterators
2059 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2060 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2062 if (g_list_length(nodepath->selected) != 2) {
2063 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2064 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2065 return;
2066 }
2068 //Selected nodes, not inclusive
2069 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2070 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2072 if ( ( a==b) || //same node
2073 (a->subpath != b->subpath ) || //not the same path
2074 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2075 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2076 {
2077 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2078 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2079 return;
2080 }
2082 //###########################################
2083 //# BEGIN EDITS
2084 //###########################################
2085 //##################################
2086 //# CLOSED PATH
2087 //##################################
2088 if (a->subpath->closed) {
2091 gboolean reversed = FALSE;
2093 //Since we can go in a circle, we need to find the shorter distance.
2094 // a->b or b->a
2095 start = end = NULL;
2096 int distance = 0;
2097 int minDistance = 0;
2098 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2099 if (curr==b) {
2100 //printf("a to b:%d\n", distance);
2101 start = a;//go from a to b
2102 end = b;
2103 minDistance = distance;
2104 //printf("A to B :\n");
2105 break;
2106 }
2107 distance++;
2108 }
2110 //try again, the other direction
2111 distance = 0;
2112 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2113 if (curr==a) {
2114 //printf("b to a:%d\n", distance);
2115 if (distance < minDistance) {
2116 start = b; //we go from b to a
2117 end = a;
2118 reversed = TRUE;
2119 //printf("B to A\n");
2120 }
2121 break;
2122 }
2123 distance++;
2124 }
2127 //Copy everything from 'end' to 'start' to a new subpath
2128 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2129 for (curr=end ; curr ; curr=curr->n.other) {
2130 NRPathcode code = (NRPathcode) curr->code;
2131 if (curr == end)
2132 code = NR_MOVETO;
2133 sp_nodepath_node_new(t, NULL,
2134 (Inkscape::NodePath::NodeType)curr->type, code,
2135 &curr->p.pos, &curr->pos, &curr->n.pos);
2136 if (curr == start)
2137 break;
2138 }
2139 sp_nodepath_subpath_destroy(a->subpath);
2142 }
2146 //##################################
2147 //# OPEN PATH
2148 //##################################
2149 else {
2151 //We need to get the direction of the list between A and B
2152 //Can we walk from a to b?
2153 start = end = NULL;
2154 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2155 if (curr==b) {
2156 start = a; //did it! we go from a to b
2157 end = b;
2158 //printf("A to B\n");
2159 break;
2160 }
2161 }
2162 if (!start) {//didn't work? let's try the other direction
2163 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2164 if (curr==a) {
2165 start = b; //did it! we go from b to a
2166 end = a;
2167 //printf("B to A\n");
2168 break;
2169 }
2170 }
2171 }
2172 if (!start) {
2173 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2174 _("Cannot find path between nodes."));
2175 return;
2176 }
2180 //Copy everything after 'end' to a new subpath
2181 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2182 for (curr=end ; curr ; curr=curr->n.other) {
2183 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2184 &curr->p.pos, &curr->pos, &curr->n.pos);
2185 }
2187 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2188 for (curr = start->n.other ; curr ; curr=next) {
2189 next = curr->n.other;
2190 sp_nodepath_node_destroy(curr);
2191 }
2193 }
2194 //###########################################
2195 //# END EDITS
2196 //###########################################
2198 //clean up the nodepath (such as for trivial subpaths)
2199 sp_nodepath_cleanup(nodepath);
2201 sp_nodepath_update_handles(nodepath);
2203 sp_nodepath_update_repr(nodepath);
2205 sp_nodepath_update_statusbar(nodepath);
2206 }
2208 /**
2209 * Call sp_nodepath_set_line() for all selected segments.
2210 */
2211 void
2212 sp_node_selected_set_line_type(NRPathcode code)
2213 {
2214 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2215 if (nodepath == NULL) return;
2217 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2218 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2219 g_assert(n->selected);
2220 if (n->p.other && n->p.other->selected) {
2221 sp_nodepath_set_line_type(n, code);
2222 }
2223 }
2225 sp_nodepath_update_repr(nodepath);
2226 }
2228 /**
2229 * Call sp_nodepath_convert_node_type() for all selected nodes.
2230 */
2231 void
2232 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2233 {
2234 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2235 if (nodepath == NULL) return;
2237 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2238 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2239 }
2241 sp_nodepath_update_repr(nodepath);
2242 }
2244 /**
2245 * Change select status of node, update its own and neighbour handles.
2246 */
2247 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2248 {
2249 node->selected = selected;
2251 if (selected) {
2252 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2253 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2254 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2255 sp_knot_update_ctrl(node->knot);
2256 } else {
2257 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2258 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2259 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2260 sp_knot_update_ctrl(node->knot);
2261 }
2263 sp_node_update_handles(node);
2264 if (node->n.other) sp_node_update_handles(node->n.other);
2265 if (node->p.other) sp_node_update_handles(node->p.other);
2266 }
2268 /**
2269 \brief Select a node
2270 \param node The node to select
2271 \param incremental If true, add to selection, otherwise deselect others
2272 \param override If true, always select this node, otherwise toggle selected status
2273 */
2274 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2275 {
2276 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2278 if (incremental) {
2279 if (override) {
2280 if (!g_list_find(nodepath->selected, node)) {
2281 nodepath->selected = g_list_prepend(nodepath->selected, node);
2282 }
2283 sp_node_set_selected(node, TRUE);
2284 } else { // toggle
2285 if (node->selected) {
2286 g_assert(g_list_find(nodepath->selected, node));
2287 nodepath->selected = g_list_remove(nodepath->selected, node);
2288 } else {
2289 g_assert(!g_list_find(nodepath->selected, node));
2290 nodepath->selected = g_list_prepend(nodepath->selected, node);
2291 }
2292 sp_node_set_selected(node, !node->selected);
2293 }
2294 } else {
2295 sp_nodepath_deselect(nodepath);
2296 nodepath->selected = g_list_prepend(nodepath->selected, node);
2297 sp_node_set_selected(node, TRUE);
2298 }
2300 sp_nodepath_update_statusbar(nodepath);
2301 }
2304 /**
2305 \brief Deselect all nodes in the nodepath
2306 */
2307 void
2308 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2309 {
2310 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2312 while (nodepath->selected) {
2313 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2314 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2315 }
2316 sp_nodepath_update_statusbar(nodepath);
2317 }
2319 /**
2320 \brief Select or invert selection of all nodes in the nodepath
2321 */
2322 void
2323 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2324 {
2325 if (!nodepath) return;
2327 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2328 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2329 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2330 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2331 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2332 }
2333 }
2334 }
2336 /**
2337 * If nothing selected, does the same as sp_nodepath_select_all();
2338 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2339 * (i.e., similar to "select all in layer", with the "selected" subpaths
2340 * being treated as "layers" in the path).
2341 */
2342 void
2343 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2344 {
2345 if (!nodepath) return;
2347 if (g_list_length (nodepath->selected) == 0) {
2348 sp_nodepath_select_all (nodepath, invert);
2349 return;
2350 }
2352 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2353 GSList *subpaths = NULL;
2355 for (GList *l = copy; l != NULL; l = l->next) {
2356 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2357 Inkscape::NodePath::SubPath *subpath = n->subpath;
2358 if (!g_slist_find (subpaths, subpath))
2359 subpaths = g_slist_prepend (subpaths, subpath);
2360 }
2362 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2363 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2364 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2365 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2366 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2367 }
2368 }
2370 g_slist_free (subpaths);
2371 g_list_free (copy);
2372 }
2374 /**
2375 * \brief Select the node after the last selected; if none is selected,
2376 * select the first within path.
2377 */
2378 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2379 {
2380 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2382 Inkscape::NodePath::Node *last = NULL;
2383 if (nodepath->selected) {
2384 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2385 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2386 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2387 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2388 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2389 if (node->selected) {
2390 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2391 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2392 if (spl->next) { // there's a next subpath
2393 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2394 last = subpath_next->first;
2395 } else if (spl->prev) { // there's a previous subpath
2396 last = NULL; // to be set later to the first node of first subpath
2397 } else {
2398 last = node->n.other;
2399 }
2400 } else {
2401 last = node->n.other;
2402 }
2403 } else {
2404 if (node->n.other) {
2405 last = node->n.other;
2406 } else {
2407 if (spl->next) { // there's a next subpath
2408 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2409 last = subpath_next->first;
2410 } else if (spl->prev) { // there's a previous subpath
2411 last = NULL; // to be set later to the first node of first subpath
2412 } else {
2413 last = (Inkscape::NodePath::Node *) subpath->first;
2414 }
2415 }
2416 }
2417 }
2418 }
2419 }
2420 sp_nodepath_deselect(nodepath);
2421 }
2423 if (last) { // there's at least one more node after selected
2424 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2425 } else { // no more nodes, select the first one in first subpath
2426 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2427 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2428 }
2429 }
2431 /**
2432 * \brief Select the node before the first selected; if none is selected,
2433 * select the last within path
2434 */
2435 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2436 {
2437 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2439 Inkscape::NodePath::Node *last = NULL;
2440 if (nodepath->selected) {
2441 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2442 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2443 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2444 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2445 if (node->selected) {
2446 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2447 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2448 if (spl->prev) { // there's a prev subpath
2449 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2450 last = subpath_prev->last;
2451 } else if (spl->next) { // there's a next subpath
2452 last = NULL; // to be set later to the last node of last subpath
2453 } else {
2454 last = node->p.other;
2455 }
2456 } else {
2457 last = node->p.other;
2458 }
2459 } else {
2460 if (node->p.other) {
2461 last = node->p.other;
2462 } else {
2463 if (spl->prev) { // there's a prev subpath
2464 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2465 last = subpath_prev->last;
2466 } else if (spl->next) { // there's a next subpath
2467 last = NULL; // to be set later to the last node of last subpath
2468 } else {
2469 last = (Inkscape::NodePath::Node *) subpath->last;
2470 }
2471 }
2472 }
2473 }
2474 }
2475 }
2476 sp_nodepath_deselect(nodepath);
2477 }
2479 if (last) { // there's at least one more node before selected
2480 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2481 } else { // no more nodes, select the last one in last subpath
2482 GList *spl = g_list_last(nodepath->subpaths);
2483 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2484 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2485 }
2486 }
2488 /**
2489 * \brief Select all nodes that are within the rectangle.
2490 */
2491 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2492 {
2493 if (!incremental) {
2494 sp_nodepath_deselect(nodepath);
2495 }
2497 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2498 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2499 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2500 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2502 if (b.contains(node->pos)) {
2503 sp_nodepath_node_select(node, TRUE, TRUE);
2504 }
2505 }
2506 }
2507 }
2510 /**
2511 \brief Saves all nodes' and handles' current positions in their origin members
2512 */
2513 void
2514 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2515 {
2516 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2517 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2518 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2519 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2520 n->origin = n->pos;
2521 n->p.origin = n->p.pos;
2522 n->n.origin = n->n.pos;
2523 }
2524 }
2525 }
2527 /**
2528 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2529 */
2530 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2531 {
2532 if (!nodepath->selected) {
2533 return NULL;
2534 }
2536 GList *r = NULL;
2537 guint i = 0;
2538 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2539 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2540 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2541 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2542 i++;
2543 if (node->selected) {
2544 r = g_list_append(r, GINT_TO_POINTER(i));
2545 }
2546 }
2547 }
2548 return r;
2549 }
2551 /**
2552 \brief Restores selection by selecting nodes whose positions are in the list
2553 */
2554 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2555 {
2556 sp_nodepath_deselect(nodepath);
2558 guint i = 0;
2559 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2560 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2561 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2562 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2563 i++;
2564 if (g_list_find(r, GINT_TO_POINTER(i))) {
2565 sp_nodepath_node_select(node, TRUE, TRUE);
2566 }
2567 }
2568 }
2570 }
2572 /**
2573 \brief Adjusts handle according to node type and line code.
2574 */
2575 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2576 {
2577 double len, otherlen, linelen;
2579 g_assert(node);
2581 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2582 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2584 /** \todo fixme: */
2585 if (me->other == NULL) return;
2586 if (other->other == NULL) return;
2588 /* I have line */
2590 NRPathcode mecode, ocode;
2591 if (which_adjust == 1) {
2592 mecode = (NRPathcode)me->other->code;
2593 ocode = (NRPathcode)node->code;
2594 } else {
2595 mecode = (NRPathcode)node->code;
2596 ocode = (NRPathcode)other->other->code;
2597 }
2599 if (mecode == NR_LINETO) return;
2601 /* I am curve */
2603 if (other->other == NULL) return;
2605 /* Other has line */
2607 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2609 NR::Point delta;
2610 if (ocode == NR_LINETO) {
2611 /* other is lineto, we are either smooth or symm */
2612 Inkscape::NodePath::Node *othernode = other->other;
2613 len = NR::L2(me->pos - node->pos);
2614 delta = node->pos - othernode->pos;
2615 linelen = NR::L2(delta);
2616 if (linelen < 1e-18)
2617 return;
2618 me->pos = node->pos + (len / linelen)*delta;
2619 return;
2620 }
2622 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2624 me->pos = 2 * node->pos - other->pos;
2625 return;
2626 }
2628 /* We are smooth */
2630 len = NR::L2(me->pos - node->pos);
2631 delta = other->pos - node->pos;
2632 otherlen = NR::L2(delta);
2633 if (otherlen < 1e-18) return;
2635 me->pos = node->pos - (len / otherlen) * delta;
2636 }
2638 /**
2639 \brief Adjusts both handles according to node type and line code
2640 */
2641 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2642 {
2643 g_assert(node);
2645 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2647 /* we are either smooth or symm */
2649 if (node->p.other == NULL) return;
2651 if (node->n.other == NULL) return;
2653 if (node->code == NR_LINETO) {
2654 if (node->n.other->code == NR_LINETO) return;
2655 sp_node_adjust_handle(node, 1);
2656 return;
2657 }
2659 if (node->n.other->code == NR_LINETO) {
2660 if (node->code == NR_LINETO) return;
2661 sp_node_adjust_handle(node, -1);
2662 return;
2663 }
2665 /* both are curves */
2666 NR::Point const delta( node->n.pos - node->p.pos );
2668 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2669 node->p.pos = node->pos - delta / 2;
2670 node->n.pos = node->pos + delta / 2;
2671 return;
2672 }
2674 /* We are smooth */
2675 double plen = NR::L2(node->p.pos - node->pos);
2676 if (plen < 1e-18) return;
2677 double nlen = NR::L2(node->n.pos - node->pos);
2678 if (nlen < 1e-18) return;
2679 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2680 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2681 }
2683 /**
2684 * Node event callback.
2685 */
2686 static gboolean node_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
2687 {
2688 gboolean ret = FALSE;
2689 switch (event->type) {
2690 case GDK_ENTER_NOTIFY:
2691 active_node = n;
2692 break;
2693 case GDK_LEAVE_NOTIFY:
2694 active_node = NULL;
2695 break;
2696 case GDK_KEY_PRESS:
2697 switch (get_group0_keyval (&event->key)) {
2698 case GDK_space:
2699 if (event->key.state & GDK_BUTTON1_MASK) {
2700 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2701 stamp_repr(nodepath);
2702 ret = TRUE;
2703 }
2704 break;
2705 default:
2706 break;
2707 }
2708 break;
2709 default:
2710 break;
2711 }
2713 return ret;
2714 }
2716 /**
2717 * Handle keypress on node; directly called.
2718 */
2719 gboolean node_key(GdkEvent *event)
2720 {
2721 Inkscape::NodePath::Path *np;
2723 // there is no way to verify nodes so set active_node to nil when deleting!!
2724 if (active_node == NULL) return FALSE;
2726 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2727 gint ret = FALSE;
2728 switch (get_group0_keyval (&event->key)) {
2729 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2730 case GDK_BackSpace:
2731 np = active_node->subpath->nodepath;
2732 sp_nodepath_node_destroy(active_node);
2733 sp_nodepath_update_repr(np);
2734 active_node = NULL;
2735 ret = TRUE;
2736 break;
2737 case GDK_c:
2738 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2739 ret = TRUE;
2740 break;
2741 case GDK_s:
2742 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2743 ret = TRUE;
2744 break;
2745 case GDK_y:
2746 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2747 ret = TRUE;
2748 break;
2749 case GDK_b:
2750 sp_nodepath_node_break(active_node);
2751 ret = TRUE;
2752 break;
2753 }
2754 return ret;
2755 }
2756 return FALSE;
2757 }
2759 /**
2760 * Mouseclick on node callback.
2761 */
2762 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2763 {
2764 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2766 if (state & GDK_CONTROL_MASK) {
2767 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2769 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2770 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2771 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2772 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2773 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2774 } else {
2775 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2776 }
2777 sp_nodepath_update_repr(nodepath);
2778 sp_nodepath_update_statusbar(nodepath);
2780 } else { //ctrl+alt+click: delete node
2781 GList *node_to_delete = NULL;
2782 node_to_delete = g_list_append(node_to_delete, n);
2783 sp_node_delete_preserve(node_to_delete);
2784 }
2786 } else {
2787 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2788 }
2789 }
2791 /**
2792 * Mouse grabbed node callback.
2793 */
2794 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2795 {
2796 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2798 if (!n->selected) {
2799 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2800 }
2802 sp_nodepath_remember_origins (n->subpath->nodepath);
2803 }
2805 /**
2806 * Mouse ungrabbed node callback.
2807 */
2808 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2809 {
2810 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2812 n->dragging_out = NULL;
2814 sp_nodepath_update_repr(n->subpath->nodepath);
2815 }
2817 /**
2818 * The point on a line, given by its angle, closest to the given point.
2819 * \param p A point.
2820 * \param a Angle of the line; it is assumed to go through coordinate origin.
2821 * \param closest Pointer to the point struct where the result is stored.
2822 * \todo FIXME: use dot product perhaps?
2823 */
2824 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
2825 {
2826 if (a == HUGE_VAL) { // vertical
2827 *closest = NR::Point(0, (*p)[NR::Y]);
2828 } else {
2829 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
2830 (*closest)[NR::Y] = a * (*closest)[NR::X];
2831 }
2832 }
2834 /**
2835 * Distance from the point to a line given by its angle.
2836 * \param p A point.
2837 * \param a Angle of the line; it is assumed to go through coordinate origin.
2838 */
2839 static double point_line_distance(NR::Point *p, double a)
2840 {
2841 NR::Point c;
2842 point_line_closest(p, a, &c);
2843 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]));
2844 }
2846 /**
2847 * Callback for node "request" signal.
2848 * \todo fixme: This goes to "moved" event? (lauris)
2849 */
2850 static gboolean
2851 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
2852 {
2853 double yn, xn, yp, xp;
2854 double an, ap, na, pa;
2855 double d_an, d_ap, d_na, d_pa;
2856 gboolean collinear = FALSE;
2857 NR::Point c;
2858 NR::Point pr;
2860 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2862 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
2863 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
2865 NR::Point mouse = (*p);
2867 if (!n->dragging_out) {
2868 // This is the first drag-out event; find out which handle to drag out
2869 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
2870 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
2872 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
2873 return FALSE;
2875 Inkscape::NodePath::NodeSide *opposite;
2876 if (appr_p > appr_n) { // closer to p
2877 n->dragging_out = &n->p;
2878 opposite = &n->n;
2879 n->code = NR_CURVETO;
2880 } else if (appr_p < appr_n) { // closer to n
2881 n->dragging_out = &n->n;
2882 opposite = &n->p;
2883 n->n.other->code = NR_CURVETO;
2884 } else { // p and n nodes are the same
2885 if (n->n.pos != n->pos) { // n handle already dragged, drag p
2886 n->dragging_out = &n->p;
2887 opposite = &n->n;
2888 n->code = NR_CURVETO;
2889 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
2890 n->dragging_out = &n->n;
2891 opposite = &n->p;
2892 n->n.other->code = NR_CURVETO;
2893 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
2894 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);
2895 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);
2896 if (appr_other_p > appr_other_n) { // closer to other's p handle
2897 n->dragging_out = &n->n;
2898 opposite = &n->p;
2899 n->n.other->code = NR_CURVETO;
2900 } else { // closer to other's n handle
2901 n->dragging_out = &n->p;
2902 opposite = &n->n;
2903 n->code = NR_CURVETO;
2904 }
2905 }
2906 }
2908 // if there's another handle, make sure the one we drag out starts parallel to it
2909 if (opposite->pos != n->pos) {
2910 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
2911 }
2913 // knots might not be created yet!
2914 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
2915 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
2916 }
2918 // pass this on to the handle-moved callback
2919 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
2920 sp_node_update_handles(n);
2921 return TRUE;
2922 }
2924 if (state & GDK_CONTROL_MASK) { // constrained motion
2926 // calculate relative distances of handles
2927 // n handle:
2928 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
2929 xn = n->n.pos[NR::X] - n->pos[NR::X];
2930 // if there's no n handle (straight line), see if we can use the direction to the next point on path
2931 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
2932 if (n->n.other) { // if there is the next point
2933 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
2934 yn = n->n.other->pos[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
2935 xn = n->n.other->pos[NR::X] - n->origin[NR::X];
2936 }
2937 }
2938 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
2939 if (yn < 0) { xn = -xn; yn = -yn; }
2941 // p handle:
2942 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
2943 xp = n->p.pos[NR::X] - n->pos[NR::X];
2944 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
2945 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
2946 if (n->p.other) {
2947 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
2948 yp = n->p.other->pos[NR::Y] - n->origin[NR::Y];
2949 xp = n->p.other->pos[NR::X] - n->origin[NR::X];
2950 }
2951 }
2952 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
2953 if (yp < 0) { xp = -xp; yp = -yp; }
2955 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
2956 // sliding on handles, only if at least one of the handles is non-vertical
2957 // (otherwise it's the same as ctrl+drag anyway)
2959 // calculate angles of the handles
2960 if (xn == 0) {
2961 if (yn == 0) { // no handle, consider it the continuation of the other one
2962 an = 0;
2963 collinear = TRUE;
2964 }
2965 else an = 0; // vertical; set the angle to horizontal
2966 } else an = yn/xn;
2968 if (xp == 0) {
2969 if (yp == 0) { // no handle, consider it the continuation of the other one
2970 ap = an;
2971 }
2972 else ap = 0; // vertical; set the angle to horizontal
2973 } else ap = yp/xp;
2975 if (collinear) an = ap;
2977 // angles of the perpendiculars; HUGE_VAL means vertical
2978 if (an == 0) na = HUGE_VAL; else na = -1/an;
2979 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
2981 // mouse point relative to the node's original pos
2982 pr = (*p) - n->origin;
2984 // distances to the four lines (two handles and two perpendiculars)
2985 d_an = point_line_distance(&pr, an);
2986 d_na = point_line_distance(&pr, na);
2987 d_ap = point_line_distance(&pr, ap);
2988 d_pa = point_line_distance(&pr, pa);
2990 // find out which line is the closest, save its closest point in c
2991 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
2992 point_line_closest(&pr, an, &c);
2993 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
2994 point_line_closest(&pr, ap, &c);
2995 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
2996 point_line_closest(&pr, na, &c);
2997 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
2998 point_line_closest(&pr, pa, &c);
2999 }
3001 // move the node to the closest point
3002 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3003 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3004 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3006 } else { // constraining to hor/vert
3008 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3009 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3010 } else { // snap to vert
3011 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3012 }
3013 }
3014 } else { // move freely
3015 if (state & GDK_MOD1_MASK) { // sculpt
3016 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3017 } else {
3018 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3019 (*p)[NR::X] - n->pos[NR::X],
3020 (*p)[NR::Y] - n->pos[NR::Y],
3021 (state & GDK_SHIFT_MASK) == 0);
3022 }
3023 }
3025 n->subpath->nodepath->desktop->scroll_to_point(p);
3027 return TRUE;
3028 }
3030 /**
3031 * Node handle clicked callback.
3032 */
3033 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3034 {
3035 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3037 if (state & GDK_CONTROL_MASK) { // "delete" handle
3038 if (n->p.knot == knot) {
3039 n->p.pos = n->pos;
3040 } else if (n->n.knot == knot) {
3041 n->n.pos = n->pos;
3042 }
3043 sp_node_update_handles(n);
3044 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3045 sp_nodepath_update_repr(nodepath);
3046 sp_nodepath_update_statusbar(nodepath);
3048 } else { // just select or add to selection, depending in Shift
3049 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3050 }
3051 }
3053 /**
3054 * Node handle grabbed callback.
3055 */
3056 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3057 {
3058 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3060 if (!n->selected) {
3061 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3062 }
3064 // remember the origin point of the handle
3065 if (n->p.knot == knot) {
3066 n->p.origin_radial = n->p.pos - n->pos;
3067 } else if (n->n.knot == knot) {
3068 n->n.origin_radial = n->n.pos - n->pos;
3069 } else {
3070 g_assert_not_reached();
3071 }
3073 }
3075 /**
3076 * Node handle ungrabbed callback.
3077 */
3078 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3079 {
3080 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3082 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3083 if (n->p.knot == knot) {
3084 n->p.origin_radial.a = 0;
3085 sp_knot_set_position(knot, &n->p.pos, state);
3086 } else if (n->n.knot == knot) {
3087 n->n.origin_radial.a = 0;
3088 sp_knot_set_position(knot, &n->n.pos, state);
3089 } else {
3090 g_assert_not_reached();
3091 }
3093 sp_nodepath_update_repr(n->subpath->nodepath);
3094 }
3096 /**
3097 * Node handle "request" signal callback.
3098 */
3099 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3100 {
3101 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3103 Inkscape::NodePath::NodeSide *me, *opposite;
3104 gint which;
3105 if (n->p.knot == knot) {
3106 me = &n->p;
3107 opposite = &n->n;
3108 which = -1;
3109 } else if (n->n.knot == knot) {
3110 me = &n->n;
3111 opposite = &n->p;
3112 which = 1;
3113 } else {
3114 me = opposite = NULL;
3115 which = 0;
3116 g_assert_not_reached();
3117 }
3119 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3121 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3123 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3124 /* We are smooth node adjacent with line */
3125 NR::Point const delta = *p - n->pos;
3126 NR::Coord const len = NR::L2(delta);
3127 Inkscape::NodePath::Node *othernode = opposite->other;
3128 NR::Point const ndelta = n->pos - othernode->pos;
3129 NR::Coord const linelen = NR::L2(ndelta);
3130 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3131 NR::Coord const scal = dot(delta, ndelta) / linelen;
3132 (*p) = n->pos + (scal / linelen) * ndelta;
3133 }
3134 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3135 } else {
3136 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3137 }
3139 sp_node_adjust_handle(n, -which);
3141 return FALSE;
3142 }
3144 /**
3145 * Node handle moved callback.
3146 */
3147 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3148 {
3149 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3151 Inkscape::NodePath::NodeSide *me;
3152 Inkscape::NodePath::NodeSide *other;
3153 if (n->p.knot == knot) {
3154 me = &n->p;
3155 other = &n->n;
3156 } else if (n->n.knot == knot) {
3157 me = &n->n;
3158 other = &n->p;
3159 } else {
3160 me = NULL;
3161 other = NULL;
3162 g_assert_not_reached();
3163 }
3165 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3166 Radial rme(me->pos - n->pos);
3167 Radial rother(other->pos - n->pos);
3168 Radial rnew(*p - n->pos);
3170 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3171 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3172 /* 0 interpreted as "no snapping". */
3174 // The closest PI/snaps angle, starting from zero.
3175 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3176 if (me->origin_radial.a == HUGE_VAL) {
3177 // ortho doesn't exist: original handle was zero length.
3178 rnew.a = a_snapped;
3179 } else {
3180 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3181 * its opposite and perpendiculars). */
3182 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3184 // Snap to the closest.
3185 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3186 ? a_snapped
3187 : a_ortho );
3188 }
3189 }
3191 if (state & GDK_MOD1_MASK) {
3192 // lock handle length
3193 rnew.r = me->origin_radial.r;
3194 }
3196 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3197 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3198 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3199 rother.a += rnew.a - rme.a;
3200 other->pos = NR::Point(rother) + n->pos;
3201 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3202 sp_knot_set_position(other->knot, &other->pos, 0);
3203 }
3205 me->pos = NR::Point(rnew) + n->pos;
3206 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3208 // this is what sp_knot_set_position does, but without emitting the signal:
3209 // we cannot emit a "moved" signal because we're now processing it
3210 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3212 knot->desktop->set_coordinate_status(me->pos);
3214 update_object(n->subpath->nodepath);
3216 /* status text */
3217 SPDesktop *desktop = n->subpath->nodepath->desktop;
3218 if (!desktop) return;
3219 SPEventContext *ec = desktop->event_context;
3220 if (!ec) return;
3221 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3222 if (!mc) return;
3224 double degrees = 180 / M_PI * rnew.a;
3225 if (degrees > 180) degrees -= 360;
3226 if (degrees < -180) degrees += 360;
3227 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3228 degrees = angle_to_compass (degrees);
3230 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3232 mc->setF(Inkscape::NORMAL_MESSAGE,
3233 _("<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);
3235 g_string_free(length, TRUE);
3236 }
3238 /**
3239 * Node handle event callback.
3240 */
3241 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3242 {
3243 gboolean ret = FALSE;
3244 switch (event->type) {
3245 case GDK_KEY_PRESS:
3246 switch (get_group0_keyval (&event->key)) {
3247 case GDK_space:
3248 if (event->key.state & GDK_BUTTON1_MASK) {
3249 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3250 stamp_repr(nodepath);
3251 ret = TRUE;
3252 }
3253 break;
3254 default:
3255 break;
3256 }
3257 break;
3258 default:
3259 break;
3260 }
3262 return ret;
3263 }
3265 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3266 Radial &rme, Radial &rother, gboolean const both)
3267 {
3268 rme.a += angle;
3269 if ( both
3270 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3271 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3272 {
3273 rother.a += angle;
3274 }
3275 }
3277 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3278 Radial &rme, Radial &rother, gboolean const both)
3279 {
3280 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3282 gdouble r;
3283 if ( both
3284 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3285 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3286 {
3287 r = MAX(rme.r, rother.r);
3288 } else {
3289 r = rme.r;
3290 }
3292 gdouble const weird_angle = atan2(norm_angle, r);
3293 /* Bulia says norm_angle is just the visible distance that the
3294 * object's end must travel on the screen. Left as 'angle' for want of
3295 * a better name.*/
3297 rme.a += weird_angle;
3298 if ( both
3299 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3300 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3301 {
3302 rother.a += weird_angle;
3303 }
3304 }
3306 /**
3307 * Rotate one node.
3308 */
3309 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3310 {
3311 Inkscape::NodePath::NodeSide *me, *other;
3312 bool both = false;
3314 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3315 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3317 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3318 me = &(n->p);
3319 other = &(n->n);
3320 } else if (!n->p.other) {
3321 me = &(n->n);
3322 other = &(n->p);
3323 } else {
3324 if (which > 0) { // right handle
3325 if (xn > xp) {
3326 me = &(n->n);
3327 other = &(n->p);
3328 } else {
3329 me = &(n->p);
3330 other = &(n->n);
3331 }
3332 } else if (which < 0){ // left handle
3333 if (xn <= xp) {
3334 me = &(n->n);
3335 other = &(n->p);
3336 } else {
3337 me = &(n->p);
3338 other = &(n->n);
3339 }
3340 } else { // both handles
3341 me = &(n->n);
3342 other = &(n->p);
3343 both = true;
3344 }
3345 }
3347 Radial rme(me->pos - n->pos);
3348 Radial rother(other->pos - n->pos);
3350 if (screen) {
3351 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3352 } else {
3353 node_rotate_one_internal (*n, angle, rme, rother, both);
3354 }
3356 me->pos = n->pos + NR::Point(rme);
3358 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3359 other->pos = n->pos + NR::Point(rother);
3360 }
3362 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3363 // so here we just move all the knots without emitting move signals, for speed
3364 sp_node_update_handles(n, false);
3365 }
3367 /**
3368 * Rotate selected nodes.
3369 */
3370 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3371 {
3372 if (!nodepath || !nodepath->selected) return;
3374 if (g_list_length(nodepath->selected) == 1) {
3375 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3376 node_rotate_one (n, angle, which, screen);
3377 } else {
3378 // rotate as an object:
3380 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3381 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3382 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3383 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3384 box.expandTo (n->pos); // contain all selected nodes
3385 }
3387 gdouble rot;
3388 if (screen) {
3389 gdouble const zoom = nodepath->desktop->current_zoom();
3390 gdouble const zmove = angle / zoom;
3391 gdouble const r = NR::L2(box.max() - box.midpoint());
3392 rot = atan2(zmove, r);
3393 } else {
3394 rot = angle;
3395 }
3397 NR::Matrix t =
3398 NR::Matrix (NR::translate(-box.midpoint())) *
3399 NR::Matrix (NR::rotate(rot)) *
3400 NR::Matrix (NR::translate(box.midpoint()));
3402 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3403 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3404 n->pos *= t;
3405 n->n.pos *= t;
3406 n->p.pos *= t;
3407 sp_node_update_handles(n, false);
3408 }
3409 }
3411 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n");
3412 }
3414 /**
3415 * Scale one node.
3416 */
3417 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3418 {
3419 bool both = false;
3420 Inkscape::NodePath::NodeSide *me, *other;
3422 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3423 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3425 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3426 me = &(n->p);
3427 other = &(n->n);
3428 n->code = NR_CURVETO;
3429 } else if (!n->p.other) {
3430 me = &(n->n);
3431 other = &(n->p);
3432 if (n->n.other)
3433 n->n.other->code = NR_CURVETO;
3434 } else {
3435 if (which > 0) { // right handle
3436 if (xn > xp) {
3437 me = &(n->n);
3438 other = &(n->p);
3439 if (n->n.other)
3440 n->n.other->code = NR_CURVETO;
3441 } else {
3442 me = &(n->p);
3443 other = &(n->n);
3444 n->code = NR_CURVETO;
3445 }
3446 } else if (which < 0){ // left handle
3447 if (xn <= xp) {
3448 me = &(n->n);
3449 other = &(n->p);
3450 if (n->n.other)
3451 n->n.other->code = NR_CURVETO;
3452 } else {
3453 me = &(n->p);
3454 other = &(n->n);
3455 n->code = NR_CURVETO;
3456 }
3457 } else { // both handles
3458 me = &(n->n);
3459 other = &(n->p);
3460 both = true;
3461 n->code = NR_CURVETO;
3462 if (n->n.other)
3463 n->n.other->code = NR_CURVETO;
3464 }
3465 }
3467 Radial rme(me->pos - n->pos);
3468 Radial rother(other->pos - n->pos);
3470 rme.r += grow;
3471 if (rme.r < 0) rme.r = 0;
3472 if (rme.a == HUGE_VAL) {
3473 if (me->other) { // if direction is unknown, initialize it towards the next node
3474 Radial rme_next(me->other->pos - n->pos);
3475 rme.a = rme_next.a;
3476 } else { // if there's no next, initialize to 0
3477 rme.a = 0;
3478 }
3479 }
3480 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3481 rother.r += grow;
3482 if (rother.r < 0) rother.r = 0;
3483 if (rother.a == HUGE_VAL) {
3484 rother.a = rme.a + M_PI;
3485 }
3486 }
3488 me->pos = n->pos + NR::Point(rme);
3490 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3491 other->pos = n->pos + NR::Point(rother);
3492 }
3494 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3495 // so here we just move all the knots without emitting move signals, for speed
3496 sp_node_update_handles(n, false);
3497 }
3499 /**
3500 * Scale selected nodes.
3501 */
3502 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3503 {
3504 if (!nodepath || !nodepath->selected) return;
3506 if (g_list_length(nodepath->selected) == 1) {
3507 // scale handles of the single selected node
3508 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3509 node_scale_one (n, grow, which);
3510 } else {
3511 // scale nodes as an "object":
3513 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3514 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3515 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3516 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3517 box.expandTo (n->pos); // contain all selected nodes
3518 }
3520 double scale = (box.maxExtent() + grow)/box.maxExtent();
3522 NR::Matrix t =
3523 NR::Matrix (NR::translate(-box.midpoint())) *
3524 NR::Matrix (NR::scale(scale, scale)) *
3525 NR::Matrix (NR::translate(box.midpoint()));
3527 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3528 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3529 n->pos *= t;
3530 n->n.pos *= t;
3531 n->p.pos *= t;
3532 sp_node_update_handles(n, false);
3533 }
3534 }
3536 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n");
3537 }
3539 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3540 {
3541 if (!nodepath) return;
3542 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3543 }
3545 /**
3546 * Flip selected nodes horizontally/vertically.
3547 */
3548 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3549 {
3550 if (!nodepath || !nodepath->selected) return;
3552 if (g_list_length(nodepath->selected) == 1) {
3553 // flip handles of the single selected node
3554 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3555 double temp = n->p.pos[axis];
3556 n->p.pos[axis] = n->n.pos[axis];
3557 n->n.pos[axis] = temp;
3558 sp_node_update_handles(n, false);
3559 } else {
3560 // scale nodes as an "object":
3562 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3563 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3564 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3565 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3566 box.expandTo (n->pos); // contain all selected nodes
3567 }
3569 NR::Matrix t =
3570 NR::Matrix (NR::translate(-box.midpoint())) *
3571 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3572 NR::Matrix (NR::translate(box.midpoint()));
3574 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3575 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3576 n->pos *= t;
3577 n->n.pos *= t;
3578 n->p.pos *= t;
3579 sp_node_update_handles(n, false);
3580 }
3581 }
3583 sp_nodepath_update_repr(nodepath);
3584 }
3586 //-----------------------------------------------
3587 /**
3588 * Return new subpath under given nodepath.
3589 */
3590 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3591 {
3592 g_assert(nodepath);
3593 g_assert(nodepath->desktop);
3595 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3597 s->nodepath = nodepath;
3598 s->closed = FALSE;
3599 s->nodes = NULL;
3600 s->first = NULL;
3601 s->last = NULL;
3603 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3604 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3605 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3607 return s;
3608 }
3610 /**
3611 * Destroy nodes in subpath, then subpath itself.
3612 */
3613 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3614 {
3615 g_assert(subpath);
3616 g_assert(subpath->nodepath);
3617 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3619 while (subpath->nodes) {
3620 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3621 }
3623 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3625 g_free(subpath);
3626 }
3628 /**
3629 * Link head to tail in subpath.
3630 */
3631 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3632 {
3633 g_assert(!sp->closed);
3634 g_assert(sp->last != sp->first);
3635 g_assert(sp->first->code == NR_MOVETO);
3637 sp->closed = TRUE;
3639 //Link the head to the tail
3640 sp->first->p.other = sp->last;
3641 sp->last->n.other = sp->first;
3642 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3643 sp->first = sp->last;
3645 //Remove the extra end node
3646 sp_nodepath_node_destroy(sp->last->n.other);
3647 }
3649 /**
3650 * Open closed (loopy) subpath at node.
3651 */
3652 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3653 {
3654 g_assert(sp->closed);
3655 g_assert(n->subpath == sp);
3656 g_assert(sp->first == sp->last);
3658 /* We create new startpoint, current node will become last one */
3660 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3661 &n->pos, &n->pos, &n->n.pos);
3664 sp->closed = FALSE;
3666 //Unlink to make a head and tail
3667 sp->first = new_path;
3668 sp->last = n;
3669 n->n.other = NULL;
3670 new_path->p.other = NULL;
3671 }
3673 /**
3674 * Returns area in triangle given by points; may be negative.
3675 */
3676 inline double
3677 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3678 {
3679 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]);
3680 }
3682 /**
3683 * Return new node in subpath with given properties.
3684 * \param pos Position of node.
3685 * \param ppos Handle position in previous direction
3686 * \param npos Handle position in previous direction
3687 */
3688 Inkscape::NodePath::Node *
3689 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)
3690 {
3691 g_assert(sp);
3692 g_assert(sp->nodepath);
3693 g_assert(sp->nodepath->desktop);
3695 if (nodechunk == NULL)
3696 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3698 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3700 n->subpath = sp;
3702 if (type != Inkscape::NodePath::NODE_NONE) {
3703 // use the type from sodipodi:nodetypes
3704 n->type = type;
3705 } else {
3706 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3707 // points are (almost) collinear
3708 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3709 // endnode, or a node with a retracted handle
3710 n->type = Inkscape::NodePath::NODE_CUSP;
3711 } else {
3712 n->type = Inkscape::NodePath::NODE_SMOOTH;
3713 }
3714 } else {
3715 n->type = Inkscape::NodePath::NODE_CUSP;
3716 }
3717 }
3719 n->code = code;
3720 n->selected = FALSE;
3721 n->pos = *pos;
3722 n->p.pos = *ppos;
3723 n->n.pos = *npos;
3725 n->dragging_out = NULL;
3727 Inkscape::NodePath::Node *prev;
3728 if (next) {
3729 //g_assert(g_list_find(sp->nodes, next));
3730 prev = next->p.other;
3731 } else {
3732 prev = sp->last;
3733 }
3735 if (prev)
3736 prev->n.other = n;
3737 else
3738 sp->first = n;
3740 if (next)
3741 next->p.other = n;
3742 else
3743 sp->last = n;
3745 n->p.other = prev;
3746 n->n.other = next;
3748 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"));
3749 sp_knot_set_position(n->knot, pos, 0);
3751 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3752 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3753 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3754 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3755 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3756 sp_knot_update_ctrl(n->knot);
3758 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3759 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3760 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3761 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3762 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3763 sp_knot_show(n->knot);
3765 // We only create handle knots and lines on demand
3766 n->p.knot = NULL;
3767 n->p.line = NULL;
3768 n->n.knot = NULL;
3769 n->n.line = NULL;
3771 sp->nodes = g_list_prepend(sp->nodes, n);
3773 return n;
3774 }
3776 /**
3777 * Destroy node and its knots, link neighbors in subpath.
3778 */
3779 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3780 {
3781 g_assert(node);
3782 g_assert(node->subpath);
3783 g_assert(SP_IS_KNOT(node->knot));
3785 Inkscape::NodePath::SubPath *sp = node->subpath;
3787 if (node->selected) { // first, deselect
3788 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3789 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3790 }
3792 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3794 g_object_unref(G_OBJECT(node->knot));
3795 if (node->p.knot)
3796 g_object_unref(G_OBJECT(node->p.knot));
3797 if (node->n.knot)
3798 g_object_unref(G_OBJECT(node->n.knot));
3800 if (node->p.line)
3801 gtk_object_destroy(GTK_OBJECT(node->p.line));
3802 if (node->n.line)
3803 gtk_object_destroy(GTK_OBJECT(node->n.line));
3805 if (sp->nodes) { // there are others nodes on the subpath
3806 if (sp->closed) {
3807 if (sp->first == node) {
3808 g_assert(sp->last == node);
3809 sp->first = node->n.other;
3810 sp->last = sp->first;
3811 }
3812 node->p.other->n.other = node->n.other;
3813 node->n.other->p.other = node->p.other;
3814 } else {
3815 if (sp->first == node) {
3816 sp->first = node->n.other;
3817 sp->first->code = NR_MOVETO;
3818 }
3819 if (sp->last == node) sp->last = node->p.other;
3820 if (node->p.other) node->p.other->n.other = node->n.other;
3821 if (node->n.other) node->n.other->p.other = node->p.other;
3822 }
3823 } else { // this was the last node on subpath
3824 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
3825 }
3827 g_mem_chunk_free(nodechunk, node);
3828 }
3830 /**
3831 * Returns one of the node's two sides.
3832 * \param which Indicates which side.
3833 * \return Pointer to previous node side if which==-1, next if which==1.
3834 */
3835 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
3836 {
3837 g_assert(node);
3839 switch (which) {
3840 case -1:
3841 return &node->p;
3842 case 1:
3843 return &node->n;
3844 default:
3845 break;
3846 }
3848 g_assert_not_reached();
3850 return NULL;
3851 }
3853 /**
3854 * Return the other side of the node, given one of its sides.
3855 */
3856 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
3857 {
3858 g_assert(node);
3860 if (me == &node->p) return &node->n;
3861 if (me == &node->n) return &node->p;
3863 g_assert_not_reached();
3865 return NULL;
3866 }
3868 /**
3869 * Return NRPathcode on the given side of the node.
3870 */
3871 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
3872 {
3873 g_assert(node);
3875 if (me == &node->p) {
3876 if (node->p.other) return (NRPathcode)node->code;
3877 return NR_MOVETO;
3878 }
3880 if (me == &node->n) {
3881 if (node->n.other) return (NRPathcode)node->n.other->code;
3882 return NR_MOVETO;
3883 }
3885 g_assert_not_reached();
3887 return NR_END;
3888 }
3890 /**
3891 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
3892 */
3893 Inkscape::NodePath::Node *
3894 sp_nodepath_get_node_by_index(int index)
3895 {
3896 Inkscape::NodePath::Node *e = NULL;
3898 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
3899 if (!nodepath) {
3900 return e;
3901 }
3903 //find segment
3904 for (GList *l = nodepath->subpaths; l ; l=l->next) {
3906 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
3907 int n = g_list_length(sp->nodes);
3908 if (sp->closed) {
3909 n++;
3910 }
3912 //if the piece belongs to this subpath grab it
3913 //otherwise move onto the next subpath
3914 if (index < n) {
3915 e = sp->first;
3916 for (int i = 0; i < index; ++i) {
3917 e = e->n.other;
3918 }
3919 break;
3920 } else {
3921 if (sp->closed) {
3922 index -= (n+1);
3923 } else {
3924 index -= n;
3925 }
3926 }
3927 }
3929 return e;
3930 }
3932 /**
3933 * Returns plain text meaning of node type.
3934 */
3935 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
3936 {
3937 unsigned retracted = 0;
3938 bool endnode = false;
3940 for (int which = -1; which <= 1; which += 2) {
3941 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
3942 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
3943 retracted ++;
3944 if (!side->other)
3945 endnode = true;
3946 }
3948 if (retracted == 0) {
3949 if (endnode) {
3950 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3951 return _("end node");
3952 } else {
3953 switch (node->type) {
3954 case Inkscape::NodePath::NODE_CUSP:
3955 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
3956 return _("cusp");
3957 case Inkscape::NodePath::NODE_SMOOTH:
3958 // TRANSLATORS: "smooth" is an adjective here
3959 return _("smooth");
3960 case Inkscape::NodePath::NODE_SYMM:
3961 return _("symmetric");
3962 }
3963 }
3964 } else if (retracted == 1) {
3965 if (endnode) {
3966 // TRANSLATORS: "end" is an adjective here (NOT a verb)
3967 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
3968 } else {
3969 return _("one handle retracted (drag with <b>Shift</b> to extend)");
3970 }
3971 } else {
3972 return _("both handles retracted (drag with <b>Shift</b> to extend)");
3973 }
3975 return NULL;
3976 }
3978 /**
3979 * Handles content of statusbar as long as node tool is active.
3980 */
3981 void
3982 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
3983 {
3984 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");
3985 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
3987 gint total_nodes = sp_nodepath_get_node_count(nodepath);
3988 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
3989 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
3990 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
3992 SPDesktop *desktop = NULL;
3993 if (nodepath) {
3994 desktop = nodepath->desktop;
3995 } else {
3996 desktop = SP_ACTIVE_DESKTOP;
3997 }
3999 SPEventContext *ec = desktop->event_context;
4000 if (!ec) return;
4001 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4002 if (!mc) return;
4004 if (selected_nodes == 0) {
4005 Inkscape::Selection *sel = desktop->selection;
4006 if (!sel || sel->isEmpty()) {
4007 mc->setF(Inkscape::NORMAL_MESSAGE,
4008 _("Select a single object to edit its nodes or handles."));
4009 } else {
4010 if (nodepath) {
4011 mc->setF(Inkscape::NORMAL_MESSAGE,
4012 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.",
4013 "<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.",
4014 total_nodes),
4015 total_nodes);
4016 } else {
4017 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4018 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4019 } else {
4020 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4021 }
4022 }
4023 }
4024 } else if (nodepath && selected_nodes == 1) {
4025 mc->setF(Inkscape::NORMAL_MESSAGE,
4026 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4027 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4028 total_nodes),
4029 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4030 } else {
4031 if (selected_subpaths > 1) {
4032 mc->setF(Inkscape::NORMAL_MESSAGE,
4033 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4034 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4035 total_nodes),
4036 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4037 } else {
4038 mc->setF(Inkscape::NORMAL_MESSAGE,
4039 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4040 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4041 total_nodes),
4042 selected_nodes, total_nodes, when_selected);
4043 }
4044 }
4045 }
4048 /*
4049 Local Variables:
4050 mode:c++
4051 c-file-style:"stroustrup"
4052 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4053 indent-tabs-mode:nil
4054 fill-column:99
4055 End:
4056 */
4057 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :