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