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