29a6f94cfab028e0f062cc0cc54310bbd827e190
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"
52 class NR::Matrix;
54 /// \todo
55 /// evil evil evil. FIXME: conflict of two different Path classes!
56 /// There is a conflict in the namespace between two classes named Path.
57 /// #include "sp-flowtext.h"
58 /// #include "sp-flowregion.h"
60 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
61 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
62 GType sp_flowregion_get_type (void);
63 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
64 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
65 GType sp_flowtext_get_type (void);
66 // end evil workaround
68 #include "helper/stlport.h"
71 /// \todo fixme: Implement these via preferences */
73 #define NODE_FILL 0xbfbfbf00
74 #define NODE_STROKE 0x000000ff
75 #define NODE_FILL_HI 0xff000000
76 #define NODE_STROKE_HI 0x000000ff
77 #define NODE_FILL_SEL 0x0000ffff
78 #define NODE_STROKE_SEL 0x000000ff
79 #define NODE_FILL_SEL_HI 0xff000000
80 #define NODE_STROKE_SEL_HI 0x000000ff
81 #define KNOT_FILL 0xffffffff
82 #define KNOT_STROKE 0x000000ff
83 #define KNOT_FILL_HI 0xff000000
84 #define KNOT_STROKE_HI 0x000000ff
86 static GMemChunk *nodechunk = NULL;
88 /* Creation from object */
90 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
91 static gchar *parse_nodetypes(gchar const *types, gint length);
93 /* Object updating */
95 static void stamp_repr(Inkscape::NodePath::Path *np);
96 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
97 static gchar *create_typestr(Inkscape::NodePath::Path *np);
99 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
101 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
103 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
105 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
107 /* Adjust handle placement, if the node or the other handle is moved */
108 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
109 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
111 /* Node event callbacks */
112 static void node_clicked(SPKnot *knot, guint state, gpointer data);
113 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
114 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
115 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 /* Handle event callbacks */
118 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
119 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
120 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
121 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
122 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
123 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
125 /* Constructors and destructors */
127 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
128 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
129 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
130 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
131 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
132 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
133 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
135 /* Helpers */
137 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
138 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
139 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
141 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
142 static void sp_nodepath_object_set_curve (SPObject *object, SPCurve *curve);
144 // active_node indicates mouseover node
145 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
147 /**
148 * \brief Creates new nodepath from item
149 * repr_key_in should be NULL, unless you are called Johan or really know what you are doing! (See "if (repr_key_in)" below)
150 */
151 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in)
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;
206 // we need to update item's transform from the repr here,
207 // because they may be out of sync when we respond
208 // to a change in repr by regenerating nodepath --bb
209 sp_object_read_attr(object, "transform");
211 np->i2d = sp_item_i2d_affine(SP_ITEM(object));
212 np->d2i = np->i2d.inverse();
214 np->repr = repr;
215 if (repr_key_in) {
216 np->repr_key = g_strdup(repr_key_in);
217 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
218 np->show_helperpath = true;
219 } else {
220 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
221 if ( SP_SHAPE(np->object)->path_effect_href ) {
222 np->repr_key = g_strdup("inkscape:original-d");
223 np->show_helperpath = true;
224 } else {
225 np->repr_key = g_strdup("d");
226 }
227 }
229 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
230 gchar *typestr = parse_nodetypes(nodetypes, length);
232 // create the subpath(s) from the bpath
233 NArtBpath *b = bpath;
234 while (b->code != NR_END) {
235 b = subpath_from_bpath(np, b, typestr + (b - bpath));
236 }
238 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
239 np->subpaths = g_list_reverse(np->subpaths);
241 g_free(typestr);
242 sp_curve_unref(curve);
244 // create the livarot representation from the same item
245 sp_nodepath_ensure_livarot_path(np);
247 // Draw helper curve
248 if (np->show_helperpath) {
249 SPCurve *helper_curve = sp_curve_copy(np->curve);
250 sp_curve_transform(helper_curve, np->i2d );
251 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
252 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), 0xff0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
253 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
254 sp_canvas_item_show(np->helper_path);
255 sp_curve_unref(helper_curve);
256 }
258 return np;
259 }
261 /**
262 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
263 */
264 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
266 if (!np) //soft fail, like delete
267 return;
269 while (np->subpaths) {
270 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
271 }
273 //Inform the ShapeEditor that made me, if any, that I am gone.
274 if (np->shape_editor)
275 np->shape_editor->nodepath_destroyed();
277 g_assert(!np->selected);
279 if (np->livarot_path) {
280 delete np->livarot_path;
281 np->livarot_path = NULL;
282 }
284 if (np->helper_path) {
285 GtkObject *temp = np->helper_path;
286 np->helper_path = NULL;
287 gtk_object_destroy(temp);
288 }
289 if (np->curve) {
290 sp_curve_unref(np->curve);
291 np->curve = NULL;
292 }
294 if (np->repr_key) {
295 g_free(np->repr_key);
296 np->repr_key = NULL;
297 }
298 if (np->repr_nodetypes_key) {
299 g_free(np->repr_nodetypes_key);
300 np->repr_nodetypes_key = NULL;
301 }
303 np->desktop = NULL;
305 g_free(np);
306 }
309 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
310 {
311 if (np && np->livarot_path == NULL && np->object && SP_IS_ITEM(np->object)) {
312 SPCurve *curve = create_curve(np);
313 NArtBpath *bpath = SP_CURVE_BPATH(curve);
314 np->livarot_path = bpath_to_Path(bpath);
316 if (np->livarot_path)
317 np->livarot_path->ConvertWithBackData(0.01);
319 sp_curve_unref(curve);
320 }
321 }
324 /**
325 * Return the node count of a given NodeSubPath.
326 */
327 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
328 {
329 if (!subpath)
330 return 0;
331 gint nodeCount = g_list_length(subpath->nodes);
332 return nodeCount;
333 }
335 /**
336 * Return the node count of a given NodePath.
337 */
338 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
339 {
340 if (!np)
341 return 0;
342 gint nodeCount = 0;
343 for (GList *item = np->subpaths ; item ; item=item->next) {
344 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
345 nodeCount += g_list_length(subpath->nodes);
346 }
347 return nodeCount;
348 }
350 /**
351 * Return the subpath count of a given NodePath.
352 */
353 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
354 {
355 if (!np)
356 return 0;
357 return g_list_length (np->subpaths);
358 }
360 /**
361 * Return the selected node count of a given NodePath.
362 */
363 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
364 {
365 if (!np)
366 return 0;
367 return g_list_length (np->selected);
368 }
370 /**
371 * Return the number of subpaths where nodes are selected in a given NodePath.
372 */
373 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
374 {
375 if (!np)
376 return 0;
377 if (!np->selected)
378 return 0;
379 if (!np->selected->next)
380 return 1;
381 gint count = 0;
382 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
383 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
384 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
385 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
386 if (node->selected) {
387 count ++;
388 break;
389 }
390 }
391 }
392 return count;
393 }
395 /**
396 * Clean up a nodepath after editing.
397 *
398 * Currently we are deleting trivial subpaths.
399 */
400 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
401 {
402 GList *badSubPaths = NULL;
404 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
405 for (GList *l = nodepath->subpaths; l ; l=l->next) {
406 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
407 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
408 badSubPaths = g_list_append(badSubPaths, sp);
409 }
411 //Delete them. This second step is because sp_nodepath_subpath_destroy()
412 //also removes the subpath from nodepath->subpaths
413 for (GList *l = badSubPaths; l ; l=l->next) {
414 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
415 sp_nodepath_subpath_destroy(sp);
416 }
418 g_list_free(badSubPaths);
419 }
421 /**
422 * Create new nodepath from b, make it subpath of np.
423 * \param t The node type.
424 * \todo Fixme: t should be a proper type, rather than gchar
425 */
426 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
427 {
428 NR::Point ppos, pos, npos;
430 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
432 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
433 bool const closed = (b->code == NR_MOVETO);
435 pos = NR::Point(b->x3, b->y3) * np->i2d;
436 if (b[1].code == NR_CURVETO) {
437 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
438 } else {
439 npos = pos;
440 }
441 Inkscape::NodePath::Node *n;
442 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
443 g_assert(sp->first == n);
444 g_assert(sp->last == n);
446 b++;
447 t++;
448 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
449 pos = NR::Point(b->x3, b->y3) * np->i2d;
450 if (b->code == NR_CURVETO) {
451 ppos = NR::Point(b->x2, b->y2) * np->i2d;
452 } else {
453 ppos = pos;
454 }
455 if (b[1].code == NR_CURVETO) {
456 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
457 } else {
458 npos = pos;
459 }
460 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
461 b++;
462 t++;
463 }
465 if (closed) sp_nodepath_subpath_close(sp);
467 return b;
468 }
470 /**
471 * Convert from sodipodi:nodetypes to new style type string.
472 */
473 static gchar *parse_nodetypes(gchar const *types, gint length)
474 {
475 g_assert(length > 0);
477 gchar *typestr = g_new(gchar, length + 1);
479 gint pos = 0;
481 if (types) {
482 for (gint i = 0; types[i] && ( i < length ); i++) {
483 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
484 if (types[i] != '\0') {
485 switch (types[i]) {
486 case 's':
487 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
488 break;
489 case 'z':
490 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
491 break;
492 case 'c':
493 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
494 break;
495 default:
496 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
497 break;
498 }
499 }
500 }
501 }
503 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
505 return typestr;
506 }
508 /**
509 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
510 * updated but repr is not (for speed). Used during curve and node drag.
511 */
512 static void update_object(Inkscape::NodePath::Path *np)
513 {
514 g_assert(np);
516 sp_curve_unref(np->curve);
517 np->curve = create_curve(np);
519 sp_nodepath_object_set_curve(np->object, np->curve);
521 if (np->show_helperpath) {
522 SPCurve * helper_curve = sp_curve_copy(np->curve);
523 sp_curve_transform(helper_curve, np->i2d );
524 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
525 sp_curve_unref(helper_curve);
526 }
527 }
529 /**
530 * Update XML path node with data from path object.
531 */
532 static void update_repr_internal(Inkscape::NodePath::Path *np)
533 {
534 g_assert(np);
536 Inkscape::XML::Node *repr = np->object->repr;
538 sp_curve_unref(np->curve);
539 np->curve = create_curve(np);
541 gchar *typestr = create_typestr(np);
542 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
544 // determine if path has an effect applied and write to correct "d" attribute.
545 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
546 np->local_change++;
547 repr->setAttribute(np->repr_key, svgpath);
548 }
550 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
551 np->local_change++;
552 repr->setAttribute(np->repr_nodetypes_key, typestr);
553 }
555 g_free(svgpath);
556 g_free(typestr);
558 if (np->show_helperpath) {
559 SPCurve * helper_curve = sp_curve_copy(np->curve);
560 sp_curve_transform(helper_curve, np->i2d );
561 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
562 sp_curve_unref(helper_curve);
563 }
564 }
566 /**
567 * Update XML path node with data from path object, commit changes forever.
568 */
569 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
570 {
571 //fixme: np can be NULL, so check before proceeding
572 g_return_if_fail(np != NULL);
574 if (np->livarot_path) {
575 delete np->livarot_path;
576 np->livarot_path = NULL;
577 }
579 update_repr_internal(np);
580 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
582 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
583 annotation);
584 }
586 /**
587 * Update XML path node with data from path object, commit changes with undo.
588 */
589 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
590 {
591 if (np->livarot_path) {
592 delete np->livarot_path;
593 np->livarot_path = NULL;
594 }
596 update_repr_internal(np);
597 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
598 annotation);
599 }
601 /**
602 * Make duplicate of path, replace corresponding XML node in tree, commit.
603 */
604 static void stamp_repr(Inkscape::NodePath::Path *np)
605 {
606 g_assert(np);
608 Inkscape::XML::Node *old_repr = np->object->repr;
609 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
611 // remember the position of the item
612 gint pos = old_repr->position();
613 // remember parent
614 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
616 SPCurve *curve = create_curve(np);
617 gchar *typestr = create_typestr(np);
619 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
621 new_repr->setAttribute(np->repr_key, svgpath);
622 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
624 // add the new repr to the parent
625 parent->appendChild(new_repr);
626 // move to the saved position
627 new_repr->setPosition(pos > 0 ? pos : 0);
629 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
630 _("Stamp"));
632 Inkscape::GC::release(new_repr);
633 g_free(svgpath);
634 g_free(typestr);
635 sp_curve_unref(curve);
636 }
638 /**
639 * Create curve from path.
640 */
641 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
642 {
643 SPCurve *curve = sp_curve_new();
645 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
646 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
647 sp_curve_moveto(curve,
648 sp->first->pos * np->d2i);
649 Inkscape::NodePath::Node *n = sp->first->n.other;
650 while (n) {
651 NR::Point const end_pt = n->pos * np->d2i;
652 switch (n->code) {
653 case NR_LINETO:
654 sp_curve_lineto(curve, end_pt);
655 break;
656 case NR_CURVETO:
657 sp_curve_curveto(curve,
658 n->p.other->n.pos * np->d2i,
659 n->p.pos * np->d2i,
660 end_pt);
661 break;
662 default:
663 g_assert_not_reached();
664 break;
665 }
666 if (n != sp->last) {
667 n = n->n.other;
668 } else {
669 n = NULL;
670 }
671 }
672 if (sp->closed) {
673 sp_curve_closepath(curve);
674 }
675 }
677 return curve;
678 }
680 /**
681 * Convert path type string to sodipodi:nodetypes style.
682 */
683 static gchar *create_typestr(Inkscape::NodePath::Path *np)
684 {
685 gchar *typestr = g_new(gchar, 32);
686 gint len = 32;
687 gint pos = 0;
689 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
690 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
692 if (pos >= len) {
693 typestr = g_renew(gchar, typestr, len + 32);
694 len += 32;
695 }
697 typestr[pos++] = 'c';
699 Inkscape::NodePath::Node *n;
700 n = sp->first->n.other;
701 while (n) {
702 gchar code;
704 switch (n->type) {
705 case Inkscape::NodePath::NODE_CUSP:
706 code = 'c';
707 break;
708 case Inkscape::NodePath::NODE_SMOOTH:
709 code = 's';
710 break;
711 case Inkscape::NodePath::NODE_SYMM:
712 code = 'z';
713 break;
714 default:
715 g_assert_not_reached();
716 code = '\0';
717 break;
718 }
720 if (pos >= len) {
721 typestr = g_renew(gchar, typestr, len + 32);
722 len += 32;
723 }
725 typestr[pos++] = code;
727 if (n != sp->last) {
728 n = n->n.other;
729 } else {
730 n = NULL;
731 }
732 }
733 }
735 if (pos >= len) {
736 typestr = g_renew(gchar, typestr, len + 1);
737 len += 1;
738 }
740 typestr[pos++] = '\0';
742 return typestr;
743 }
745 /**
746 * Returns current path in context. // later eliminate this function at all!
747 */
748 static Inkscape::NodePath::Path *sp_nodepath_current()
749 {
750 if (!SP_ACTIVE_DESKTOP) {
751 return NULL;
752 }
754 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
756 if (!SP_IS_NODE_CONTEXT(event_context)) {
757 return NULL;
758 }
760 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
761 }
765 /**
766 \brief Fills node and handle positions for three nodes, splitting line
767 marked by end at distance t.
768 */
769 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
770 {
771 g_assert(new_path != NULL);
772 g_assert(end != NULL);
774 g_assert(end->p.other == new_path);
775 Inkscape::NodePath::Node *start = new_path->p.other;
776 g_assert(start);
778 if (end->code == NR_LINETO) {
779 new_path->type =Inkscape::NodePath::NODE_CUSP;
780 new_path->code = NR_LINETO;
781 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
782 } else {
783 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
784 new_path->code = NR_CURVETO;
785 gdouble s = 1 - t;
786 for (int dim = 0; dim < 2; dim++) {
787 NR::Coord const f000 = start->pos[dim];
788 NR::Coord const f001 = start->n.pos[dim];
789 NR::Coord const f011 = end->p.pos[dim];
790 NR::Coord const f111 = end->pos[dim];
791 NR::Coord const f00t = s * f000 + t * f001;
792 NR::Coord const f01t = s * f001 + t * f011;
793 NR::Coord const f11t = s * f011 + t * f111;
794 NR::Coord const f0tt = s * f00t + t * f01t;
795 NR::Coord const f1tt = s * f01t + t * f11t;
796 NR::Coord const fttt = s * f0tt + t * f1tt;
797 start->n.pos[dim] = f00t;
798 new_path->p.pos[dim] = f0tt;
799 new_path->pos[dim] = fttt;
800 new_path->n.pos[dim] = f1tt;
801 end->p.pos[dim] = f11t;
802 }
803 }
804 }
806 /**
807 * Adds new node on direct line between two nodes, activates handles of all
808 * three nodes.
809 */
810 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
811 {
812 g_assert(end);
813 g_assert(end->subpath);
814 g_assert(g_list_find(end->subpath->nodes, end));
816 Inkscape::NodePath::Node *start = end->p.other;
817 g_assert( start->n.other == end );
818 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
819 end,
820 (NRPathcode)end->code == NR_LINETO?
821 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
822 (NRPathcode)end->code,
823 &start->pos, &start->pos, &start->n.pos);
824 sp_nodepath_line_midpoint(newnode, end, t);
826 sp_node_adjust_handles(start);
827 sp_node_update_handles(start);
828 sp_node_update_handles(newnode);
829 sp_node_adjust_handles(end);
830 sp_node_update_handles(end);
832 return newnode;
833 }
835 /**
836 \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
837 */
838 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
839 {
840 g_assert(node);
841 g_assert(node->subpath);
842 g_assert(g_list_find(node->subpath->nodes, node));
844 Inkscape::NodePath::SubPath *sp = node->subpath;
845 Inkscape::NodePath::Path *np = sp->nodepath;
847 if (sp->closed) {
848 sp_nodepath_subpath_open(sp, node);
849 return sp->first;
850 } else {
851 // no break for end nodes
852 if (node == sp->first) return NULL;
853 if (node == sp->last ) return NULL;
855 // create a new subpath
856 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
858 // duplicate the break node as start of the new subpath
859 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
861 while (node->n.other) { // copy the remaining nodes into the new subpath
862 Inkscape::NodePath::Node *n = node->n.other;
863 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);
864 if (n->selected) {
865 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
866 }
867 sp_nodepath_node_destroy(n); // remove the point on the original subpath
868 }
870 return newnode;
871 }
872 }
874 /**
875 * Duplicate node and connect to neighbours.
876 */
877 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
878 {
879 g_assert(node);
880 g_assert(node->subpath);
881 g_assert(g_list_find(node->subpath->nodes, node));
883 Inkscape::NodePath::SubPath *sp = node->subpath;
885 NRPathcode code = (NRPathcode) node->code;
886 if (code == NR_MOVETO) { // if node is the endnode,
887 node->code = NR_LINETO; // new one is inserted before it, so change that to line
888 }
890 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
892 if (!node->n.other || !node->p.other) // if node is an endnode, select it
893 return node;
894 else
895 return newnode; // otherwise select the newly created node
896 }
898 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
899 {
900 node->p.pos = (node->pos + (node->pos - node->n.pos));
901 }
903 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
904 {
905 node->n.pos = (node->pos + (node->pos - node->p.pos));
906 }
908 /**
909 * Change line type at node, with side effects on neighbours.
910 */
911 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
912 {
913 g_assert(end);
914 g_assert(end->subpath);
915 g_assert(end->p.other);
917 if (end->code == static_cast< guint > ( code ) )
918 return;
920 Inkscape::NodePath::Node *start = end->p.other;
922 end->code = code;
924 if (code == NR_LINETO) {
925 if (start->code == NR_LINETO) {
926 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
927 }
928 if (end->n.other) {
929 if (end->n.other->code == NR_LINETO) {
930 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
931 }
932 }
933 } else {
934 NR::Point delta = end->pos - start->pos;
935 start->n.pos = start->pos + delta / 3;
936 end->p.pos = end->pos - delta / 3;
937 sp_node_adjust_handle(start, 1);
938 sp_node_adjust_handle(end, -1);
939 }
941 sp_node_update_handles(start);
942 sp_node_update_handles(end);
943 }
945 /**
946 * Change node type, and its handles accordingly.
947 */
948 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
949 {
950 g_assert(node);
951 g_assert(node->subpath);
953 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
954 return node;
956 if ((node->p.other != NULL) && (node->n.other != NULL)) {
957 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
958 type =Inkscape::NodePath::NODE_CUSP;
959 }
960 }
962 node->type = type;
964 if (node->type == Inkscape::NodePath::NODE_CUSP) {
965 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
966 node->knot->setSize (node->selected? 11 : 9);
967 sp_knot_update_ctrl(node->knot);
968 } else {
969 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
970 node->knot->setSize (node->selected? 9 : 7);
971 sp_knot_update_ctrl(node->knot);
972 }
974 // if one of handles is mouseovered, preserve its position
975 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
976 sp_node_adjust_handle(node, 1);
977 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
978 sp_node_adjust_handle(node, -1);
979 } else {
980 sp_node_adjust_handles(node);
981 }
983 sp_node_update_handles(node);
985 sp_nodepath_update_statusbar(node->subpath->nodepath);
987 return node;
988 }
990 /**
991 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
992 * adjacent segments from lines to curves.
993 */
994 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
995 {
996 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
997 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
999 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1000 if (p_line && n_line) {
1001 // only if both adjacent segments are lines,
1002 // convert both to curves:
1004 // BEFORE:
1005 {
1006 node->code = NR_CURVETO;
1007 NR::Point delta = node->n.other->pos - node->p.other->pos;
1008 node->p.pos = node->pos - delta / 4;
1009 }
1011 // AFTER:
1012 {
1013 node->n.other->code = NR_CURVETO;
1014 NR::Point delta = node->p.other->pos - node->n.other->pos;
1015 node->n.pos = node->pos - delta / 4;
1016 }
1018 sp_node_update_handles(node);
1019 }
1020 }
1022 sp_nodepath_set_node_type (node, type);
1023 }
1025 /**
1026 * Move node to point, and adjust its and neighbouring handles.
1027 */
1028 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1029 {
1030 NR::Point delta = p - node->pos;
1031 node->pos = p;
1033 node->p.pos += delta;
1034 node->n.pos += delta;
1036 Inkscape::NodePath::Node *node_p = NULL;
1037 Inkscape::NodePath::Node *node_n = NULL;
1039 if (node->p.other) {
1040 if (node->code == NR_LINETO) {
1041 sp_node_adjust_handle(node, 1);
1042 sp_node_adjust_handle(node->p.other, -1);
1043 node_p = node->p.other;
1044 }
1045 }
1046 if (node->n.other) {
1047 if (node->n.other->code == NR_LINETO) {
1048 sp_node_adjust_handle(node, -1);
1049 sp_node_adjust_handle(node->n.other, 1);
1050 node_n = node->n.other;
1051 }
1052 }
1054 // this function is only called from batch movers that will update display at the end
1055 // themselves, so here we just move all the knots without emitting move signals, for speed
1056 sp_node_update_handles(node, false);
1057 if (node_n) {
1058 sp_node_update_handles(node_n, false);
1059 }
1060 if (node_p) {
1061 sp_node_update_handles(node_p, false);
1062 }
1063 }
1065 /**
1066 * Call sp_node_moveto() for node selection and handle possible snapping.
1067 */
1068 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1069 bool const snap = true)
1070 {
1071 NR::Coord best = NR_HUGE;
1072 NR::Point delta(dx, dy);
1073 NR::Point best_pt = delta;
1075 if (snap) {
1076 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1078 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1079 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1080 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->object));
1081 if (s.getDistance() < best) {
1082 best = s.getDistance();
1083 best_pt = s.getPoint() - n->pos;
1084 }
1085 }
1086 }
1088 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1089 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1090 sp_node_moveto(n, n->pos + best_pt);
1091 }
1093 // do not update repr here so that node dragging is acceptably fast
1094 update_object(nodepath);
1095 }
1097 /**
1098 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1099 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1100 near x = 0.
1101 */
1102 double
1103 sculpt_profile (double x, double alpha, guint profile)
1104 {
1105 if (x >= 1)
1106 return 0;
1107 if (x <= 0)
1108 return 1;
1110 switch (profile) {
1111 case SCULPT_PROFILE_LINEAR:
1112 return 1 - x;
1113 case SCULPT_PROFILE_BELL:
1114 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1115 case SCULPT_PROFILE_ELLIPTIC:
1116 return sqrt(1 - x*x);
1117 }
1119 return 1;
1120 }
1122 double
1123 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1124 {
1125 // extremely primitive for now, don't have time to look for the real one
1126 double lower = NR::L2(b - a);
1127 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1128 return (lower + upper)/2;
1129 }
1131 void
1132 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1133 {
1134 n->pos = n->origin + delta;
1135 n->n.pos = n->n.origin + delta_n;
1136 n->p.pos = n->p.origin + delta_p;
1137 sp_node_adjust_handles(n);
1138 sp_node_update_handles(n, false);
1139 }
1141 /**
1142 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1143 * on how far they are from the dragged node n.
1144 */
1145 static void
1146 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1147 {
1148 g_assert (n);
1149 g_assert (nodepath);
1150 g_assert (n->subpath->nodepath == nodepath);
1152 double pressure = n->knot->pressure;
1153 if (pressure == 0)
1154 pressure = 0.5; // default
1155 pressure = CLAMP (pressure, 0.2, 0.8);
1157 // map pressure to alpha = 1/5 ... 5
1158 double alpha = 1 - 2 * fabs(pressure - 0.5);
1159 if (pressure > 0.5)
1160 alpha = 1/alpha;
1162 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1164 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1165 // Only one subpath has selected nodes:
1166 // use linear mode, where the distance from n to node being dragged is calculated along the path
1168 double n_sel_range = 0, p_sel_range = 0;
1169 guint n_nodes = 0, p_nodes = 0;
1170 guint n_sel_nodes = 0, p_sel_nodes = 0;
1172 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1173 {
1174 double n_range = 0, p_range = 0;
1175 bool n_going = true, p_going = true;
1176 Inkscape::NodePath::Node *n_node = n;
1177 Inkscape::NodePath::Node *p_node = n;
1178 do {
1179 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1180 if (n_node && n_going)
1181 n_node = n_node->n.other;
1182 if (n_node == NULL) {
1183 n_going = false;
1184 } else {
1185 n_nodes ++;
1186 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1187 if (n_node->selected) {
1188 n_sel_nodes ++;
1189 n_sel_range = n_range;
1190 }
1191 if (n_node == p_node) {
1192 n_going = false;
1193 p_going = false;
1194 }
1195 }
1196 if (p_node && p_going)
1197 p_node = p_node->p.other;
1198 if (p_node == NULL) {
1199 p_going = false;
1200 } else {
1201 p_nodes ++;
1202 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1203 if (p_node->selected) {
1204 p_sel_nodes ++;
1205 p_sel_range = p_range;
1206 }
1207 if (p_node == n_node) {
1208 n_going = false;
1209 p_going = false;
1210 }
1211 }
1212 } while (n_going || p_going);
1213 }
1215 // Second pass: actually move nodes in this subpath
1216 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1217 {
1218 double n_range = 0, p_range = 0;
1219 bool n_going = true, p_going = true;
1220 Inkscape::NodePath::Node *n_node = n;
1221 Inkscape::NodePath::Node *p_node = n;
1222 do {
1223 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1224 if (n_node && n_going)
1225 n_node = n_node->n.other;
1226 if (n_node == NULL) {
1227 n_going = false;
1228 } else {
1229 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1230 if (n_node->selected) {
1231 sp_nodepath_move_node_and_handles (n_node,
1232 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1233 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1234 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1235 }
1236 if (n_node == p_node) {
1237 n_going = false;
1238 p_going = false;
1239 }
1240 }
1241 if (p_node && p_going)
1242 p_node = p_node->p.other;
1243 if (p_node == NULL) {
1244 p_going = false;
1245 } else {
1246 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1247 if (p_node->selected) {
1248 sp_nodepath_move_node_and_handles (p_node,
1249 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1250 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1251 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1252 }
1253 if (p_node == n_node) {
1254 n_going = false;
1255 p_going = false;
1256 }
1257 }
1258 } while (n_going || p_going);
1259 }
1261 } else {
1262 // Multiple subpaths have selected nodes:
1263 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1264 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1265 // fix the pear-like shape when sculpting e.g. a ring
1267 // First pass: calculate range
1268 gdouble direct_range = 0;
1269 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1270 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1271 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1272 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1273 if (node->selected) {
1274 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1275 }
1276 }
1277 }
1279 // Second pass: actually move nodes
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 if (direct_range > 1e-6) {
1286 sp_nodepath_move_node_and_handles (node,
1287 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1288 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1289 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1290 } else {
1291 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1292 }
1294 }
1295 }
1296 }
1297 }
1299 // do not update repr here so that node dragging is acceptably fast
1300 update_object(nodepath);
1301 }
1304 /**
1305 * Move node selection to point, adjust its and neighbouring handles,
1306 * handle possible snapping, and commit the change with possible undo.
1307 */
1308 void
1309 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1310 {
1311 if (!nodepath) return;
1313 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1315 if (dx == 0) {
1316 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1317 } else if (dy == 0) {
1318 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1319 } else {
1320 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1321 }
1322 }
1324 /**
1325 * Move node selection off screen and commit the change.
1326 */
1327 void
1328 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1329 {
1330 // borrowed from sp_selection_move_screen in selection-chemistry.c
1331 // we find out the current zoom factor and divide deltas by it
1332 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1334 gdouble zoom = desktop->current_zoom();
1335 gdouble zdx = dx / zoom;
1336 gdouble zdy = dy / zoom;
1338 if (!nodepath) return;
1340 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1342 if (dx == 0) {
1343 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1344 } else if (dy == 0) {
1345 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1346 } else {
1347 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1348 }
1349 }
1351 /** If they don't yet exist, creates knot and line for the given side of the node */
1352 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1353 {
1354 if (!side->knot) {
1355 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"));
1357 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1358 side->knot->setSize (7);
1359 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1360 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1361 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1362 sp_knot_update_ctrl(side->knot);
1364 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1365 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1366 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1367 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1368 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1369 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1370 }
1372 if (!side->line) {
1373 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1374 SP_TYPE_CTRLLINE, NULL);
1375 }
1376 }
1378 /**
1379 * Ensure the given handle of the node is visible/invisible, update its screen position
1380 */
1381 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1382 {
1383 g_assert(node != NULL);
1385 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1386 NRPathcode code = sp_node_path_code_from_side(node, side);
1388 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1390 if (show_handle) {
1391 if (!side->knot) { // No handle knot at all
1392 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1393 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1394 side->knot->pos = side->pos;
1395 if (side->knot->item)
1396 SP_CTRL(side->knot->item)->moveto(side->pos);
1397 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1398 sp_knot_show(side->knot);
1399 } else {
1400 if (side->knot->pos != side->pos) { // only if it's really moved
1401 if (fire_move_signals) {
1402 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1403 } else {
1404 sp_knot_moveto(side->knot, &side->pos);
1405 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1406 }
1407 }
1408 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1409 sp_knot_show(side->knot);
1410 }
1411 }
1412 sp_canvas_item_show(side->line);
1413 } else {
1414 if (side->knot) {
1415 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1416 sp_knot_hide(side->knot);
1417 }
1418 }
1419 if (side->line) {
1420 sp_canvas_item_hide(side->line);
1421 }
1422 }
1423 }
1425 /**
1426 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1427 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1428 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1429 * updated; otherwise, just move the knots silently (used in batch moves).
1430 */
1431 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1432 {
1433 g_assert(node != NULL);
1435 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1436 sp_knot_show(node->knot);
1437 }
1439 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1440 if (fire_move_signals)
1441 sp_knot_set_position(node->knot, &node->pos, 0);
1442 else
1443 sp_knot_moveto(node->knot, &node->pos);
1444 }
1446 gboolean show_handles = node->selected;
1447 if (node->p.other != NULL) {
1448 if (node->p.other->selected) show_handles = TRUE;
1449 }
1450 if (node->n.other != NULL) {
1451 if (node->n.other->selected) show_handles = TRUE;
1452 }
1454 if (node->subpath->nodepath->show_handles == false)
1455 show_handles = FALSE;
1457 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1458 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1459 }
1461 /**
1462 * Call sp_node_update_handles() for all nodes on subpath.
1463 */
1464 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1465 {
1466 g_assert(subpath != NULL);
1468 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1469 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1470 }
1471 }
1473 /**
1474 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1475 */
1476 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1477 {
1478 g_assert(nodepath != NULL);
1480 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1481 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1482 }
1483 }
1485 void
1486 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1487 {
1488 if (nodepath == NULL) return;
1490 nodepath->show_handles = show;
1491 sp_nodepath_update_handles(nodepath);
1492 }
1494 /**
1495 * Adds all selected nodes in nodepath to list.
1496 */
1497 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1498 {
1499 StlConv<Node *>::list(l, selected);
1500 /// \todo this adds a copying, rework when the selection becomes a stl list
1501 }
1503 /**
1504 * Align selected nodes on the specified axis.
1505 */
1506 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1507 {
1508 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1509 return;
1510 }
1512 if ( !nodepath->selected->next ) { // only one node selected
1513 return;
1514 }
1515 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1516 NR::Point dest(pNode->pos);
1517 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1518 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1519 if (pNode) {
1520 dest[axis] = pNode->pos[axis];
1521 sp_node_moveto(pNode, dest);
1522 }
1523 }
1525 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1526 }
1528 /// Helper struct.
1529 struct NodeSort
1530 {
1531 Inkscape::NodePath::Node *_node;
1532 NR::Coord _coord;
1533 /// \todo use vectorof pointers instead of calling copy ctor
1534 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1535 _node(node), _coord(node->pos[axis])
1536 {}
1538 };
1540 static bool operator<(NodeSort const &a, NodeSort const &b)
1541 {
1542 return (a._coord < b._coord);
1543 }
1545 /**
1546 * Distribute selected nodes on the specified axis.
1547 */
1548 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1549 {
1550 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1551 return;
1552 }
1554 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1555 return;
1556 }
1558 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1559 std::vector<NodeSort> sorted;
1560 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1561 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1562 if (pNode) {
1563 NodeSort n(pNode, axis);
1564 sorted.push_back(n);
1565 //dest[axis] = pNode->pos[axis];
1566 //sp_node_moveto(pNode, dest);
1567 }
1568 }
1569 std::sort(sorted.begin(), sorted.end());
1570 unsigned int len = sorted.size();
1571 //overall bboxes span
1572 float dist = (sorted.back()._coord -
1573 sorted.front()._coord);
1574 //new distance between each bbox
1575 float step = (dist) / (len - 1);
1576 float pos = sorted.front()._coord;
1577 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1578 it < sorted.end();
1579 it ++ )
1580 {
1581 NR::Point dest((*it)._node->pos);
1582 dest[axis] = pos;
1583 sp_node_moveto((*it)._node, dest);
1584 pos += step;
1585 }
1587 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1588 }
1591 /**
1592 * Call sp_nodepath_line_add_node() for all selected segments.
1593 */
1594 void
1595 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1596 {
1597 if (!nodepath) {
1598 return;
1599 }
1601 GList *nl = NULL;
1603 int n_added = 0;
1605 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1606 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1607 g_assert(t->selected);
1608 if (t->p.other && t->p.other->selected) {
1609 nl = g_list_prepend(nl, t);
1610 }
1611 }
1613 while (nl) {
1614 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1615 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1616 sp_nodepath_node_select(n, TRUE, FALSE);
1617 n_added ++;
1618 nl = g_list_remove(nl, t);
1619 }
1621 /** \todo fixme: adjust ? */
1622 sp_nodepath_update_handles(nodepath);
1624 if (n_added > 1) {
1625 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1626 } else if (n_added > 0) {
1627 sp_nodepath_update_repr(nodepath, _("Add node"));
1628 }
1630 sp_nodepath_update_statusbar(nodepath);
1631 }
1633 /**
1634 * Select segment nearest to point
1635 */
1636 void
1637 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1638 {
1639 if (!nodepath) {
1640 return;
1641 }
1643 sp_nodepath_ensure_livarot_path(nodepath);
1644 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1645 if (!maybe_position) {
1646 return;
1647 }
1648 Path::cut_position position = *maybe_position;
1650 //find segment to segment
1651 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1653 //fixme: this can return NULL, so check before proceeding.
1654 g_return_if_fail(e != NULL);
1656 gboolean force = FALSE;
1657 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1658 force = TRUE;
1659 }
1660 sp_nodepath_node_select(e, (gboolean) toggle, force);
1661 if (e->p.other)
1662 sp_nodepath_node_select(e->p.other, TRUE, force);
1664 sp_nodepath_update_handles(nodepath);
1666 sp_nodepath_update_statusbar(nodepath);
1667 }
1669 /**
1670 * Add a node nearest to point
1671 */
1672 void
1673 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1674 {
1675 if (!nodepath) {
1676 return;
1677 }
1679 sp_nodepath_ensure_livarot_path(nodepath);
1680 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1681 if (!maybe_position) {
1682 return;
1683 }
1684 Path::cut_position position = *maybe_position;
1686 //find segment to split
1687 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1689 //don't know why but t seems to flip for lines
1690 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1691 position.t = 1.0 - position.t;
1692 }
1693 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1694 sp_nodepath_node_select(n, FALSE, TRUE);
1696 /* fixme: adjust ? */
1697 sp_nodepath_update_handles(nodepath);
1699 sp_nodepath_update_repr(nodepath, _("Add node"));
1701 sp_nodepath_update_statusbar(nodepath);
1702 }
1704 /*
1705 * Adjusts a segment so that t moves by a certain delta for dragging
1706 * converts lines to curves
1707 *
1708 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1709 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1710 */
1711 void
1712 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1713 {
1714 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1716 //fixme: e and e->p can be NULL, so check for those before proceeding
1717 g_return_if_fail(e != NULL);
1718 g_return_if_fail(&e->p != NULL);
1720 /* feel good is an arbitrary parameter that distributes the delta between handles
1721 * if t of the drag point is less than 1/6 distance form the endpoint only
1722 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1723 */
1724 double feel_good;
1725 if (t <= 1.0 / 6.0)
1726 feel_good = 0;
1727 else if (t <= 0.5)
1728 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1729 else if (t <= 5.0 / 6.0)
1730 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1731 else
1732 feel_good = 1;
1734 //if we're dragging a line convert it to a curve
1735 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1736 sp_nodepath_set_line_type(e, NR_CURVETO);
1737 }
1739 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1740 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1741 e->p.other->n.pos += offsetcoord0;
1742 e->p.pos += offsetcoord1;
1744 // adjust handles of adjacent nodes where necessary
1745 sp_node_adjust_handle(e,1);
1746 sp_node_adjust_handle(e->p.other,-1);
1748 sp_nodepath_update_handles(e->subpath->nodepath);
1750 update_object(e->subpath->nodepath);
1752 sp_nodepath_update_statusbar(e->subpath->nodepath);
1753 }
1756 /**
1757 * Call sp_nodepath_break() for all selected segments.
1758 */
1759 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1760 {
1761 if (!nodepath) return;
1763 GList *temp = NULL;
1764 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1765 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1766 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1767 if (nn == NULL) continue; // no break, no new node
1768 temp = g_list_prepend(temp, nn);
1769 }
1771 if (temp) {
1772 sp_nodepath_deselect(nodepath);
1773 }
1774 for (GList *l = temp; l != NULL; l = l->next) {
1775 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1776 }
1778 sp_nodepath_update_handles(nodepath);
1780 sp_nodepath_update_repr(nodepath, _("Break path"));
1781 }
1783 /**
1784 * Duplicate the selected node(s).
1785 */
1786 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1787 {
1788 if (!nodepath) {
1789 return;
1790 }
1792 GList *temp = NULL;
1793 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1794 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1795 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1796 if (nn == NULL) continue; // could not duplicate
1797 temp = g_list_prepend(temp, nn);
1798 }
1800 if (temp) {
1801 sp_nodepath_deselect(nodepath);
1802 }
1803 for (GList *l = temp; l != NULL; l = l->next) {
1804 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1805 }
1807 sp_nodepath_update_handles(nodepath);
1809 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1810 }
1812 /**
1813 * Join two nodes by merging them into one.
1814 */
1815 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1816 {
1817 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1819 if (g_list_length(nodepath->selected) != 2) {
1820 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1821 return;
1822 }
1824 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1825 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1827 g_assert(a != b);
1828 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1829 // someone tried to join an orphan node (i.e. a single-node subpath).
1830 // this is not worth an error message, just fail silently.
1831 return;
1832 }
1834 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1835 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1836 return;
1837 }
1839 /* a and b are endpoints */
1841 NR::Point c;
1842 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1843 c = a->pos;
1844 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1845 c = b->pos;
1846 } else {
1847 c = (a->pos + b->pos) / 2;
1848 }
1850 if (a->subpath == b->subpath) {
1851 Inkscape::NodePath::SubPath *sp = a->subpath;
1852 sp_nodepath_subpath_close(sp);
1853 sp_node_moveto (sp->first, c);
1855 sp_nodepath_update_handles(sp->nodepath);
1856 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1857 return;
1858 }
1860 /* a and b are separate subpaths */
1861 Inkscape::NodePath::SubPath *sa = a->subpath;
1862 Inkscape::NodePath::SubPath *sb = b->subpath;
1863 NR::Point p;
1864 Inkscape::NodePath::Node *n;
1865 NRPathcode code;
1866 if (a == sa->first) {
1867 p = sa->first->n.pos;
1868 code = (NRPathcode)sa->first->n.other->code;
1869 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1870 n = sa->last;
1871 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1872 n = n->p.other;
1873 while (n) {
1874 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1875 n = n->p.other;
1876 if (n == sa->first) n = NULL;
1877 }
1878 sp_nodepath_subpath_destroy(sa);
1879 sa = t;
1880 } else if (a == sa->last) {
1881 p = sa->last->p.pos;
1882 code = (NRPathcode)sa->last->code;
1883 sp_nodepath_node_destroy(sa->last);
1884 } else {
1885 code = NR_END;
1886 g_assert_not_reached();
1887 }
1889 if (b == sb->first) {
1890 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1891 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1892 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1893 }
1894 } else if (b == sb->last) {
1895 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1896 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1897 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1898 }
1899 } else {
1900 g_assert_not_reached();
1901 }
1902 /* and now destroy sb */
1904 sp_nodepath_subpath_destroy(sb);
1906 sp_nodepath_update_handles(sa->nodepath);
1908 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1910 sp_nodepath_update_statusbar(nodepath);
1911 }
1913 /**
1914 * Join two nodes by adding a segment between them.
1915 */
1916 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1917 {
1918 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1920 if (g_list_length(nodepath->selected) != 2) {
1921 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1922 return;
1923 }
1925 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1926 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1928 g_assert(a != b);
1929 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1930 // someone tried to join an orphan node (i.e. a single-node subpath).
1931 // this is not worth an error message, just fail silently.
1932 return;
1933 }
1935 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1936 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1937 return;
1938 }
1940 if (a->subpath == b->subpath) {
1941 Inkscape::NodePath::SubPath *sp = a->subpath;
1943 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1944 sp->closed = TRUE;
1946 sp->first->p.other = sp->last;
1947 sp->last->n.other = sp->first;
1949 sp_node_handle_mirror_p_to_n(sp->last);
1950 sp_node_handle_mirror_n_to_p(sp->first);
1952 sp->first->code = sp->last->code;
1953 sp->first = sp->last;
1955 sp_nodepath_update_handles(sp->nodepath);
1957 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1959 return;
1960 }
1962 /* a and b are separate subpaths */
1963 Inkscape::NodePath::SubPath *sa = a->subpath;
1964 Inkscape::NodePath::SubPath *sb = b->subpath;
1966 Inkscape::NodePath::Node *n;
1967 NR::Point p;
1968 NRPathcode code;
1969 if (a == sa->first) {
1970 code = (NRPathcode) sa->first->n.other->code;
1971 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1972 n = sa->last;
1973 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1974 for (n = n->p.other; n != NULL; n = n->p.other) {
1975 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1976 }
1977 sp_nodepath_subpath_destroy(sa);
1978 sa = t;
1979 } else if (a == sa->last) {
1980 code = (NRPathcode)sa->last->code;
1981 } else {
1982 code = NR_END;
1983 g_assert_not_reached();
1984 }
1986 if (b == sb->first) {
1987 n = sb->first;
1988 sp_node_handle_mirror_p_to_n(sa->last);
1989 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1990 sp_node_handle_mirror_n_to_p(sa->last);
1991 for (n = n->n.other; n != NULL; n = n->n.other) {
1992 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1993 }
1994 } else if (b == sb->last) {
1995 n = sb->last;
1996 sp_node_handle_mirror_p_to_n(sa->last);
1997 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1998 sp_node_handle_mirror_n_to_p(sa->last);
1999 for (n = n->p.other; n != NULL; n = n->p.other) {
2000 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2001 }
2002 } else {
2003 g_assert_not_reached();
2004 }
2005 /* and now destroy sb */
2007 sp_nodepath_subpath_destroy(sb);
2009 sp_nodepath_update_handles(sa->nodepath);
2011 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2012 }
2014 /**
2015 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2016 */
2017 void sp_node_delete_preserve(GList *nodes_to_delete)
2018 {
2019 GSList *nodepaths = NULL;
2021 while (nodes_to_delete) {
2022 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2023 Inkscape::NodePath::SubPath *sp = node->subpath;
2024 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2025 Inkscape::NodePath::Node *sample_cursor = NULL;
2026 Inkscape::NodePath::Node *sample_end = NULL;
2027 Inkscape::NodePath::Node *delete_cursor = node;
2028 bool just_delete = false;
2030 //find the start of this contiguous selection
2031 //move left to the first node that is not selected
2032 //or the start of the non-closed path
2033 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2034 delete_cursor = curr;
2035 }
2037 //just delete at the beginning of an open path
2038 if (!delete_cursor->p.other) {
2039 sample_cursor = delete_cursor;
2040 just_delete = true;
2041 } else {
2042 sample_cursor = delete_cursor->p.other;
2043 }
2045 //calculate points for each segment
2046 int rate = 5;
2047 float period = 1.0 / rate;
2048 std::vector<NR::Point> data;
2049 if (!just_delete) {
2050 data.push_back(sample_cursor->pos);
2051 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2052 //just delete at the end of an open path
2053 if (!sp->closed && curr == sp->last) {
2054 just_delete = true;
2055 break;
2056 }
2058 //sample points on the contiguous selected segment
2059 NR::Point *bez;
2060 bez = new NR::Point [4];
2061 bez[0] = curr->pos;
2062 bez[1] = curr->n.pos;
2063 bez[2] = curr->n.other->p.pos;
2064 bez[3] = curr->n.other->pos;
2065 for (int i=1; i<rate; i++) {
2066 gdouble t = i * period;
2067 NR::Point p = bezier_pt(3, bez, t);
2068 data.push_back(p);
2069 }
2070 data.push_back(curr->n.other->pos);
2072 sample_end = curr->n.other;
2073 //break if we've come full circle or hit the end of the selection
2074 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2075 break;
2076 }
2077 }
2078 }
2080 if (!just_delete) {
2081 //calculate the best fitting single segment and adjust the endpoints
2082 NR::Point *adata;
2083 adata = new NR::Point [data.size()];
2084 copy(data.begin(), data.end(), adata);
2086 NR::Point *bez;
2087 bez = new NR::Point [4];
2088 //would decreasing error create a better fitting approximation?
2089 gdouble error = 1.0;
2090 gint ret;
2091 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2093 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2094 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2095 //the resulting nodes behave as expected.
2096 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2097 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2099 //adjust endpoints
2100 sample_cursor->n.pos = bez[1];
2101 sample_end->p.pos = bez[2];
2102 }
2104 //destroy this contiguous selection
2105 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2106 Inkscape::NodePath::Node *temp = delete_cursor;
2107 if (delete_cursor->n.other == delete_cursor) {
2108 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2109 delete_cursor = NULL;
2110 } else {
2111 delete_cursor = delete_cursor->n.other;
2112 }
2113 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2114 sp_nodepath_node_destroy(temp);
2115 }
2117 sp_nodepath_update_handles(nodepath);
2119 if (!g_slist_find(nodepaths, nodepath))
2120 nodepaths = g_slist_prepend (nodepaths, nodepath);
2121 }
2123 for (GSList *i = nodepaths; i; i = i->next) {
2124 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2125 // different nodepaths will give us one undo event per nodepath
2126 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2128 // if the entire nodepath is removed, delete the selected object.
2129 if (nodepath->subpaths == NULL ||
2130 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2131 //at least 2
2132 sp_nodepath_get_node_count(nodepath) < 2) {
2133 SPDocument *document = sp_desktop_document (nodepath->desktop);
2134 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2135 //delete this nodepath's object, not the entire selection! (though at this time, this
2136 //does not matter)
2137 sp_selection_delete();
2138 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2139 _("Delete nodes"));
2140 } else {
2141 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2142 sp_nodepath_update_statusbar(nodepath);
2143 }
2144 }
2146 g_slist_free (nodepaths);
2147 }
2149 /**
2150 * Delete one or more selected nodes.
2151 */
2152 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2153 {
2154 if (!nodepath) return;
2155 if (!nodepath->selected) return;
2157 /** \todo fixme: do it the right way */
2158 while (nodepath->selected) {
2159 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2160 sp_nodepath_node_destroy(node);
2161 }
2164 //clean up the nodepath (such as for trivial subpaths)
2165 sp_nodepath_cleanup(nodepath);
2167 sp_nodepath_update_handles(nodepath);
2169 // if the entire nodepath is removed, delete the selected object.
2170 if (nodepath->subpaths == NULL ||
2171 sp_nodepath_get_node_count(nodepath) < 2) {
2172 SPDocument *document = sp_desktop_document (nodepath->desktop);
2173 sp_selection_delete();
2174 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2175 _("Delete nodes"));
2176 return;
2177 }
2179 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2181 sp_nodepath_update_statusbar(nodepath);
2182 }
2184 /**
2185 * Delete one or more segments between two selected nodes.
2186 * This is the code for 'split'.
2187 */
2188 void
2189 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2190 {
2191 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2192 Inkscape::NodePath::Node *curr, *next; //Iterators
2194 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2196 if (g_list_length(nodepath->selected) != 2) {
2197 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2198 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2199 return;
2200 }
2202 //Selected nodes, not inclusive
2203 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2204 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2206 if ( ( a==b) || //same node
2207 (a->subpath != b->subpath ) || //not the same path
2208 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2209 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2210 {
2211 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2212 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2213 return;
2214 }
2216 //###########################################
2217 //# BEGIN EDITS
2218 //###########################################
2219 //##################################
2220 //# CLOSED PATH
2221 //##################################
2222 if (a->subpath->closed) {
2225 gboolean reversed = FALSE;
2227 //Since we can go in a circle, we need to find the shorter distance.
2228 // a->b or b->a
2229 start = end = NULL;
2230 int distance = 0;
2231 int minDistance = 0;
2232 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2233 if (curr==b) {
2234 //printf("a to b:%d\n", distance);
2235 start = a;//go from a to b
2236 end = b;
2237 minDistance = distance;
2238 //printf("A to B :\n");
2239 break;
2240 }
2241 distance++;
2242 }
2244 //try again, the other direction
2245 distance = 0;
2246 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2247 if (curr==a) {
2248 //printf("b to a:%d\n", distance);
2249 if (distance < minDistance) {
2250 start = b; //we go from b to a
2251 end = a;
2252 reversed = TRUE;
2253 //printf("B to A\n");
2254 }
2255 break;
2256 }
2257 distance++;
2258 }
2261 //Copy everything from 'end' to 'start' to a new subpath
2262 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2263 for (curr=end ; curr ; curr=curr->n.other) {
2264 NRPathcode code = (NRPathcode) curr->code;
2265 if (curr == end)
2266 code = NR_MOVETO;
2267 sp_nodepath_node_new(t, NULL,
2268 (Inkscape::NodePath::NodeType)curr->type, code,
2269 &curr->p.pos, &curr->pos, &curr->n.pos);
2270 if (curr == start)
2271 break;
2272 }
2273 sp_nodepath_subpath_destroy(a->subpath);
2276 }
2280 //##################################
2281 //# OPEN PATH
2282 //##################################
2283 else {
2285 //We need to get the direction of the list between A and B
2286 //Can we walk from a to b?
2287 start = end = NULL;
2288 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2289 if (curr==b) {
2290 start = a; //did it! we go from a to b
2291 end = b;
2292 //printf("A to B\n");
2293 break;
2294 }
2295 }
2296 if (!start) {//didn't work? let's try the other direction
2297 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2298 if (curr==a) {
2299 start = b; //did it! we go from b to a
2300 end = a;
2301 //printf("B to A\n");
2302 break;
2303 }
2304 }
2305 }
2306 if (!start) {
2307 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2308 _("Cannot find path between nodes."));
2309 return;
2310 }
2314 //Copy everything after 'end' to a new subpath
2315 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2316 for (curr=end ; curr ; curr=curr->n.other) {
2317 NRPathcode code = (NRPathcode) curr->code;
2318 if (curr == end)
2319 code = NR_MOVETO;
2320 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2321 &curr->p.pos, &curr->pos, &curr->n.pos);
2322 }
2324 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2325 for (curr = start->n.other ; curr ; curr=next) {
2326 next = curr->n.other;
2327 sp_nodepath_node_destroy(curr);
2328 }
2330 }
2331 //###########################################
2332 //# END EDITS
2333 //###########################################
2335 //clean up the nodepath (such as for trivial subpaths)
2336 sp_nodepath_cleanup(nodepath);
2338 sp_nodepath_update_handles(nodepath);
2340 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2342 sp_nodepath_update_statusbar(nodepath);
2343 }
2345 /**
2346 * Call sp_nodepath_set_line() for all selected segments.
2347 */
2348 void
2349 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2350 {
2351 if (nodepath == NULL) return;
2353 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2354 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2355 g_assert(n->selected);
2356 if (n->p.other && n->p.other->selected) {
2357 sp_nodepath_set_line_type(n, code);
2358 }
2359 }
2361 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2362 }
2364 /**
2365 * Call sp_nodepath_convert_node_type() for all selected nodes.
2366 */
2367 void
2368 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2369 {
2370 if (nodepath == NULL) return;
2372 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2373 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2374 }
2376 sp_nodepath_update_repr(nodepath, _("Change node type"));
2377 }
2379 /**
2380 * Change select status of node, update its own and neighbour handles.
2381 */
2382 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2383 {
2384 node->selected = selected;
2386 if (selected) {
2387 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2388 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2389 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2390 sp_knot_update_ctrl(node->knot);
2391 } else {
2392 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2393 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2394 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2395 sp_knot_update_ctrl(node->knot);
2396 }
2398 sp_node_update_handles(node);
2399 if (node->n.other) sp_node_update_handles(node->n.other);
2400 if (node->p.other) sp_node_update_handles(node->p.other);
2401 }
2403 /**
2404 \brief Select a node
2405 \param node The node to select
2406 \param incremental If true, add to selection, otherwise deselect others
2407 \param override If true, always select this node, otherwise toggle selected status
2408 */
2409 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2410 {
2411 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2413 if (incremental) {
2414 if (override) {
2415 if (!g_list_find(nodepath->selected, node)) {
2416 nodepath->selected = g_list_prepend(nodepath->selected, node);
2417 }
2418 sp_node_set_selected(node, TRUE);
2419 } else { // toggle
2420 if (node->selected) {
2421 g_assert(g_list_find(nodepath->selected, node));
2422 nodepath->selected = g_list_remove(nodepath->selected, node);
2423 } else {
2424 g_assert(!g_list_find(nodepath->selected, node));
2425 nodepath->selected = g_list_prepend(nodepath->selected, node);
2426 }
2427 sp_node_set_selected(node, !node->selected);
2428 }
2429 } else {
2430 sp_nodepath_deselect(nodepath);
2431 nodepath->selected = g_list_prepend(nodepath->selected, node);
2432 sp_node_set_selected(node, TRUE);
2433 }
2435 sp_nodepath_update_statusbar(nodepath);
2436 }
2439 /**
2440 \brief Deselect all nodes in the nodepath
2441 */
2442 void
2443 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2444 {
2445 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2447 while (nodepath->selected) {
2448 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2449 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2450 }
2451 sp_nodepath_update_statusbar(nodepath);
2452 }
2454 /**
2455 \brief Select or invert selection of all nodes in the nodepath
2456 */
2457 void
2458 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2459 {
2460 if (!nodepath) return;
2462 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2463 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2464 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2465 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2466 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2467 }
2468 }
2469 }
2471 /**
2472 * If nothing selected, does the same as sp_nodepath_select_all();
2473 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2474 * (i.e., similar to "select all in layer", with the "selected" subpaths
2475 * being treated as "layers" in the path).
2476 */
2477 void
2478 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2479 {
2480 if (!nodepath) return;
2482 if (g_list_length (nodepath->selected) == 0) {
2483 sp_nodepath_select_all (nodepath, invert);
2484 return;
2485 }
2487 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2488 GSList *subpaths = NULL;
2490 for (GList *l = copy; l != NULL; l = l->next) {
2491 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2492 Inkscape::NodePath::SubPath *subpath = n->subpath;
2493 if (!g_slist_find (subpaths, subpath))
2494 subpaths = g_slist_prepend (subpaths, subpath);
2495 }
2497 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2498 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2499 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2500 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2501 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2502 }
2503 }
2505 g_slist_free (subpaths);
2506 g_list_free (copy);
2507 }
2509 /**
2510 * \brief Select the node after the last selected; if none is selected,
2511 * select the first within path.
2512 */
2513 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2514 {
2515 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2517 Inkscape::NodePath::Node *last = NULL;
2518 if (nodepath->selected) {
2519 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2520 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2521 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2522 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2523 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2524 if (node->selected) {
2525 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2526 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2527 if (spl->next) { // there's a next subpath
2528 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2529 last = subpath_next->first;
2530 } else if (spl->prev) { // there's a previous subpath
2531 last = NULL; // to be set later to the first node of first subpath
2532 } else {
2533 last = node->n.other;
2534 }
2535 } else {
2536 last = node->n.other;
2537 }
2538 } else {
2539 if (node->n.other) {
2540 last = node->n.other;
2541 } else {
2542 if (spl->next) { // there's a next subpath
2543 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2544 last = subpath_next->first;
2545 } else if (spl->prev) { // there's a previous subpath
2546 last = NULL; // to be set later to the first node of first subpath
2547 } else {
2548 last = (Inkscape::NodePath::Node *) subpath->first;
2549 }
2550 }
2551 }
2552 }
2553 }
2554 }
2555 sp_nodepath_deselect(nodepath);
2556 }
2558 if (last) { // there's at least one more node after selected
2559 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2560 } else { // no more nodes, select the first one in first subpath
2561 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2562 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2563 }
2564 }
2566 /**
2567 * \brief Select the node before the first selected; if none is selected,
2568 * select the last within path
2569 */
2570 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2571 {
2572 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2574 Inkscape::NodePath::Node *last = NULL;
2575 if (nodepath->selected) {
2576 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2577 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2578 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2579 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2580 if (node->selected) {
2581 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2582 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2583 if (spl->prev) { // there's a prev subpath
2584 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2585 last = subpath_prev->last;
2586 } else if (spl->next) { // there's a next subpath
2587 last = NULL; // to be set later to the last node of last subpath
2588 } else {
2589 last = node->p.other;
2590 }
2591 } else {
2592 last = node->p.other;
2593 }
2594 } else {
2595 if (node->p.other) {
2596 last = node->p.other;
2597 } else {
2598 if (spl->prev) { // there's a prev subpath
2599 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2600 last = subpath_prev->last;
2601 } else if (spl->next) { // there's a next subpath
2602 last = NULL; // to be set later to the last node of last subpath
2603 } else {
2604 last = (Inkscape::NodePath::Node *) subpath->last;
2605 }
2606 }
2607 }
2608 }
2609 }
2610 }
2611 sp_nodepath_deselect(nodepath);
2612 }
2614 if (last) { // there's at least one more node before selected
2615 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2616 } else { // no more nodes, select the last one in last subpath
2617 GList *spl = g_list_last(nodepath->subpaths);
2618 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2619 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2620 }
2621 }
2623 /**
2624 * \brief Select all nodes that are within the rectangle.
2625 */
2626 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2627 {
2628 if (!incremental) {
2629 sp_nodepath_deselect(nodepath);
2630 }
2632 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2633 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2634 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2635 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2637 if (b.contains(node->pos)) {
2638 sp_nodepath_node_select(node, TRUE, TRUE);
2639 }
2640 }
2641 }
2642 }
2645 void
2646 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2647 {
2648 g_assert (n);
2649 g_assert (nodepath);
2650 g_assert (n->subpath->nodepath == nodepath);
2652 if (g_list_length (nodepath->selected) == 0) {
2653 if (grow > 0) {
2654 sp_nodepath_node_select(n, TRUE, TRUE);
2655 }
2656 return;
2657 }
2659 if (g_list_length (nodepath->selected) == 1) {
2660 if (grow < 0) {
2661 sp_nodepath_deselect (nodepath);
2662 return;
2663 }
2664 }
2666 double n_sel_range = 0, p_sel_range = 0;
2667 Inkscape::NodePath::Node *farthest_n_node = n;
2668 Inkscape::NodePath::Node *farthest_p_node = n;
2670 // Calculate ranges
2671 {
2672 double n_range = 0, p_range = 0;
2673 bool n_going = true, p_going = true;
2674 Inkscape::NodePath::Node *n_node = n;
2675 Inkscape::NodePath::Node *p_node = n;
2676 do {
2677 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2678 if (n_node && n_going)
2679 n_node = n_node->n.other;
2680 if (n_node == NULL) {
2681 n_going = false;
2682 } else {
2683 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2684 if (n_node->selected) {
2685 n_sel_range = n_range;
2686 farthest_n_node = n_node;
2687 }
2688 if (n_node == p_node) {
2689 n_going = false;
2690 p_going = false;
2691 }
2692 }
2693 if (p_node && p_going)
2694 p_node = p_node->p.other;
2695 if (p_node == NULL) {
2696 p_going = false;
2697 } else {
2698 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2699 if (p_node->selected) {
2700 p_sel_range = p_range;
2701 farthest_p_node = p_node;
2702 }
2703 if (p_node == n_node) {
2704 n_going = false;
2705 p_going = false;
2706 }
2707 }
2708 } while (n_going || p_going);
2709 }
2711 if (grow > 0) {
2712 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2713 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2714 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2715 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2716 }
2717 } else {
2718 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2719 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2720 } else if (farthest_p_node && farthest_p_node->selected) {
2721 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2722 }
2723 }
2724 }
2726 void
2727 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2728 {
2729 g_assert (n);
2730 g_assert (nodepath);
2731 g_assert (n->subpath->nodepath == nodepath);
2733 if (g_list_length (nodepath->selected) == 0) {
2734 if (grow > 0) {
2735 sp_nodepath_node_select(n, TRUE, TRUE);
2736 }
2737 return;
2738 }
2740 if (g_list_length (nodepath->selected) == 1) {
2741 if (grow < 0) {
2742 sp_nodepath_deselect (nodepath);
2743 return;
2744 }
2745 }
2747 Inkscape::NodePath::Node *farthest_selected = NULL;
2748 double farthest_dist = 0;
2750 Inkscape::NodePath::Node *closest_unselected = NULL;
2751 double closest_dist = NR_HUGE;
2753 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2754 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2755 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2756 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2757 if (node == n)
2758 continue;
2759 if (node->selected) {
2760 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2761 farthest_dist = NR::L2(node->pos - n->pos);
2762 farthest_selected = node;
2763 }
2764 } else {
2765 if (NR::L2(node->pos - n->pos) < closest_dist) {
2766 closest_dist = NR::L2(node->pos - n->pos);
2767 closest_unselected = node;
2768 }
2769 }
2770 }
2771 }
2773 if (grow > 0) {
2774 if (closest_unselected) {
2775 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2776 }
2777 } else {
2778 if (farthest_selected) {
2779 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2780 }
2781 }
2782 }
2785 /**
2786 \brief Saves all nodes' and handles' current positions in their origin members
2787 */
2788 void
2789 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2790 {
2791 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2792 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2793 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2794 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2795 n->origin = n->pos;
2796 n->p.origin = n->p.pos;
2797 n->n.origin = n->n.pos;
2798 }
2799 }
2800 }
2802 /**
2803 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2804 */
2805 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2806 {
2807 if (!nodepath->selected) {
2808 return NULL;
2809 }
2811 GList *r = NULL;
2812 guint i = 0;
2813 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2814 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2815 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2816 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2817 i++;
2818 if (node->selected) {
2819 r = g_list_append(r, GINT_TO_POINTER(i));
2820 }
2821 }
2822 }
2823 return r;
2824 }
2826 /**
2827 \brief Restores selection by selecting nodes whose positions are in the list
2828 */
2829 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2830 {
2831 sp_nodepath_deselect(nodepath);
2833 guint i = 0;
2834 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2835 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2836 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2837 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2838 i++;
2839 if (g_list_find(r, GINT_TO_POINTER(i))) {
2840 sp_nodepath_node_select(node, TRUE, TRUE);
2841 }
2842 }
2843 }
2845 }
2847 /**
2848 \brief Adjusts handle according to node type and line code.
2849 */
2850 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2851 {
2852 double len, otherlen, linelen;
2854 g_assert(node);
2856 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2857 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2859 /** \todo fixme: */
2860 if (me->other == NULL) return;
2861 if (other->other == NULL) return;
2863 /* I have line */
2865 NRPathcode mecode, ocode;
2866 if (which_adjust == 1) {
2867 mecode = (NRPathcode)me->other->code;
2868 ocode = (NRPathcode)node->code;
2869 } else {
2870 mecode = (NRPathcode)node->code;
2871 ocode = (NRPathcode)other->other->code;
2872 }
2874 if (mecode == NR_LINETO) return;
2876 /* I am curve */
2878 if (other->other == NULL) return;
2880 /* Other has line */
2882 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2884 NR::Point delta;
2885 if (ocode == NR_LINETO) {
2886 /* other is lineto, we are either smooth or symm */
2887 Inkscape::NodePath::Node *othernode = other->other;
2888 len = NR::L2(me->pos - node->pos);
2889 delta = node->pos - othernode->pos;
2890 linelen = NR::L2(delta);
2891 if (linelen < 1e-18)
2892 return;
2893 me->pos = node->pos + (len / linelen)*delta;
2894 return;
2895 }
2897 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2899 me->pos = 2 * node->pos - other->pos;
2900 return;
2901 }
2903 /* We are smooth */
2905 len = NR::L2(me->pos - node->pos);
2906 delta = other->pos - node->pos;
2907 otherlen = NR::L2(delta);
2908 if (otherlen < 1e-18) return;
2910 me->pos = node->pos - (len / otherlen) * delta;
2911 }
2913 /**
2914 \brief Adjusts both handles according to node type and line code
2915 */
2916 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2917 {
2918 g_assert(node);
2920 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2922 /* we are either smooth or symm */
2924 if (node->p.other == NULL) return;
2926 if (node->n.other == NULL) return;
2928 if (node->code == NR_LINETO) {
2929 if (node->n.other->code == NR_LINETO) return;
2930 sp_node_adjust_handle(node, 1);
2931 return;
2932 }
2934 if (node->n.other->code == NR_LINETO) {
2935 if (node->code == NR_LINETO) return;
2936 sp_node_adjust_handle(node, -1);
2937 return;
2938 }
2940 /* both are curves */
2941 NR::Point const delta( node->n.pos - node->p.pos );
2943 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2944 node->p.pos = node->pos - delta / 2;
2945 node->n.pos = node->pos + delta / 2;
2946 return;
2947 }
2949 /* We are smooth */
2950 double plen = NR::L2(node->p.pos - node->pos);
2951 if (plen < 1e-18) return;
2952 double nlen = NR::L2(node->n.pos - node->pos);
2953 if (nlen < 1e-18) return;
2954 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2955 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2956 }
2958 /**
2959 * Node event callback.
2960 */
2961 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2962 {
2963 gboolean ret = FALSE;
2964 switch (event->type) {
2965 case GDK_ENTER_NOTIFY:
2966 Inkscape::NodePath::Path::active_node = n;
2967 break;
2968 case GDK_LEAVE_NOTIFY:
2969 Inkscape::NodePath::Path::active_node = NULL;
2970 break;
2971 case GDK_SCROLL:
2972 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2973 switch (event->scroll.direction) {
2974 case GDK_SCROLL_UP:
2975 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2976 break;
2977 case GDK_SCROLL_DOWN:
2978 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2979 break;
2980 default:
2981 break;
2982 }
2983 ret = TRUE;
2984 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2985 switch (event->scroll.direction) {
2986 case GDK_SCROLL_UP:
2987 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2988 break;
2989 case GDK_SCROLL_DOWN:
2990 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2991 break;
2992 default:
2993 break;
2994 }
2995 ret = TRUE;
2996 }
2997 break;
2998 case GDK_KEY_PRESS:
2999 switch (get_group0_keyval (&event->key)) {
3000 case GDK_space:
3001 if (event->key.state & GDK_BUTTON1_MASK) {
3002 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3003 stamp_repr(nodepath);
3004 ret = TRUE;
3005 }
3006 break;
3007 case GDK_Page_Up:
3008 if (event->key.state & GDK_CONTROL_MASK) {
3009 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3010 } else {
3011 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3012 }
3013 break;
3014 case GDK_Page_Down:
3015 if (event->key.state & GDK_CONTROL_MASK) {
3016 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3017 } else {
3018 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3019 }
3020 break;
3021 default:
3022 break;
3023 }
3024 break;
3025 default:
3026 break;
3027 }
3029 return ret;
3030 }
3032 /**
3033 * Handle keypress on node; directly called.
3034 */
3035 gboolean node_key(GdkEvent *event)
3036 {
3037 Inkscape::NodePath::Path *np;
3039 // there is no way to verify nodes so set active_node to nil when deleting!!
3040 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3042 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3043 gint ret = FALSE;
3044 switch (get_group0_keyval (&event->key)) {
3045 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3046 case GDK_BackSpace:
3047 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3048 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3049 sp_nodepath_update_repr(np, _("Delete node"));
3050 Inkscape::NodePath::Path::active_node = NULL;
3051 ret = TRUE;
3052 break;
3053 case GDK_c:
3054 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3055 ret = TRUE;
3056 break;
3057 case GDK_s:
3058 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3059 ret = TRUE;
3060 break;
3061 case GDK_y:
3062 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3063 ret = TRUE;
3064 break;
3065 case GDK_b:
3066 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3067 ret = TRUE;
3068 break;
3069 }
3070 return ret;
3071 }
3072 return FALSE;
3073 }
3075 /**
3076 * Mouseclick on node callback.
3077 */
3078 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3079 {
3080 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3082 if (state & GDK_CONTROL_MASK) {
3083 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3085 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3086 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3087 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3088 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3089 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3090 } else {
3091 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3092 }
3093 sp_nodepath_update_repr(nodepath, _("Change node type"));
3094 sp_nodepath_update_statusbar(nodepath);
3096 } else { //ctrl+alt+click: delete node
3097 GList *node_to_delete = NULL;
3098 node_to_delete = g_list_append(node_to_delete, n);
3099 sp_node_delete_preserve(node_to_delete);
3100 }
3102 } else {
3103 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3104 }
3105 }
3107 /**
3108 * Mouse grabbed node callback.
3109 */
3110 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3111 {
3112 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3114 if (!n->selected) {
3115 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3116 }
3118 n->is_dragging = true;
3119 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3121 sp_nodepath_remember_origins (n->subpath->nodepath);
3122 }
3124 /**
3125 * Mouse ungrabbed node callback.
3126 */
3127 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3128 {
3129 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3131 n->dragging_out = NULL;
3132 n->is_dragging = false;
3133 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3135 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3136 }
3138 /**
3139 * The point on a line, given by its angle, closest to the given point.
3140 * \param p A point.
3141 * \param a Angle of the line; it is assumed to go through coordinate origin.
3142 * \param closest Pointer to the point struct where the result is stored.
3143 * \todo FIXME: use dot product perhaps?
3144 */
3145 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3146 {
3147 if (a == HUGE_VAL) { // vertical
3148 *closest = NR::Point(0, (*p)[NR::Y]);
3149 } else {
3150 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3151 (*closest)[NR::Y] = a * (*closest)[NR::X];
3152 }
3153 }
3155 /**
3156 * Distance from the point to a line given by its angle.
3157 * \param p A point.
3158 * \param a Angle of the line; it is assumed to go through coordinate origin.
3159 */
3160 static double point_line_distance(NR::Point *p, double a)
3161 {
3162 NR::Point c;
3163 point_line_closest(p, a, &c);
3164 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]));
3165 }
3167 /**
3168 * Callback for node "request" signal.
3169 * \todo fixme: This goes to "moved" event? (lauris)
3170 */
3171 static gboolean
3172 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3173 {
3174 double yn, xn, yp, xp;
3175 double an, ap, na, pa;
3176 double d_an, d_ap, d_na, d_pa;
3177 gboolean collinear = FALSE;
3178 NR::Point c;
3179 NR::Point pr;
3181 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3183 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3184 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3186 NR::Point mouse = (*p);
3188 if (!n->dragging_out) {
3189 // This is the first drag-out event; find out which handle to drag out
3190 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3191 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3193 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3194 return FALSE;
3196 Inkscape::NodePath::NodeSide *opposite;
3197 if (appr_p > appr_n) { // closer to p
3198 n->dragging_out = &n->p;
3199 opposite = &n->n;
3200 n->code = NR_CURVETO;
3201 } else if (appr_p < appr_n) { // closer to n
3202 n->dragging_out = &n->n;
3203 opposite = &n->p;
3204 n->n.other->code = NR_CURVETO;
3205 } else { // p and n nodes are the same
3206 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3207 n->dragging_out = &n->p;
3208 opposite = &n->n;
3209 n->code = NR_CURVETO;
3210 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3211 n->dragging_out = &n->n;
3212 opposite = &n->p;
3213 n->n.other->code = NR_CURVETO;
3214 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3215 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);
3216 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);
3217 if (appr_other_p > appr_other_n) { // closer to other's p handle
3218 n->dragging_out = &n->n;
3219 opposite = &n->p;
3220 n->n.other->code = NR_CURVETO;
3221 } else { // closer to other's n handle
3222 n->dragging_out = &n->p;
3223 opposite = &n->n;
3224 n->code = NR_CURVETO;
3225 }
3226 }
3227 }
3229 // if there's another handle, make sure the one we drag out starts parallel to it
3230 if (opposite->pos != n->pos) {
3231 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3232 }
3234 // knots might not be created yet!
3235 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3236 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3237 }
3239 // pass this on to the handle-moved callback
3240 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3241 sp_node_update_handles(n);
3242 return TRUE;
3243 }
3245 if (state & GDK_CONTROL_MASK) { // constrained motion
3247 // calculate relative distances of handles
3248 // n handle:
3249 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3250 xn = n->n.pos[NR::X] - n->pos[NR::X];
3251 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3252 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3253 if (n->n.other) { // if there is the next point
3254 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3255 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3256 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3257 }
3258 }
3259 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3260 if (yn < 0) { xn = -xn; yn = -yn; }
3262 // p handle:
3263 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3264 xp = n->p.pos[NR::X] - n->pos[NR::X];
3265 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3266 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3267 if (n->p.other) {
3268 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3269 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3270 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3271 }
3272 }
3273 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3274 if (yp < 0) { xp = -xp; yp = -yp; }
3276 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3277 // sliding on handles, only if at least one of the handles is non-vertical
3278 // (otherwise it's the same as ctrl+drag anyway)
3280 // calculate angles of the handles
3281 if (xn == 0) {
3282 if (yn == 0) { // no handle, consider it the continuation of the other one
3283 an = 0;
3284 collinear = TRUE;
3285 }
3286 else an = 0; // vertical; set the angle to horizontal
3287 } else an = yn/xn;
3289 if (xp == 0) {
3290 if (yp == 0) { // no handle, consider it the continuation of the other one
3291 ap = an;
3292 }
3293 else ap = 0; // vertical; set the angle to horizontal
3294 } else ap = yp/xp;
3296 if (collinear) an = ap;
3298 // angles of the perpendiculars; HUGE_VAL means vertical
3299 if (an == 0) na = HUGE_VAL; else na = -1/an;
3300 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3302 // mouse point relative to the node's original pos
3303 pr = (*p) - n->origin;
3305 // distances to the four lines (two handles and two perpendiculars)
3306 d_an = point_line_distance(&pr, an);
3307 d_na = point_line_distance(&pr, na);
3308 d_ap = point_line_distance(&pr, ap);
3309 d_pa = point_line_distance(&pr, pa);
3311 // find out which line is the closest, save its closest point in c
3312 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3313 point_line_closest(&pr, an, &c);
3314 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3315 point_line_closest(&pr, ap, &c);
3316 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3317 point_line_closest(&pr, na, &c);
3318 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3319 point_line_closest(&pr, pa, &c);
3320 }
3322 // move the node to the closest point
3323 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3324 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3325 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3327 } else { // constraining to hor/vert
3329 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3330 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3331 } else { // snap to vert
3332 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3333 }
3334 }
3335 } else { // move freely
3336 if (n->is_dragging) {
3337 if (state & GDK_MOD1_MASK) { // sculpt
3338 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3339 } else {
3340 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3341 (*p)[NR::X] - n->pos[NR::X],
3342 (*p)[NR::Y] - n->pos[NR::Y],
3343 (state & GDK_SHIFT_MASK) == 0);
3344 }
3345 }
3346 }
3348 n->subpath->nodepath->desktop->scroll_to_point(p);
3350 return TRUE;
3351 }
3353 /**
3354 * Node handle clicked callback.
3355 */
3356 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3357 {
3358 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3360 if (state & GDK_CONTROL_MASK) { // "delete" handle
3361 if (n->p.knot == knot) {
3362 n->p.pos = n->pos;
3363 } else if (n->n.knot == knot) {
3364 n->n.pos = n->pos;
3365 }
3366 sp_node_update_handles(n);
3367 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3368 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3369 sp_nodepath_update_statusbar(nodepath);
3371 } else { // just select or add to selection, depending in Shift
3372 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3373 }
3374 }
3376 /**
3377 * Node handle grabbed callback.
3378 */
3379 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3380 {
3381 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3383 if (!n->selected) {
3384 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3385 }
3387 // remember the origin point of the handle
3388 if (n->p.knot == knot) {
3389 n->p.origin_radial = n->p.pos - n->pos;
3390 } else if (n->n.knot == knot) {
3391 n->n.origin_radial = n->n.pos - n->pos;
3392 } else {
3393 g_assert_not_reached();
3394 }
3396 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3397 }
3399 /**
3400 * Node handle ungrabbed callback.
3401 */
3402 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3403 {
3404 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3406 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3407 if (n->p.knot == knot) {
3408 n->p.origin_radial.a = 0;
3409 sp_knot_set_position(knot, &n->p.pos, state);
3410 } else if (n->n.knot == knot) {
3411 n->n.origin_radial.a = 0;
3412 sp_knot_set_position(knot, &n->n.pos, state);
3413 } else {
3414 g_assert_not_reached();
3415 }
3417 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3418 }
3420 /**
3421 * Node handle "request" signal callback.
3422 */
3423 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3424 {
3425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3427 Inkscape::NodePath::NodeSide *me, *opposite;
3428 gint which;
3429 if (n->p.knot == knot) {
3430 me = &n->p;
3431 opposite = &n->n;
3432 which = -1;
3433 } else if (n->n.knot == knot) {
3434 me = &n->n;
3435 opposite = &n->p;
3436 which = 1;
3437 } else {
3438 me = opposite = NULL;
3439 which = 0;
3440 g_assert_not_reached();
3441 }
3443 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3445 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3447 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3448 /* We are smooth node adjacent with line */
3449 NR::Point const delta = *p - n->pos;
3450 NR::Coord const len = NR::L2(delta);
3451 Inkscape::NodePath::Node *othernode = opposite->other;
3452 NR::Point const ndelta = n->pos - othernode->pos;
3453 NR::Coord const linelen = NR::L2(ndelta);
3454 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3455 NR::Coord const scal = dot(delta, ndelta) / linelen;
3456 (*p) = n->pos + (scal / linelen) * ndelta;
3457 }
3458 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3459 } else {
3460 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, NULL).getPoint();
3461 }
3463 sp_node_adjust_handle(n, -which);
3465 return FALSE;
3466 }
3468 /**
3469 * Node handle moved callback.
3470 */
3471 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3472 {
3473 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3475 Inkscape::NodePath::NodeSide *me;
3476 Inkscape::NodePath::NodeSide *other;
3477 if (n->p.knot == knot) {
3478 me = &n->p;
3479 other = &n->n;
3480 } else if (n->n.knot == knot) {
3481 me = &n->n;
3482 other = &n->p;
3483 } else {
3484 me = NULL;
3485 other = NULL;
3486 g_assert_not_reached();
3487 }
3489 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3490 Radial rme(me->pos - n->pos);
3491 Radial rother(other->pos - n->pos);
3492 Radial rnew(*p - n->pos);
3494 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3495 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3496 /* 0 interpreted as "no snapping". */
3498 // The closest PI/snaps angle, starting from zero.
3499 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3500 if (me->origin_radial.a == HUGE_VAL) {
3501 // ortho doesn't exist: original handle was zero length.
3502 rnew.a = a_snapped;
3503 } else {
3504 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3505 * its opposite and perpendiculars). */
3506 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3508 // Snap to the closest.
3509 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3510 ? a_snapped
3511 : a_ortho );
3512 }
3513 }
3515 if (state & GDK_MOD1_MASK) {
3516 // lock handle length
3517 rnew.r = me->origin_radial.r;
3518 }
3520 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3521 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3522 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3523 rother.a += rnew.a - rme.a;
3524 other->pos = NR::Point(rother) + n->pos;
3525 if (other->knot) {
3526 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3527 sp_knot_moveto(other->knot, &other->pos);
3528 }
3529 }
3531 me->pos = NR::Point(rnew) + n->pos;
3532 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3534 // move knot, but without emitting the signal:
3535 // we cannot emit a "moved" signal because we're now processing it
3536 sp_knot_moveto(me->knot, &(me->pos));
3538 update_object(n->subpath->nodepath);
3540 /* status text */
3541 SPDesktop *desktop = n->subpath->nodepath->desktop;
3542 if (!desktop) return;
3543 SPEventContext *ec = desktop->event_context;
3544 if (!ec) return;
3545 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3546 if (!mc) return;
3548 double degrees = 180 / M_PI * rnew.a;
3549 if (degrees > 180) degrees -= 360;
3550 if (degrees < -180) degrees += 360;
3551 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3552 degrees = angle_to_compass (degrees);
3554 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3556 mc->setF(Inkscape::NORMAL_MESSAGE,
3557 _("<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);
3559 g_string_free(length, TRUE);
3560 }
3562 /**
3563 * Node handle event callback.
3564 */
3565 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3566 {
3567 gboolean ret = FALSE;
3568 switch (event->type) {
3569 case GDK_KEY_PRESS:
3570 switch (get_group0_keyval (&event->key)) {
3571 case GDK_space:
3572 if (event->key.state & GDK_BUTTON1_MASK) {
3573 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3574 stamp_repr(nodepath);
3575 ret = TRUE;
3576 }
3577 break;
3578 default:
3579 break;
3580 }
3581 break;
3582 case GDK_ENTER_NOTIFY:
3583 // we use an experimentally determined threshold that seems to work fine
3584 if (NR::L2(n->pos - knot->pos) < 0.75)
3585 Inkscape::NodePath::Path::active_node = n;
3586 break;
3587 case GDK_LEAVE_NOTIFY:
3588 // we use an experimentally determined threshold that seems to work fine
3589 if (NR::L2(n->pos - knot->pos) < 0.75)
3590 Inkscape::NodePath::Path::active_node = NULL;
3591 break;
3592 default:
3593 break;
3594 }
3596 return ret;
3597 }
3599 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3600 Radial &rme, Radial &rother, gboolean const both)
3601 {
3602 rme.a += angle;
3603 if ( both
3604 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3605 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3606 {
3607 rother.a += angle;
3608 }
3609 }
3611 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3612 Radial &rme, Radial &rother, gboolean const both)
3613 {
3614 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3616 gdouble r;
3617 if ( both
3618 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3619 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3620 {
3621 r = MAX(rme.r, rother.r);
3622 } else {
3623 r = rme.r;
3624 }
3626 gdouble const weird_angle = atan2(norm_angle, r);
3627 /* Bulia says norm_angle is just the visible distance that the
3628 * object's end must travel on the screen. Left as 'angle' for want of
3629 * a better name.*/
3631 rme.a += weird_angle;
3632 if ( both
3633 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3634 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3635 {
3636 rother.a += weird_angle;
3637 }
3638 }
3640 /**
3641 * Rotate one node.
3642 */
3643 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3644 {
3645 Inkscape::NodePath::NodeSide *me, *other;
3646 bool both = false;
3648 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3649 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3651 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3652 me = &(n->p);
3653 other = &(n->n);
3654 } else if (!n->p.other) {
3655 me = &(n->n);
3656 other = &(n->p);
3657 } else {
3658 if (which > 0) { // right handle
3659 if (xn > xp) {
3660 me = &(n->n);
3661 other = &(n->p);
3662 } else {
3663 me = &(n->p);
3664 other = &(n->n);
3665 }
3666 } else if (which < 0){ // left handle
3667 if (xn <= xp) {
3668 me = &(n->n);
3669 other = &(n->p);
3670 } else {
3671 me = &(n->p);
3672 other = &(n->n);
3673 }
3674 } else { // both handles
3675 me = &(n->n);
3676 other = &(n->p);
3677 both = true;
3678 }
3679 }
3681 Radial rme(me->pos - n->pos);
3682 Radial rother(other->pos - n->pos);
3684 if (screen) {
3685 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3686 } else {
3687 node_rotate_one_internal (*n, angle, rme, rother, both);
3688 }
3690 me->pos = n->pos + NR::Point(rme);
3692 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3693 other->pos = n->pos + NR::Point(rother);
3694 }
3696 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3697 // so here we just move all the knots without emitting move signals, for speed
3698 sp_node_update_handles(n, false);
3699 }
3701 /**
3702 * Rotate selected nodes.
3703 */
3704 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3705 {
3706 if (!nodepath || !nodepath->selected) return;
3708 if (g_list_length(nodepath->selected) == 1) {
3709 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3710 node_rotate_one (n, angle, which, screen);
3711 } else {
3712 // rotate as an object:
3714 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3715 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3716 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3717 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3718 box.expandTo (n->pos); // contain all selected nodes
3719 }
3721 gdouble rot;
3722 if (screen) {
3723 gdouble const zoom = nodepath->desktop->current_zoom();
3724 gdouble const zmove = angle / zoom;
3725 gdouble const r = NR::L2(box.max() - box.midpoint());
3726 rot = atan2(zmove, r);
3727 } else {
3728 rot = angle;
3729 }
3731 NR::Point rot_center;
3732 if (Inkscape::NodePath::Path::active_node == NULL)
3733 rot_center = box.midpoint();
3734 else
3735 rot_center = Inkscape::NodePath::Path::active_node->pos;
3737 NR::Matrix t =
3738 NR::Matrix (NR::translate(-rot_center)) *
3739 NR::Matrix (NR::rotate(rot)) *
3740 NR::Matrix (NR::translate(rot_center));
3742 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3743 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3744 n->pos *= t;
3745 n->n.pos *= t;
3746 n->p.pos *= t;
3747 sp_node_update_handles(n, false);
3748 }
3749 }
3751 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3752 }
3754 /**
3755 * Scale one node.
3756 */
3757 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3758 {
3759 bool both = false;
3760 Inkscape::NodePath::NodeSide *me, *other;
3762 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3763 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3765 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3766 me = &(n->p);
3767 other = &(n->n);
3768 n->code = NR_CURVETO;
3769 } else if (!n->p.other) {
3770 me = &(n->n);
3771 other = &(n->p);
3772 if (n->n.other)
3773 n->n.other->code = NR_CURVETO;
3774 } else {
3775 if (which > 0) { // right handle
3776 if (xn > xp) {
3777 me = &(n->n);
3778 other = &(n->p);
3779 if (n->n.other)
3780 n->n.other->code = NR_CURVETO;
3781 } else {
3782 me = &(n->p);
3783 other = &(n->n);
3784 n->code = NR_CURVETO;
3785 }
3786 } else if (which < 0){ // left handle
3787 if (xn <= xp) {
3788 me = &(n->n);
3789 other = &(n->p);
3790 if (n->n.other)
3791 n->n.other->code = NR_CURVETO;
3792 } else {
3793 me = &(n->p);
3794 other = &(n->n);
3795 n->code = NR_CURVETO;
3796 }
3797 } else { // both handles
3798 me = &(n->n);
3799 other = &(n->p);
3800 both = true;
3801 n->code = NR_CURVETO;
3802 if (n->n.other)
3803 n->n.other->code = NR_CURVETO;
3804 }
3805 }
3807 Radial rme(me->pos - n->pos);
3808 Radial rother(other->pos - n->pos);
3810 rme.r += grow;
3811 if (rme.r < 0) rme.r = 0;
3812 if (rme.a == HUGE_VAL) {
3813 if (me->other) { // if direction is unknown, initialize it towards the next node
3814 Radial rme_next(me->other->pos - n->pos);
3815 rme.a = rme_next.a;
3816 } else { // if there's no next, initialize to 0
3817 rme.a = 0;
3818 }
3819 }
3820 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3821 rother.r += grow;
3822 if (rother.r < 0) rother.r = 0;
3823 if (rother.a == HUGE_VAL) {
3824 rother.a = rme.a + M_PI;
3825 }
3826 }
3828 me->pos = n->pos + NR::Point(rme);
3830 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3831 other->pos = n->pos + NR::Point(rother);
3832 }
3834 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3835 // so here we just move all the knots without emitting move signals, for speed
3836 sp_node_update_handles(n, false);
3837 }
3839 /**
3840 * Scale selected nodes.
3841 */
3842 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3843 {
3844 if (!nodepath || !nodepath->selected) return;
3846 if (g_list_length(nodepath->selected) == 1) {
3847 // scale handles of the single selected node
3848 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3849 node_scale_one (n, grow, which);
3850 } else {
3851 // scale nodes as an "object":
3853 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3854 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3855 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3856 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3857 box.expandTo (n->pos); // contain all selected nodes
3858 }
3860 double scale = (box.maxExtent() + grow)/box.maxExtent();
3862 NR::Point scale_center;
3863 if (Inkscape::NodePath::Path::active_node == NULL)
3864 scale_center = box.midpoint();
3865 else
3866 scale_center = Inkscape::NodePath::Path::active_node->pos;
3868 NR::Matrix t =
3869 NR::Matrix (NR::translate(-scale_center)) *
3870 NR::Matrix (NR::scale(scale, scale)) *
3871 NR::Matrix (NR::translate(scale_center));
3873 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3874 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3875 n->pos *= t;
3876 n->n.pos *= t;
3877 n->p.pos *= t;
3878 sp_node_update_handles(n, false);
3879 }
3880 }
3882 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3883 }
3885 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3886 {
3887 if (!nodepath) return;
3888 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3889 }
3891 /**
3892 * Flip selected nodes horizontally/vertically.
3893 */
3894 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3895 {
3896 if (!nodepath || !nodepath->selected) return;
3898 if (g_list_length(nodepath->selected) == 1 && !center) {
3899 // flip handles of the single selected node
3900 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3901 double temp = n->p.pos[axis];
3902 n->p.pos[axis] = n->n.pos[axis];
3903 n->n.pos[axis] = temp;
3904 sp_node_update_handles(n, false);
3905 } else {
3906 // scale nodes as an "object":
3908 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3909 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3910 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3911 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3912 box.expandTo (n->pos); // contain all selected nodes
3913 }
3915 if (!center) {
3916 center = box.midpoint();
3917 }
3918 NR::Matrix t =
3919 NR::Matrix (NR::translate(- *center)) *
3920 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3921 NR::Matrix (NR::translate(*center));
3923 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3924 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3925 n->pos *= t;
3926 n->n.pos *= t;
3927 n->p.pos *= t;
3928 sp_node_update_handles(n, false);
3929 }
3930 }
3932 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3933 }
3935 //-----------------------------------------------
3936 /**
3937 * Return new subpath under given nodepath.
3938 */
3939 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3940 {
3941 g_assert(nodepath);
3942 g_assert(nodepath->desktop);
3944 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3946 s->nodepath = nodepath;
3947 s->closed = FALSE;
3948 s->nodes = NULL;
3949 s->first = NULL;
3950 s->last = NULL;
3952 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3953 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3954 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3956 return s;
3957 }
3959 /**
3960 * Destroy nodes in subpath, then subpath itself.
3961 */
3962 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3963 {
3964 g_assert(subpath);
3965 g_assert(subpath->nodepath);
3966 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3968 while (subpath->nodes) {
3969 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3970 }
3972 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3974 g_free(subpath);
3975 }
3977 /**
3978 * Link head to tail in subpath.
3979 */
3980 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3981 {
3982 g_assert(!sp->closed);
3983 g_assert(sp->last != sp->first);
3984 g_assert(sp->first->code == NR_MOVETO);
3986 sp->closed = TRUE;
3988 //Link the head to the tail
3989 sp->first->p.other = sp->last;
3990 sp->last->n.other = sp->first;
3991 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3992 sp->first = sp->last;
3994 //Remove the extra end node
3995 sp_nodepath_node_destroy(sp->last->n.other);
3996 }
3998 /**
3999 * Open closed (loopy) subpath at node.
4000 */
4001 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4002 {
4003 g_assert(sp->closed);
4004 g_assert(n->subpath == sp);
4005 g_assert(sp->first == sp->last);
4007 /* We create new startpoint, current node will become last one */
4009 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4010 &n->pos, &n->pos, &n->n.pos);
4013 sp->closed = FALSE;
4015 //Unlink to make a head and tail
4016 sp->first = new_path;
4017 sp->last = n;
4018 n->n.other = NULL;
4019 new_path->p.other = NULL;
4020 }
4022 /**
4023 * Returns area in triangle given by points; may be negative.
4024 */
4025 inline double
4026 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
4027 {
4028 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]);
4029 }
4031 /**
4032 * Return new node in subpath with given properties.
4033 * \param pos Position of node.
4034 * \param ppos Handle position in previous direction
4035 * \param npos Handle position in previous direction
4036 */
4037 Inkscape::NodePath::Node *
4038 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)
4039 {
4040 g_assert(sp);
4041 g_assert(sp->nodepath);
4042 g_assert(sp->nodepath->desktop);
4044 if (nodechunk == NULL)
4045 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4047 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4049 n->subpath = sp;
4051 if (type != Inkscape::NodePath::NODE_NONE) {
4052 // use the type from sodipodi:nodetypes
4053 n->type = type;
4054 } else {
4055 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4056 // points are (almost) collinear
4057 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4058 // endnode, or a node with a retracted handle
4059 n->type = Inkscape::NodePath::NODE_CUSP;
4060 } else {
4061 n->type = Inkscape::NodePath::NODE_SMOOTH;
4062 }
4063 } else {
4064 n->type = Inkscape::NodePath::NODE_CUSP;
4065 }
4066 }
4068 n->code = code;
4069 n->selected = FALSE;
4070 n->pos = *pos;
4071 n->p.pos = *ppos;
4072 n->n.pos = *npos;
4074 n->dragging_out = NULL;
4076 Inkscape::NodePath::Node *prev;
4077 if (next) {
4078 //g_assert(g_list_find(sp->nodes, next));
4079 prev = next->p.other;
4080 } else {
4081 prev = sp->last;
4082 }
4084 if (prev)
4085 prev->n.other = n;
4086 else
4087 sp->first = n;
4089 if (next)
4090 next->p.other = n;
4091 else
4092 sp->last = n;
4094 n->p.other = prev;
4095 n->n.other = next;
4097 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"));
4098 sp_knot_set_position(n->knot, pos, 0);
4100 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4101 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4102 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4103 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4104 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4105 sp_knot_update_ctrl(n->knot);
4107 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4108 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4109 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4110 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4111 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4112 sp_knot_show(n->knot);
4114 // We only create handle knots and lines on demand
4115 n->p.knot = NULL;
4116 n->p.line = NULL;
4117 n->n.knot = NULL;
4118 n->n.line = NULL;
4120 sp->nodes = g_list_prepend(sp->nodes, n);
4122 return n;
4123 }
4125 /**
4126 * Destroy node and its knots, link neighbors in subpath.
4127 */
4128 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4129 {
4130 g_assert(node);
4131 g_assert(node->subpath);
4132 g_assert(SP_IS_KNOT(node->knot));
4134 Inkscape::NodePath::SubPath *sp = node->subpath;
4136 if (node->selected) { // first, deselect
4137 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4138 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4139 }
4141 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4143 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4144 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4145 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4146 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4147 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4148 g_object_unref(G_OBJECT(node->knot));
4150 if (node->p.knot) {
4151 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4152 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4153 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4154 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4155 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4156 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4157 g_object_unref(G_OBJECT(node->p.knot));
4158 node->p.knot = NULL;
4159 }
4161 if (node->n.knot) {
4162 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4163 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4164 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4165 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4166 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4167 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4168 g_object_unref(G_OBJECT(node->n.knot));
4169 node->n.knot = NULL;
4170 }
4172 if (node->p.line)
4173 gtk_object_destroy(GTK_OBJECT(node->p.line));
4174 if (node->n.line)
4175 gtk_object_destroy(GTK_OBJECT(node->n.line));
4177 if (sp->nodes) { // there are others nodes on the subpath
4178 if (sp->closed) {
4179 if (sp->first == node) {
4180 g_assert(sp->last == node);
4181 sp->first = node->n.other;
4182 sp->last = sp->first;
4183 }
4184 node->p.other->n.other = node->n.other;
4185 node->n.other->p.other = node->p.other;
4186 } else {
4187 if (sp->first == node) {
4188 sp->first = node->n.other;
4189 sp->first->code = NR_MOVETO;
4190 }
4191 if (sp->last == node) sp->last = node->p.other;
4192 if (node->p.other) node->p.other->n.other = node->n.other;
4193 if (node->n.other) node->n.other->p.other = node->p.other;
4194 }
4195 } else { // this was the last node on subpath
4196 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4197 }
4199 g_mem_chunk_free(nodechunk, node);
4200 }
4202 /**
4203 * Returns one of the node's two sides.
4204 * \param which Indicates which side.
4205 * \return Pointer to previous node side if which==-1, next if which==1.
4206 */
4207 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4208 {
4209 g_assert(node);
4211 switch (which) {
4212 case -1:
4213 return &node->p;
4214 case 1:
4215 return &node->n;
4216 default:
4217 break;
4218 }
4220 g_assert_not_reached();
4222 return NULL;
4223 }
4225 /**
4226 * Return the other side of the node, given one of its sides.
4227 */
4228 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4229 {
4230 g_assert(node);
4232 if (me == &node->p) return &node->n;
4233 if (me == &node->n) return &node->p;
4235 g_assert_not_reached();
4237 return NULL;
4238 }
4240 /**
4241 * Return NRPathcode on the given side of the node.
4242 */
4243 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4244 {
4245 g_assert(node);
4247 if (me == &node->p) {
4248 if (node->p.other) return (NRPathcode)node->code;
4249 return NR_MOVETO;
4250 }
4252 if (me == &node->n) {
4253 if (node->n.other) return (NRPathcode)node->n.other->code;
4254 return NR_MOVETO;
4255 }
4257 g_assert_not_reached();
4259 return NR_END;
4260 }
4262 /**
4263 * Return node with the given index
4264 */
4265 Inkscape::NodePath::Node *
4266 sp_nodepath_get_node_by_index(int index)
4267 {
4268 Inkscape::NodePath::Node *e = NULL;
4270 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4271 if (!nodepath) {
4272 return e;
4273 }
4275 //find segment
4276 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4278 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4279 int n = g_list_length(sp->nodes);
4280 if (sp->closed) {
4281 n++;
4282 }
4284 //if the piece belongs to this subpath grab it
4285 //otherwise move onto the next subpath
4286 if (index < n) {
4287 e = sp->first;
4288 for (int i = 0; i < index; ++i) {
4289 e = e->n.other;
4290 }
4291 break;
4292 } else {
4293 if (sp->closed) {
4294 index -= (n+1);
4295 } else {
4296 index -= n;
4297 }
4298 }
4299 }
4301 return e;
4302 }
4304 /**
4305 * Returns plain text meaning of node type.
4306 */
4307 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4308 {
4309 unsigned retracted = 0;
4310 bool endnode = false;
4312 for (int which = -1; which <= 1; which += 2) {
4313 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4314 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4315 retracted ++;
4316 if (!side->other)
4317 endnode = true;
4318 }
4320 if (retracted == 0) {
4321 if (endnode) {
4322 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4323 return _("end node");
4324 } else {
4325 switch (node->type) {
4326 case Inkscape::NodePath::NODE_CUSP:
4327 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4328 return _("cusp");
4329 case Inkscape::NodePath::NODE_SMOOTH:
4330 // TRANSLATORS: "smooth" is an adjective here
4331 return _("smooth");
4332 case Inkscape::NodePath::NODE_SYMM:
4333 return _("symmetric");
4334 }
4335 }
4336 } else if (retracted == 1) {
4337 if (endnode) {
4338 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4339 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4340 } else {
4341 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4342 }
4343 } else {
4344 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4345 }
4347 return NULL;
4348 }
4350 /**
4351 * Handles content of statusbar as long as node tool is active.
4352 */
4353 void
4354 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4355 {
4356 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");
4357 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4359 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4360 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4361 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4362 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4364 SPDesktop *desktop = NULL;
4365 if (nodepath) {
4366 desktop = nodepath->desktop;
4367 } else {
4368 desktop = SP_ACTIVE_DESKTOP;
4369 }
4371 SPEventContext *ec = desktop->event_context;
4372 if (!ec) return;
4373 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4374 if (!mc) return;
4376 if (selected_nodes == 0) {
4377 Inkscape::Selection *sel = desktop->selection;
4378 if (!sel || sel->isEmpty()) {
4379 mc->setF(Inkscape::NORMAL_MESSAGE,
4380 _("Select a single object to edit its nodes or handles."));
4381 } else {
4382 if (nodepath) {
4383 mc->setF(Inkscape::NORMAL_MESSAGE,
4384 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.",
4385 "<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.",
4386 total_nodes),
4387 total_nodes);
4388 } else {
4389 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4390 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4391 } else {
4392 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4393 }
4394 }
4395 }
4396 } else if (nodepath && selected_nodes == 1) {
4397 mc->setF(Inkscape::NORMAL_MESSAGE,
4398 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4399 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4400 total_nodes),
4401 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4402 } else {
4403 if (selected_subpaths > 1) {
4404 mc->setF(Inkscape::NORMAL_MESSAGE,
4405 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4406 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4407 total_nodes),
4408 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4409 } else {
4410 mc->setF(Inkscape::NORMAL_MESSAGE,
4411 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4412 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4413 total_nodes),
4414 selected_nodes, total_nodes, when_selected);
4415 }
4416 }
4417 }
4419 /*
4420 * returns a *copy* of the curve of that object.
4421 */
4422 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4423 if (!object)
4424 return NULL;
4426 SPCurve *curve = NULL;
4427 if (SP_IS_PATH(object)) {
4428 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4429 curve = sp_curve_copy(curve_new);
4430 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4431 const gchar *svgd = object->repr->attribute(key);
4432 if (svgd) {
4433 NArtBpath *bpath = sp_svg_read_path(svgd);
4434 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4435 if (curve_new) {
4436 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4437 } else {
4438 g_free(bpath);
4439 }
4440 }
4441 }
4443 return curve;
4444 }
4446 void sp_nodepath_object_set_curve (SPObject *object, SPCurve *curve) {
4447 if (!object || !curve)
4448 return;
4450 if (SP_IS_PATH(object)) {
4451 if (SP_SHAPE(object)->path_effect_href) {
4452 sp_path_set_original_curve(SP_PATH(object), curve, true, false);
4453 } else {
4454 sp_shape_set_curve(SP_SHAPE(object), curve, true);
4455 }
4456 } else if ( IS_LIVEPATHEFFECT(object) ) {
4457 g_warning("sp_nodepath_set_curve not implemented yet for lpeobjects");
4458 }
4459 }
4461 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4462 np->show_helperpath = show;
4463 }
4465 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4466 np->straight_path = true;
4467 np->show_handles = false;
4468 g_message("add code to make the path straight.");
4469 // do sp_nodepath_convert_node_type on all nodes?
4470 // search for this text !!! "Make selected segments lines"
4471 }
4474 /*
4475 Local Variables:
4476 mode:c++
4477 c-file-style:"stroustrup"
4478 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4479 indent-tabs-mode:nil
4480 fill-column:99
4481 End:
4482 */
4483 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :