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/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include <glibmm/i18n.h>
23 #include "libnr/n-art-bpath.h"
24 #include "libnr/nr-path.h"
25 #include "helper/units.h"
26 #include "knot.h"
27 #include "inkscape.h"
28 #include "document.h"
29 #include "sp-namedview.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "snap.h"
33 #include "message-stack.h"
34 #include "message-context.h"
35 #include "node-context.h"
36 #include "shape-editor.h"
37 #include "selection-chemistry.h"
38 #include "selection.h"
39 #include "xml/repr.h"
40 #include "prefs-utils.h"
41 #include "sp-metrics.h"
42 #include "sp-path.h"
43 #include "libnr/nr-matrix-ops.h"
44 #include "splivarot.h"
45 #include "svg/svg.h"
46 #include "verbs.h"
47 #include "display/bezier-utils.h"
48 #include <vector>
49 #include <algorithm>
50 #include "live_effects/lpeobject.h"
51 #include "live_effects/parameter/parameter.h"
53 class NR::Matrix;
55 /// \todo
56 /// evil evil evil. FIXME: conflict of two different Path classes!
57 /// There is a conflict in the namespace between two classes named Path.
58 /// #include "sp-flowtext.h"
59 /// #include "sp-flowregion.h"
61 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
62 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
63 GType sp_flowregion_get_type (void);
64 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
65 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
66 GType sp_flowtext_get_type (void);
67 // end evil workaround
69 #include "helper/stlport.h"
72 /// \todo fixme: Implement these via preferences */
74 #define NODE_FILL 0xbfbfbf00
75 #define NODE_STROKE 0x000000ff
76 #define NODE_FILL_HI 0xff000000
77 #define NODE_STROKE_HI 0x000000ff
78 #define NODE_FILL_SEL 0x0000ffff
79 #define NODE_STROKE_SEL 0x000000ff
80 #define NODE_FILL_SEL_HI 0xff000000
81 #define NODE_STROKE_SEL_HI 0x000000ff
82 #define KNOT_FILL 0xffffffff
83 #define KNOT_STROKE 0x000000ff
84 #define KNOT_FILL_HI 0xff000000
85 #define KNOT_STROKE_HI 0x000000ff
87 static GMemChunk *nodechunk = NULL;
89 /* Creation from object */
91 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
92 static gchar *parse_nodetypes(gchar const *types, gint length);
94 /* Object updating */
96 static void stamp_repr(Inkscape::NodePath::Path *np);
97 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
98 static gchar *create_typestr(Inkscape::NodePath::Path *np);
100 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
102 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
104 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
106 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
108 /* Adjust handle placement, if the node or the other handle is moved */
109 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
110 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
112 /* Node event callbacks */
113 static void node_clicked(SPKnot *knot, guint state, gpointer data);
114 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
115 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
116 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
118 /* Handle event callbacks */
119 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
120 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
121 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
122 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
123 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
124 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
126 /* Constructors and destructors */
128 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
129 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
130 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
131 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
132 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
133 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
134 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
136 /* Helpers */
138 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
139 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
140 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
142 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
143 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
145 // active_node indicates mouseover node
146 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
148 /**
149 * \brief Creates new nodepath from item
150 */
151 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
152 {
153 Inkscape::XML::Node *repr = object->repr;
155 /** \todo
156 * FIXME: remove this. We don't want to edit paths inside flowtext.
157 * Instead we will build our flowtext with cloned paths, so that the
158 * real paths are outside the flowtext and thus editable as usual.
159 */
160 if (SP_IS_FLOWTEXT(object)) {
161 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
162 if SP_IS_FLOWREGION(child) {
163 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
164 if (grandchild && SP_IS_PATH(grandchild)) {
165 object = SP_ITEM(grandchild);
166 break;
167 }
168 }
169 }
170 }
172 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
174 if (curve == NULL)
175 return NULL;
177 NArtBpath *bpath = sp_curve_first_bpath(curve);
178 gint length = curve->end;
179 if (length == 0) {
180 sp_curve_unref(curve);
181 return NULL; // prevent crash for one-node paths
182 }
184 //Create new nodepath
185 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
186 if (!np) {
187 sp_curve_unref(curve);
188 return NULL;
189 }
191 // Set defaults
192 np->desktop = desktop;
193 np->object = object;
194 np->subpaths = NULL;
195 np->selected = NULL;
196 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
197 np->livarot_path = NULL;
198 np->local_change = 0;
199 np->show_handles = show_handles;
200 np->helper_path = NULL;
201 np->curve = sp_curve_copy(curve);
202 np->show_helperpath = false;
203 np->straight_path = false;
204 if (IS_LIVEPATHEFFECT(object) && item) {
205 np->item = item;
206 } else {
207 np->item = SP_ITEM(object);
208 }
210 // we need to update item's transform from the repr here,
211 // because they may be out of sync when we respond
212 // to a change in repr by regenerating nodepath --bb
213 sp_object_read_attr(SP_OBJECT(np->item), "transform");
215 np->i2d = sp_item_i2d_affine(np->item);
216 np->d2i = np->i2d.inverse();
218 np->repr = repr;
219 if (repr_key_in) { // apparantly the object is an LPEObject
220 np->repr_key = g_strdup(repr_key_in);
221 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
222 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
223 if (lpeparam) {
224 lpeparam->param_setup_notepath(np);
225 }
226 } else {
227 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
228 if ( SP_SHAPE(np->object)->path_effect_href ) {
229 np->repr_key = g_strdup("inkscape:original-d");
231 LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(SP_SHAPE(np->object));
232 if (lpeobj && lpeobj->lpe) {
233 lpeobj->lpe->setup_notepath(np);
234 }
235 } else {
236 np->repr_key = g_strdup("d");
237 }
238 }
240 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
241 gchar *typestr = parse_nodetypes(nodetypes, length);
243 // create the subpath(s) from the bpath
244 NArtBpath *b = bpath;
245 while (b->code != NR_END) {
246 b = subpath_from_bpath(np, b, typestr + (b - bpath));
247 }
249 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
250 np->subpaths = g_list_reverse(np->subpaths);
252 g_free(typestr);
253 sp_curve_unref(curve);
255 // create the livarot representation from the same item
256 sp_nodepath_ensure_livarot_path(np);
258 // Draw helper curve
259 if (np->show_helperpath) {
260 SPCurve *helper_curve = sp_curve_copy(np->curve);
261 sp_curve_transform(helper_curve, np->i2d );
262 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
263 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
264 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
265 sp_canvas_item_show(np->helper_path);
266 sp_curve_unref(helper_curve);
267 }
269 return np;
270 }
272 /**
273 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
274 */
275 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
277 if (!np) //soft fail, like delete
278 return;
280 while (np->subpaths) {
281 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
282 }
284 //Inform the ShapeEditor that made me, if any, that I am gone.
285 if (np->shape_editor)
286 np->shape_editor->nodepath_destroyed();
288 g_assert(!np->selected);
290 if (np->livarot_path) {
291 delete np->livarot_path;
292 np->livarot_path = NULL;
293 }
295 if (np->helper_path) {
296 GtkObject *temp = np->helper_path;
297 np->helper_path = NULL;
298 gtk_object_destroy(temp);
299 }
300 if (np->curve) {
301 sp_curve_unref(np->curve);
302 np->curve = NULL;
303 }
305 if (np->repr_key) {
306 g_free(np->repr_key);
307 np->repr_key = NULL;
308 }
309 if (np->repr_nodetypes_key) {
310 g_free(np->repr_nodetypes_key);
311 np->repr_nodetypes_key = NULL;
312 }
314 np->desktop = NULL;
316 g_free(np);
317 }
320 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
321 {
322 if (np && np->livarot_path == NULL) {
323 SPCurve *curve = create_curve(np);
324 NArtBpath *bpath = SP_CURVE_BPATH(curve);
325 np->livarot_path = bpath_to_Path(bpath);
327 if (np->livarot_path)
328 np->livarot_path->ConvertWithBackData(0.01);
330 sp_curve_unref(curve);
331 }
332 }
335 /**
336 * Return the node count of a given NodeSubPath.
337 */
338 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
339 {
340 if (!subpath)
341 return 0;
342 gint nodeCount = g_list_length(subpath->nodes);
343 return nodeCount;
344 }
346 /**
347 * Return the node count of a given NodePath.
348 */
349 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
350 {
351 if (!np)
352 return 0;
353 gint nodeCount = 0;
354 for (GList *item = np->subpaths ; item ; item=item->next) {
355 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
356 nodeCount += g_list_length(subpath->nodes);
357 }
358 return nodeCount;
359 }
361 /**
362 * Return the subpath count of a given NodePath.
363 */
364 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
365 {
366 if (!np)
367 return 0;
368 return g_list_length (np->subpaths);
369 }
371 /**
372 * Return the selected node count of a given NodePath.
373 */
374 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
375 {
376 if (!np)
377 return 0;
378 return g_list_length (np->selected);
379 }
381 /**
382 * Return the number of subpaths where nodes are selected in a given NodePath.
383 */
384 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
385 {
386 if (!np)
387 return 0;
388 if (!np->selected)
389 return 0;
390 if (!np->selected->next)
391 return 1;
392 gint count = 0;
393 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
394 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
395 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
396 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
397 if (node->selected) {
398 count ++;
399 break;
400 }
401 }
402 }
403 return count;
404 }
406 /**
407 * Clean up a nodepath after editing.
408 *
409 * Currently we are deleting trivial subpaths.
410 */
411 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
412 {
413 GList *badSubPaths = NULL;
415 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
416 for (GList *l = nodepath->subpaths; l ; l=l->next) {
417 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
418 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
419 badSubPaths = g_list_append(badSubPaths, sp);
420 }
422 //Delete them. This second step is because sp_nodepath_subpath_destroy()
423 //also removes the subpath from nodepath->subpaths
424 for (GList *l = badSubPaths; l ; l=l->next) {
425 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
426 sp_nodepath_subpath_destroy(sp);
427 }
429 g_list_free(badSubPaths);
430 }
432 /**
433 * Create new nodepath from b, make it subpath of np.
434 * \param t The node type.
435 * \todo Fixme: t should be a proper type, rather than gchar
436 */
437 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
438 {
439 NR::Point ppos, pos, npos;
441 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
443 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
444 bool const closed = (b->code == NR_MOVETO);
446 pos = NR::Point(b->x3, b->y3) * np->i2d;
447 if (b[1].code == NR_CURVETO) {
448 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
449 } else {
450 npos = pos;
451 }
452 Inkscape::NodePath::Node *n;
453 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
454 g_assert(sp->first == n);
455 g_assert(sp->last == n);
457 b++;
458 t++;
459 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
460 pos = NR::Point(b->x3, b->y3) * np->i2d;
461 if (b->code == NR_CURVETO) {
462 ppos = NR::Point(b->x2, b->y2) * np->i2d;
463 } else {
464 ppos = pos;
465 }
466 if (b[1].code == NR_CURVETO) {
467 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
468 } else {
469 npos = pos;
470 }
471 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
472 b++;
473 t++;
474 }
476 if (closed) sp_nodepath_subpath_close(sp);
478 return b;
479 }
481 /**
482 * Convert from sodipodi:nodetypes to new style type string.
483 */
484 static gchar *parse_nodetypes(gchar const *types, gint length)
485 {
486 g_assert(length > 0);
488 gchar *typestr = g_new(gchar, length + 1);
490 gint pos = 0;
492 if (types) {
493 for (gint i = 0; types[i] && ( i < length ); i++) {
494 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
495 if (types[i] != '\0') {
496 switch (types[i]) {
497 case 's':
498 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
499 break;
500 case 'z':
501 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
502 break;
503 case 'c':
504 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
505 break;
506 default:
507 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
508 break;
509 }
510 }
511 }
512 }
514 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
516 return typestr;
517 }
519 /**
520 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
521 * updated but repr is not (for speed). Used during curve and node drag.
522 */
523 static void update_object(Inkscape::NodePath::Path *np)
524 {
525 g_assert(np);
527 sp_curve_unref(np->curve);
528 np->curve = create_curve(np);
530 sp_nodepath_set_curve(np, np->curve);
532 if (np->show_helperpath) {
533 SPCurve * helper_curve = sp_curve_copy(np->curve);
534 sp_curve_transform(helper_curve, np->i2d );
535 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
536 sp_curve_unref(helper_curve);
537 }
538 }
540 /**
541 * Update XML path node with data from path object.
542 */
543 static void update_repr_internal(Inkscape::NodePath::Path *np)
544 {
545 g_assert(np);
547 Inkscape::XML::Node *repr = np->object->repr;
549 sp_curve_unref(np->curve);
550 np->curve = create_curve(np);
552 gchar *typestr = create_typestr(np);
553 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
555 // determine if path has an effect applied and write to correct "d" attribute.
556 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
557 np->local_change++;
558 repr->setAttribute(np->repr_key, svgpath);
559 }
561 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
562 np->local_change++;
563 repr->setAttribute(np->repr_nodetypes_key, typestr);
564 }
566 g_free(svgpath);
567 g_free(typestr);
569 if (np->show_helperpath) {
570 SPCurve * helper_curve = sp_curve_copy(np->curve);
571 sp_curve_transform(helper_curve, np->i2d );
572 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
573 sp_curve_unref(helper_curve);
574 }
575 }
577 /**
578 * Update XML path node with data from path object, commit changes forever.
579 */
580 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
581 {
582 //fixme: np can be NULL, so check before proceeding
583 g_return_if_fail(np != NULL);
585 if (np->livarot_path) {
586 delete np->livarot_path;
587 np->livarot_path = NULL;
588 }
590 update_repr_internal(np);
591 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
593 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
594 annotation);
595 }
597 /**
598 * Update XML path node with data from path object, commit changes with undo.
599 */
600 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
601 {
602 if (np->livarot_path) {
603 delete np->livarot_path;
604 np->livarot_path = NULL;
605 }
607 update_repr_internal(np);
608 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
609 annotation);
610 }
612 /**
613 * Make duplicate of path, replace corresponding XML node in tree, commit.
614 */
615 static void stamp_repr(Inkscape::NodePath::Path *np)
616 {
617 g_assert(np);
619 Inkscape::XML::Node *old_repr = np->object->repr;
620 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
622 // remember the position of the item
623 gint pos = old_repr->position();
624 // remember parent
625 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
627 SPCurve *curve = create_curve(np);
628 gchar *typestr = create_typestr(np);
630 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
632 new_repr->setAttribute(np->repr_key, svgpath);
633 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
635 // add the new repr to the parent
636 parent->appendChild(new_repr);
637 // move to the saved position
638 new_repr->setPosition(pos > 0 ? pos : 0);
640 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
641 _("Stamp"));
643 Inkscape::GC::release(new_repr);
644 g_free(svgpath);
645 g_free(typestr);
646 sp_curve_unref(curve);
647 }
649 /**
650 * Create curve from path.
651 */
652 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
653 {
654 SPCurve *curve = sp_curve_new();
656 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
657 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
658 sp_curve_moveto(curve,
659 sp->first->pos * np->d2i);
660 Inkscape::NodePath::Node *n = sp->first->n.other;
661 while (n) {
662 NR::Point const end_pt = n->pos * np->d2i;
663 switch (n->code) {
664 case NR_LINETO:
665 sp_curve_lineto(curve, end_pt);
666 break;
667 case NR_CURVETO:
668 sp_curve_curveto(curve,
669 n->p.other->n.pos * np->d2i,
670 n->p.pos * np->d2i,
671 end_pt);
672 break;
673 default:
674 g_assert_not_reached();
675 break;
676 }
677 if (n != sp->last) {
678 n = n->n.other;
679 } else {
680 n = NULL;
681 }
682 }
683 if (sp->closed) {
684 sp_curve_closepath(curve);
685 }
686 }
688 return curve;
689 }
691 /**
692 * Convert path type string to sodipodi:nodetypes style.
693 */
694 static gchar *create_typestr(Inkscape::NodePath::Path *np)
695 {
696 gchar *typestr = g_new(gchar, 32);
697 gint len = 32;
698 gint pos = 0;
700 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
701 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
703 if (pos >= len) {
704 typestr = g_renew(gchar, typestr, len + 32);
705 len += 32;
706 }
708 typestr[pos++] = 'c';
710 Inkscape::NodePath::Node *n;
711 n = sp->first->n.other;
712 while (n) {
713 gchar code;
715 switch (n->type) {
716 case Inkscape::NodePath::NODE_CUSP:
717 code = 'c';
718 break;
719 case Inkscape::NodePath::NODE_SMOOTH:
720 code = 's';
721 break;
722 case Inkscape::NodePath::NODE_SYMM:
723 code = 'z';
724 break;
725 default:
726 g_assert_not_reached();
727 code = '\0';
728 break;
729 }
731 if (pos >= len) {
732 typestr = g_renew(gchar, typestr, len + 32);
733 len += 32;
734 }
736 typestr[pos++] = code;
738 if (n != sp->last) {
739 n = n->n.other;
740 } else {
741 n = NULL;
742 }
743 }
744 }
746 if (pos >= len) {
747 typestr = g_renew(gchar, typestr, len + 1);
748 len += 1;
749 }
751 typestr[pos++] = '\0';
753 return typestr;
754 }
756 /**
757 * Returns current path in context. // later eliminate this function at all!
758 */
759 static Inkscape::NodePath::Path *sp_nodepath_current()
760 {
761 if (!SP_ACTIVE_DESKTOP) {
762 return NULL;
763 }
765 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
767 if (!SP_IS_NODE_CONTEXT(event_context)) {
768 return NULL;
769 }
771 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
772 }
776 /**
777 \brief Fills node and handle positions for three nodes, splitting line
778 marked by end at distance t.
779 */
780 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
781 {
782 g_assert(new_path != NULL);
783 g_assert(end != NULL);
785 g_assert(end->p.other == new_path);
786 Inkscape::NodePath::Node *start = new_path->p.other;
787 g_assert(start);
789 if (end->code == NR_LINETO) {
790 new_path->type =Inkscape::NodePath::NODE_CUSP;
791 new_path->code = NR_LINETO;
792 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
793 } else {
794 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
795 new_path->code = NR_CURVETO;
796 gdouble s = 1 - t;
797 for (int dim = 0; dim < 2; dim++) {
798 NR::Coord const f000 = start->pos[dim];
799 NR::Coord const f001 = start->n.pos[dim];
800 NR::Coord const f011 = end->p.pos[dim];
801 NR::Coord const f111 = end->pos[dim];
802 NR::Coord const f00t = s * f000 + t * f001;
803 NR::Coord const f01t = s * f001 + t * f011;
804 NR::Coord const f11t = s * f011 + t * f111;
805 NR::Coord const f0tt = s * f00t + t * f01t;
806 NR::Coord const f1tt = s * f01t + t * f11t;
807 NR::Coord const fttt = s * f0tt + t * f1tt;
808 start->n.pos[dim] = f00t;
809 new_path->p.pos[dim] = f0tt;
810 new_path->pos[dim] = fttt;
811 new_path->n.pos[dim] = f1tt;
812 end->p.pos[dim] = f11t;
813 }
814 }
815 }
817 /**
818 * Adds new node on direct line between two nodes, activates handles of all
819 * three nodes.
820 */
821 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
822 {
823 g_assert(end);
824 g_assert(end->subpath);
825 g_assert(g_list_find(end->subpath->nodes, end));
827 Inkscape::NodePath::Node *start = end->p.other;
828 g_assert( start->n.other == end );
829 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
830 end,
831 (NRPathcode)end->code == NR_LINETO?
832 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
833 (NRPathcode)end->code,
834 &start->pos, &start->pos, &start->n.pos);
835 sp_nodepath_line_midpoint(newnode, end, t);
837 sp_node_adjust_handles(start);
838 sp_node_update_handles(start);
839 sp_node_update_handles(newnode);
840 sp_node_adjust_handles(end);
841 sp_node_update_handles(end);
843 return newnode;
844 }
846 /**
847 \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
848 */
849 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
850 {
851 g_assert(node);
852 g_assert(node->subpath);
853 g_assert(g_list_find(node->subpath->nodes, node));
855 Inkscape::NodePath::SubPath *sp = node->subpath;
856 Inkscape::NodePath::Path *np = sp->nodepath;
858 if (sp->closed) {
859 sp_nodepath_subpath_open(sp, node);
860 return sp->first;
861 } else {
862 // no break for end nodes
863 if (node == sp->first) return NULL;
864 if (node == sp->last ) return NULL;
866 // create a new subpath
867 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
869 // duplicate the break node as start of the new subpath
870 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
872 while (node->n.other) { // copy the remaining nodes into the new subpath
873 Inkscape::NodePath::Node *n = node->n.other;
874 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);
875 if (n->selected) {
876 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
877 }
878 sp_nodepath_node_destroy(n); // remove the point on the original subpath
879 }
881 return newnode;
882 }
883 }
885 /**
886 * Duplicate node and connect to neighbours.
887 */
888 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
889 {
890 g_assert(node);
891 g_assert(node->subpath);
892 g_assert(g_list_find(node->subpath->nodes, node));
894 Inkscape::NodePath::SubPath *sp = node->subpath;
896 NRPathcode code = (NRPathcode) node->code;
897 if (code == NR_MOVETO) { // if node is the endnode,
898 node->code = NR_LINETO; // new one is inserted before it, so change that to line
899 }
901 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
903 if (!node->n.other || !node->p.other) // if node is an endnode, select it
904 return node;
905 else
906 return newnode; // otherwise select the newly created node
907 }
909 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
910 {
911 node->p.pos = (node->pos + (node->pos - node->n.pos));
912 }
914 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
915 {
916 node->n.pos = (node->pos + (node->pos - node->p.pos));
917 }
919 /**
920 * Change line type at node, with side effects on neighbours.
921 */
922 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
923 {
924 g_assert(end);
925 g_assert(end->subpath);
926 g_assert(end->p.other);
928 if (end->code == static_cast< guint > ( code ) )
929 return;
931 Inkscape::NodePath::Node *start = end->p.other;
933 end->code = code;
935 if (code == NR_LINETO) {
936 if (start->code == NR_LINETO) {
937 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
938 }
939 if (end->n.other) {
940 if (end->n.other->code == NR_LINETO) {
941 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
942 }
943 }
944 } else {
945 NR::Point delta = end->pos - start->pos;
946 start->n.pos = start->pos + delta / 3;
947 end->p.pos = end->pos - delta / 3;
948 sp_node_adjust_handle(start, 1);
949 sp_node_adjust_handle(end, -1);
950 }
952 sp_node_update_handles(start);
953 sp_node_update_handles(end);
954 }
956 /**
957 * Change node type, and its handles accordingly.
958 */
959 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
960 {
961 g_assert(node);
962 g_assert(node->subpath);
964 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
965 return node;
967 if ((node->p.other != NULL) && (node->n.other != NULL)) {
968 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
969 type =Inkscape::NodePath::NODE_CUSP;
970 }
971 }
973 node->type = type;
975 if (node->type == Inkscape::NodePath::NODE_CUSP) {
976 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
977 node->knot->setSize (node->selected? 11 : 9);
978 sp_knot_update_ctrl(node->knot);
979 } else {
980 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
981 node->knot->setSize (node->selected? 9 : 7);
982 sp_knot_update_ctrl(node->knot);
983 }
985 // if one of handles is mouseovered, preserve its position
986 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
987 sp_node_adjust_handle(node, 1);
988 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
989 sp_node_adjust_handle(node, -1);
990 } else {
991 sp_node_adjust_handles(node);
992 }
994 sp_node_update_handles(node);
996 sp_nodepath_update_statusbar(node->subpath->nodepath);
998 return node;
999 }
1001 /**
1002 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
1003 * adjacent segments from lines to curves.
1004 */
1005 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1006 {
1007 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1008 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1010 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1011 if (p_line && n_line) {
1012 // only if both adjacent segments are lines,
1013 // convert both to curves:
1015 // BEFORE:
1016 {
1017 node->code = NR_CURVETO;
1018 NR::Point delta = node->n.other->pos - node->p.other->pos;
1019 node->p.pos = node->pos - delta / 4;
1020 }
1022 // AFTER:
1023 {
1024 node->n.other->code = NR_CURVETO;
1025 NR::Point delta = node->p.other->pos - node->n.other->pos;
1026 node->n.pos = node->pos - delta / 4;
1027 }
1029 sp_node_update_handles(node);
1030 }
1031 }
1033 sp_nodepath_set_node_type (node, type);
1034 }
1036 /**
1037 * Move node to point, and adjust its and neighbouring handles.
1038 */
1039 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1040 {
1041 NR::Point delta = p - node->pos;
1042 node->pos = p;
1044 node->p.pos += delta;
1045 node->n.pos += delta;
1047 Inkscape::NodePath::Node *node_p = NULL;
1048 Inkscape::NodePath::Node *node_n = NULL;
1050 if (node->p.other) {
1051 if (node->code == NR_LINETO) {
1052 sp_node_adjust_handle(node, 1);
1053 sp_node_adjust_handle(node->p.other, -1);
1054 node_p = node->p.other;
1055 }
1056 }
1057 if (node->n.other) {
1058 if (node->n.other->code == NR_LINETO) {
1059 sp_node_adjust_handle(node, -1);
1060 sp_node_adjust_handle(node->n.other, 1);
1061 node_n = node->n.other;
1062 }
1063 }
1065 // this function is only called from batch movers that will update display at the end
1066 // themselves, so here we just move all the knots without emitting move signals, for speed
1067 sp_node_update_handles(node, false);
1068 if (node_n) {
1069 sp_node_update_handles(node_n, false);
1070 }
1071 if (node_p) {
1072 sp_node_update_handles(node_p, false);
1073 }
1074 }
1076 /**
1077 * Call sp_node_moveto() for node selection and handle possible snapping.
1078 */
1079 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1080 bool const snap = true)
1081 {
1082 NR::Coord best = NR_HUGE;
1083 NR::Point delta(dx, dy);
1084 NR::Point best_pt = delta;
1086 if (snap) {
1087 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1089 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1090 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1091 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item));
1092 if (s.getDistance() < best) {
1093 best = s.getDistance();
1094 best_pt = s.getPoint() - n->pos;
1095 }
1096 }
1097 }
1099 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1100 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1101 sp_node_moveto(n, n->pos + best_pt);
1102 }
1104 // do not update repr here so that node dragging is acceptably fast
1105 update_object(nodepath);
1106 }
1108 /**
1109 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1110 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1111 near x = 0.
1112 */
1113 double
1114 sculpt_profile (double x, double alpha, guint profile)
1115 {
1116 if (x >= 1)
1117 return 0;
1118 if (x <= 0)
1119 return 1;
1121 switch (profile) {
1122 case SCULPT_PROFILE_LINEAR:
1123 return 1 - x;
1124 case SCULPT_PROFILE_BELL:
1125 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1126 case SCULPT_PROFILE_ELLIPTIC:
1127 return sqrt(1 - x*x);
1128 }
1130 return 1;
1131 }
1133 double
1134 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1135 {
1136 // extremely primitive for now, don't have time to look for the real one
1137 double lower = NR::L2(b - a);
1138 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1139 return (lower + upper)/2;
1140 }
1142 void
1143 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1144 {
1145 n->pos = n->origin + delta;
1146 n->n.pos = n->n.origin + delta_n;
1147 n->p.pos = n->p.origin + delta_p;
1148 sp_node_adjust_handles(n);
1149 sp_node_update_handles(n, false);
1150 }
1152 /**
1153 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1154 * on how far they are from the dragged node n.
1155 */
1156 static void
1157 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1158 {
1159 g_assert (n);
1160 g_assert (nodepath);
1161 g_assert (n->subpath->nodepath == nodepath);
1163 double pressure = n->knot->pressure;
1164 if (pressure == 0)
1165 pressure = 0.5; // default
1166 pressure = CLAMP (pressure, 0.2, 0.8);
1168 // map pressure to alpha = 1/5 ... 5
1169 double alpha = 1 - 2 * fabs(pressure - 0.5);
1170 if (pressure > 0.5)
1171 alpha = 1/alpha;
1173 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1175 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1176 // Only one subpath has selected nodes:
1177 // use linear mode, where the distance from n to node being dragged is calculated along the path
1179 double n_sel_range = 0, p_sel_range = 0;
1180 guint n_nodes = 0, p_nodes = 0;
1181 guint n_sel_nodes = 0, p_sel_nodes = 0;
1183 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1184 {
1185 double n_range = 0, p_range = 0;
1186 bool n_going = true, p_going = true;
1187 Inkscape::NodePath::Node *n_node = n;
1188 Inkscape::NodePath::Node *p_node = n;
1189 do {
1190 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1191 if (n_node && n_going)
1192 n_node = n_node->n.other;
1193 if (n_node == NULL) {
1194 n_going = false;
1195 } else {
1196 n_nodes ++;
1197 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1198 if (n_node->selected) {
1199 n_sel_nodes ++;
1200 n_sel_range = n_range;
1201 }
1202 if (n_node == p_node) {
1203 n_going = false;
1204 p_going = false;
1205 }
1206 }
1207 if (p_node && p_going)
1208 p_node = p_node->p.other;
1209 if (p_node == NULL) {
1210 p_going = false;
1211 } else {
1212 p_nodes ++;
1213 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1214 if (p_node->selected) {
1215 p_sel_nodes ++;
1216 p_sel_range = p_range;
1217 }
1218 if (p_node == n_node) {
1219 n_going = false;
1220 p_going = false;
1221 }
1222 }
1223 } while (n_going || p_going);
1224 }
1226 // Second pass: actually move nodes in this subpath
1227 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1228 {
1229 double n_range = 0, p_range = 0;
1230 bool n_going = true, p_going = true;
1231 Inkscape::NodePath::Node *n_node = n;
1232 Inkscape::NodePath::Node *p_node = n;
1233 do {
1234 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1235 if (n_node && n_going)
1236 n_node = n_node->n.other;
1237 if (n_node == NULL) {
1238 n_going = false;
1239 } else {
1240 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1241 if (n_node->selected) {
1242 sp_nodepath_move_node_and_handles (n_node,
1243 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1244 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1245 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1246 }
1247 if (n_node == p_node) {
1248 n_going = false;
1249 p_going = false;
1250 }
1251 }
1252 if (p_node && p_going)
1253 p_node = p_node->p.other;
1254 if (p_node == NULL) {
1255 p_going = false;
1256 } else {
1257 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1258 if (p_node->selected) {
1259 sp_nodepath_move_node_and_handles (p_node,
1260 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1261 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1262 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1263 }
1264 if (p_node == n_node) {
1265 n_going = false;
1266 p_going = false;
1267 }
1268 }
1269 } while (n_going || p_going);
1270 }
1272 } else {
1273 // Multiple subpaths have selected nodes:
1274 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1275 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1276 // fix the pear-like shape when sculpting e.g. a ring
1278 // First pass: calculate range
1279 gdouble direct_range = 0;
1280 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1281 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1282 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1283 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1284 if (node->selected) {
1285 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1286 }
1287 }
1288 }
1290 // Second pass: actually move nodes
1291 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1292 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1293 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1294 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1295 if (node->selected) {
1296 if (direct_range > 1e-6) {
1297 sp_nodepath_move_node_and_handles (node,
1298 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1299 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1300 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1301 } else {
1302 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1303 }
1305 }
1306 }
1307 }
1308 }
1310 // do not update repr here so that node dragging is acceptably fast
1311 update_object(nodepath);
1312 }
1315 /**
1316 * Move node selection to point, adjust its and neighbouring handles,
1317 * handle possible snapping, and commit the change with possible undo.
1318 */
1319 void
1320 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1321 {
1322 if (!nodepath) return;
1324 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1326 if (dx == 0) {
1327 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1328 } else if (dy == 0) {
1329 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1330 } else {
1331 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1332 }
1333 }
1335 /**
1336 * Move node selection off screen and commit the change.
1337 */
1338 void
1339 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1340 {
1341 // borrowed from sp_selection_move_screen in selection-chemistry.c
1342 // we find out the current zoom factor and divide deltas by it
1343 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1345 gdouble zoom = desktop->current_zoom();
1346 gdouble zdx = dx / zoom;
1347 gdouble zdy = dy / zoom;
1349 if (!nodepath) return;
1351 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1353 if (dx == 0) {
1354 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1355 } else if (dy == 0) {
1356 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1357 } else {
1358 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1359 }
1360 }
1362 /** If they don't yet exist, creates knot and line for the given side of the node */
1363 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1364 {
1365 if (!side->knot) {
1366 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"));
1368 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1369 side->knot->setSize (7);
1370 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1371 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1372 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1373 sp_knot_update_ctrl(side->knot);
1375 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1376 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1377 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1378 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1379 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1380 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1381 }
1383 if (!side->line) {
1384 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1385 SP_TYPE_CTRLLINE, NULL);
1386 }
1387 }
1389 /**
1390 * Ensure the given handle of the node is visible/invisible, update its screen position
1391 */
1392 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1393 {
1394 g_assert(node != NULL);
1396 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1397 NRPathcode code = sp_node_path_code_from_side(node, side);
1399 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1401 if (show_handle) {
1402 if (!side->knot) { // No handle knot at all
1403 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1404 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1405 side->knot->pos = side->pos;
1406 if (side->knot->item)
1407 SP_CTRL(side->knot->item)->moveto(side->pos);
1408 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1409 sp_knot_show(side->knot);
1410 } else {
1411 if (side->knot->pos != side->pos) { // only if it's really moved
1412 if (fire_move_signals) {
1413 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1414 } else {
1415 sp_knot_moveto(side->knot, &side->pos);
1416 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1417 }
1418 }
1419 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1420 sp_knot_show(side->knot);
1421 }
1422 }
1423 sp_canvas_item_show(side->line);
1424 } else {
1425 if (side->knot) {
1426 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1427 sp_knot_hide(side->knot);
1428 }
1429 }
1430 if (side->line) {
1431 sp_canvas_item_hide(side->line);
1432 }
1433 }
1434 }
1436 /**
1437 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1438 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1439 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1440 * updated; otherwise, just move the knots silently (used in batch moves).
1441 */
1442 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1443 {
1444 g_assert(node != NULL);
1446 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1447 sp_knot_show(node->knot);
1448 }
1450 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1451 if (fire_move_signals)
1452 sp_knot_set_position(node->knot, &node->pos, 0);
1453 else
1454 sp_knot_moveto(node->knot, &node->pos);
1455 }
1457 gboolean show_handles = node->selected;
1458 if (node->p.other != NULL) {
1459 if (node->p.other->selected) show_handles = TRUE;
1460 }
1461 if (node->n.other != NULL) {
1462 if (node->n.other->selected) show_handles = TRUE;
1463 }
1465 if (node->subpath->nodepath->show_handles == false)
1466 show_handles = FALSE;
1468 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1469 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1470 }
1472 /**
1473 * Call sp_node_update_handles() for all nodes on subpath.
1474 */
1475 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1476 {
1477 g_assert(subpath != NULL);
1479 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1480 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1481 }
1482 }
1484 /**
1485 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1486 */
1487 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1488 {
1489 g_assert(nodepath != NULL);
1491 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1492 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1493 }
1494 }
1496 void
1497 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1498 {
1499 if (nodepath == NULL) return;
1501 nodepath->show_handles = show;
1502 sp_nodepath_update_handles(nodepath);
1503 }
1505 /**
1506 * Adds all selected nodes in nodepath to list.
1507 */
1508 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1509 {
1510 StlConv<Node *>::list(l, selected);
1511 /// \todo this adds a copying, rework when the selection becomes a stl list
1512 }
1514 /**
1515 * Align selected nodes on the specified axis.
1516 */
1517 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1518 {
1519 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1520 return;
1521 }
1523 if ( !nodepath->selected->next ) { // only one node selected
1524 return;
1525 }
1526 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1527 NR::Point dest(pNode->pos);
1528 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1529 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1530 if (pNode) {
1531 dest[axis] = pNode->pos[axis];
1532 sp_node_moveto(pNode, dest);
1533 }
1534 }
1536 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1537 }
1539 /// Helper struct.
1540 struct NodeSort
1541 {
1542 Inkscape::NodePath::Node *_node;
1543 NR::Coord _coord;
1544 /// \todo use vectorof pointers instead of calling copy ctor
1545 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1546 _node(node), _coord(node->pos[axis])
1547 {}
1549 };
1551 static bool operator<(NodeSort const &a, NodeSort const &b)
1552 {
1553 return (a._coord < b._coord);
1554 }
1556 /**
1557 * Distribute selected nodes on the specified axis.
1558 */
1559 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1560 {
1561 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1562 return;
1563 }
1565 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1566 return;
1567 }
1569 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1570 std::vector<NodeSort> sorted;
1571 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1572 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1573 if (pNode) {
1574 NodeSort n(pNode, axis);
1575 sorted.push_back(n);
1576 //dest[axis] = pNode->pos[axis];
1577 //sp_node_moveto(pNode, dest);
1578 }
1579 }
1580 std::sort(sorted.begin(), sorted.end());
1581 unsigned int len = sorted.size();
1582 //overall bboxes span
1583 float dist = (sorted.back()._coord -
1584 sorted.front()._coord);
1585 //new distance between each bbox
1586 float step = (dist) / (len - 1);
1587 float pos = sorted.front()._coord;
1588 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1589 it < sorted.end();
1590 it ++ )
1591 {
1592 NR::Point dest((*it)._node->pos);
1593 dest[axis] = pos;
1594 sp_node_moveto((*it)._node, dest);
1595 pos += step;
1596 }
1598 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1599 }
1602 /**
1603 * Call sp_nodepath_line_add_node() for all selected segments.
1604 */
1605 void
1606 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1607 {
1608 if (!nodepath) {
1609 return;
1610 }
1612 GList *nl = NULL;
1614 int n_added = 0;
1616 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1617 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1618 g_assert(t->selected);
1619 if (t->p.other && t->p.other->selected) {
1620 nl = g_list_prepend(nl, t);
1621 }
1622 }
1624 while (nl) {
1625 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1626 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1627 sp_nodepath_node_select(n, TRUE, FALSE);
1628 n_added ++;
1629 nl = g_list_remove(nl, t);
1630 }
1632 /** \todo fixme: adjust ? */
1633 sp_nodepath_update_handles(nodepath);
1635 if (n_added > 1) {
1636 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1637 } else if (n_added > 0) {
1638 sp_nodepath_update_repr(nodepath, _("Add node"));
1639 }
1641 sp_nodepath_update_statusbar(nodepath);
1642 }
1644 /**
1645 * Select segment nearest to point
1646 */
1647 void
1648 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1649 {
1650 if (!nodepath) {
1651 return;
1652 }
1654 sp_nodepath_ensure_livarot_path(nodepath);
1655 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1656 if (!maybe_position) {
1657 return;
1658 }
1659 Path::cut_position position = *maybe_position;
1661 //find segment to segment
1662 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1664 //fixme: this can return NULL, so check before proceeding.
1665 g_return_if_fail(e != NULL);
1667 gboolean force = FALSE;
1668 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1669 force = TRUE;
1670 }
1671 sp_nodepath_node_select(e, (gboolean) toggle, force);
1672 if (e->p.other)
1673 sp_nodepath_node_select(e->p.other, TRUE, force);
1675 sp_nodepath_update_handles(nodepath);
1677 sp_nodepath_update_statusbar(nodepath);
1678 }
1680 /**
1681 * Add a node nearest to point
1682 */
1683 void
1684 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1685 {
1686 if (!nodepath) {
1687 return;
1688 }
1690 sp_nodepath_ensure_livarot_path(nodepath);
1691 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1692 if (!maybe_position) {
1693 return;
1694 }
1695 Path::cut_position position = *maybe_position;
1697 //find segment to split
1698 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1700 //don't know why but t seems to flip for lines
1701 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1702 position.t = 1.0 - position.t;
1703 }
1704 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1705 sp_nodepath_node_select(n, FALSE, TRUE);
1707 /* fixme: adjust ? */
1708 sp_nodepath_update_handles(nodepath);
1710 sp_nodepath_update_repr(nodepath, _("Add node"));
1712 sp_nodepath_update_statusbar(nodepath);
1713 }
1715 /*
1716 * Adjusts a segment so that t moves by a certain delta for dragging
1717 * converts lines to curves
1718 *
1719 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1720 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1721 */
1722 void
1723 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1724 {
1725 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1727 //fixme: e and e->p can be NULL, so check for those before proceeding
1728 g_return_if_fail(e != NULL);
1729 g_return_if_fail(&e->p != NULL);
1731 /* feel good is an arbitrary parameter that distributes the delta between handles
1732 * if t of the drag point is less than 1/6 distance form the endpoint only
1733 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1734 */
1735 double feel_good;
1736 if (t <= 1.0 / 6.0)
1737 feel_good = 0;
1738 else if (t <= 0.5)
1739 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1740 else if (t <= 5.0 / 6.0)
1741 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1742 else
1743 feel_good = 1;
1745 //if we're dragging a line convert it to a curve
1746 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1747 sp_nodepath_set_line_type(e, NR_CURVETO);
1748 }
1750 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1751 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1752 e->p.other->n.pos += offsetcoord0;
1753 e->p.pos += offsetcoord1;
1755 // adjust handles of adjacent nodes where necessary
1756 sp_node_adjust_handle(e,1);
1757 sp_node_adjust_handle(e->p.other,-1);
1759 sp_nodepath_update_handles(e->subpath->nodepath);
1761 update_object(e->subpath->nodepath);
1763 sp_nodepath_update_statusbar(e->subpath->nodepath);
1764 }
1767 /**
1768 * Call sp_nodepath_break() for all selected segments.
1769 */
1770 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1771 {
1772 if (!nodepath) return;
1774 GList *temp = NULL;
1775 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1777 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1778 if (nn == NULL) continue; // no break, no new node
1779 temp = g_list_prepend(temp, nn);
1780 }
1782 if (temp) {
1783 sp_nodepath_deselect(nodepath);
1784 }
1785 for (GList *l = temp; l != NULL; l = l->next) {
1786 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1787 }
1789 sp_nodepath_update_handles(nodepath);
1791 sp_nodepath_update_repr(nodepath, _("Break path"));
1792 }
1794 /**
1795 * Duplicate the selected node(s).
1796 */
1797 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1798 {
1799 if (!nodepath) {
1800 return;
1801 }
1803 GList *temp = NULL;
1804 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1805 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1806 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1807 if (nn == NULL) continue; // could not duplicate
1808 temp = g_list_prepend(temp, nn);
1809 }
1811 if (temp) {
1812 sp_nodepath_deselect(nodepath);
1813 }
1814 for (GList *l = temp; l != NULL; l = l->next) {
1815 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1816 }
1818 sp_nodepath_update_handles(nodepath);
1820 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1821 }
1823 /**
1824 * Join two nodes by merging them into one.
1825 */
1826 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1827 {
1828 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1830 if (g_list_length(nodepath->selected) != 2) {
1831 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1832 return;
1833 }
1835 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1836 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1838 g_assert(a != b);
1839 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1840 // someone tried to join an orphan node (i.e. a single-node subpath).
1841 // this is not worth an error message, just fail silently.
1842 return;
1843 }
1845 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1846 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1847 return;
1848 }
1850 /* a and b are endpoints */
1852 NR::Point c;
1853 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1854 c = a->pos;
1855 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1856 c = b->pos;
1857 } else {
1858 c = (a->pos + b->pos) / 2;
1859 }
1861 if (a->subpath == b->subpath) {
1862 Inkscape::NodePath::SubPath *sp = a->subpath;
1863 sp_nodepath_subpath_close(sp);
1864 sp_node_moveto (sp->first, c);
1866 sp_nodepath_update_handles(sp->nodepath);
1867 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1868 return;
1869 }
1871 /* a and b are separate subpaths */
1872 Inkscape::NodePath::SubPath *sa = a->subpath;
1873 Inkscape::NodePath::SubPath *sb = b->subpath;
1874 NR::Point p;
1875 Inkscape::NodePath::Node *n;
1876 NRPathcode code;
1877 if (a == sa->first) {
1878 p = sa->first->n.pos;
1879 code = (NRPathcode)sa->first->n.other->code;
1880 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1881 n = sa->last;
1882 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1883 n = n->p.other;
1884 while (n) {
1885 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1886 n = n->p.other;
1887 if (n == sa->first) n = NULL;
1888 }
1889 sp_nodepath_subpath_destroy(sa);
1890 sa = t;
1891 } else if (a == sa->last) {
1892 p = sa->last->p.pos;
1893 code = (NRPathcode)sa->last->code;
1894 sp_nodepath_node_destroy(sa->last);
1895 } else {
1896 code = NR_END;
1897 g_assert_not_reached();
1898 }
1900 if (b == sb->first) {
1901 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1902 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1903 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1904 }
1905 } else if (b == sb->last) {
1906 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1907 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1908 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1909 }
1910 } else {
1911 g_assert_not_reached();
1912 }
1913 /* and now destroy sb */
1915 sp_nodepath_subpath_destroy(sb);
1917 sp_nodepath_update_handles(sa->nodepath);
1919 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1921 sp_nodepath_update_statusbar(nodepath);
1922 }
1924 /**
1925 * Join two nodes by adding a segment between them.
1926 */
1927 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1928 {
1929 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1931 if (g_list_length(nodepath->selected) != 2) {
1932 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1933 return;
1934 }
1936 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1937 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1939 g_assert(a != b);
1940 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1941 // someone tried to join an orphan node (i.e. a single-node subpath).
1942 // this is not worth an error message, just fail silently.
1943 return;
1944 }
1946 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1947 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1948 return;
1949 }
1951 if (a->subpath == b->subpath) {
1952 Inkscape::NodePath::SubPath *sp = a->subpath;
1954 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1955 sp->closed = TRUE;
1957 sp->first->p.other = sp->last;
1958 sp->last->n.other = sp->first;
1960 sp_node_handle_mirror_p_to_n(sp->last);
1961 sp_node_handle_mirror_n_to_p(sp->first);
1963 sp->first->code = sp->last->code;
1964 sp->first = sp->last;
1966 sp_nodepath_update_handles(sp->nodepath);
1968 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1970 return;
1971 }
1973 /* a and b are separate subpaths */
1974 Inkscape::NodePath::SubPath *sa = a->subpath;
1975 Inkscape::NodePath::SubPath *sb = b->subpath;
1977 Inkscape::NodePath::Node *n;
1978 NR::Point p;
1979 NRPathcode code;
1980 if (a == sa->first) {
1981 code = (NRPathcode) sa->first->n.other->code;
1982 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1983 n = sa->last;
1984 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1985 for (n = n->p.other; n != NULL; n = n->p.other) {
1986 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1987 }
1988 sp_nodepath_subpath_destroy(sa);
1989 sa = t;
1990 } else if (a == sa->last) {
1991 code = (NRPathcode)sa->last->code;
1992 } else {
1993 code = NR_END;
1994 g_assert_not_reached();
1995 }
1997 if (b == sb->first) {
1998 n = sb->first;
1999 sp_node_handle_mirror_p_to_n(sa->last);
2000 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2001 sp_node_handle_mirror_n_to_p(sa->last);
2002 for (n = n->n.other; n != NULL; n = n->n.other) {
2003 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2004 }
2005 } else if (b == sb->last) {
2006 n = sb->last;
2007 sp_node_handle_mirror_p_to_n(sa->last);
2008 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2009 sp_node_handle_mirror_n_to_p(sa->last);
2010 for (n = n->p.other; n != NULL; n = n->p.other) {
2011 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2012 }
2013 } else {
2014 g_assert_not_reached();
2015 }
2016 /* and now destroy sb */
2018 sp_nodepath_subpath_destroy(sb);
2020 sp_nodepath_update_handles(sa->nodepath);
2022 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2023 }
2025 /**
2026 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2027 */
2028 void sp_node_delete_preserve(GList *nodes_to_delete)
2029 {
2030 GSList *nodepaths = NULL;
2032 while (nodes_to_delete) {
2033 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2034 Inkscape::NodePath::SubPath *sp = node->subpath;
2035 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2036 Inkscape::NodePath::Node *sample_cursor = NULL;
2037 Inkscape::NodePath::Node *sample_end = NULL;
2038 Inkscape::NodePath::Node *delete_cursor = node;
2039 bool just_delete = false;
2041 //find the start of this contiguous selection
2042 //move left to the first node that is not selected
2043 //or the start of the non-closed path
2044 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2045 delete_cursor = curr;
2046 }
2048 //just delete at the beginning of an open path
2049 if (!delete_cursor->p.other) {
2050 sample_cursor = delete_cursor;
2051 just_delete = true;
2052 } else {
2053 sample_cursor = delete_cursor->p.other;
2054 }
2056 //calculate points for each segment
2057 int rate = 5;
2058 float period = 1.0 / rate;
2059 std::vector<NR::Point> data;
2060 if (!just_delete) {
2061 data.push_back(sample_cursor->pos);
2062 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2063 //just delete at the end of an open path
2064 if (!sp->closed && curr == sp->last) {
2065 just_delete = true;
2066 break;
2067 }
2069 //sample points on the contiguous selected segment
2070 NR::Point *bez;
2071 bez = new NR::Point [4];
2072 bez[0] = curr->pos;
2073 bez[1] = curr->n.pos;
2074 bez[2] = curr->n.other->p.pos;
2075 bez[3] = curr->n.other->pos;
2076 for (int i=1; i<rate; i++) {
2077 gdouble t = i * period;
2078 NR::Point p = bezier_pt(3, bez, t);
2079 data.push_back(p);
2080 }
2081 data.push_back(curr->n.other->pos);
2083 sample_end = curr->n.other;
2084 //break if we've come full circle or hit the end of the selection
2085 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2086 break;
2087 }
2088 }
2089 }
2091 if (!just_delete) {
2092 //calculate the best fitting single segment and adjust the endpoints
2093 NR::Point *adata;
2094 adata = new NR::Point [data.size()];
2095 copy(data.begin(), data.end(), adata);
2097 NR::Point *bez;
2098 bez = new NR::Point [4];
2099 //would decreasing error create a better fitting approximation?
2100 gdouble error = 1.0;
2101 gint ret;
2102 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2104 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2105 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2106 //the resulting nodes behave as expected.
2107 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2108 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2110 //adjust endpoints
2111 sample_cursor->n.pos = bez[1];
2112 sample_end->p.pos = bez[2];
2113 }
2115 //destroy this contiguous selection
2116 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2117 Inkscape::NodePath::Node *temp = delete_cursor;
2118 if (delete_cursor->n.other == delete_cursor) {
2119 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2120 delete_cursor = NULL;
2121 } else {
2122 delete_cursor = delete_cursor->n.other;
2123 }
2124 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2125 sp_nodepath_node_destroy(temp);
2126 }
2128 sp_nodepath_update_handles(nodepath);
2130 if (!g_slist_find(nodepaths, nodepath))
2131 nodepaths = g_slist_prepend (nodepaths, nodepath);
2132 }
2134 for (GSList *i = nodepaths; i; i = i->next) {
2135 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2136 // different nodepaths will give us one undo event per nodepath
2137 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2139 // if the entire nodepath is removed, delete the selected object.
2140 if (nodepath->subpaths == NULL ||
2141 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2142 //at least 2
2143 sp_nodepath_get_node_count(nodepath) < 2) {
2144 SPDocument *document = sp_desktop_document (nodepath->desktop);
2145 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2146 //delete this nodepath's object, not the entire selection! (though at this time, this
2147 //does not matter)
2148 sp_selection_delete();
2149 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2150 _("Delete nodes"));
2151 } else {
2152 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2153 sp_nodepath_update_statusbar(nodepath);
2154 }
2155 }
2157 g_slist_free (nodepaths);
2158 }
2160 /**
2161 * Delete one or more selected nodes.
2162 */
2163 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2164 {
2165 if (!nodepath) return;
2166 if (!nodepath->selected) return;
2168 /** \todo fixme: do it the right way */
2169 while (nodepath->selected) {
2170 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2171 sp_nodepath_node_destroy(node);
2172 }
2175 //clean up the nodepath (such as for trivial subpaths)
2176 sp_nodepath_cleanup(nodepath);
2178 sp_nodepath_update_handles(nodepath);
2180 // if the entire nodepath is removed, delete the selected object.
2181 if (nodepath->subpaths == NULL ||
2182 sp_nodepath_get_node_count(nodepath) < 2) {
2183 SPDocument *document = sp_desktop_document (nodepath->desktop);
2184 sp_selection_delete();
2185 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2186 _("Delete nodes"));
2187 return;
2188 }
2190 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2192 sp_nodepath_update_statusbar(nodepath);
2193 }
2195 /**
2196 * Delete one or more segments between two selected nodes.
2197 * This is the code for 'split'.
2198 */
2199 void
2200 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2201 {
2202 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2203 Inkscape::NodePath::Node *curr, *next; //Iterators
2205 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2207 if (g_list_length(nodepath->selected) != 2) {
2208 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2209 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2210 return;
2211 }
2213 //Selected nodes, not inclusive
2214 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2215 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2217 if ( ( a==b) || //same node
2218 (a->subpath != b->subpath ) || //not the same path
2219 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2220 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2221 {
2222 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2223 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2224 return;
2225 }
2227 //###########################################
2228 //# BEGIN EDITS
2229 //###########################################
2230 //##################################
2231 //# CLOSED PATH
2232 //##################################
2233 if (a->subpath->closed) {
2236 gboolean reversed = FALSE;
2238 //Since we can go in a circle, we need to find the shorter distance.
2239 // a->b or b->a
2240 start = end = NULL;
2241 int distance = 0;
2242 int minDistance = 0;
2243 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2244 if (curr==b) {
2245 //printf("a to b:%d\n", distance);
2246 start = a;//go from a to b
2247 end = b;
2248 minDistance = distance;
2249 //printf("A to B :\n");
2250 break;
2251 }
2252 distance++;
2253 }
2255 //try again, the other direction
2256 distance = 0;
2257 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2258 if (curr==a) {
2259 //printf("b to a:%d\n", distance);
2260 if (distance < minDistance) {
2261 start = b; //we go from b to a
2262 end = a;
2263 reversed = TRUE;
2264 //printf("B to A\n");
2265 }
2266 break;
2267 }
2268 distance++;
2269 }
2272 //Copy everything from 'end' to 'start' to a new subpath
2273 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2274 for (curr=end ; curr ; curr=curr->n.other) {
2275 NRPathcode code = (NRPathcode) curr->code;
2276 if (curr == end)
2277 code = NR_MOVETO;
2278 sp_nodepath_node_new(t, NULL,
2279 (Inkscape::NodePath::NodeType)curr->type, code,
2280 &curr->p.pos, &curr->pos, &curr->n.pos);
2281 if (curr == start)
2282 break;
2283 }
2284 sp_nodepath_subpath_destroy(a->subpath);
2287 }
2291 //##################################
2292 //# OPEN PATH
2293 //##################################
2294 else {
2296 //We need to get the direction of the list between A and B
2297 //Can we walk from a to b?
2298 start = end = NULL;
2299 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2300 if (curr==b) {
2301 start = a; //did it! we go from a to b
2302 end = b;
2303 //printf("A to B\n");
2304 break;
2305 }
2306 }
2307 if (!start) {//didn't work? let's try the other direction
2308 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2309 if (curr==a) {
2310 start = b; //did it! we go from b to a
2311 end = a;
2312 //printf("B to A\n");
2313 break;
2314 }
2315 }
2316 }
2317 if (!start) {
2318 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2319 _("Cannot find path between nodes."));
2320 return;
2321 }
2325 //Copy everything after 'end' to a new subpath
2326 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2327 for (curr=end ; curr ; curr=curr->n.other) {
2328 NRPathcode code = (NRPathcode) curr->code;
2329 if (curr == end)
2330 code = NR_MOVETO;
2331 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2332 &curr->p.pos, &curr->pos, &curr->n.pos);
2333 }
2335 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2336 for (curr = start->n.other ; curr ; curr=next) {
2337 next = curr->n.other;
2338 sp_nodepath_node_destroy(curr);
2339 }
2341 }
2342 //###########################################
2343 //# END EDITS
2344 //###########################################
2346 //clean up the nodepath (such as for trivial subpaths)
2347 sp_nodepath_cleanup(nodepath);
2349 sp_nodepath_update_handles(nodepath);
2351 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2353 sp_nodepath_update_statusbar(nodepath);
2354 }
2356 /**
2357 * Call sp_nodepath_set_line() for all selected segments.
2358 */
2359 void
2360 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2361 {
2362 if (nodepath == NULL) return;
2364 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2365 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2366 g_assert(n->selected);
2367 if (n->p.other && n->p.other->selected) {
2368 sp_nodepath_set_line_type(n, code);
2369 }
2370 }
2372 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2373 }
2375 /**
2376 * Call sp_nodepath_convert_node_type() for all selected nodes.
2377 */
2378 void
2379 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2380 {
2381 if (nodepath == NULL) return;
2383 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2385 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2386 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2387 }
2389 sp_nodepath_update_repr(nodepath, _("Change node type"));
2390 }
2392 /**
2393 * Change select status of node, update its own and neighbour handles.
2394 */
2395 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2396 {
2397 node->selected = selected;
2399 if (selected) {
2400 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2401 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2402 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2403 sp_knot_update_ctrl(node->knot);
2404 } else {
2405 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2406 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2407 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2408 sp_knot_update_ctrl(node->knot);
2409 }
2411 sp_node_update_handles(node);
2412 if (node->n.other) sp_node_update_handles(node->n.other);
2413 if (node->p.other) sp_node_update_handles(node->p.other);
2414 }
2416 /**
2417 \brief Select a node
2418 \param node The node to select
2419 \param incremental If true, add to selection, otherwise deselect others
2420 \param override If true, always select this node, otherwise toggle selected status
2421 */
2422 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2423 {
2424 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2426 if (incremental) {
2427 if (override) {
2428 if (!g_list_find(nodepath->selected, node)) {
2429 nodepath->selected = g_list_prepend(nodepath->selected, node);
2430 }
2431 sp_node_set_selected(node, TRUE);
2432 } else { // toggle
2433 if (node->selected) {
2434 g_assert(g_list_find(nodepath->selected, node));
2435 nodepath->selected = g_list_remove(nodepath->selected, node);
2436 } else {
2437 g_assert(!g_list_find(nodepath->selected, node));
2438 nodepath->selected = g_list_prepend(nodepath->selected, node);
2439 }
2440 sp_node_set_selected(node, !node->selected);
2441 }
2442 } else {
2443 sp_nodepath_deselect(nodepath);
2444 nodepath->selected = g_list_prepend(nodepath->selected, node);
2445 sp_node_set_selected(node, TRUE);
2446 }
2448 sp_nodepath_update_statusbar(nodepath);
2449 }
2452 /**
2453 \brief Deselect all nodes in the nodepath
2454 */
2455 void
2456 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2457 {
2458 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2460 while (nodepath->selected) {
2461 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2462 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2463 }
2464 sp_nodepath_update_statusbar(nodepath);
2465 }
2467 /**
2468 \brief Select or invert selection of all nodes in the nodepath
2469 */
2470 void
2471 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2472 {
2473 if (!nodepath) return;
2475 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2476 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2477 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2478 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2479 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2480 }
2481 }
2482 }
2484 /**
2485 * If nothing selected, does the same as sp_nodepath_select_all();
2486 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2487 * (i.e., similar to "select all in layer", with the "selected" subpaths
2488 * being treated as "layers" in the path).
2489 */
2490 void
2491 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2492 {
2493 if (!nodepath) return;
2495 if (g_list_length (nodepath->selected) == 0) {
2496 sp_nodepath_select_all (nodepath, invert);
2497 return;
2498 }
2500 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2501 GSList *subpaths = NULL;
2503 for (GList *l = copy; l != NULL; l = l->next) {
2504 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2505 Inkscape::NodePath::SubPath *subpath = n->subpath;
2506 if (!g_slist_find (subpaths, subpath))
2507 subpaths = g_slist_prepend (subpaths, subpath);
2508 }
2510 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2511 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2512 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2513 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2514 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2515 }
2516 }
2518 g_slist_free (subpaths);
2519 g_list_free (copy);
2520 }
2522 /**
2523 * \brief Select the node after the last selected; if none is selected,
2524 * select the first within path.
2525 */
2526 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2527 {
2528 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2530 Inkscape::NodePath::Node *last = NULL;
2531 if (nodepath->selected) {
2532 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2533 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2534 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2535 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2536 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2537 if (node->selected) {
2538 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2539 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2540 if (spl->next) { // there's a next subpath
2541 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2542 last = subpath_next->first;
2543 } else if (spl->prev) { // there's a previous subpath
2544 last = NULL; // to be set later to the first node of first subpath
2545 } else {
2546 last = node->n.other;
2547 }
2548 } else {
2549 last = node->n.other;
2550 }
2551 } else {
2552 if (node->n.other) {
2553 last = node->n.other;
2554 } else {
2555 if (spl->next) { // there's a next subpath
2556 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2557 last = subpath_next->first;
2558 } else if (spl->prev) { // there's a previous subpath
2559 last = NULL; // to be set later to the first node of first subpath
2560 } else {
2561 last = (Inkscape::NodePath::Node *) subpath->first;
2562 }
2563 }
2564 }
2565 }
2566 }
2567 }
2568 sp_nodepath_deselect(nodepath);
2569 }
2571 if (last) { // there's at least one more node after selected
2572 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2573 } else { // no more nodes, select the first one in first subpath
2574 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2575 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2576 }
2577 }
2579 /**
2580 * \brief Select the node before the first selected; if none is selected,
2581 * select the last within path
2582 */
2583 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2584 {
2585 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2587 Inkscape::NodePath::Node *last = NULL;
2588 if (nodepath->selected) {
2589 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2590 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2591 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2592 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2593 if (node->selected) {
2594 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2595 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2596 if (spl->prev) { // there's a prev subpath
2597 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2598 last = subpath_prev->last;
2599 } else if (spl->next) { // there's a next subpath
2600 last = NULL; // to be set later to the last node of last subpath
2601 } else {
2602 last = node->p.other;
2603 }
2604 } else {
2605 last = node->p.other;
2606 }
2607 } else {
2608 if (node->p.other) {
2609 last = node->p.other;
2610 } else {
2611 if (spl->prev) { // there's a prev subpath
2612 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2613 last = subpath_prev->last;
2614 } else if (spl->next) { // there's a next subpath
2615 last = NULL; // to be set later to the last node of last subpath
2616 } else {
2617 last = (Inkscape::NodePath::Node *) subpath->last;
2618 }
2619 }
2620 }
2621 }
2622 }
2623 }
2624 sp_nodepath_deselect(nodepath);
2625 }
2627 if (last) { // there's at least one more node before selected
2628 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2629 } else { // no more nodes, select the last one in last subpath
2630 GList *spl = g_list_last(nodepath->subpaths);
2631 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2632 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2633 }
2634 }
2636 /**
2637 * \brief Select all nodes that are within the rectangle.
2638 */
2639 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2640 {
2641 if (!incremental) {
2642 sp_nodepath_deselect(nodepath);
2643 }
2645 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2646 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2647 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2648 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2650 if (b.contains(node->pos)) {
2651 sp_nodepath_node_select(node, TRUE, TRUE);
2652 }
2653 }
2654 }
2655 }
2658 void
2659 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2660 {
2661 g_assert (n);
2662 g_assert (nodepath);
2663 g_assert (n->subpath->nodepath == nodepath);
2665 if (g_list_length (nodepath->selected) == 0) {
2666 if (grow > 0) {
2667 sp_nodepath_node_select(n, TRUE, TRUE);
2668 }
2669 return;
2670 }
2672 if (g_list_length (nodepath->selected) == 1) {
2673 if (grow < 0) {
2674 sp_nodepath_deselect (nodepath);
2675 return;
2676 }
2677 }
2679 double n_sel_range = 0, p_sel_range = 0;
2680 Inkscape::NodePath::Node *farthest_n_node = n;
2681 Inkscape::NodePath::Node *farthest_p_node = n;
2683 // Calculate ranges
2684 {
2685 double n_range = 0, p_range = 0;
2686 bool n_going = true, p_going = true;
2687 Inkscape::NodePath::Node *n_node = n;
2688 Inkscape::NodePath::Node *p_node = n;
2689 do {
2690 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2691 if (n_node && n_going)
2692 n_node = n_node->n.other;
2693 if (n_node == NULL) {
2694 n_going = false;
2695 } else {
2696 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2697 if (n_node->selected) {
2698 n_sel_range = n_range;
2699 farthest_n_node = n_node;
2700 }
2701 if (n_node == p_node) {
2702 n_going = false;
2703 p_going = false;
2704 }
2705 }
2706 if (p_node && p_going)
2707 p_node = p_node->p.other;
2708 if (p_node == NULL) {
2709 p_going = false;
2710 } else {
2711 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2712 if (p_node->selected) {
2713 p_sel_range = p_range;
2714 farthest_p_node = p_node;
2715 }
2716 if (p_node == n_node) {
2717 n_going = false;
2718 p_going = false;
2719 }
2720 }
2721 } while (n_going || p_going);
2722 }
2724 if (grow > 0) {
2725 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2726 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2727 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2728 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2729 }
2730 } else {
2731 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2732 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2733 } else if (farthest_p_node && farthest_p_node->selected) {
2734 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2735 }
2736 }
2737 }
2739 void
2740 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2741 {
2742 g_assert (n);
2743 g_assert (nodepath);
2744 g_assert (n->subpath->nodepath == nodepath);
2746 if (g_list_length (nodepath->selected) == 0) {
2747 if (grow > 0) {
2748 sp_nodepath_node_select(n, TRUE, TRUE);
2749 }
2750 return;
2751 }
2753 if (g_list_length (nodepath->selected) == 1) {
2754 if (grow < 0) {
2755 sp_nodepath_deselect (nodepath);
2756 return;
2757 }
2758 }
2760 Inkscape::NodePath::Node *farthest_selected = NULL;
2761 double farthest_dist = 0;
2763 Inkscape::NodePath::Node *closest_unselected = NULL;
2764 double closest_dist = NR_HUGE;
2766 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2767 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2768 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2769 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2770 if (node == n)
2771 continue;
2772 if (node->selected) {
2773 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2774 farthest_dist = NR::L2(node->pos - n->pos);
2775 farthest_selected = node;
2776 }
2777 } else {
2778 if (NR::L2(node->pos - n->pos) < closest_dist) {
2779 closest_dist = NR::L2(node->pos - n->pos);
2780 closest_unselected = node;
2781 }
2782 }
2783 }
2784 }
2786 if (grow > 0) {
2787 if (closest_unselected) {
2788 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2789 }
2790 } else {
2791 if (farthest_selected) {
2792 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2793 }
2794 }
2795 }
2798 /**
2799 \brief Saves all nodes' and handles' current positions in their origin members
2800 */
2801 void
2802 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2803 {
2804 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2805 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2806 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2807 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2808 n->origin = n->pos;
2809 n->p.origin = n->p.pos;
2810 n->n.origin = n->n.pos;
2811 }
2812 }
2813 }
2815 /**
2816 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2817 */
2818 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2819 {
2820 if (!nodepath->selected) {
2821 return NULL;
2822 }
2824 GList *r = NULL;
2825 guint i = 0;
2826 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2827 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2828 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2829 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2830 i++;
2831 if (node->selected) {
2832 r = g_list_append(r, GINT_TO_POINTER(i));
2833 }
2834 }
2835 }
2836 return r;
2837 }
2839 /**
2840 \brief Restores selection by selecting nodes whose positions are in the list
2841 */
2842 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2843 {
2844 sp_nodepath_deselect(nodepath);
2846 guint i = 0;
2847 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2848 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2849 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2850 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2851 i++;
2852 if (g_list_find(r, GINT_TO_POINTER(i))) {
2853 sp_nodepath_node_select(node, TRUE, TRUE);
2854 }
2855 }
2856 }
2858 }
2860 /**
2861 \brief Adjusts handle according to node type and line code.
2862 */
2863 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2864 {
2865 double len, otherlen, linelen;
2867 g_assert(node);
2869 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2870 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2872 /** \todo fixme: */
2873 if (me->other == NULL) return;
2874 if (other->other == NULL) return;
2876 /* I have line */
2878 NRPathcode mecode, ocode;
2879 if (which_adjust == 1) {
2880 mecode = (NRPathcode)me->other->code;
2881 ocode = (NRPathcode)node->code;
2882 } else {
2883 mecode = (NRPathcode)node->code;
2884 ocode = (NRPathcode)other->other->code;
2885 }
2887 if (mecode == NR_LINETO) return;
2889 /* I am curve */
2891 if (other->other == NULL) return;
2893 /* Other has line */
2895 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2897 NR::Point delta;
2898 if (ocode == NR_LINETO) {
2899 /* other is lineto, we are either smooth or symm */
2900 Inkscape::NodePath::Node *othernode = other->other;
2901 len = NR::L2(me->pos - node->pos);
2902 delta = node->pos - othernode->pos;
2903 linelen = NR::L2(delta);
2904 if (linelen < 1e-18)
2905 return;
2906 me->pos = node->pos + (len / linelen)*delta;
2907 return;
2908 }
2910 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2912 me->pos = 2 * node->pos - other->pos;
2913 return;
2914 }
2916 /* We are smooth */
2918 len = NR::L2(me->pos - node->pos);
2919 delta = other->pos - node->pos;
2920 otherlen = NR::L2(delta);
2921 if (otherlen < 1e-18) return;
2923 me->pos = node->pos - (len / otherlen) * delta;
2924 }
2926 /**
2927 \brief Adjusts both handles according to node type and line code
2928 */
2929 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2930 {
2931 g_assert(node);
2933 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2935 /* we are either smooth or symm */
2937 if (node->p.other == NULL) return;
2939 if (node->n.other == NULL) return;
2941 if (node->code == NR_LINETO) {
2942 if (node->n.other->code == NR_LINETO) return;
2943 sp_node_adjust_handle(node, 1);
2944 return;
2945 }
2947 if (node->n.other->code == NR_LINETO) {
2948 if (node->code == NR_LINETO) return;
2949 sp_node_adjust_handle(node, -1);
2950 return;
2951 }
2953 /* both are curves */
2954 NR::Point const delta( node->n.pos - node->p.pos );
2956 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2957 node->p.pos = node->pos - delta / 2;
2958 node->n.pos = node->pos + delta / 2;
2959 return;
2960 }
2962 /* We are smooth */
2963 double plen = NR::L2(node->p.pos - node->pos);
2964 if (plen < 1e-18) return;
2965 double nlen = NR::L2(node->n.pos - node->pos);
2966 if (nlen < 1e-18) return;
2967 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2968 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2969 }
2971 /**
2972 * Node event callback.
2973 */
2974 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
2975 {
2976 gboolean ret = FALSE;
2977 switch (event->type) {
2978 case GDK_ENTER_NOTIFY:
2979 Inkscape::NodePath::Path::active_node = n;
2980 break;
2981 case GDK_LEAVE_NOTIFY:
2982 Inkscape::NodePath::Path::active_node = NULL;
2983 break;
2984 case GDK_SCROLL:
2985 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2986 switch (event->scroll.direction) {
2987 case GDK_SCROLL_UP:
2988 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2989 break;
2990 case GDK_SCROLL_DOWN:
2991 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2992 break;
2993 default:
2994 break;
2995 }
2996 ret = TRUE;
2997 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2998 switch (event->scroll.direction) {
2999 case GDK_SCROLL_UP:
3000 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3001 break;
3002 case GDK_SCROLL_DOWN:
3003 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3004 break;
3005 default:
3006 break;
3007 }
3008 ret = TRUE;
3009 }
3010 break;
3011 case GDK_KEY_PRESS:
3012 switch (get_group0_keyval (&event->key)) {
3013 case GDK_space:
3014 if (event->key.state & GDK_BUTTON1_MASK) {
3015 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3016 stamp_repr(nodepath);
3017 ret = TRUE;
3018 }
3019 break;
3020 case GDK_Page_Up:
3021 if (event->key.state & GDK_CONTROL_MASK) {
3022 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3023 } else {
3024 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3025 }
3026 break;
3027 case GDK_Page_Down:
3028 if (event->key.state & GDK_CONTROL_MASK) {
3029 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3030 } else {
3031 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3032 }
3033 break;
3034 default:
3035 break;
3036 }
3037 break;
3038 default:
3039 break;
3040 }
3042 return ret;
3043 }
3045 /**
3046 * Handle keypress on node; directly called.
3047 */
3048 gboolean node_key(GdkEvent *event)
3049 {
3050 Inkscape::NodePath::Path *np;
3052 // there is no way to verify nodes so set active_node to nil when deleting!!
3053 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3055 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3056 gint ret = FALSE;
3057 switch (get_group0_keyval (&event->key)) {
3058 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3059 case GDK_BackSpace:
3060 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3061 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3062 sp_nodepath_update_repr(np, _("Delete node"));
3063 Inkscape::NodePath::Path::active_node = NULL;
3064 ret = TRUE;
3065 break;
3066 case GDK_c:
3067 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3068 ret = TRUE;
3069 break;
3070 case GDK_s:
3071 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3072 ret = TRUE;
3073 break;
3074 case GDK_y:
3075 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3076 ret = TRUE;
3077 break;
3078 case GDK_b:
3079 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3080 ret = TRUE;
3081 break;
3082 }
3083 return ret;
3084 }
3085 return FALSE;
3086 }
3088 /**
3089 * Mouseclick on node callback.
3090 */
3091 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3092 {
3093 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3095 if (state & GDK_CONTROL_MASK) {
3096 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3098 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3099 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3100 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3101 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3102 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3103 } else {
3104 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3105 }
3106 sp_nodepath_update_repr(nodepath, _("Change node type"));
3107 sp_nodepath_update_statusbar(nodepath);
3109 } else { //ctrl+alt+click: delete node
3110 GList *node_to_delete = NULL;
3111 node_to_delete = g_list_append(node_to_delete, n);
3112 sp_node_delete_preserve(node_to_delete);
3113 }
3115 } else {
3116 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3117 }
3118 }
3120 /**
3121 * Mouse grabbed node callback.
3122 */
3123 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3124 {
3125 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3127 if (!n->selected) {
3128 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3129 }
3131 n->is_dragging = true;
3132 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3134 sp_nodepath_remember_origins (n->subpath->nodepath);
3135 }
3137 /**
3138 * Mouse ungrabbed node callback.
3139 */
3140 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3141 {
3142 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3144 n->dragging_out = NULL;
3145 n->is_dragging = false;
3146 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3148 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3149 }
3151 /**
3152 * The point on a line, given by its angle, closest to the given point.
3153 * \param p A point.
3154 * \param a Angle of the line; it is assumed to go through coordinate origin.
3155 * \param closest Pointer to the point struct where the result is stored.
3156 * \todo FIXME: use dot product perhaps?
3157 */
3158 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3159 {
3160 if (a == HUGE_VAL) { // vertical
3161 *closest = NR::Point(0, (*p)[NR::Y]);
3162 } else {
3163 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3164 (*closest)[NR::Y] = a * (*closest)[NR::X];
3165 }
3166 }
3168 /**
3169 * Distance from the point to a line given by its angle.
3170 * \param p A point.
3171 * \param a Angle of the line; it is assumed to go through coordinate origin.
3172 */
3173 static double point_line_distance(NR::Point *p, double a)
3174 {
3175 NR::Point c;
3176 point_line_closest(p, a, &c);
3177 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]));
3178 }
3180 /**
3181 * Callback for node "request" signal.
3182 * \todo fixme: This goes to "moved" event? (lauris)
3183 */
3184 static gboolean
3185 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3186 {
3187 double yn, xn, yp, xp;
3188 double an, ap, na, pa;
3189 double d_an, d_ap, d_na, d_pa;
3190 gboolean collinear = FALSE;
3191 NR::Point c;
3192 NR::Point pr;
3194 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3196 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3197 if ( (!n->subpath->nodepath->straight_path) &&
3198 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3199 || n->dragging_out ) )
3200 {
3201 NR::Point mouse = (*p);
3203 if (!n->dragging_out) {
3204 // This is the first drag-out event; find out which handle to drag out
3205 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3206 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3208 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3209 return FALSE;
3211 Inkscape::NodePath::NodeSide *opposite;
3212 if (appr_p > appr_n) { // closer to p
3213 n->dragging_out = &n->p;
3214 opposite = &n->n;
3215 n->code = NR_CURVETO;
3216 } else if (appr_p < appr_n) { // closer to n
3217 n->dragging_out = &n->n;
3218 opposite = &n->p;
3219 n->n.other->code = NR_CURVETO;
3220 } else { // p and n nodes are the same
3221 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3222 n->dragging_out = &n->p;
3223 opposite = &n->n;
3224 n->code = NR_CURVETO;
3225 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3226 n->dragging_out = &n->n;
3227 opposite = &n->p;
3228 n->n.other->code = NR_CURVETO;
3229 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3230 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);
3231 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);
3232 if (appr_other_p > appr_other_n) { // closer to other's p handle
3233 n->dragging_out = &n->n;
3234 opposite = &n->p;
3235 n->n.other->code = NR_CURVETO;
3236 } else { // closer to other's n handle
3237 n->dragging_out = &n->p;
3238 opposite = &n->n;
3239 n->code = NR_CURVETO;
3240 }
3241 }
3242 }
3244 // if there's another handle, make sure the one we drag out starts parallel to it
3245 if (opposite->pos != n->pos) {
3246 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3247 }
3249 // knots might not be created yet!
3250 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3251 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3252 }
3254 // pass this on to the handle-moved callback
3255 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3256 sp_node_update_handles(n);
3257 return TRUE;
3258 }
3260 if (state & GDK_CONTROL_MASK) { // constrained motion
3262 // calculate relative distances of handles
3263 // n handle:
3264 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3265 xn = n->n.pos[NR::X] - n->pos[NR::X];
3266 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3267 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3268 if (n->n.other) { // if there is the next point
3269 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3270 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3271 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3272 }
3273 }
3274 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3275 if (yn < 0) { xn = -xn; yn = -yn; }
3277 // p handle:
3278 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3279 xp = n->p.pos[NR::X] - n->pos[NR::X];
3280 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3281 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3282 if (n->p.other) {
3283 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3284 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3285 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3286 }
3287 }
3288 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3289 if (yp < 0) { xp = -xp; yp = -yp; }
3291 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3292 // sliding on handles, only if at least one of the handles is non-vertical
3293 // (otherwise it's the same as ctrl+drag anyway)
3295 // calculate angles of the handles
3296 if (xn == 0) {
3297 if (yn == 0) { // no handle, consider it the continuation of the other one
3298 an = 0;
3299 collinear = TRUE;
3300 }
3301 else an = 0; // vertical; set the angle to horizontal
3302 } else an = yn/xn;
3304 if (xp == 0) {
3305 if (yp == 0) { // no handle, consider it the continuation of the other one
3306 ap = an;
3307 }
3308 else ap = 0; // vertical; set the angle to horizontal
3309 } else ap = yp/xp;
3311 if (collinear) an = ap;
3313 // angles of the perpendiculars; HUGE_VAL means vertical
3314 if (an == 0) na = HUGE_VAL; else na = -1/an;
3315 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3317 // mouse point relative to the node's original pos
3318 pr = (*p) - n->origin;
3320 // distances to the four lines (two handles and two perpendiculars)
3321 d_an = point_line_distance(&pr, an);
3322 d_na = point_line_distance(&pr, na);
3323 d_ap = point_line_distance(&pr, ap);
3324 d_pa = point_line_distance(&pr, pa);
3326 // find out which line is the closest, save its closest point in c
3327 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3328 point_line_closest(&pr, an, &c);
3329 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3330 point_line_closest(&pr, ap, &c);
3331 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3332 point_line_closest(&pr, na, &c);
3333 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3334 point_line_closest(&pr, pa, &c);
3335 }
3337 // move the node to the closest point
3338 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3339 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3340 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3342 } else { // constraining to hor/vert
3344 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3345 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3346 } else { // snap to vert
3347 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3348 }
3349 }
3350 } else { // move freely
3351 if (n->is_dragging) {
3352 if (state & GDK_MOD1_MASK) { // sculpt
3353 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3354 } else {
3355 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3356 (*p)[NR::X] - n->pos[NR::X],
3357 (*p)[NR::Y] - n->pos[NR::Y],
3358 (state & GDK_SHIFT_MASK) == 0);
3359 }
3360 }
3361 }
3363 n->subpath->nodepath->desktop->scroll_to_point(p);
3365 return TRUE;
3366 }
3368 /**
3369 * Node handle clicked callback.
3370 */
3371 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3372 {
3373 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3375 if (state & GDK_CONTROL_MASK) { // "delete" handle
3376 if (n->p.knot == knot) {
3377 n->p.pos = n->pos;
3378 } else if (n->n.knot == knot) {
3379 n->n.pos = n->pos;
3380 }
3381 sp_node_update_handles(n);
3382 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3383 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3384 sp_nodepath_update_statusbar(nodepath);
3386 } else { // just select or add to selection, depending in Shift
3387 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3388 }
3389 }
3391 /**
3392 * Node handle grabbed callback.
3393 */
3394 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3395 {
3396 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3398 if (!n->selected) {
3399 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3400 }
3402 // remember the origin point of the handle
3403 if (n->p.knot == knot) {
3404 n->p.origin_radial = n->p.pos - n->pos;
3405 } else if (n->n.knot == knot) {
3406 n->n.origin_radial = n->n.pos - n->pos;
3407 } else {
3408 g_assert_not_reached();
3409 }
3411 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3412 }
3414 /**
3415 * Node handle ungrabbed callback.
3416 */
3417 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3418 {
3419 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3421 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3422 if (n->p.knot == knot) {
3423 n->p.origin_radial.a = 0;
3424 sp_knot_set_position(knot, &n->p.pos, state);
3425 } else if (n->n.knot == knot) {
3426 n->n.origin_radial.a = 0;
3427 sp_knot_set_position(knot, &n->n.pos, state);
3428 } else {
3429 g_assert_not_reached();
3430 }
3432 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3433 }
3435 /**
3436 * Node handle "request" signal callback.
3437 */
3438 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3439 {
3440 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3442 Inkscape::NodePath::NodeSide *me, *opposite;
3443 gint which;
3444 if (n->p.knot == knot) {
3445 me = &n->p;
3446 opposite = &n->n;
3447 which = -1;
3448 } else if (n->n.knot == knot) {
3449 me = &n->n;
3450 opposite = &n->p;
3451 which = 1;
3452 } else {
3453 me = opposite = NULL;
3454 which = 0;
3455 g_assert_not_reached();
3456 }
3458 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3460 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3462 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3463 /* We are smooth node adjacent with line */
3464 NR::Point const delta = *p - n->pos;
3465 NR::Coord const len = NR::L2(delta);
3466 Inkscape::NodePath::Node *othernode = opposite->other;
3467 NR::Point const ndelta = n->pos - othernode->pos;
3468 NR::Coord const linelen = NR::L2(ndelta);
3469 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3470 NR::Coord const scal = dot(delta, ndelta) / linelen;
3471 (*p) = n->pos + (scal / linelen) * ndelta;
3472 }
3473 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item).getPoint();
3474 } else {
3475 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item).getPoint();
3476 }
3478 sp_node_adjust_handle(n, -which);
3480 return FALSE;
3481 }
3483 /**
3484 * Node handle moved callback.
3485 */
3486 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3487 {
3488 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3490 Inkscape::NodePath::NodeSide *me;
3491 Inkscape::NodePath::NodeSide *other;
3492 if (n->p.knot == knot) {
3493 me = &n->p;
3494 other = &n->n;
3495 } else if (n->n.knot == knot) {
3496 me = &n->n;
3497 other = &n->p;
3498 } else {
3499 me = NULL;
3500 other = NULL;
3501 g_assert_not_reached();
3502 }
3504 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3505 Radial rme(me->pos - n->pos);
3506 Radial rother(other->pos - n->pos);
3507 Radial rnew(*p - n->pos);
3509 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3510 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3511 /* 0 interpreted as "no snapping". */
3513 // The closest PI/snaps angle, starting from zero.
3514 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3515 if (me->origin_radial.a == HUGE_VAL) {
3516 // ortho doesn't exist: original handle was zero length.
3517 rnew.a = a_snapped;
3518 } else {
3519 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3520 * its opposite and perpendiculars). */
3521 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3523 // Snap to the closest.
3524 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3525 ? a_snapped
3526 : a_ortho );
3527 }
3528 }
3530 if (state & GDK_MOD1_MASK) {
3531 // lock handle length
3532 rnew.r = me->origin_radial.r;
3533 }
3535 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3536 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3537 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3538 rother.a += rnew.a - rme.a;
3539 other->pos = NR::Point(rother) + n->pos;
3540 if (other->knot) {
3541 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3542 sp_knot_moveto(other->knot, &other->pos);
3543 }
3544 }
3546 me->pos = NR::Point(rnew) + n->pos;
3547 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3549 // move knot, but without emitting the signal:
3550 // we cannot emit a "moved" signal because we're now processing it
3551 sp_knot_moveto(me->knot, &(me->pos));
3553 update_object(n->subpath->nodepath);
3555 /* status text */
3556 SPDesktop *desktop = n->subpath->nodepath->desktop;
3557 if (!desktop) return;
3558 SPEventContext *ec = desktop->event_context;
3559 if (!ec) return;
3560 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3561 if (!mc) return;
3563 double degrees = 180 / M_PI * rnew.a;
3564 if (degrees > 180) degrees -= 360;
3565 if (degrees < -180) degrees += 360;
3566 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3567 degrees = angle_to_compass (degrees);
3569 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3571 mc->setF(Inkscape::NORMAL_MESSAGE,
3572 _("<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);
3574 g_string_free(length, TRUE);
3575 }
3577 /**
3578 * Node handle event callback.
3579 */
3580 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3581 {
3582 gboolean ret = FALSE;
3583 switch (event->type) {
3584 case GDK_KEY_PRESS:
3585 switch (get_group0_keyval (&event->key)) {
3586 case GDK_space:
3587 if (event->key.state & GDK_BUTTON1_MASK) {
3588 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3589 stamp_repr(nodepath);
3590 ret = TRUE;
3591 }
3592 break;
3593 default:
3594 break;
3595 }
3596 break;
3597 case GDK_ENTER_NOTIFY:
3598 // we use an experimentally determined threshold that seems to work fine
3599 if (NR::L2(n->pos - knot->pos) < 0.75)
3600 Inkscape::NodePath::Path::active_node = n;
3601 break;
3602 case GDK_LEAVE_NOTIFY:
3603 // we use an experimentally determined threshold that seems to work fine
3604 if (NR::L2(n->pos - knot->pos) < 0.75)
3605 Inkscape::NodePath::Path::active_node = NULL;
3606 break;
3607 default:
3608 break;
3609 }
3611 return ret;
3612 }
3614 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3615 Radial &rme, Radial &rother, gboolean const both)
3616 {
3617 rme.a += angle;
3618 if ( both
3619 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3620 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3621 {
3622 rother.a += angle;
3623 }
3624 }
3626 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3627 Radial &rme, Radial &rother, gboolean const both)
3628 {
3629 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3631 gdouble r;
3632 if ( both
3633 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3634 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3635 {
3636 r = MAX(rme.r, rother.r);
3637 } else {
3638 r = rme.r;
3639 }
3641 gdouble const weird_angle = atan2(norm_angle, r);
3642 /* Bulia says norm_angle is just the visible distance that the
3643 * object's end must travel on the screen. Left as 'angle' for want of
3644 * a better name.*/
3646 rme.a += weird_angle;
3647 if ( both
3648 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3649 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3650 {
3651 rother.a += weird_angle;
3652 }
3653 }
3655 /**
3656 * Rotate one node.
3657 */
3658 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3659 {
3660 Inkscape::NodePath::NodeSide *me, *other;
3661 bool both = false;
3663 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3664 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3666 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3667 me = &(n->p);
3668 other = &(n->n);
3669 } else if (!n->p.other) {
3670 me = &(n->n);
3671 other = &(n->p);
3672 } else {
3673 if (which > 0) { // right handle
3674 if (xn > xp) {
3675 me = &(n->n);
3676 other = &(n->p);
3677 } else {
3678 me = &(n->p);
3679 other = &(n->n);
3680 }
3681 } else if (which < 0){ // left handle
3682 if (xn <= xp) {
3683 me = &(n->n);
3684 other = &(n->p);
3685 } else {
3686 me = &(n->p);
3687 other = &(n->n);
3688 }
3689 } else { // both handles
3690 me = &(n->n);
3691 other = &(n->p);
3692 both = true;
3693 }
3694 }
3696 Radial rme(me->pos - n->pos);
3697 Radial rother(other->pos - n->pos);
3699 if (screen) {
3700 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3701 } else {
3702 node_rotate_one_internal (*n, angle, rme, rother, both);
3703 }
3705 me->pos = n->pos + NR::Point(rme);
3707 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3708 other->pos = n->pos + NR::Point(rother);
3709 }
3711 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3712 // so here we just move all the knots without emitting move signals, for speed
3713 sp_node_update_handles(n, false);
3714 }
3716 /**
3717 * Rotate selected nodes.
3718 */
3719 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3720 {
3721 if (!nodepath || !nodepath->selected) return;
3723 if (g_list_length(nodepath->selected) == 1) {
3724 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3725 node_rotate_one (n, angle, which, screen);
3726 } else {
3727 // rotate as an object:
3729 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3730 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3731 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3732 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3733 box.expandTo (n->pos); // contain all selected nodes
3734 }
3736 gdouble rot;
3737 if (screen) {
3738 gdouble const zoom = nodepath->desktop->current_zoom();
3739 gdouble const zmove = angle / zoom;
3740 gdouble const r = NR::L2(box.max() - box.midpoint());
3741 rot = atan2(zmove, r);
3742 } else {
3743 rot = angle;
3744 }
3746 NR::Point rot_center;
3747 if (Inkscape::NodePath::Path::active_node == NULL)
3748 rot_center = box.midpoint();
3749 else
3750 rot_center = Inkscape::NodePath::Path::active_node->pos;
3752 NR::Matrix t =
3753 NR::Matrix (NR::translate(-rot_center)) *
3754 NR::Matrix (NR::rotate(rot)) *
3755 NR::Matrix (NR::translate(rot_center));
3757 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3758 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3759 n->pos *= t;
3760 n->n.pos *= t;
3761 n->p.pos *= t;
3762 sp_node_update_handles(n, false);
3763 }
3764 }
3766 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3767 }
3769 /**
3770 * Scale one node.
3771 */
3772 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3773 {
3774 bool both = false;
3775 Inkscape::NodePath::NodeSide *me, *other;
3777 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3778 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3780 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3781 me = &(n->p);
3782 other = &(n->n);
3783 n->code = NR_CURVETO;
3784 } else if (!n->p.other) {
3785 me = &(n->n);
3786 other = &(n->p);
3787 if (n->n.other)
3788 n->n.other->code = NR_CURVETO;
3789 } else {
3790 if (which > 0) { // right handle
3791 if (xn > xp) {
3792 me = &(n->n);
3793 other = &(n->p);
3794 if (n->n.other)
3795 n->n.other->code = NR_CURVETO;
3796 } else {
3797 me = &(n->p);
3798 other = &(n->n);
3799 n->code = NR_CURVETO;
3800 }
3801 } else if (which < 0){ // left handle
3802 if (xn <= xp) {
3803 me = &(n->n);
3804 other = &(n->p);
3805 if (n->n.other)
3806 n->n.other->code = NR_CURVETO;
3807 } else {
3808 me = &(n->p);
3809 other = &(n->n);
3810 n->code = NR_CURVETO;
3811 }
3812 } else { // both handles
3813 me = &(n->n);
3814 other = &(n->p);
3815 both = true;
3816 n->code = NR_CURVETO;
3817 if (n->n.other)
3818 n->n.other->code = NR_CURVETO;
3819 }
3820 }
3822 Radial rme(me->pos - n->pos);
3823 Radial rother(other->pos - n->pos);
3825 rme.r += grow;
3826 if (rme.r < 0) rme.r = 0;
3827 if (rme.a == HUGE_VAL) {
3828 if (me->other) { // if direction is unknown, initialize it towards the next node
3829 Radial rme_next(me->other->pos - n->pos);
3830 rme.a = rme_next.a;
3831 } else { // if there's no next, initialize to 0
3832 rme.a = 0;
3833 }
3834 }
3835 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3836 rother.r += grow;
3837 if (rother.r < 0) rother.r = 0;
3838 if (rother.a == HUGE_VAL) {
3839 rother.a = rme.a + M_PI;
3840 }
3841 }
3843 me->pos = n->pos + NR::Point(rme);
3845 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3846 other->pos = n->pos + NR::Point(rother);
3847 }
3849 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3850 // so here we just move all the knots without emitting move signals, for speed
3851 sp_node_update_handles(n, false);
3852 }
3854 /**
3855 * Scale selected nodes.
3856 */
3857 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3858 {
3859 if (!nodepath || !nodepath->selected) return;
3861 if (g_list_length(nodepath->selected) == 1) {
3862 // scale handles of the single selected node
3863 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3864 node_scale_one (n, grow, which);
3865 } else {
3866 // scale nodes as an "object":
3868 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3869 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3870 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3871 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3872 box.expandTo (n->pos); // contain all selected nodes
3873 }
3875 double scale = (box.maxExtent() + grow)/box.maxExtent();
3877 NR::Point scale_center;
3878 if (Inkscape::NodePath::Path::active_node == NULL)
3879 scale_center = box.midpoint();
3880 else
3881 scale_center = Inkscape::NodePath::Path::active_node->pos;
3883 NR::Matrix t =
3884 NR::Matrix (NR::translate(-scale_center)) *
3885 NR::Matrix (NR::scale(scale, scale)) *
3886 NR::Matrix (NR::translate(scale_center));
3888 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3889 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3890 n->pos *= t;
3891 n->n.pos *= t;
3892 n->p.pos *= t;
3893 sp_node_update_handles(n, false);
3894 }
3895 }
3897 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3898 }
3900 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3901 {
3902 if (!nodepath) return;
3903 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3904 }
3906 /**
3907 * Flip selected nodes horizontally/vertically.
3908 */
3909 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3910 {
3911 if (!nodepath || !nodepath->selected) return;
3913 if (g_list_length(nodepath->selected) == 1 && !center) {
3914 // flip handles of the single selected node
3915 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3916 double temp = n->p.pos[axis];
3917 n->p.pos[axis] = n->n.pos[axis];
3918 n->n.pos[axis] = temp;
3919 sp_node_update_handles(n, false);
3920 } else {
3921 // scale nodes as an "object":
3923 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3924 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3925 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3926 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3927 box.expandTo (n->pos); // contain all selected nodes
3928 }
3930 if (!center) {
3931 center = box.midpoint();
3932 }
3933 NR::Matrix t =
3934 NR::Matrix (NR::translate(- *center)) *
3935 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3936 NR::Matrix (NR::translate(*center));
3938 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3939 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3940 n->pos *= t;
3941 n->n.pos *= t;
3942 n->p.pos *= t;
3943 sp_node_update_handles(n, false);
3944 }
3945 }
3947 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3948 }
3950 //-----------------------------------------------
3951 /**
3952 * Return new subpath under given nodepath.
3953 */
3954 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3955 {
3956 g_assert(nodepath);
3957 g_assert(nodepath->desktop);
3959 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3961 s->nodepath = nodepath;
3962 s->closed = FALSE;
3963 s->nodes = NULL;
3964 s->first = NULL;
3965 s->last = NULL;
3967 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3968 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3969 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3971 return s;
3972 }
3974 /**
3975 * Destroy nodes in subpath, then subpath itself.
3976 */
3977 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3978 {
3979 g_assert(subpath);
3980 g_assert(subpath->nodepath);
3981 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3983 while (subpath->nodes) {
3984 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3985 }
3987 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3989 g_free(subpath);
3990 }
3992 /**
3993 * Link head to tail in subpath.
3994 */
3995 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3996 {
3997 g_assert(!sp->closed);
3998 g_assert(sp->last != sp->first);
3999 g_assert(sp->first->code == NR_MOVETO);
4001 sp->closed = TRUE;
4003 //Link the head to the tail
4004 sp->first->p.other = sp->last;
4005 sp->last->n.other = sp->first;
4006 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4007 sp->first = sp->last;
4009 //Remove the extra end node
4010 sp_nodepath_node_destroy(sp->last->n.other);
4011 }
4013 /**
4014 * Open closed (loopy) subpath at node.
4015 */
4016 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4017 {
4018 g_assert(sp->closed);
4019 g_assert(n->subpath == sp);
4020 g_assert(sp->first == sp->last);
4022 /* We create new startpoint, current node will become last one */
4024 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4025 &n->pos, &n->pos, &n->n.pos);
4028 sp->closed = FALSE;
4030 //Unlink to make a head and tail
4031 sp->first = new_path;
4032 sp->last = n;
4033 n->n.other = NULL;
4034 new_path->p.other = NULL;
4035 }
4037 /**
4038 * Returns area in triangle given by points; may be negative.
4039 */
4040 inline double
4041 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
4042 {
4043 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]);
4044 }
4046 /**
4047 * Return new node in subpath with given properties.
4048 * \param pos Position of node.
4049 * \param ppos Handle position in previous direction
4050 * \param npos Handle position in previous direction
4051 */
4052 Inkscape::NodePath::Node *
4053 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)
4054 {
4055 g_assert(sp);
4056 g_assert(sp->nodepath);
4057 g_assert(sp->nodepath->desktop);
4059 if (nodechunk == NULL)
4060 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4062 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4064 n->subpath = sp;
4066 if (type != Inkscape::NodePath::NODE_NONE) {
4067 // use the type from sodipodi:nodetypes
4068 n->type = type;
4069 } else {
4070 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4071 // points are (almost) collinear
4072 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4073 // endnode, or a node with a retracted handle
4074 n->type = Inkscape::NodePath::NODE_CUSP;
4075 } else {
4076 n->type = Inkscape::NodePath::NODE_SMOOTH;
4077 }
4078 } else {
4079 n->type = Inkscape::NodePath::NODE_CUSP;
4080 }
4081 }
4083 n->code = code;
4084 n->selected = FALSE;
4085 n->pos = *pos;
4086 n->p.pos = *ppos;
4087 n->n.pos = *npos;
4089 n->dragging_out = NULL;
4091 Inkscape::NodePath::Node *prev;
4092 if (next) {
4093 //g_assert(g_list_find(sp->nodes, next));
4094 prev = next->p.other;
4095 } else {
4096 prev = sp->last;
4097 }
4099 if (prev)
4100 prev->n.other = n;
4101 else
4102 sp->first = n;
4104 if (next)
4105 next->p.other = n;
4106 else
4107 sp->last = n;
4109 n->p.other = prev;
4110 n->n.other = next;
4112 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"));
4113 sp_knot_set_position(n->knot, pos, 0);
4115 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4116 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4117 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4118 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4119 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4120 sp_knot_update_ctrl(n->knot);
4122 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4123 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4124 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4125 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4126 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4127 sp_knot_show(n->knot);
4129 // We only create handle knots and lines on demand
4130 n->p.knot = NULL;
4131 n->p.line = NULL;
4132 n->n.knot = NULL;
4133 n->n.line = NULL;
4135 sp->nodes = g_list_prepend(sp->nodes, n);
4137 return n;
4138 }
4140 /**
4141 * Destroy node and its knots, link neighbors in subpath.
4142 */
4143 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4144 {
4145 g_assert(node);
4146 g_assert(node->subpath);
4147 g_assert(SP_IS_KNOT(node->knot));
4149 Inkscape::NodePath::SubPath *sp = node->subpath;
4151 if (node->selected) { // first, deselect
4152 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4153 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4154 }
4156 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4158 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4159 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4160 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4161 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4162 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4163 g_object_unref(G_OBJECT(node->knot));
4165 if (node->p.knot) {
4166 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4167 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4168 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4169 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4170 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4171 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4172 g_object_unref(G_OBJECT(node->p.knot));
4173 node->p.knot = NULL;
4174 }
4176 if (node->n.knot) {
4177 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4178 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4179 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4180 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4181 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4182 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4183 g_object_unref(G_OBJECT(node->n.knot));
4184 node->n.knot = NULL;
4185 }
4187 if (node->p.line)
4188 gtk_object_destroy(GTK_OBJECT(node->p.line));
4189 if (node->n.line)
4190 gtk_object_destroy(GTK_OBJECT(node->n.line));
4192 if (sp->nodes) { // there are others nodes on the subpath
4193 if (sp->closed) {
4194 if (sp->first == node) {
4195 g_assert(sp->last == node);
4196 sp->first = node->n.other;
4197 sp->last = sp->first;
4198 }
4199 node->p.other->n.other = node->n.other;
4200 node->n.other->p.other = node->p.other;
4201 } else {
4202 if (sp->first == node) {
4203 sp->first = node->n.other;
4204 sp->first->code = NR_MOVETO;
4205 }
4206 if (sp->last == node) sp->last = node->p.other;
4207 if (node->p.other) node->p.other->n.other = node->n.other;
4208 if (node->n.other) node->n.other->p.other = node->p.other;
4209 }
4210 } else { // this was the last node on subpath
4211 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4212 }
4214 g_mem_chunk_free(nodechunk, node);
4215 }
4217 /**
4218 * Returns one of the node's two sides.
4219 * \param which Indicates which side.
4220 * \return Pointer to previous node side if which==-1, next if which==1.
4221 */
4222 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4223 {
4224 g_assert(node);
4226 switch (which) {
4227 case -1:
4228 return &node->p;
4229 case 1:
4230 return &node->n;
4231 default:
4232 break;
4233 }
4235 g_assert_not_reached();
4237 return NULL;
4238 }
4240 /**
4241 * Return the other side of the node, given one of its sides.
4242 */
4243 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4244 {
4245 g_assert(node);
4247 if (me == &node->p) return &node->n;
4248 if (me == &node->n) return &node->p;
4250 g_assert_not_reached();
4252 return NULL;
4253 }
4255 /**
4256 * Return NRPathcode on the given side of the node.
4257 */
4258 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4259 {
4260 g_assert(node);
4262 if (me == &node->p) {
4263 if (node->p.other) return (NRPathcode)node->code;
4264 return NR_MOVETO;
4265 }
4267 if (me == &node->n) {
4268 if (node->n.other) return (NRPathcode)node->n.other->code;
4269 return NR_MOVETO;
4270 }
4272 g_assert_not_reached();
4274 return NR_END;
4275 }
4277 /**
4278 * Return node with the given index
4279 */
4280 Inkscape::NodePath::Node *
4281 sp_nodepath_get_node_by_index(int index)
4282 {
4283 Inkscape::NodePath::Node *e = NULL;
4285 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4286 if (!nodepath) {
4287 return e;
4288 }
4290 //find segment
4291 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4293 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4294 int n = g_list_length(sp->nodes);
4295 if (sp->closed) {
4296 n++;
4297 }
4299 //if the piece belongs to this subpath grab it
4300 //otherwise move onto the next subpath
4301 if (index < n) {
4302 e = sp->first;
4303 for (int i = 0; i < index; ++i) {
4304 e = e->n.other;
4305 }
4306 break;
4307 } else {
4308 if (sp->closed) {
4309 index -= (n+1);
4310 } else {
4311 index -= n;
4312 }
4313 }
4314 }
4316 return e;
4317 }
4319 /**
4320 * Returns plain text meaning of node type.
4321 */
4322 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4323 {
4324 unsigned retracted = 0;
4325 bool endnode = false;
4327 for (int which = -1; which <= 1; which += 2) {
4328 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4329 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4330 retracted ++;
4331 if (!side->other)
4332 endnode = true;
4333 }
4335 if (retracted == 0) {
4336 if (endnode) {
4337 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4338 return _("end node");
4339 } else {
4340 switch (node->type) {
4341 case Inkscape::NodePath::NODE_CUSP:
4342 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4343 return _("cusp");
4344 case Inkscape::NodePath::NODE_SMOOTH:
4345 // TRANSLATORS: "smooth" is an adjective here
4346 return _("smooth");
4347 case Inkscape::NodePath::NODE_SYMM:
4348 return _("symmetric");
4349 }
4350 }
4351 } else if (retracted == 1) {
4352 if (endnode) {
4353 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4354 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4355 } else {
4356 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4357 }
4358 } else {
4359 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4360 }
4362 return NULL;
4363 }
4365 /**
4366 * Handles content of statusbar as long as node tool is active.
4367 */
4368 void
4369 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4370 {
4371 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");
4372 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4374 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4375 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4376 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4377 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4379 SPDesktop *desktop = NULL;
4380 if (nodepath) {
4381 desktop = nodepath->desktop;
4382 } else {
4383 desktop = SP_ACTIVE_DESKTOP;
4384 }
4386 SPEventContext *ec = desktop->event_context;
4387 if (!ec) return;
4388 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4389 if (!mc) return;
4391 if (selected_nodes == 0) {
4392 Inkscape::Selection *sel = desktop->selection;
4393 if (!sel || sel->isEmpty()) {
4394 mc->setF(Inkscape::NORMAL_MESSAGE,
4395 _("Select a single object to edit its nodes or handles."));
4396 } else {
4397 if (nodepath) {
4398 mc->setF(Inkscape::NORMAL_MESSAGE,
4399 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.",
4400 "<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.",
4401 total_nodes),
4402 total_nodes);
4403 } else {
4404 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4405 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4406 } else {
4407 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4408 }
4409 }
4410 }
4411 } else if (nodepath && selected_nodes == 1) {
4412 mc->setF(Inkscape::NORMAL_MESSAGE,
4413 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4414 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4415 total_nodes),
4416 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4417 } else {
4418 if (selected_subpaths > 1) {
4419 mc->setF(Inkscape::NORMAL_MESSAGE,
4420 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4421 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4422 total_nodes),
4423 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4424 } else {
4425 mc->setF(Inkscape::NORMAL_MESSAGE,
4426 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4427 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4428 total_nodes),
4429 selected_nodes, total_nodes, when_selected);
4430 }
4431 }
4432 }
4434 /*
4435 * returns a *copy* of the curve of that object.
4436 */
4437 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4438 if (!object)
4439 return NULL;
4441 SPCurve *curve = NULL;
4442 if (SP_IS_PATH(object)) {
4443 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4444 curve = sp_curve_copy(curve_new);
4445 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4446 const gchar *svgd = object->repr->attribute(key);
4447 if (svgd) {
4448 NArtBpath *bpath = sp_svg_read_path(svgd);
4449 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4450 if (curve_new) {
4451 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4452 } else {
4453 g_free(bpath);
4454 }
4455 }
4456 }
4458 return curve;
4459 }
4461 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4462 if (!np || !np->object || !curve)
4463 return;
4465 if (SP_IS_PATH(np->object)) {
4466 if (SP_SHAPE(np->object)->path_effect_href) {
4467 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4468 } else {
4469 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4470 }
4471 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4472 // FIXME: this writing to string and then reading from string is bound to be slow.
4473 // create a method to convert from curve directly to 2geom...
4474 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4475 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4476 g_free(svgpath);
4478 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4479 }
4480 }
4482 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4483 np->show_helperpath = show;
4484 }
4486 /* this function does not work yet */
4487 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4488 np->straight_path = true;
4489 np->show_handles = false;
4490 g_message("add code to make the path straight.");
4491 // do sp_nodepath_convert_node_type on all nodes?
4492 // search for this text !!! "Make selected segments lines"
4493 }
4496 /*
4497 Local Variables:
4498 mode:c++
4499 c-file-style:"stroustrup"
4500 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4501 indent-tabs-mode:nil
4502 fill-column:99
4503 End:
4504 */
4505 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :