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 <cstring>
51 #include <string>
52 #include "live_effects/lpeobject.h"
53 #include "live_effects/parameter/parameter.h"
54 #include "util/mathfns.h"
56 class NR::Matrix;
58 /// \todo
59 /// evil evil evil. FIXME: conflict of two different Path classes!
60 /// There is a conflict in the namespace between two classes named Path.
61 /// #include "sp-flowtext.h"
62 /// #include "sp-flowregion.h"
64 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
65 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
66 GType sp_flowregion_get_type (void);
67 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
68 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
69 GType sp_flowtext_get_type (void);
70 // end evil workaround
72 #include "helper/stlport.h"
75 /// \todo fixme: Implement these via preferences */
77 #define NODE_FILL 0xbfbfbf00
78 #define NODE_STROKE 0x000000ff
79 #define NODE_FILL_HI 0xff000000
80 #define NODE_STROKE_HI 0x000000ff
81 #define NODE_FILL_SEL 0x0000ffff
82 #define NODE_STROKE_SEL 0x000000ff
83 #define NODE_FILL_SEL_HI 0xff000000
84 #define NODE_STROKE_SEL_HI 0x000000ff
85 #define KNOT_FILL 0xffffffff
86 #define KNOT_STROKE 0x000000ff
87 #define KNOT_FILL_HI 0xff000000
88 #define KNOT_STROKE_HI 0x000000ff
90 static GMemChunk *nodechunk = NULL;
92 /* Creation from object */
94 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
95 static gchar *parse_nodetypes(gchar const *types, gint length);
97 /* Object updating */
99 static void stamp_repr(Inkscape::NodePath::Path *np);
100 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
101 static gchar *create_typestr(Inkscape::NodePath::Path *np);
103 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
105 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
107 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
109 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
111 /* Adjust handle placement, if the node or the other handle is moved */
112 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
113 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
115 /* Node event callbacks */
116 static void node_clicked(SPKnot *knot, guint state, gpointer data);
117 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
118 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
119 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
121 /* Handle event callbacks */
122 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
123 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
124 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
125 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
126 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
127 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
129 /* Constructors and destructors */
131 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
132 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
133 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
134 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
135 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
136 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
137 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
139 /* Helpers */
141 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
142 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
143 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
145 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
146 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
148 // active_node indicates mouseover node
149 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
151 /**
152 * \brief Creates new nodepath from item
153 */
154 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
155 {
156 Inkscape::XML::Node *repr = object->repr;
158 /** \todo
159 * FIXME: remove this. We don't want to edit paths inside flowtext.
160 * Instead we will build our flowtext with cloned paths, so that the
161 * real paths are outside the flowtext and thus editable as usual.
162 */
163 if (SP_IS_FLOWTEXT(object)) {
164 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
165 if SP_IS_FLOWREGION(child) {
166 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
167 if (grandchild && SP_IS_PATH(grandchild)) {
168 object = SP_ITEM(grandchild);
169 break;
170 }
171 }
172 }
173 }
175 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
177 if (curve == NULL)
178 return NULL;
180 NArtBpath *bpath = sp_curve_first_bpath(curve);
181 gint length = curve->end;
182 if (length == 0) {
183 sp_curve_unref(curve);
184 return NULL; // prevent crash for one-node paths
185 }
187 //Create new nodepath
188 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
189 if (!np) {
190 sp_curve_unref(curve);
191 return NULL;
192 }
194 // Set defaults
195 np->desktop = desktop;
196 np->object = object;
197 np->subpaths = NULL;
198 np->selected = NULL;
199 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
200 np->livarot_path = NULL;
201 np->local_change = 0;
202 np->show_handles = show_handles;
203 np->helper_path = NULL;
204 np->helperpath_rgba = 0xff0000ff;
205 np->helperpath_width = 1.0;
206 np->curve = sp_curve_copy(curve);
207 np->show_helperpath = prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1;
208 np->straight_path = false;
209 if (IS_LIVEPATHEFFECT(object) && item) {
210 np->item = item;
211 } else {
212 np->item = SP_ITEM(object);
213 }
215 // we need to update item's transform from the repr here,
216 // because they may be out of sync when we respond
217 // to a change in repr by regenerating nodepath --bb
218 sp_object_read_attr(SP_OBJECT(np->item), "transform");
220 np->i2d = sp_item_i2d_affine(np->item);
221 np->d2i = np->i2d.inverse();
223 np->repr = repr;
224 if (repr_key_in) { // apparantly the object is an LPEObject
225 np->repr_key = g_strdup(repr_key_in);
226 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
227 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
228 if (lpeparam) {
229 lpeparam->param_setup_nodepath(np);
230 }
231 } else {
232 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
233 if ( SP_SHAPE(np->object)->path_effect_href ) {
234 np->repr_key = g_strdup("inkscape:original-d");
236 LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(SP_SHAPE(np->object));
237 if (lpeobj && lpeobj->lpe) {
238 lpeobj->lpe->setup_nodepath(np);
239 }
240 } else {
241 np->repr_key = g_strdup("d");
242 }
243 }
245 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
246 gchar *typestr = parse_nodetypes(nodetypes, length);
248 // create the subpath(s) from the bpath
249 NArtBpath *b = bpath;
250 while (b->code != NR_END) {
251 b = subpath_from_bpath(np, b, typestr + (b - bpath));
252 }
254 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
255 np->subpaths = g_list_reverse(np->subpaths);
257 g_free(typestr);
258 sp_curve_unref(curve);
260 // create the livarot representation from the same item
261 sp_nodepath_ensure_livarot_path(np);
263 // Draw helper curve
264 if (np->show_helperpath) {
265 SPCurve *helper_curve = sp_curve_copy(np->curve);
266 sp_curve_transform(helper_curve, np->i2d );
267 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
268 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
269 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
270 sp_canvas_item_show(np->helper_path);
271 sp_curve_unref(helper_curve);
272 }
274 return np;
275 }
277 /**
278 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
279 */
280 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
282 if (!np) //soft fail, like delete
283 return;
285 while (np->subpaths) {
286 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
287 }
289 //Inform the ShapeEditor that made me, if any, that I am gone.
290 if (np->shape_editor)
291 np->shape_editor->nodepath_destroyed();
293 g_assert(!np->selected);
295 if (np->livarot_path) {
296 delete np->livarot_path;
297 np->livarot_path = NULL;
298 }
300 if (np->helper_path) {
301 GtkObject *temp = np->helper_path;
302 np->helper_path = NULL;
303 gtk_object_destroy(temp);
304 }
305 if (np->curve) {
306 sp_curve_unref(np->curve);
307 np->curve = NULL;
308 }
310 if (np->repr_key) {
311 g_free(np->repr_key);
312 np->repr_key = NULL;
313 }
314 if (np->repr_nodetypes_key) {
315 g_free(np->repr_nodetypes_key);
316 np->repr_nodetypes_key = NULL;
317 }
319 np->desktop = NULL;
321 g_free(np);
322 }
325 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
326 {
327 if (np && np->livarot_path == NULL) {
328 SPCurve *curve = create_curve(np);
329 NArtBpath *bpath = SP_CURVE_BPATH(curve);
330 np->livarot_path = bpath_to_Path(bpath);
332 if (np->livarot_path)
333 np->livarot_path->ConvertWithBackData(0.01);
335 sp_curve_unref(curve);
336 }
337 }
340 /**
341 * Return the node count of a given NodeSubPath.
342 */
343 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
344 {
345 if (!subpath)
346 return 0;
347 gint nodeCount = g_list_length(subpath->nodes);
348 return nodeCount;
349 }
351 /**
352 * Return the node count of a given NodePath.
353 */
354 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
355 {
356 if (!np)
357 return 0;
358 gint nodeCount = 0;
359 for (GList *item = np->subpaths ; item ; item=item->next) {
360 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
361 nodeCount += g_list_length(subpath->nodes);
362 }
363 return nodeCount;
364 }
366 /**
367 * Return the subpath count of a given NodePath.
368 */
369 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
370 {
371 if (!np)
372 return 0;
373 return g_list_length (np->subpaths);
374 }
376 /**
377 * Return the selected node count of a given NodePath.
378 */
379 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
380 {
381 if (!np)
382 return 0;
383 return g_list_length (np->selected);
384 }
386 /**
387 * Return the number of subpaths where nodes are selected in a given NodePath.
388 */
389 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
390 {
391 if (!np)
392 return 0;
393 if (!np->selected)
394 return 0;
395 if (!np->selected->next)
396 return 1;
397 gint count = 0;
398 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
399 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
400 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
401 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
402 if (node->selected) {
403 count ++;
404 break;
405 }
406 }
407 }
408 return count;
409 }
411 /**
412 * Clean up a nodepath after editing.
413 *
414 * Currently we are deleting trivial subpaths.
415 */
416 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
417 {
418 GList *badSubPaths = NULL;
420 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
421 for (GList *l = nodepath->subpaths; l ; l=l->next) {
422 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
423 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
424 badSubPaths = g_list_append(badSubPaths, sp);
425 }
427 //Delete them. This second step is because sp_nodepath_subpath_destroy()
428 //also removes the subpath from nodepath->subpaths
429 for (GList *l = badSubPaths; l ; l=l->next) {
430 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
431 sp_nodepath_subpath_destroy(sp);
432 }
434 g_list_free(badSubPaths);
435 }
437 /**
438 * Create new nodepath from b, make it subpath of np.
439 * \param t The node type.
440 * \todo Fixme: t should be a proper type, rather than gchar
441 */
442 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
443 {
444 NR::Point ppos, pos, npos;
446 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
448 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
449 bool const closed = (b->code == NR_MOVETO);
451 pos = NR::Point(b->x3, b->y3) * np->i2d;
452 if (b[1].code == NR_CURVETO) {
453 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
454 } else {
455 npos = pos;
456 }
457 Inkscape::NodePath::Node *n;
458 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
459 g_assert(sp->first == n);
460 g_assert(sp->last == n);
462 b++;
463 t++;
464 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
465 pos = NR::Point(b->x3, b->y3) * np->i2d;
466 if (b->code == NR_CURVETO) {
467 ppos = NR::Point(b->x2, b->y2) * np->i2d;
468 } else {
469 ppos = pos;
470 }
471 if (b[1].code == NR_CURVETO) {
472 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
473 } else {
474 npos = pos;
475 }
476 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
477 b++;
478 t++;
479 }
481 if (closed) sp_nodepath_subpath_close(sp);
483 return b;
484 }
486 /**
487 * Convert from sodipodi:nodetypes to new style type string.
488 */
489 static gchar *parse_nodetypes(gchar const *types, gint length)
490 {
491 g_assert(length > 0);
493 gchar *typestr = g_new(gchar, length + 1);
495 gint pos = 0;
497 if (types) {
498 for (gint i = 0; types[i] && ( i < length ); i++) {
499 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
500 if (types[i] != '\0') {
501 switch (types[i]) {
502 case 's':
503 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
504 break;
505 case 'z':
506 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
507 break;
508 case 'c':
509 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
510 break;
511 default:
512 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
513 break;
514 }
515 }
516 }
517 }
519 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
521 return typestr;
522 }
524 /**
525 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
526 * updated but repr is not (for speed). Used during curve and node drag.
527 */
528 static void update_object(Inkscape::NodePath::Path *np)
529 {
530 g_assert(np);
532 sp_curve_unref(np->curve);
533 np->curve = create_curve(np);
535 sp_nodepath_set_curve(np, np->curve);
537 if (np->show_helperpath) {
538 SPCurve * helper_curve = sp_curve_copy(np->curve);
539 sp_curve_transform(helper_curve, np->i2d );
540 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
541 sp_curve_unref(helper_curve);
542 }
543 }
545 /**
546 * Update XML path node with data from path object.
547 */
548 static void update_repr_internal(Inkscape::NodePath::Path *np)
549 {
550 g_assert(np);
552 Inkscape::XML::Node *repr = np->object->repr;
554 sp_curve_unref(np->curve);
555 np->curve = create_curve(np);
557 gchar *typestr = create_typestr(np);
558 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
560 // determine if path has an effect applied and write to correct "d" attribute.
561 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
562 np->local_change++;
563 repr->setAttribute(np->repr_key, svgpath);
564 }
566 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
567 np->local_change++;
568 repr->setAttribute(np->repr_nodetypes_key, typestr);
569 }
571 g_free(svgpath);
572 g_free(typestr);
574 if (np->show_helperpath) {
575 SPCurve * helper_curve = sp_curve_copy(np->curve);
576 sp_curve_transform(helper_curve, np->i2d );
577 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
578 sp_curve_unref(helper_curve);
579 }
580 }
582 /**
583 * Update XML path node with data from path object, commit changes forever.
584 */
585 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
586 {
587 //fixme: np can be NULL, so check before proceeding
588 g_return_if_fail(np != NULL);
590 if (np->livarot_path) {
591 delete np->livarot_path;
592 np->livarot_path = NULL;
593 }
595 update_repr_internal(np);
596 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
598 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
599 annotation);
600 }
602 /**
603 * Update XML path node with data from path object, commit changes with undo.
604 */
605 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
606 {
607 if (np->livarot_path) {
608 delete np->livarot_path;
609 np->livarot_path = NULL;
610 }
612 update_repr_internal(np);
613 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
614 annotation);
615 }
617 /**
618 * Make duplicate of path, replace corresponding XML node in tree, commit.
619 */
620 static void stamp_repr(Inkscape::NodePath::Path *np)
621 {
622 g_assert(np);
624 Inkscape::XML::Node *old_repr = np->object->repr;
625 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
627 // remember the position of the item
628 gint pos = old_repr->position();
629 // remember parent
630 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
632 SPCurve *curve = create_curve(np);
633 gchar *typestr = create_typestr(np);
635 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
637 new_repr->setAttribute(np->repr_key, svgpath);
638 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
640 // add the new repr to the parent
641 parent->appendChild(new_repr);
642 // move to the saved position
643 new_repr->setPosition(pos > 0 ? pos : 0);
645 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
646 _("Stamp"));
648 Inkscape::GC::release(new_repr);
649 g_free(svgpath);
650 g_free(typestr);
651 sp_curve_unref(curve);
652 }
654 /**
655 * Create curve from path.
656 */
657 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
658 {
659 SPCurve *curve = sp_curve_new();
661 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
662 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
663 sp_curve_moveto(curve,
664 sp->first->pos * np->d2i);
665 Inkscape::NodePath::Node *n = sp->first->n.other;
666 while (n) {
667 NR::Point const end_pt = n->pos * np->d2i;
668 switch (n->code) {
669 case NR_LINETO:
670 sp_curve_lineto(curve, end_pt);
671 break;
672 case NR_CURVETO:
673 sp_curve_curveto(curve,
674 n->p.other->n.pos * np->d2i,
675 n->p.pos * np->d2i,
676 end_pt);
677 break;
678 default:
679 g_assert_not_reached();
680 break;
681 }
682 if (n != sp->last) {
683 n = n->n.other;
684 } else {
685 n = NULL;
686 }
687 }
688 if (sp->closed) {
689 sp_curve_closepath(curve);
690 }
691 }
693 return curve;
694 }
696 /**
697 * Convert path type string to sodipodi:nodetypes style.
698 */
699 static gchar *create_typestr(Inkscape::NodePath::Path *np)
700 {
701 gchar *typestr = g_new(gchar, 32);
702 gint len = 32;
703 gint pos = 0;
705 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
706 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
708 if (pos >= len) {
709 typestr = g_renew(gchar, typestr, len + 32);
710 len += 32;
711 }
713 typestr[pos++] = 'c';
715 Inkscape::NodePath::Node *n;
716 n = sp->first->n.other;
717 while (n) {
718 gchar code;
720 switch (n->type) {
721 case Inkscape::NodePath::NODE_CUSP:
722 code = 'c';
723 break;
724 case Inkscape::NodePath::NODE_SMOOTH:
725 code = 's';
726 break;
727 case Inkscape::NodePath::NODE_SYMM:
728 code = 'z';
729 break;
730 default:
731 g_assert_not_reached();
732 code = '\0';
733 break;
734 }
736 if (pos >= len) {
737 typestr = g_renew(gchar, typestr, len + 32);
738 len += 32;
739 }
741 typestr[pos++] = code;
743 if (n != sp->last) {
744 n = n->n.other;
745 } else {
746 n = NULL;
747 }
748 }
749 }
751 if (pos >= len) {
752 typestr = g_renew(gchar, typestr, len + 1);
753 len += 1;
754 }
756 typestr[pos++] = '\0';
758 return typestr;
759 }
761 /**
762 * Returns current path in context. // later eliminate this function at all!
763 */
764 static Inkscape::NodePath::Path *sp_nodepath_current()
765 {
766 if (!SP_ACTIVE_DESKTOP) {
767 return NULL;
768 }
770 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
772 if (!SP_IS_NODE_CONTEXT(event_context)) {
773 return NULL;
774 }
776 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
777 }
781 /**
782 \brief Fills node and handle positions for three nodes, splitting line
783 marked by end at distance t.
784 */
785 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
786 {
787 g_assert(new_path != NULL);
788 g_assert(end != NULL);
790 g_assert(end->p.other == new_path);
791 Inkscape::NodePath::Node *start = new_path->p.other;
792 g_assert(start);
794 if (end->code == NR_LINETO) {
795 new_path->type =Inkscape::NodePath::NODE_CUSP;
796 new_path->code = NR_LINETO;
797 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
798 } else {
799 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
800 new_path->code = NR_CURVETO;
801 gdouble s = 1 - t;
802 for (int dim = 0; dim < 2; dim++) {
803 NR::Coord const f000 = start->pos[dim];
804 NR::Coord const f001 = start->n.pos[dim];
805 NR::Coord const f011 = end->p.pos[dim];
806 NR::Coord const f111 = end->pos[dim];
807 NR::Coord const f00t = s * f000 + t * f001;
808 NR::Coord const f01t = s * f001 + t * f011;
809 NR::Coord const f11t = s * f011 + t * f111;
810 NR::Coord const f0tt = s * f00t + t * f01t;
811 NR::Coord const f1tt = s * f01t + t * f11t;
812 NR::Coord const fttt = s * f0tt + t * f1tt;
813 start->n.pos[dim] = f00t;
814 new_path->p.pos[dim] = f0tt;
815 new_path->pos[dim] = fttt;
816 new_path->n.pos[dim] = f1tt;
817 end->p.pos[dim] = f11t;
818 }
819 }
820 }
822 /**
823 * Adds new node on direct line between two nodes, activates handles of all
824 * three nodes.
825 */
826 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
827 {
828 g_assert(end);
829 g_assert(end->subpath);
830 g_assert(g_list_find(end->subpath->nodes, end));
832 Inkscape::NodePath::Node *start = end->p.other;
833 g_assert( start->n.other == end );
834 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
835 end,
836 (NRPathcode)end->code == NR_LINETO?
837 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
838 (NRPathcode)end->code,
839 &start->pos, &start->pos, &start->n.pos);
840 sp_nodepath_line_midpoint(newnode, end, t);
842 sp_node_adjust_handles(start);
843 sp_node_update_handles(start);
844 sp_node_update_handles(newnode);
845 sp_node_adjust_handles(end);
846 sp_node_update_handles(end);
848 return newnode;
849 }
851 /**
852 \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
853 */
854 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
855 {
856 g_assert(node);
857 g_assert(node->subpath);
858 g_assert(g_list_find(node->subpath->nodes, node));
860 Inkscape::NodePath::SubPath *sp = node->subpath;
861 Inkscape::NodePath::Path *np = sp->nodepath;
863 if (sp->closed) {
864 sp_nodepath_subpath_open(sp, node);
865 return sp->first;
866 } else {
867 // no break for end nodes
868 if (node == sp->first) return NULL;
869 if (node == sp->last ) return NULL;
871 // create a new subpath
872 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
874 // duplicate the break node as start of the new subpath
875 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
877 while (node->n.other) { // copy the remaining nodes into the new subpath
878 Inkscape::NodePath::Node *n = node->n.other;
879 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);
880 if (n->selected) {
881 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
882 }
883 sp_nodepath_node_destroy(n); // remove the point on the original subpath
884 }
886 return newnode;
887 }
888 }
890 /**
891 * Duplicate node and connect to neighbours.
892 */
893 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
894 {
895 g_assert(node);
896 g_assert(node->subpath);
897 g_assert(g_list_find(node->subpath->nodes, node));
899 Inkscape::NodePath::SubPath *sp = node->subpath;
901 NRPathcode code = (NRPathcode) node->code;
902 if (code == NR_MOVETO) { // if node is the endnode,
903 node->code = NR_LINETO; // new one is inserted before it, so change that to line
904 }
906 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
908 if (!node->n.other || !node->p.other) // if node is an endnode, select it
909 return node;
910 else
911 return newnode; // otherwise select the newly created node
912 }
914 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
915 {
916 node->p.pos = (node->pos + (node->pos - node->n.pos));
917 }
919 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
920 {
921 node->n.pos = (node->pos + (node->pos - node->p.pos));
922 }
924 /**
925 * Change line type at node, with side effects on neighbours.
926 */
927 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
928 {
929 g_assert(end);
930 g_assert(end->subpath);
931 g_assert(end->p.other);
933 if (end->code == static_cast< guint > ( code ) )
934 return;
936 Inkscape::NodePath::Node *start = end->p.other;
938 end->code = code;
940 if (code == NR_LINETO) {
941 if (start->code == NR_LINETO) {
942 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
943 }
944 if (end->n.other) {
945 if (end->n.other->code == NR_LINETO) {
946 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
947 }
948 }
949 } else {
950 NR::Point delta = end->pos - start->pos;
951 start->n.pos = start->pos + delta / 3;
952 end->p.pos = end->pos - delta / 3;
953 sp_node_adjust_handle(start, 1);
954 sp_node_adjust_handle(end, -1);
955 }
957 sp_node_update_handles(start);
958 sp_node_update_handles(end);
959 }
961 /**
962 * Change node type, and its handles accordingly.
963 */
964 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
965 {
966 g_assert(node);
967 g_assert(node->subpath);
969 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
970 return node;
972 if ((node->p.other != NULL) && (node->n.other != NULL)) {
973 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
974 type =Inkscape::NodePath::NODE_CUSP;
975 }
976 }
978 node->type = type;
980 if (node->type == Inkscape::NodePath::NODE_CUSP) {
981 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
982 node->knot->setSize (node->selected? 11 : 9);
983 sp_knot_update_ctrl(node->knot);
984 } else {
985 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
986 node->knot->setSize (node->selected? 9 : 7);
987 sp_knot_update_ctrl(node->knot);
988 }
990 // if one of handles is mouseovered, preserve its position
991 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
992 sp_node_adjust_handle(node, 1);
993 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
994 sp_node_adjust_handle(node, -1);
995 } else {
996 sp_node_adjust_handles(node);
997 }
999 sp_node_update_handles(node);
1001 sp_nodepath_update_statusbar(node->subpath->nodepath);
1003 return node;
1004 }
1006 /**
1007 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
1008 * adjacent segments from lines to curves.
1009 */
1010 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1011 {
1012 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1013 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1015 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1016 if (p_line && n_line) {
1017 // only if both adjacent segments are lines,
1018 // convert both to curves:
1020 node->code = NR_CURVETO;
1021 node->n.other->code = NR_CURVETO;
1023 NR::Point leg_prev = node->pos - node->p.other->pos;
1024 NR::Point leg_next = node->pos - node->n.other->pos;
1026 double norm_leg_prev = L2(leg_prev);
1027 double norm_leg_next = L2(leg_next);
1029 // delta has length 1 and is orthogonal to bisecting line
1030 NR::Point delta;
1031 if (norm_leg_next > 0.0) {
1032 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1033 (&delta)->normalize();
1034 }
1036 if (type == Inkscape::NodePath::NODE_SYMM) {
1037 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1038 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1039 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1040 } else {
1041 // length of handle is proportional to distance to adjacent node
1042 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1043 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1044 }
1046 sp_node_update_handles(node);
1047 }
1048 }
1050 sp_nodepath_set_node_type (node, type);
1051 }
1053 /**
1054 * Move node to point, and adjust its and neighbouring handles.
1055 */
1056 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1057 {
1058 NR::Point delta = p - node->pos;
1059 node->pos = p;
1061 node->p.pos += delta;
1062 node->n.pos += delta;
1064 Inkscape::NodePath::Node *node_p = NULL;
1065 Inkscape::NodePath::Node *node_n = NULL;
1067 if (node->p.other) {
1068 if (node->code == NR_LINETO) {
1069 sp_node_adjust_handle(node, 1);
1070 sp_node_adjust_handle(node->p.other, -1);
1071 node_p = node->p.other;
1072 }
1073 }
1074 if (node->n.other) {
1075 if (node->n.other->code == NR_LINETO) {
1076 sp_node_adjust_handle(node, -1);
1077 sp_node_adjust_handle(node->n.other, 1);
1078 node_n = node->n.other;
1079 }
1080 }
1082 // this function is only called from batch movers that will update display at the end
1083 // themselves, so here we just move all the knots without emitting move signals, for speed
1084 sp_node_update_handles(node, false);
1085 if (node_n) {
1086 sp_node_update_handles(node_n, false);
1087 }
1088 if (node_p) {
1089 sp_node_update_handles(node_p, false);
1090 }
1091 }
1093 /**
1094 * Call sp_node_moveto() for node selection and handle possible snapping.
1095 */
1096 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1097 bool const snap = true)
1098 {
1099 NR::Coord best = NR_HUGE;
1100 NR::Point delta(dx, dy);
1101 NR::Point best_pt = delta;
1103 if (snap) {
1104 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1106 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1107 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1108 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item));
1109 if (s.getDistance() < best) {
1110 best = s.getDistance();
1111 best_pt = s.getPoint() - n->pos;
1112 }
1113 }
1114 }
1116 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1117 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1118 sp_node_moveto(n, n->pos + best_pt);
1119 }
1121 // do not update repr here so that node dragging is acceptably fast
1122 update_object(nodepath);
1123 }
1125 /**
1126 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1127 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1128 near x = 0.
1129 */
1130 double
1131 sculpt_profile (double x, double alpha, guint profile)
1132 {
1133 if (x >= 1)
1134 return 0;
1135 if (x <= 0)
1136 return 1;
1138 switch (profile) {
1139 case SCULPT_PROFILE_LINEAR:
1140 return 1 - x;
1141 case SCULPT_PROFILE_BELL:
1142 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1143 case SCULPT_PROFILE_ELLIPTIC:
1144 return sqrt(1 - x*x);
1145 }
1147 return 1;
1148 }
1150 double
1151 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1152 {
1153 // extremely primitive for now, don't have time to look for the real one
1154 double lower = NR::L2(b - a);
1155 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1156 return (lower + upper)/2;
1157 }
1159 void
1160 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1161 {
1162 n->pos = n->origin + delta;
1163 n->n.pos = n->n.origin + delta_n;
1164 n->p.pos = n->p.origin + delta_p;
1165 sp_node_adjust_handles(n);
1166 sp_node_update_handles(n, false);
1167 }
1169 /**
1170 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1171 * on how far they are from the dragged node n.
1172 */
1173 static void
1174 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1175 {
1176 g_assert (n);
1177 g_assert (nodepath);
1178 g_assert (n->subpath->nodepath == nodepath);
1180 double pressure = n->knot->pressure;
1181 if (pressure == 0)
1182 pressure = 0.5; // default
1183 pressure = CLAMP (pressure, 0.2, 0.8);
1185 // map pressure to alpha = 1/5 ... 5
1186 double alpha = 1 - 2 * fabs(pressure - 0.5);
1187 if (pressure > 0.5)
1188 alpha = 1/alpha;
1190 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1192 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1193 // Only one subpath has selected nodes:
1194 // use linear mode, where the distance from n to node being dragged is calculated along the path
1196 double n_sel_range = 0, p_sel_range = 0;
1197 guint n_nodes = 0, p_nodes = 0;
1198 guint n_sel_nodes = 0, p_sel_nodes = 0;
1200 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1201 {
1202 double n_range = 0, p_range = 0;
1203 bool n_going = true, p_going = true;
1204 Inkscape::NodePath::Node *n_node = n;
1205 Inkscape::NodePath::Node *p_node = n;
1206 do {
1207 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1208 if (n_node && n_going)
1209 n_node = n_node->n.other;
1210 if (n_node == NULL) {
1211 n_going = false;
1212 } else {
1213 n_nodes ++;
1214 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1215 if (n_node->selected) {
1216 n_sel_nodes ++;
1217 n_sel_range = n_range;
1218 }
1219 if (n_node == p_node) {
1220 n_going = false;
1221 p_going = false;
1222 }
1223 }
1224 if (p_node && p_going)
1225 p_node = p_node->p.other;
1226 if (p_node == NULL) {
1227 p_going = false;
1228 } else {
1229 p_nodes ++;
1230 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1231 if (p_node->selected) {
1232 p_sel_nodes ++;
1233 p_sel_range = p_range;
1234 }
1235 if (p_node == n_node) {
1236 n_going = false;
1237 p_going = false;
1238 }
1239 }
1240 } while (n_going || p_going);
1241 }
1243 // Second pass: actually move nodes in this subpath
1244 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1245 {
1246 double n_range = 0, p_range = 0;
1247 bool n_going = true, p_going = true;
1248 Inkscape::NodePath::Node *n_node = n;
1249 Inkscape::NodePath::Node *p_node = n;
1250 do {
1251 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1252 if (n_node && n_going)
1253 n_node = n_node->n.other;
1254 if (n_node == NULL) {
1255 n_going = false;
1256 } else {
1257 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1258 if (n_node->selected) {
1259 sp_nodepath_move_node_and_handles (n_node,
1260 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1261 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1262 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1263 }
1264 if (n_node == p_node) {
1265 n_going = false;
1266 p_going = false;
1267 }
1268 }
1269 if (p_node && p_going)
1270 p_node = p_node->p.other;
1271 if (p_node == NULL) {
1272 p_going = false;
1273 } else {
1274 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1275 if (p_node->selected) {
1276 sp_nodepath_move_node_and_handles (p_node,
1277 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1278 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1279 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1280 }
1281 if (p_node == n_node) {
1282 n_going = false;
1283 p_going = false;
1284 }
1285 }
1286 } while (n_going || p_going);
1287 }
1289 } else {
1290 // Multiple subpaths have selected nodes:
1291 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1292 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1293 // fix the pear-like shape when sculpting e.g. a ring
1295 // First pass: calculate range
1296 gdouble direct_range = 0;
1297 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1298 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1299 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1300 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1301 if (node->selected) {
1302 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1303 }
1304 }
1305 }
1307 // Second pass: actually move nodes
1308 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1309 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1310 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1311 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1312 if (node->selected) {
1313 if (direct_range > 1e-6) {
1314 sp_nodepath_move_node_and_handles (node,
1315 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1316 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1317 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1318 } else {
1319 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1320 }
1322 }
1323 }
1324 }
1325 }
1327 // do not update repr here so that node dragging is acceptably fast
1328 update_object(nodepath);
1329 }
1332 /**
1333 * Move node selection to point, adjust its and neighbouring handles,
1334 * handle possible snapping, and commit the change with possible undo.
1335 */
1336 void
1337 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1338 {
1339 if (!nodepath) return;
1341 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1343 if (dx == 0) {
1344 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1345 } else if (dy == 0) {
1346 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1347 } else {
1348 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1349 }
1350 }
1352 /**
1353 * Move node selection off screen and commit the change.
1354 */
1355 void
1356 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1357 {
1358 // borrowed from sp_selection_move_screen in selection-chemistry.c
1359 // we find out the current zoom factor and divide deltas by it
1360 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1362 gdouble zoom = desktop->current_zoom();
1363 gdouble zdx = dx / zoom;
1364 gdouble zdy = dy / zoom;
1366 if (!nodepath) return;
1368 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1370 if (dx == 0) {
1371 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1372 } else if (dy == 0) {
1373 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1374 } else {
1375 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1376 }
1377 }
1379 /**
1380 * Move selected nodes to the absolute position given
1381 */
1382 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1383 {
1384 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1385 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1386 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1387 sp_node_moveto(n, npos);
1388 }
1390 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1391 }
1393 /**
1394 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1395 */
1396 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1397 {
1398 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1399 g_return_val_if_fail(nodepath->selected, no_coord);
1401 // determine coordinate of first selected node
1402 GList *nsel = nodepath->selected;
1403 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1404 NR::Coord coord = n->pos[axis];
1405 bool coincide = true;
1407 // compare it to the coordinates of all the other selected nodes
1408 for (GList *l = nsel->next; l != NULL; l = l->next) {
1409 n = (Inkscape::NodePath::Node *) l->data;
1410 if (n->pos[axis] != coord) {
1411 coincide = false;
1412 }
1413 }
1414 if (coincide) {
1415 return coord;
1416 } else {
1417 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1418 // currently we return the coordinate of the bounding box midpoint because I don't know how
1419 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1420 return bbox.midpoint()[axis];
1421 }
1422 }
1424 /** If they don't yet exist, creates knot and line for the given side of the node */
1425 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1426 {
1427 if (!side->knot) {
1428 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"));
1430 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1431 side->knot->setSize (7);
1432 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1433 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1434 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1435 sp_knot_update_ctrl(side->knot);
1437 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1438 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1439 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1440 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1441 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1442 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1443 }
1445 if (!side->line) {
1446 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1447 SP_TYPE_CTRLLINE, NULL);
1448 }
1449 }
1451 /**
1452 * Ensure the given handle of the node is visible/invisible, update its screen position
1453 */
1454 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1455 {
1456 g_assert(node != NULL);
1458 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1459 NRPathcode code = sp_node_path_code_from_side(node, side);
1461 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1463 if (show_handle) {
1464 if (!side->knot) { // No handle knot at all
1465 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1466 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1467 side->knot->pos = side->pos;
1468 if (side->knot->item)
1469 SP_CTRL(side->knot->item)->moveto(side->pos);
1470 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1471 sp_knot_show(side->knot);
1472 } else {
1473 if (side->knot->pos != side->pos) { // only if it's really moved
1474 if (fire_move_signals) {
1475 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1476 } else {
1477 sp_knot_moveto(side->knot, &side->pos);
1478 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1479 }
1480 }
1481 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1482 sp_knot_show(side->knot);
1483 }
1484 }
1485 sp_canvas_item_show(side->line);
1486 } else {
1487 if (side->knot) {
1488 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1489 sp_knot_hide(side->knot);
1490 }
1491 }
1492 if (side->line) {
1493 sp_canvas_item_hide(side->line);
1494 }
1495 }
1496 }
1498 /**
1499 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1500 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1501 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1502 * updated; otherwise, just move the knots silently (used in batch moves).
1503 */
1504 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1505 {
1506 g_assert(node != NULL);
1508 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1509 sp_knot_show(node->knot);
1510 }
1512 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1513 if (fire_move_signals)
1514 sp_knot_set_position(node->knot, &node->pos, 0);
1515 else
1516 sp_knot_moveto(node->knot, &node->pos);
1517 }
1519 gboolean show_handles = node->selected;
1520 if (node->p.other != NULL) {
1521 if (node->p.other->selected) show_handles = TRUE;
1522 }
1523 if (node->n.other != NULL) {
1524 if (node->n.other->selected) show_handles = TRUE;
1525 }
1527 if (node->subpath->nodepath->show_handles == false)
1528 show_handles = FALSE;
1530 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1531 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1532 }
1534 /**
1535 * Call sp_node_update_handles() for all nodes on subpath.
1536 */
1537 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1538 {
1539 g_assert(subpath != NULL);
1541 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1542 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1543 }
1544 }
1546 /**
1547 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1548 */
1549 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1550 {
1551 g_assert(nodepath != NULL);
1553 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1554 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1555 }
1556 }
1558 void
1559 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1560 {
1561 if (nodepath == NULL) return;
1563 nodepath->show_handles = show;
1564 sp_nodepath_update_handles(nodepath);
1565 }
1567 /**
1568 * Adds all selected nodes in nodepath to list.
1569 */
1570 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1571 {
1572 StlConv<Node *>::list(l, selected);
1573 /// \todo this adds a copying, rework when the selection becomes a stl list
1574 }
1576 /**
1577 * Align selected nodes on the specified axis.
1578 */
1579 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1580 {
1581 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1582 return;
1583 }
1585 if ( !nodepath->selected->next ) { // only one node selected
1586 return;
1587 }
1588 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1589 NR::Point dest(pNode->pos);
1590 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1591 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1592 if (pNode) {
1593 dest[axis] = pNode->pos[axis];
1594 sp_node_moveto(pNode, dest);
1595 }
1596 }
1598 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1599 }
1601 /// Helper struct.
1602 struct NodeSort
1603 {
1604 Inkscape::NodePath::Node *_node;
1605 NR::Coord _coord;
1606 /// \todo use vectorof pointers instead of calling copy ctor
1607 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1608 _node(node), _coord(node->pos[axis])
1609 {}
1611 };
1613 static bool operator<(NodeSort const &a, NodeSort const &b)
1614 {
1615 return (a._coord < b._coord);
1616 }
1618 /**
1619 * Distribute selected nodes on the specified axis.
1620 */
1621 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1622 {
1623 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1624 return;
1625 }
1627 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1628 return;
1629 }
1631 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1632 std::vector<NodeSort> sorted;
1633 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1634 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1635 if (pNode) {
1636 NodeSort n(pNode, axis);
1637 sorted.push_back(n);
1638 //dest[axis] = pNode->pos[axis];
1639 //sp_node_moveto(pNode, dest);
1640 }
1641 }
1642 std::sort(sorted.begin(), sorted.end());
1643 unsigned int len = sorted.size();
1644 //overall bboxes span
1645 float dist = (sorted.back()._coord -
1646 sorted.front()._coord);
1647 //new distance between each bbox
1648 float step = (dist) / (len - 1);
1649 float pos = sorted.front()._coord;
1650 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1651 it < sorted.end();
1652 it ++ )
1653 {
1654 NR::Point dest((*it)._node->pos);
1655 dest[axis] = pos;
1656 sp_node_moveto((*it)._node, dest);
1657 pos += step;
1658 }
1660 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1661 }
1664 /**
1665 * Call sp_nodepath_line_add_node() for all selected segments.
1666 */
1667 void
1668 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1669 {
1670 if (!nodepath) {
1671 return;
1672 }
1674 GList *nl = NULL;
1676 int n_added = 0;
1678 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1679 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1680 g_assert(t->selected);
1681 if (t->p.other && t->p.other->selected) {
1682 nl = g_list_prepend(nl, t);
1683 }
1684 }
1686 while (nl) {
1687 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1688 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1689 sp_nodepath_node_select(n, TRUE, FALSE);
1690 n_added ++;
1691 nl = g_list_remove(nl, t);
1692 }
1694 /** \todo fixme: adjust ? */
1695 sp_nodepath_update_handles(nodepath);
1697 if (n_added > 1) {
1698 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1699 } else if (n_added > 0) {
1700 sp_nodepath_update_repr(nodepath, _("Add node"));
1701 }
1703 sp_nodepath_update_statusbar(nodepath);
1704 }
1706 /**
1707 * Select segment nearest to point
1708 */
1709 void
1710 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1711 {
1712 if (!nodepath) {
1713 return;
1714 }
1716 sp_nodepath_ensure_livarot_path(nodepath);
1717 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1718 if (!maybe_position) {
1719 return;
1720 }
1721 Path::cut_position position = *maybe_position;
1723 //find segment to segment
1724 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1726 //fixme: this can return NULL, so check before proceeding.
1727 g_return_if_fail(e != NULL);
1729 gboolean force = FALSE;
1730 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1731 force = TRUE;
1732 }
1733 sp_nodepath_node_select(e, (gboolean) toggle, force);
1734 if (e->p.other)
1735 sp_nodepath_node_select(e->p.other, TRUE, force);
1737 sp_nodepath_update_handles(nodepath);
1739 sp_nodepath_update_statusbar(nodepath);
1740 }
1742 /**
1743 * Add a node nearest to point
1744 */
1745 void
1746 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1747 {
1748 if (!nodepath) {
1749 return;
1750 }
1752 sp_nodepath_ensure_livarot_path(nodepath);
1753 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1754 if (!maybe_position) {
1755 return;
1756 }
1757 Path::cut_position position = *maybe_position;
1759 //find segment to split
1760 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1762 //don't know why but t seems to flip for lines
1763 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1764 position.t = 1.0 - position.t;
1765 }
1766 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1767 sp_nodepath_node_select(n, FALSE, TRUE);
1769 /* fixme: adjust ? */
1770 sp_nodepath_update_handles(nodepath);
1772 sp_nodepath_update_repr(nodepath, _("Add node"));
1774 sp_nodepath_update_statusbar(nodepath);
1775 }
1777 /*
1778 * Adjusts a segment so that t moves by a certain delta for dragging
1779 * converts lines to curves
1780 *
1781 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1782 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1783 */
1784 void
1785 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1786 {
1787 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1789 //fixme: e and e->p can be NULL, so check for those before proceeding
1790 g_return_if_fail(e != NULL);
1791 g_return_if_fail(&e->p != NULL);
1793 /* feel good is an arbitrary parameter that distributes the delta between handles
1794 * if t of the drag point is less than 1/6 distance form the endpoint only
1795 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1796 */
1797 double feel_good;
1798 if (t <= 1.0 / 6.0)
1799 feel_good = 0;
1800 else if (t <= 0.5)
1801 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1802 else if (t <= 5.0 / 6.0)
1803 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1804 else
1805 feel_good = 1;
1807 //if we're dragging a line convert it to a curve
1808 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1809 sp_nodepath_set_line_type(e, NR_CURVETO);
1810 }
1812 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1813 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1814 e->p.other->n.pos += offsetcoord0;
1815 e->p.pos += offsetcoord1;
1817 // adjust handles of adjacent nodes where necessary
1818 sp_node_adjust_handle(e,1);
1819 sp_node_adjust_handle(e->p.other,-1);
1821 sp_nodepath_update_handles(e->subpath->nodepath);
1823 update_object(e->subpath->nodepath);
1825 sp_nodepath_update_statusbar(e->subpath->nodepath);
1826 }
1829 /**
1830 * Call sp_nodepath_break() for all selected segments.
1831 */
1832 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1833 {
1834 if (!nodepath) return;
1836 GList *temp = NULL;
1837 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1838 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1839 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1840 if (nn == NULL) continue; // no break, no new node
1841 temp = g_list_prepend(temp, nn);
1842 }
1844 if (temp) {
1845 sp_nodepath_deselect(nodepath);
1846 }
1847 for (GList *l = temp; l != NULL; l = l->next) {
1848 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1849 }
1851 sp_nodepath_update_handles(nodepath);
1853 sp_nodepath_update_repr(nodepath, _("Break path"));
1854 }
1856 /**
1857 * Duplicate the selected node(s).
1858 */
1859 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1860 {
1861 if (!nodepath) {
1862 return;
1863 }
1865 GList *temp = NULL;
1866 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1867 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1868 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1869 if (nn == NULL) continue; // could not duplicate
1870 temp = g_list_prepend(temp, nn);
1871 }
1873 if (temp) {
1874 sp_nodepath_deselect(nodepath);
1875 }
1876 for (GList *l = temp; l != NULL; l = l->next) {
1877 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1878 }
1880 sp_nodepath_update_handles(nodepath);
1882 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1883 }
1885 /**
1886 * Join two nodes by merging them into one.
1887 */
1888 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1889 {
1890 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1892 if (g_list_length(nodepath->selected) != 2) {
1893 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1894 return;
1895 }
1897 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1898 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1900 g_assert(a != b);
1901 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1902 // someone tried to join an orphan node (i.e. a single-node subpath).
1903 // this is not worth an error message, just fail silently.
1904 return;
1905 }
1907 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1908 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1909 return;
1910 }
1912 /* a and b are endpoints */
1914 NR::Point c;
1915 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1916 c = a->pos;
1917 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1918 c = b->pos;
1919 } else {
1920 c = (a->pos + b->pos) / 2;
1921 }
1923 if (a->subpath == b->subpath) {
1924 Inkscape::NodePath::SubPath *sp = a->subpath;
1925 sp_nodepath_subpath_close(sp);
1926 sp_node_moveto (sp->first, c);
1928 sp_nodepath_update_handles(sp->nodepath);
1929 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1930 return;
1931 }
1933 /* a and b are separate subpaths */
1934 Inkscape::NodePath::SubPath *sa = a->subpath;
1935 Inkscape::NodePath::SubPath *sb = b->subpath;
1936 NR::Point p;
1937 Inkscape::NodePath::Node *n;
1938 NRPathcode code;
1939 if (a == sa->first) {
1940 p = sa->first->n.pos;
1941 code = (NRPathcode)sa->first->n.other->code;
1942 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1943 n = sa->last;
1944 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1945 n = n->p.other;
1946 while (n) {
1947 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1948 n = n->p.other;
1949 if (n == sa->first) n = NULL;
1950 }
1951 sp_nodepath_subpath_destroy(sa);
1952 sa = t;
1953 } else if (a == sa->last) {
1954 p = sa->last->p.pos;
1955 code = (NRPathcode)sa->last->code;
1956 sp_nodepath_node_destroy(sa->last);
1957 } else {
1958 code = NR_END;
1959 g_assert_not_reached();
1960 }
1962 if (b == sb->first) {
1963 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1964 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1965 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1966 }
1967 } else if (b == sb->last) {
1968 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1969 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1970 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1971 }
1972 } else {
1973 g_assert_not_reached();
1974 }
1975 /* and now destroy sb */
1977 sp_nodepath_subpath_destroy(sb);
1979 sp_nodepath_update_handles(sa->nodepath);
1981 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1983 sp_nodepath_update_statusbar(nodepath);
1984 }
1986 /**
1987 * Join two nodes by adding a segment between them.
1988 */
1989 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1990 {
1991 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1993 if (g_list_length(nodepath->selected) != 2) {
1994 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1995 return;
1996 }
1998 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1999 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2001 g_assert(a != b);
2002 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2003 // someone tried to join an orphan node (i.e. a single-node subpath).
2004 // this is not worth an error message, just fail silently.
2005 return;
2006 }
2008 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2009 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2010 return;
2011 }
2013 if (a->subpath == b->subpath) {
2014 Inkscape::NodePath::SubPath *sp = a->subpath;
2016 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2017 sp->closed = TRUE;
2019 sp->first->p.other = sp->last;
2020 sp->last->n.other = sp->first;
2022 sp_node_handle_mirror_p_to_n(sp->last);
2023 sp_node_handle_mirror_n_to_p(sp->first);
2025 sp->first->code = sp->last->code;
2026 sp->first = sp->last;
2028 sp_nodepath_update_handles(sp->nodepath);
2030 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2032 return;
2033 }
2035 /* a and b are separate subpaths */
2036 Inkscape::NodePath::SubPath *sa = a->subpath;
2037 Inkscape::NodePath::SubPath *sb = b->subpath;
2039 Inkscape::NodePath::Node *n;
2040 NR::Point p;
2041 NRPathcode code;
2042 if (a == sa->first) {
2043 code = (NRPathcode) sa->first->n.other->code;
2044 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2045 n = sa->last;
2046 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2047 for (n = n->p.other; n != NULL; n = n->p.other) {
2048 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2049 }
2050 sp_nodepath_subpath_destroy(sa);
2051 sa = t;
2052 } else if (a == sa->last) {
2053 code = (NRPathcode)sa->last->code;
2054 } else {
2055 code = NR_END;
2056 g_assert_not_reached();
2057 }
2059 if (b == sb->first) {
2060 n = sb->first;
2061 sp_node_handle_mirror_p_to_n(sa->last);
2062 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2063 sp_node_handle_mirror_n_to_p(sa->last);
2064 for (n = n->n.other; n != NULL; n = n->n.other) {
2065 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2066 }
2067 } else if (b == sb->last) {
2068 n = sb->last;
2069 sp_node_handle_mirror_p_to_n(sa->last);
2070 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2071 sp_node_handle_mirror_n_to_p(sa->last);
2072 for (n = n->p.other; n != NULL; n = n->p.other) {
2073 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2074 }
2075 } else {
2076 g_assert_not_reached();
2077 }
2078 /* and now destroy sb */
2080 sp_nodepath_subpath_destroy(sb);
2082 sp_nodepath_update_handles(sa->nodepath);
2084 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2085 }
2087 /**
2088 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2089 */
2090 void sp_node_delete_preserve(GList *nodes_to_delete)
2091 {
2092 GSList *nodepaths = NULL;
2094 while (nodes_to_delete) {
2095 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2096 Inkscape::NodePath::SubPath *sp = node->subpath;
2097 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2098 Inkscape::NodePath::Node *sample_cursor = NULL;
2099 Inkscape::NodePath::Node *sample_end = NULL;
2100 Inkscape::NodePath::Node *delete_cursor = node;
2101 bool just_delete = false;
2103 //find the start of this contiguous selection
2104 //move left to the first node that is not selected
2105 //or the start of the non-closed path
2106 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2107 delete_cursor = curr;
2108 }
2110 //just delete at the beginning of an open path
2111 if (!delete_cursor->p.other) {
2112 sample_cursor = delete_cursor;
2113 just_delete = true;
2114 } else {
2115 sample_cursor = delete_cursor->p.other;
2116 }
2118 //calculate points for each segment
2119 int rate = 5;
2120 float period = 1.0 / rate;
2121 std::vector<NR::Point> data;
2122 if (!just_delete) {
2123 data.push_back(sample_cursor->pos);
2124 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2125 //just delete at the end of an open path
2126 if (!sp->closed && curr == sp->last) {
2127 just_delete = true;
2128 break;
2129 }
2131 //sample points on the contiguous selected segment
2132 NR::Point *bez;
2133 bez = new NR::Point [4];
2134 bez[0] = curr->pos;
2135 bez[1] = curr->n.pos;
2136 bez[2] = curr->n.other->p.pos;
2137 bez[3] = curr->n.other->pos;
2138 for (int i=1; i<rate; i++) {
2139 gdouble t = i * period;
2140 NR::Point p = bezier_pt(3, bez, t);
2141 data.push_back(p);
2142 }
2143 data.push_back(curr->n.other->pos);
2145 sample_end = curr->n.other;
2146 //break if we've come full circle or hit the end of the selection
2147 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2148 break;
2149 }
2150 }
2151 }
2153 if (!just_delete) {
2154 //calculate the best fitting single segment and adjust the endpoints
2155 NR::Point *adata;
2156 adata = new NR::Point [data.size()];
2157 copy(data.begin(), data.end(), adata);
2159 NR::Point *bez;
2160 bez = new NR::Point [4];
2161 //would decreasing error create a better fitting approximation?
2162 gdouble error = 1.0;
2163 gint ret;
2164 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2166 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2167 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2168 //the resulting nodes behave as expected.
2169 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2170 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2172 //adjust endpoints
2173 sample_cursor->n.pos = bez[1];
2174 sample_end->p.pos = bez[2];
2175 }
2177 //destroy this contiguous selection
2178 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2179 Inkscape::NodePath::Node *temp = delete_cursor;
2180 if (delete_cursor->n.other == delete_cursor) {
2181 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2182 delete_cursor = NULL;
2183 } else {
2184 delete_cursor = delete_cursor->n.other;
2185 }
2186 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2187 sp_nodepath_node_destroy(temp);
2188 }
2190 sp_nodepath_update_handles(nodepath);
2192 if (!g_slist_find(nodepaths, nodepath))
2193 nodepaths = g_slist_prepend (nodepaths, nodepath);
2194 }
2196 for (GSList *i = nodepaths; i; i = i->next) {
2197 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2198 // different nodepaths will give us one undo event per nodepath
2199 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2201 // if the entire nodepath is removed, delete the selected object.
2202 if (nodepath->subpaths == NULL ||
2203 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2204 //at least 2
2205 sp_nodepath_get_node_count(nodepath) < 2) {
2206 SPDocument *document = sp_desktop_document (nodepath->desktop);
2207 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2208 //delete this nodepath's object, not the entire selection! (though at this time, this
2209 //does not matter)
2210 sp_selection_delete();
2211 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2212 _("Delete nodes"));
2213 } else {
2214 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2215 sp_nodepath_update_statusbar(nodepath);
2216 }
2217 }
2219 g_slist_free (nodepaths);
2220 }
2222 /**
2223 * Delete one or more selected nodes.
2224 */
2225 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2226 {
2227 if (!nodepath) return;
2228 if (!nodepath->selected) return;
2230 /** \todo fixme: do it the right way */
2231 while (nodepath->selected) {
2232 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2233 sp_nodepath_node_destroy(node);
2234 }
2237 //clean up the nodepath (such as for trivial subpaths)
2238 sp_nodepath_cleanup(nodepath);
2240 sp_nodepath_update_handles(nodepath);
2242 // if the entire nodepath is removed, delete the selected object.
2243 if (nodepath->subpaths == NULL ||
2244 sp_nodepath_get_node_count(nodepath) < 2) {
2245 SPDocument *document = sp_desktop_document (nodepath->desktop);
2246 sp_selection_delete();
2247 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2248 _("Delete nodes"));
2249 return;
2250 }
2252 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2254 sp_nodepath_update_statusbar(nodepath);
2255 }
2257 /**
2258 * Delete one or more segments between two selected nodes.
2259 * This is the code for 'split'.
2260 */
2261 void
2262 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2263 {
2264 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2265 Inkscape::NodePath::Node *curr, *next; //Iterators
2267 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2269 if (g_list_length(nodepath->selected) != 2) {
2270 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2271 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2272 return;
2273 }
2275 //Selected nodes, not inclusive
2276 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2277 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2279 if ( ( a==b) || //same node
2280 (a->subpath != b->subpath ) || //not the same path
2281 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2282 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2283 {
2284 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2285 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2286 return;
2287 }
2289 //###########################################
2290 //# BEGIN EDITS
2291 //###########################################
2292 //##################################
2293 //# CLOSED PATH
2294 //##################################
2295 if (a->subpath->closed) {
2298 gboolean reversed = FALSE;
2300 //Since we can go in a circle, we need to find the shorter distance.
2301 // a->b or b->a
2302 start = end = NULL;
2303 int distance = 0;
2304 int minDistance = 0;
2305 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2306 if (curr==b) {
2307 //printf("a to b:%d\n", distance);
2308 start = a;//go from a to b
2309 end = b;
2310 minDistance = distance;
2311 //printf("A to B :\n");
2312 break;
2313 }
2314 distance++;
2315 }
2317 //try again, the other direction
2318 distance = 0;
2319 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2320 if (curr==a) {
2321 //printf("b to a:%d\n", distance);
2322 if (distance < minDistance) {
2323 start = b; //we go from b to a
2324 end = a;
2325 reversed = TRUE;
2326 //printf("B to A\n");
2327 }
2328 break;
2329 }
2330 distance++;
2331 }
2334 //Copy everything from 'end' to 'start' to a new subpath
2335 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2336 for (curr=end ; curr ; curr=curr->n.other) {
2337 NRPathcode code = (NRPathcode) curr->code;
2338 if (curr == end)
2339 code = NR_MOVETO;
2340 sp_nodepath_node_new(t, NULL,
2341 (Inkscape::NodePath::NodeType)curr->type, code,
2342 &curr->p.pos, &curr->pos, &curr->n.pos);
2343 if (curr == start)
2344 break;
2345 }
2346 sp_nodepath_subpath_destroy(a->subpath);
2349 }
2353 //##################################
2354 //# OPEN PATH
2355 //##################################
2356 else {
2358 //We need to get the direction of the list between A and B
2359 //Can we walk from a to b?
2360 start = end = NULL;
2361 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2362 if (curr==b) {
2363 start = a; //did it! we go from a to b
2364 end = b;
2365 //printf("A to B\n");
2366 break;
2367 }
2368 }
2369 if (!start) {//didn't work? let's try the other direction
2370 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2371 if (curr==a) {
2372 start = b; //did it! we go from b to a
2373 end = a;
2374 //printf("B to A\n");
2375 break;
2376 }
2377 }
2378 }
2379 if (!start) {
2380 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2381 _("Cannot find path between nodes."));
2382 return;
2383 }
2387 //Copy everything after 'end' to a new subpath
2388 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2389 for (curr=end ; curr ; curr=curr->n.other) {
2390 NRPathcode code = (NRPathcode) curr->code;
2391 if (curr == end)
2392 code = NR_MOVETO;
2393 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2394 &curr->p.pos, &curr->pos, &curr->n.pos);
2395 }
2397 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2398 for (curr = start->n.other ; curr ; curr=next) {
2399 next = curr->n.other;
2400 sp_nodepath_node_destroy(curr);
2401 }
2403 }
2404 //###########################################
2405 //# END EDITS
2406 //###########################################
2408 //clean up the nodepath (such as for trivial subpaths)
2409 sp_nodepath_cleanup(nodepath);
2411 sp_nodepath_update_handles(nodepath);
2413 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2415 sp_nodepath_update_statusbar(nodepath);
2416 }
2418 /**
2419 * Call sp_nodepath_set_line() for all selected segments.
2420 */
2421 void
2422 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2423 {
2424 if (nodepath == NULL) return;
2426 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2427 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2428 g_assert(n->selected);
2429 if (n->p.other && n->p.other->selected) {
2430 sp_nodepath_set_line_type(n, code);
2431 }
2432 }
2434 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2435 }
2437 /**
2438 * Call sp_nodepath_convert_node_type() for all selected nodes.
2439 */
2440 void
2441 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2442 {
2443 if (nodepath == NULL) return;
2445 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2447 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2448 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2449 }
2451 sp_nodepath_update_repr(nodepath, _("Change node type"));
2452 }
2454 /**
2455 * Change select status of node, update its own and neighbour handles.
2456 */
2457 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2458 {
2459 node->selected = selected;
2461 if (selected) {
2462 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2463 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2464 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2465 sp_knot_update_ctrl(node->knot);
2466 } else {
2467 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2468 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2469 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2470 sp_knot_update_ctrl(node->knot);
2471 }
2473 sp_node_update_handles(node);
2474 if (node->n.other) sp_node_update_handles(node->n.other);
2475 if (node->p.other) sp_node_update_handles(node->p.other);
2476 }
2478 /**
2479 \brief Select a node
2480 \param node The node to select
2481 \param incremental If true, add to selection, otherwise deselect others
2482 \param override If true, always select this node, otherwise toggle selected status
2483 */
2484 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2485 {
2486 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2488 if (incremental) {
2489 if (override) {
2490 if (!g_list_find(nodepath->selected, node)) {
2491 nodepath->selected = g_list_prepend(nodepath->selected, node);
2492 }
2493 sp_node_set_selected(node, TRUE);
2494 } else { // toggle
2495 if (node->selected) {
2496 g_assert(g_list_find(nodepath->selected, node));
2497 nodepath->selected = g_list_remove(nodepath->selected, node);
2498 } else {
2499 g_assert(!g_list_find(nodepath->selected, node));
2500 nodepath->selected = g_list_prepend(nodepath->selected, node);
2501 }
2502 sp_node_set_selected(node, !node->selected);
2503 }
2504 } else {
2505 sp_nodepath_deselect(nodepath);
2506 nodepath->selected = g_list_prepend(nodepath->selected, node);
2507 sp_node_set_selected(node, TRUE);
2508 }
2510 sp_nodepath_update_statusbar(nodepath);
2511 }
2514 /**
2515 \brief Deselect all nodes in the nodepath
2516 */
2517 void
2518 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2519 {
2520 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2522 while (nodepath->selected) {
2523 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2524 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2525 }
2526 sp_nodepath_update_statusbar(nodepath);
2527 }
2529 /**
2530 \brief Select or invert selection of all nodes in the nodepath
2531 */
2532 void
2533 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2534 {
2535 if (!nodepath) return;
2537 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2538 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2539 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2540 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2541 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2542 }
2543 }
2544 }
2546 /**
2547 * If nothing selected, does the same as sp_nodepath_select_all();
2548 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2549 * (i.e., similar to "select all in layer", with the "selected" subpaths
2550 * being treated as "layers" in the path).
2551 */
2552 void
2553 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2554 {
2555 if (!nodepath) return;
2557 if (g_list_length (nodepath->selected) == 0) {
2558 sp_nodepath_select_all (nodepath, invert);
2559 return;
2560 }
2562 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2563 GSList *subpaths = NULL;
2565 for (GList *l = copy; l != NULL; l = l->next) {
2566 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2567 Inkscape::NodePath::SubPath *subpath = n->subpath;
2568 if (!g_slist_find (subpaths, subpath))
2569 subpaths = g_slist_prepend (subpaths, subpath);
2570 }
2572 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2573 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2574 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2575 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2576 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2577 }
2578 }
2580 g_slist_free (subpaths);
2581 g_list_free (copy);
2582 }
2584 /**
2585 * \brief Select the node after the last selected; if none is selected,
2586 * select the first within path.
2587 */
2588 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2589 {
2590 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2592 Inkscape::NodePath::Node *last = NULL;
2593 if (nodepath->selected) {
2594 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2595 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2596 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2597 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2598 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2599 if (node->selected) {
2600 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2601 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2602 if (spl->next) { // there's a next subpath
2603 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2604 last = subpath_next->first;
2605 } else if (spl->prev) { // there's a previous subpath
2606 last = NULL; // to be set later to the first node of first subpath
2607 } else {
2608 last = node->n.other;
2609 }
2610 } else {
2611 last = node->n.other;
2612 }
2613 } else {
2614 if (node->n.other) {
2615 last = node->n.other;
2616 } else {
2617 if (spl->next) { // there's a next subpath
2618 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2619 last = subpath_next->first;
2620 } else if (spl->prev) { // there's a previous subpath
2621 last = NULL; // to be set later to the first node of first subpath
2622 } else {
2623 last = (Inkscape::NodePath::Node *) subpath->first;
2624 }
2625 }
2626 }
2627 }
2628 }
2629 }
2630 sp_nodepath_deselect(nodepath);
2631 }
2633 if (last) { // there's at least one more node after selected
2634 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2635 } else { // no more nodes, select the first one in first subpath
2636 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2637 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2638 }
2639 }
2641 /**
2642 * \brief Select the node before the first selected; if none is selected,
2643 * select the last within path
2644 */
2645 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2646 {
2647 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2649 Inkscape::NodePath::Node *last = NULL;
2650 if (nodepath->selected) {
2651 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2652 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2653 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2654 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2655 if (node->selected) {
2656 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2657 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2658 if (spl->prev) { // there's a prev subpath
2659 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2660 last = subpath_prev->last;
2661 } else if (spl->next) { // there's a next subpath
2662 last = NULL; // to be set later to the last node of last subpath
2663 } else {
2664 last = node->p.other;
2665 }
2666 } else {
2667 last = node->p.other;
2668 }
2669 } else {
2670 if (node->p.other) {
2671 last = node->p.other;
2672 } else {
2673 if (spl->prev) { // there's a prev subpath
2674 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2675 last = subpath_prev->last;
2676 } else if (spl->next) { // there's a next subpath
2677 last = NULL; // to be set later to the last node of last subpath
2678 } else {
2679 last = (Inkscape::NodePath::Node *) subpath->last;
2680 }
2681 }
2682 }
2683 }
2684 }
2685 }
2686 sp_nodepath_deselect(nodepath);
2687 }
2689 if (last) { // there's at least one more node before selected
2690 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2691 } else { // no more nodes, select the last one in last subpath
2692 GList *spl = g_list_last(nodepath->subpaths);
2693 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2694 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2695 }
2696 }
2698 /**
2699 * \brief Select all nodes that are within the rectangle.
2700 */
2701 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2702 {
2703 if (!incremental) {
2704 sp_nodepath_deselect(nodepath);
2705 }
2707 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2708 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2709 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2710 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2712 if (b.contains(node->pos)) {
2713 sp_nodepath_node_select(node, TRUE, TRUE);
2714 }
2715 }
2716 }
2717 }
2720 void
2721 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2722 {
2723 g_assert (n);
2724 g_assert (nodepath);
2725 g_assert (n->subpath->nodepath == nodepath);
2727 if (g_list_length (nodepath->selected) == 0) {
2728 if (grow > 0) {
2729 sp_nodepath_node_select(n, TRUE, TRUE);
2730 }
2731 return;
2732 }
2734 if (g_list_length (nodepath->selected) == 1) {
2735 if (grow < 0) {
2736 sp_nodepath_deselect (nodepath);
2737 return;
2738 }
2739 }
2741 double n_sel_range = 0, p_sel_range = 0;
2742 Inkscape::NodePath::Node *farthest_n_node = n;
2743 Inkscape::NodePath::Node *farthest_p_node = n;
2745 // Calculate ranges
2746 {
2747 double n_range = 0, p_range = 0;
2748 bool n_going = true, p_going = true;
2749 Inkscape::NodePath::Node *n_node = n;
2750 Inkscape::NodePath::Node *p_node = n;
2751 do {
2752 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2753 if (n_node && n_going)
2754 n_node = n_node->n.other;
2755 if (n_node == NULL) {
2756 n_going = false;
2757 } else {
2758 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2759 if (n_node->selected) {
2760 n_sel_range = n_range;
2761 farthest_n_node = n_node;
2762 }
2763 if (n_node == p_node) {
2764 n_going = false;
2765 p_going = false;
2766 }
2767 }
2768 if (p_node && p_going)
2769 p_node = p_node->p.other;
2770 if (p_node == NULL) {
2771 p_going = false;
2772 } else {
2773 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2774 if (p_node->selected) {
2775 p_sel_range = p_range;
2776 farthest_p_node = p_node;
2777 }
2778 if (p_node == n_node) {
2779 n_going = false;
2780 p_going = false;
2781 }
2782 }
2783 } while (n_going || p_going);
2784 }
2786 if (grow > 0) {
2787 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2788 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2789 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2790 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2791 }
2792 } else {
2793 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2794 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2795 } else if (farthest_p_node && farthest_p_node->selected) {
2796 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2797 }
2798 }
2799 }
2801 void
2802 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2803 {
2804 g_assert (n);
2805 g_assert (nodepath);
2806 g_assert (n->subpath->nodepath == nodepath);
2808 if (g_list_length (nodepath->selected) == 0) {
2809 if (grow > 0) {
2810 sp_nodepath_node_select(n, TRUE, TRUE);
2811 }
2812 return;
2813 }
2815 if (g_list_length (nodepath->selected) == 1) {
2816 if (grow < 0) {
2817 sp_nodepath_deselect (nodepath);
2818 return;
2819 }
2820 }
2822 Inkscape::NodePath::Node *farthest_selected = NULL;
2823 double farthest_dist = 0;
2825 Inkscape::NodePath::Node *closest_unselected = NULL;
2826 double closest_dist = NR_HUGE;
2828 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2829 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2830 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2831 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2832 if (node == n)
2833 continue;
2834 if (node->selected) {
2835 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2836 farthest_dist = NR::L2(node->pos - n->pos);
2837 farthest_selected = node;
2838 }
2839 } else {
2840 if (NR::L2(node->pos - n->pos) < closest_dist) {
2841 closest_dist = NR::L2(node->pos - n->pos);
2842 closest_unselected = node;
2843 }
2844 }
2845 }
2846 }
2848 if (grow > 0) {
2849 if (closest_unselected) {
2850 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2851 }
2852 } else {
2853 if (farthest_selected) {
2854 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2855 }
2856 }
2857 }
2860 /**
2861 \brief Saves all nodes' and handles' current positions in their origin members
2862 */
2863 void
2864 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2865 {
2866 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2867 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2868 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2869 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2870 n->origin = n->pos;
2871 n->p.origin = n->p.pos;
2872 n->n.origin = n->n.pos;
2873 }
2874 }
2875 }
2877 /**
2878 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2879 */
2880 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2881 {
2882 if (!nodepath->selected) {
2883 return NULL;
2884 }
2886 GList *r = NULL;
2887 guint i = 0;
2888 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2889 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2890 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2891 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2892 i++;
2893 if (node->selected) {
2894 r = g_list_append(r, GINT_TO_POINTER(i));
2895 }
2896 }
2897 }
2898 return r;
2899 }
2901 /**
2902 \brief Restores selection by selecting nodes whose positions are in the list
2903 */
2904 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2905 {
2906 sp_nodepath_deselect(nodepath);
2908 guint i = 0;
2909 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2910 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2911 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2912 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2913 i++;
2914 if (g_list_find(r, GINT_TO_POINTER(i))) {
2915 sp_nodepath_node_select(node, TRUE, TRUE);
2916 }
2917 }
2918 }
2920 }
2922 /**
2923 \brief Adjusts handle according to node type and line code.
2924 */
2925 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2926 {
2927 double len, otherlen, linelen;
2929 g_assert(node);
2931 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2932 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2934 /** \todo fixme: */
2935 if (me->other == NULL) return;
2936 if (other->other == NULL) return;
2938 /* I have line */
2940 NRPathcode mecode, ocode;
2941 if (which_adjust == 1) {
2942 mecode = (NRPathcode)me->other->code;
2943 ocode = (NRPathcode)node->code;
2944 } else {
2945 mecode = (NRPathcode)node->code;
2946 ocode = (NRPathcode)other->other->code;
2947 }
2949 if (mecode == NR_LINETO) return;
2951 /* I am curve */
2953 if (other->other == NULL) return;
2955 /* Other has line */
2957 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2959 NR::Point delta;
2960 if (ocode == NR_LINETO) {
2961 /* other is lineto, we are either smooth or symm */
2962 Inkscape::NodePath::Node *othernode = other->other;
2963 len = NR::L2(me->pos - node->pos);
2964 delta = node->pos - othernode->pos;
2965 linelen = NR::L2(delta);
2966 if (linelen < 1e-18)
2967 return;
2968 me->pos = node->pos + (len / linelen)*delta;
2969 return;
2970 }
2972 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2974 me->pos = 2 * node->pos - other->pos;
2975 return;
2976 }
2978 /* We are smooth */
2980 len = NR::L2(me->pos - node->pos);
2981 delta = other->pos - node->pos;
2982 otherlen = NR::L2(delta);
2983 if (otherlen < 1e-18) return;
2985 me->pos = node->pos - (len / otherlen) * delta;
2986 }
2988 /**
2989 \brief Adjusts both handles according to node type and line code
2990 */
2991 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2992 {
2993 g_assert(node);
2995 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2997 /* we are either smooth or symm */
2999 if (node->p.other == NULL) return;
3001 if (node->n.other == NULL) return;
3003 if (node->code == NR_LINETO) {
3004 if (node->n.other->code == NR_LINETO) return;
3005 sp_node_adjust_handle(node, 1);
3006 return;
3007 }
3009 if (node->n.other->code == NR_LINETO) {
3010 if (node->code == NR_LINETO) return;
3011 sp_node_adjust_handle(node, -1);
3012 return;
3013 }
3015 /* both are curves */
3016 NR::Point const delta( node->n.pos - node->p.pos );
3018 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3019 node->p.pos = node->pos - delta / 2;
3020 node->n.pos = node->pos + delta / 2;
3021 return;
3022 }
3024 /* We are smooth */
3025 double plen = NR::L2(node->p.pos - node->pos);
3026 if (plen < 1e-18) return;
3027 double nlen = NR::L2(node->n.pos - node->pos);
3028 if (nlen < 1e-18) return;
3029 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3030 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3031 }
3033 /**
3034 * Node event callback.
3035 */
3036 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3037 {
3038 gboolean ret = FALSE;
3039 switch (event->type) {
3040 case GDK_ENTER_NOTIFY:
3041 Inkscape::NodePath::Path::active_node = n;
3042 break;
3043 case GDK_LEAVE_NOTIFY:
3044 Inkscape::NodePath::Path::active_node = NULL;
3045 break;
3046 case GDK_SCROLL:
3047 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3048 switch (event->scroll.direction) {
3049 case GDK_SCROLL_UP:
3050 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3051 break;
3052 case GDK_SCROLL_DOWN:
3053 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3054 break;
3055 default:
3056 break;
3057 }
3058 ret = TRUE;
3059 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3060 switch (event->scroll.direction) {
3061 case GDK_SCROLL_UP:
3062 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3063 break;
3064 case GDK_SCROLL_DOWN:
3065 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3066 break;
3067 default:
3068 break;
3069 }
3070 ret = TRUE;
3071 }
3072 break;
3073 case GDK_KEY_PRESS:
3074 switch (get_group0_keyval (&event->key)) {
3075 case GDK_space:
3076 if (event->key.state & GDK_BUTTON1_MASK) {
3077 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3078 stamp_repr(nodepath);
3079 ret = TRUE;
3080 }
3081 break;
3082 case GDK_Page_Up:
3083 if (event->key.state & GDK_CONTROL_MASK) {
3084 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3085 } else {
3086 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3087 }
3088 break;
3089 case GDK_Page_Down:
3090 if (event->key.state & GDK_CONTROL_MASK) {
3091 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3092 } else {
3093 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3094 }
3095 break;
3096 default:
3097 break;
3098 }
3099 break;
3100 default:
3101 break;
3102 }
3104 return ret;
3105 }
3107 /**
3108 * Handle keypress on node; directly called.
3109 */
3110 gboolean node_key(GdkEvent *event)
3111 {
3112 Inkscape::NodePath::Path *np;
3114 // there is no way to verify nodes so set active_node to nil when deleting!!
3115 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3117 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3118 gint ret = FALSE;
3119 switch (get_group0_keyval (&event->key)) {
3120 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3121 case GDK_BackSpace:
3122 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3123 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3124 sp_nodepath_update_repr(np, _("Delete node"));
3125 Inkscape::NodePath::Path::active_node = NULL;
3126 ret = TRUE;
3127 break;
3128 case GDK_c:
3129 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3130 ret = TRUE;
3131 break;
3132 case GDK_s:
3133 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3134 ret = TRUE;
3135 break;
3136 case GDK_y:
3137 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3138 ret = TRUE;
3139 break;
3140 case GDK_b:
3141 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3142 ret = TRUE;
3143 break;
3144 }
3145 return ret;
3146 }
3147 return FALSE;
3148 }
3150 /**
3151 * Mouseclick on node callback.
3152 */
3153 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3154 {
3155 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3157 if (state & GDK_CONTROL_MASK) {
3158 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3160 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3161 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3162 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3163 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3164 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3165 } else {
3166 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3167 }
3168 sp_nodepath_update_repr(nodepath, _("Change node type"));
3169 sp_nodepath_update_statusbar(nodepath);
3171 } else { //ctrl+alt+click: delete node
3172 GList *node_to_delete = NULL;
3173 node_to_delete = g_list_append(node_to_delete, n);
3174 sp_node_delete_preserve(node_to_delete);
3175 }
3177 } else {
3178 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3179 }
3180 }
3182 /**
3183 * Mouse grabbed node callback.
3184 */
3185 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3186 {
3187 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3189 if (!n->selected) {
3190 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3191 }
3193 n->is_dragging = true;
3194 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3196 sp_nodepath_remember_origins (n->subpath->nodepath);
3197 }
3199 /**
3200 * Mouse ungrabbed node callback.
3201 */
3202 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3203 {
3204 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3206 n->dragging_out = NULL;
3207 n->is_dragging = false;
3208 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3210 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3211 }
3213 /**
3214 * The point on a line, given by its angle, closest to the given point.
3215 * \param p A point.
3216 * \param a Angle of the line; it is assumed to go through coordinate origin.
3217 * \param closest Pointer to the point struct where the result is stored.
3218 * \todo FIXME: use dot product perhaps?
3219 */
3220 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3221 {
3222 if (a == HUGE_VAL) { // vertical
3223 *closest = NR::Point(0, (*p)[NR::Y]);
3224 } else {
3225 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3226 (*closest)[NR::Y] = a * (*closest)[NR::X];
3227 }
3228 }
3230 /**
3231 * Distance from the point to a line given by its angle.
3232 * \param p A point.
3233 * \param a Angle of the line; it is assumed to go through coordinate origin.
3234 */
3235 static double point_line_distance(NR::Point *p, double a)
3236 {
3237 NR::Point c;
3238 point_line_closest(p, a, &c);
3239 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]));
3240 }
3242 /**
3243 * Callback for node "request" signal.
3244 * \todo fixme: This goes to "moved" event? (lauris)
3245 */
3246 static gboolean
3247 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3248 {
3249 double yn, xn, yp, xp;
3250 double an, ap, na, pa;
3251 double d_an, d_ap, d_na, d_pa;
3252 gboolean collinear = FALSE;
3253 NR::Point c;
3254 NR::Point pr;
3256 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3258 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3259 if ( (!n->subpath->nodepath->straight_path) &&
3260 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3261 || n->dragging_out ) )
3262 {
3263 NR::Point mouse = (*p);
3265 if (!n->dragging_out) {
3266 // This is the first drag-out event; find out which handle to drag out
3267 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3268 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3270 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3271 return FALSE;
3273 Inkscape::NodePath::NodeSide *opposite;
3274 if (appr_p > appr_n) { // closer to p
3275 n->dragging_out = &n->p;
3276 opposite = &n->n;
3277 n->code = NR_CURVETO;
3278 } else if (appr_p < appr_n) { // closer to n
3279 n->dragging_out = &n->n;
3280 opposite = &n->p;
3281 n->n.other->code = NR_CURVETO;
3282 } else { // p and n nodes are the same
3283 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3284 n->dragging_out = &n->p;
3285 opposite = &n->n;
3286 n->code = NR_CURVETO;
3287 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3288 n->dragging_out = &n->n;
3289 opposite = &n->p;
3290 n->n.other->code = NR_CURVETO;
3291 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3292 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);
3293 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);
3294 if (appr_other_p > appr_other_n) { // closer to other's p handle
3295 n->dragging_out = &n->n;
3296 opposite = &n->p;
3297 n->n.other->code = NR_CURVETO;
3298 } else { // closer to other's n handle
3299 n->dragging_out = &n->p;
3300 opposite = &n->n;
3301 n->code = NR_CURVETO;
3302 }
3303 }
3304 }
3306 // if there's another handle, make sure the one we drag out starts parallel to it
3307 if (opposite->pos != n->pos) {
3308 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3309 }
3311 // knots might not be created yet!
3312 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3313 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3314 }
3316 // pass this on to the handle-moved callback
3317 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3318 sp_node_update_handles(n);
3319 return TRUE;
3320 }
3322 if (state & GDK_CONTROL_MASK) { // constrained motion
3324 // calculate relative distances of handles
3325 // n handle:
3326 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3327 xn = n->n.pos[NR::X] - n->pos[NR::X];
3328 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3329 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3330 if (n->n.other) { // if there is the next point
3331 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3332 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3333 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3334 }
3335 }
3336 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3337 if (yn < 0) { xn = -xn; yn = -yn; }
3339 // p handle:
3340 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3341 xp = n->p.pos[NR::X] - n->pos[NR::X];
3342 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3343 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3344 if (n->p.other) {
3345 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3346 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3347 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3348 }
3349 }
3350 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3351 if (yp < 0) { xp = -xp; yp = -yp; }
3353 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3354 // sliding on handles, only if at least one of the handles is non-vertical
3355 // (otherwise it's the same as ctrl+drag anyway)
3357 // calculate angles of the handles
3358 if (xn == 0) {
3359 if (yn == 0) { // no handle, consider it the continuation of the other one
3360 an = 0;
3361 collinear = TRUE;
3362 }
3363 else an = 0; // vertical; set the angle to horizontal
3364 } else an = yn/xn;
3366 if (xp == 0) {
3367 if (yp == 0) { // no handle, consider it the continuation of the other one
3368 ap = an;
3369 }
3370 else ap = 0; // vertical; set the angle to horizontal
3371 } else ap = yp/xp;
3373 if (collinear) an = ap;
3375 // angles of the perpendiculars; HUGE_VAL means vertical
3376 if (an == 0) na = HUGE_VAL; else na = -1/an;
3377 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3379 // mouse point relative to the node's original pos
3380 pr = (*p) - n->origin;
3382 // distances to the four lines (two handles and two perpendiculars)
3383 d_an = point_line_distance(&pr, an);
3384 d_na = point_line_distance(&pr, na);
3385 d_ap = point_line_distance(&pr, ap);
3386 d_pa = point_line_distance(&pr, pa);
3388 // find out which line is the closest, save its closest point in c
3389 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3390 point_line_closest(&pr, an, &c);
3391 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3392 point_line_closest(&pr, ap, &c);
3393 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3394 point_line_closest(&pr, na, &c);
3395 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3396 point_line_closest(&pr, pa, &c);
3397 }
3399 // move the node to the closest point
3400 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3401 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3402 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3404 } else { // constraining to hor/vert
3406 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3407 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3408 } else { // snap to vert
3409 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3410 }
3411 }
3412 } else { // move freely
3413 if (n->is_dragging) {
3414 if (state & GDK_MOD1_MASK) { // sculpt
3415 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3416 } else {
3417 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3418 (*p)[NR::X] - n->pos[NR::X],
3419 (*p)[NR::Y] - n->pos[NR::Y],
3420 (state & GDK_SHIFT_MASK) == 0);
3421 }
3422 }
3423 }
3425 n->subpath->nodepath->desktop->scroll_to_point(p);
3427 return TRUE;
3428 }
3430 /**
3431 * Node handle clicked callback.
3432 */
3433 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3434 {
3435 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3437 if (state & GDK_CONTROL_MASK) { // "delete" handle
3438 if (n->p.knot == knot) {
3439 n->p.pos = n->pos;
3440 } else if (n->n.knot == knot) {
3441 n->n.pos = n->pos;
3442 }
3443 sp_node_update_handles(n);
3444 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3445 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3446 sp_nodepath_update_statusbar(nodepath);
3448 } else { // just select or add to selection, depending in Shift
3449 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3450 }
3451 }
3453 /**
3454 * Node handle grabbed callback.
3455 */
3456 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3457 {
3458 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3460 if (!n->selected) {
3461 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3462 }
3464 // remember the origin point of the handle
3465 if (n->p.knot == knot) {
3466 n->p.origin_radial = n->p.pos - n->pos;
3467 } else if (n->n.knot == knot) {
3468 n->n.origin_radial = n->n.pos - n->pos;
3469 } else {
3470 g_assert_not_reached();
3471 }
3473 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3474 }
3476 /**
3477 * Node handle ungrabbed callback.
3478 */
3479 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3480 {
3481 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3483 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3484 if (n->p.knot == knot) {
3485 n->p.origin_radial.a = 0;
3486 sp_knot_set_position(knot, &n->p.pos, state);
3487 } else if (n->n.knot == knot) {
3488 n->n.origin_radial.a = 0;
3489 sp_knot_set_position(knot, &n->n.pos, state);
3490 } else {
3491 g_assert_not_reached();
3492 }
3494 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3495 }
3497 /**
3498 * Node handle "request" signal callback.
3499 */
3500 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3501 {
3502 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3504 Inkscape::NodePath::NodeSide *me, *opposite;
3505 gint which;
3506 if (n->p.knot == knot) {
3507 me = &n->p;
3508 opposite = &n->n;
3509 which = -1;
3510 } else if (n->n.knot == knot) {
3511 me = &n->n;
3512 opposite = &n->p;
3513 which = 1;
3514 } else {
3515 me = opposite = NULL;
3516 which = 0;
3517 g_assert_not_reached();
3518 }
3520 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3522 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3524 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3525 /* We are smooth node adjacent with line */
3526 NR::Point const delta = *p - n->pos;
3527 NR::Coord const len = NR::L2(delta);
3528 Inkscape::NodePath::Node *othernode = opposite->other;
3529 NR::Point const ndelta = n->pos - othernode->pos;
3530 NR::Coord const linelen = NR::L2(ndelta);
3531 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3532 NR::Coord const scal = dot(delta, ndelta) / linelen;
3533 (*p) = n->pos + (scal / linelen) * ndelta;
3534 }
3535 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item).getPoint();
3536 } else {
3537 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item).getPoint();
3538 }
3540 sp_node_adjust_handle(n, -which);
3542 return FALSE;
3543 }
3545 /**
3546 * Node handle moved callback.
3547 */
3548 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3549 {
3550 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3552 Inkscape::NodePath::NodeSide *me;
3553 Inkscape::NodePath::NodeSide *other;
3554 if (n->p.knot == knot) {
3555 me = &n->p;
3556 other = &n->n;
3557 } else if (n->n.knot == knot) {
3558 me = &n->n;
3559 other = &n->p;
3560 } else {
3561 me = NULL;
3562 other = NULL;
3563 g_assert_not_reached();
3564 }
3566 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3567 Radial rme(me->pos - n->pos);
3568 Radial rother(other->pos - n->pos);
3569 Radial rnew(*p - n->pos);
3571 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3572 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3573 /* 0 interpreted as "no snapping". */
3575 // The closest PI/snaps angle, starting from zero.
3576 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3577 if (me->origin_radial.a == HUGE_VAL) {
3578 // ortho doesn't exist: original handle was zero length.
3579 rnew.a = a_snapped;
3580 } else {
3581 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3582 * its opposite and perpendiculars). */
3583 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3585 // Snap to the closest.
3586 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3587 ? a_snapped
3588 : a_ortho );
3589 }
3590 }
3592 if (state & GDK_MOD1_MASK) {
3593 // lock handle length
3594 rnew.r = me->origin_radial.r;
3595 }
3597 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3598 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3599 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3600 rother.a += rnew.a - rme.a;
3601 other->pos = NR::Point(rother) + n->pos;
3602 if (other->knot) {
3603 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3604 sp_knot_moveto(other->knot, &other->pos);
3605 }
3606 }
3608 me->pos = NR::Point(rnew) + n->pos;
3609 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3611 // move knot, but without emitting the signal:
3612 // we cannot emit a "moved" signal because we're now processing it
3613 sp_knot_moveto(me->knot, &(me->pos));
3615 update_object(n->subpath->nodepath);
3617 /* status text */
3618 SPDesktop *desktop = n->subpath->nodepath->desktop;
3619 if (!desktop) return;
3620 SPEventContext *ec = desktop->event_context;
3621 if (!ec) return;
3622 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3623 if (!mc) return;
3625 double degrees = 180 / M_PI * rnew.a;
3626 if (degrees > 180) degrees -= 360;
3627 if (degrees < -180) degrees += 360;
3628 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3629 degrees = angle_to_compass (degrees);
3631 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3633 mc->setF(Inkscape::NORMAL_MESSAGE,
3634 _("<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);
3636 g_string_free(length, TRUE);
3637 }
3639 /**
3640 * Node handle event callback.
3641 */
3642 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3643 {
3644 gboolean ret = FALSE;
3645 switch (event->type) {
3646 case GDK_KEY_PRESS:
3647 switch (get_group0_keyval (&event->key)) {
3648 case GDK_space:
3649 if (event->key.state & GDK_BUTTON1_MASK) {
3650 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3651 stamp_repr(nodepath);
3652 ret = TRUE;
3653 }
3654 break;
3655 default:
3656 break;
3657 }
3658 break;
3659 case GDK_ENTER_NOTIFY:
3660 // we use an experimentally determined threshold that seems to work fine
3661 if (NR::L2(n->pos - knot->pos) < 0.75)
3662 Inkscape::NodePath::Path::active_node = n;
3663 break;
3664 case GDK_LEAVE_NOTIFY:
3665 // we use an experimentally determined threshold that seems to work fine
3666 if (NR::L2(n->pos - knot->pos) < 0.75)
3667 Inkscape::NodePath::Path::active_node = NULL;
3668 break;
3669 default:
3670 break;
3671 }
3673 return ret;
3674 }
3676 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3677 Radial &rme, Radial &rother, gboolean const both)
3678 {
3679 rme.a += angle;
3680 if ( both
3681 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3682 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3683 {
3684 rother.a += angle;
3685 }
3686 }
3688 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3689 Radial &rme, Radial &rother, gboolean const both)
3690 {
3691 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3693 gdouble r;
3694 if ( both
3695 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3696 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3697 {
3698 r = MAX(rme.r, rother.r);
3699 } else {
3700 r = rme.r;
3701 }
3703 gdouble const weird_angle = atan2(norm_angle, r);
3704 /* Bulia says norm_angle is just the visible distance that the
3705 * object's end must travel on the screen. Left as 'angle' for want of
3706 * a better name.*/
3708 rme.a += weird_angle;
3709 if ( both
3710 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3711 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3712 {
3713 rother.a += weird_angle;
3714 }
3715 }
3717 /**
3718 * Rotate one node.
3719 */
3720 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3721 {
3722 Inkscape::NodePath::NodeSide *me, *other;
3723 bool both = false;
3725 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3726 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3728 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3729 me = &(n->p);
3730 other = &(n->n);
3731 } else if (!n->p.other) {
3732 me = &(n->n);
3733 other = &(n->p);
3734 } else {
3735 if (which > 0) { // right handle
3736 if (xn > xp) {
3737 me = &(n->n);
3738 other = &(n->p);
3739 } else {
3740 me = &(n->p);
3741 other = &(n->n);
3742 }
3743 } else if (which < 0){ // left handle
3744 if (xn <= xp) {
3745 me = &(n->n);
3746 other = &(n->p);
3747 } else {
3748 me = &(n->p);
3749 other = &(n->n);
3750 }
3751 } else { // both handles
3752 me = &(n->n);
3753 other = &(n->p);
3754 both = true;
3755 }
3756 }
3758 Radial rme(me->pos - n->pos);
3759 Radial rother(other->pos - n->pos);
3761 if (screen) {
3762 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3763 } else {
3764 node_rotate_one_internal (*n, angle, rme, rother, both);
3765 }
3767 me->pos = n->pos + NR::Point(rme);
3769 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3770 other->pos = n->pos + NR::Point(rother);
3771 }
3773 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3774 // so here we just move all the knots without emitting move signals, for speed
3775 sp_node_update_handles(n, false);
3776 }
3778 /**
3779 * Rotate selected nodes.
3780 */
3781 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3782 {
3783 if (!nodepath || !nodepath->selected) return;
3785 if (g_list_length(nodepath->selected) == 1) {
3786 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3787 node_rotate_one (n, angle, which, screen);
3788 } else {
3789 // rotate as an object:
3791 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3792 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3793 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3794 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3795 box.expandTo (n->pos); // contain all selected nodes
3796 }
3798 gdouble rot;
3799 if (screen) {
3800 gdouble const zoom = nodepath->desktop->current_zoom();
3801 gdouble const zmove = angle / zoom;
3802 gdouble const r = NR::L2(box.max() - box.midpoint());
3803 rot = atan2(zmove, r);
3804 } else {
3805 rot = angle;
3806 }
3808 NR::Point rot_center;
3809 if (Inkscape::NodePath::Path::active_node == NULL)
3810 rot_center = box.midpoint();
3811 else
3812 rot_center = Inkscape::NodePath::Path::active_node->pos;
3814 NR::Matrix t =
3815 NR::Matrix (NR::translate(-rot_center)) *
3816 NR::Matrix (NR::rotate(rot)) *
3817 NR::Matrix (NR::translate(rot_center));
3819 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3820 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3821 n->pos *= t;
3822 n->n.pos *= t;
3823 n->p.pos *= t;
3824 sp_node_update_handles(n, false);
3825 }
3826 }
3828 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3829 }
3831 /**
3832 * Scale one node.
3833 */
3834 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3835 {
3836 bool both = false;
3837 Inkscape::NodePath::NodeSide *me, *other;
3839 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3840 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3842 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3843 me = &(n->p);
3844 other = &(n->n);
3845 n->code = NR_CURVETO;
3846 } else if (!n->p.other) {
3847 me = &(n->n);
3848 other = &(n->p);
3849 if (n->n.other)
3850 n->n.other->code = NR_CURVETO;
3851 } else {
3852 if (which > 0) { // right handle
3853 if (xn > xp) {
3854 me = &(n->n);
3855 other = &(n->p);
3856 if (n->n.other)
3857 n->n.other->code = NR_CURVETO;
3858 } else {
3859 me = &(n->p);
3860 other = &(n->n);
3861 n->code = NR_CURVETO;
3862 }
3863 } else if (which < 0){ // left handle
3864 if (xn <= xp) {
3865 me = &(n->n);
3866 other = &(n->p);
3867 if (n->n.other)
3868 n->n.other->code = NR_CURVETO;
3869 } else {
3870 me = &(n->p);
3871 other = &(n->n);
3872 n->code = NR_CURVETO;
3873 }
3874 } else { // both handles
3875 me = &(n->n);
3876 other = &(n->p);
3877 both = true;
3878 n->code = NR_CURVETO;
3879 if (n->n.other)
3880 n->n.other->code = NR_CURVETO;
3881 }
3882 }
3884 Radial rme(me->pos - n->pos);
3885 Radial rother(other->pos - n->pos);
3887 rme.r += grow;
3888 if (rme.r < 0) rme.r = 0;
3889 if (rme.a == HUGE_VAL) {
3890 if (me->other) { // if direction is unknown, initialize it towards the next node
3891 Radial rme_next(me->other->pos - n->pos);
3892 rme.a = rme_next.a;
3893 } else { // if there's no next, initialize to 0
3894 rme.a = 0;
3895 }
3896 }
3897 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3898 rother.r += grow;
3899 if (rother.r < 0) rother.r = 0;
3900 if (rother.a == HUGE_VAL) {
3901 rother.a = rme.a + M_PI;
3902 }
3903 }
3905 me->pos = n->pos + NR::Point(rme);
3907 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3908 other->pos = n->pos + NR::Point(rother);
3909 }
3911 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3912 // so here we just move all the knots without emitting move signals, for speed
3913 sp_node_update_handles(n, false);
3914 }
3916 /**
3917 * Scale selected nodes.
3918 */
3919 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3920 {
3921 if (!nodepath || !nodepath->selected) return;
3923 if (g_list_length(nodepath->selected) == 1) {
3924 // scale handles of the single selected node
3925 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3926 node_scale_one (n, grow, which);
3927 } else {
3928 // scale nodes as an "object":
3930 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3931 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3932 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3933 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3934 box.expandTo (n->pos); // contain all selected nodes
3935 }
3937 double scale = (box.maxExtent() + grow)/box.maxExtent();
3939 NR::Point scale_center;
3940 if (Inkscape::NodePath::Path::active_node == NULL)
3941 scale_center = box.midpoint();
3942 else
3943 scale_center = Inkscape::NodePath::Path::active_node->pos;
3945 NR::Matrix t =
3946 NR::Matrix (NR::translate(-scale_center)) *
3947 NR::Matrix (NR::scale(scale, scale)) *
3948 NR::Matrix (NR::translate(scale_center));
3950 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3951 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3952 n->pos *= t;
3953 n->n.pos *= t;
3954 n->p.pos *= t;
3955 sp_node_update_handles(n, false);
3956 }
3957 }
3959 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3960 }
3962 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3963 {
3964 if (!nodepath) return;
3965 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3966 }
3968 /**
3969 * Flip selected nodes horizontally/vertically.
3970 */
3971 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3972 {
3973 if (!nodepath || !nodepath->selected) return;
3975 if (g_list_length(nodepath->selected) == 1 && !center) {
3976 // flip handles of the single selected node
3977 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3978 double temp = n->p.pos[axis];
3979 n->p.pos[axis] = n->n.pos[axis];
3980 n->n.pos[axis] = temp;
3981 sp_node_update_handles(n, false);
3982 } else {
3983 // scale nodes as an "object":
3985 NR::Rect box = sp_node_selected_bbox (nodepath);
3986 if (!center) {
3987 center = box.midpoint();
3988 }
3989 NR::Matrix t =
3990 NR::Matrix (NR::translate(- *center)) *
3991 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3992 NR::Matrix (NR::translate(*center));
3994 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3995 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3996 n->pos *= t;
3997 n->n.pos *= t;
3998 n->p.pos *= t;
3999 sp_node_update_handles(n, false);
4000 }
4001 }
4003 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4004 }
4006 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4007 {
4008 g_assert (nodepath->selected);
4010 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4011 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4012 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4013 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4014 box.expandTo (n->pos); // contain all selected nodes
4015 }
4016 return box;
4017 }
4019 //-----------------------------------------------
4020 /**
4021 * Return new subpath under given nodepath.
4022 */
4023 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4024 {
4025 g_assert(nodepath);
4026 g_assert(nodepath->desktop);
4028 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4030 s->nodepath = nodepath;
4031 s->closed = FALSE;
4032 s->nodes = NULL;
4033 s->first = NULL;
4034 s->last = NULL;
4036 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4037 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4038 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4040 return s;
4041 }
4043 /**
4044 * Destroy nodes in subpath, then subpath itself.
4045 */
4046 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4047 {
4048 g_assert(subpath);
4049 g_assert(subpath->nodepath);
4050 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4052 while (subpath->nodes) {
4053 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4054 }
4056 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4058 g_free(subpath);
4059 }
4061 /**
4062 * Link head to tail in subpath.
4063 */
4064 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4065 {
4066 g_assert(!sp->closed);
4067 g_assert(sp->last != sp->first);
4068 g_assert(sp->first->code == NR_MOVETO);
4070 sp->closed = TRUE;
4072 //Link the head to the tail
4073 sp->first->p.other = sp->last;
4074 sp->last->n.other = sp->first;
4075 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4076 sp->first = sp->last;
4078 //Remove the extra end node
4079 sp_nodepath_node_destroy(sp->last->n.other);
4080 }
4082 /**
4083 * Open closed (loopy) subpath at node.
4084 */
4085 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4086 {
4087 g_assert(sp->closed);
4088 g_assert(n->subpath == sp);
4089 g_assert(sp->first == sp->last);
4091 /* We create new startpoint, current node will become last one */
4093 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4094 &n->pos, &n->pos, &n->n.pos);
4097 sp->closed = FALSE;
4099 //Unlink to make a head and tail
4100 sp->first = new_path;
4101 sp->last = n;
4102 n->n.other = NULL;
4103 new_path->p.other = NULL;
4104 }
4106 /**
4107 * Return new node in subpath with given properties.
4108 * \param pos Position of node.
4109 * \param ppos Handle position in previous direction
4110 * \param npos Handle position in previous direction
4111 */
4112 Inkscape::NodePath::Node *
4113 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)
4114 {
4115 g_assert(sp);
4116 g_assert(sp->nodepath);
4117 g_assert(sp->nodepath->desktop);
4119 if (nodechunk == NULL)
4120 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4122 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4124 n->subpath = sp;
4126 if (type != Inkscape::NodePath::NODE_NONE) {
4127 // use the type from sodipodi:nodetypes
4128 n->type = type;
4129 } else {
4130 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4131 // points are (almost) collinear
4132 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4133 // endnode, or a node with a retracted handle
4134 n->type = Inkscape::NodePath::NODE_CUSP;
4135 } else {
4136 n->type = Inkscape::NodePath::NODE_SMOOTH;
4137 }
4138 } else {
4139 n->type = Inkscape::NodePath::NODE_CUSP;
4140 }
4141 }
4143 n->code = code;
4144 n->selected = FALSE;
4145 n->pos = *pos;
4146 n->p.pos = *ppos;
4147 n->n.pos = *npos;
4149 n->dragging_out = NULL;
4151 Inkscape::NodePath::Node *prev;
4152 if (next) {
4153 //g_assert(g_list_find(sp->nodes, next));
4154 prev = next->p.other;
4155 } else {
4156 prev = sp->last;
4157 }
4159 if (prev)
4160 prev->n.other = n;
4161 else
4162 sp->first = n;
4164 if (next)
4165 next->p.other = n;
4166 else
4167 sp->last = n;
4169 n->p.other = prev;
4170 n->n.other = next;
4172 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"));
4173 sp_knot_set_position(n->knot, pos, 0);
4175 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4176 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4177 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4178 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4179 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4180 sp_knot_update_ctrl(n->knot);
4182 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4183 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4184 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4185 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4186 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4187 sp_knot_show(n->knot);
4189 // We only create handle knots and lines on demand
4190 n->p.knot = NULL;
4191 n->p.line = NULL;
4192 n->n.knot = NULL;
4193 n->n.line = NULL;
4195 sp->nodes = g_list_prepend(sp->nodes, n);
4197 return n;
4198 }
4200 /**
4201 * Destroy node and its knots, link neighbors in subpath.
4202 */
4203 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4204 {
4205 g_assert(node);
4206 g_assert(node->subpath);
4207 g_assert(SP_IS_KNOT(node->knot));
4209 Inkscape::NodePath::SubPath *sp = node->subpath;
4211 if (node->selected) { // first, deselect
4212 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4213 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4214 }
4216 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4218 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4219 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4220 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4221 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4222 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4223 g_object_unref(G_OBJECT(node->knot));
4225 if (node->p.knot) {
4226 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4227 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4228 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4229 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4230 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4231 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4232 g_object_unref(G_OBJECT(node->p.knot));
4233 node->p.knot = NULL;
4234 }
4236 if (node->n.knot) {
4237 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4238 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4239 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4240 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4241 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4242 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4243 g_object_unref(G_OBJECT(node->n.knot));
4244 node->n.knot = NULL;
4245 }
4247 if (node->p.line)
4248 gtk_object_destroy(GTK_OBJECT(node->p.line));
4249 if (node->n.line)
4250 gtk_object_destroy(GTK_OBJECT(node->n.line));
4252 if (sp->nodes) { // there are others nodes on the subpath
4253 if (sp->closed) {
4254 if (sp->first == node) {
4255 g_assert(sp->last == node);
4256 sp->first = node->n.other;
4257 sp->last = sp->first;
4258 }
4259 node->p.other->n.other = node->n.other;
4260 node->n.other->p.other = node->p.other;
4261 } else {
4262 if (sp->first == node) {
4263 sp->first = node->n.other;
4264 sp->first->code = NR_MOVETO;
4265 }
4266 if (sp->last == node) sp->last = node->p.other;
4267 if (node->p.other) node->p.other->n.other = node->n.other;
4268 if (node->n.other) node->n.other->p.other = node->p.other;
4269 }
4270 } else { // this was the last node on subpath
4271 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4272 }
4274 g_mem_chunk_free(nodechunk, node);
4275 }
4277 /**
4278 * Returns one of the node's two sides.
4279 * \param which Indicates which side.
4280 * \return Pointer to previous node side if which==-1, next if which==1.
4281 */
4282 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4283 {
4284 g_assert(node);
4286 switch (which) {
4287 case -1:
4288 return &node->p;
4289 case 1:
4290 return &node->n;
4291 default:
4292 break;
4293 }
4295 g_assert_not_reached();
4297 return NULL;
4298 }
4300 /**
4301 * Return the other side of the node, given one of its sides.
4302 */
4303 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4304 {
4305 g_assert(node);
4307 if (me == &node->p) return &node->n;
4308 if (me == &node->n) return &node->p;
4310 g_assert_not_reached();
4312 return NULL;
4313 }
4315 /**
4316 * Return NRPathcode on the given side of the node.
4317 */
4318 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4319 {
4320 g_assert(node);
4322 if (me == &node->p) {
4323 if (node->p.other) return (NRPathcode)node->code;
4324 return NR_MOVETO;
4325 }
4327 if (me == &node->n) {
4328 if (node->n.other) return (NRPathcode)node->n.other->code;
4329 return NR_MOVETO;
4330 }
4332 g_assert_not_reached();
4334 return NR_END;
4335 }
4337 /**
4338 * Return node with the given index
4339 */
4340 Inkscape::NodePath::Node *
4341 sp_nodepath_get_node_by_index(int index)
4342 {
4343 Inkscape::NodePath::Node *e = NULL;
4345 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4346 if (!nodepath) {
4347 return e;
4348 }
4350 //find segment
4351 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4353 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4354 int n = g_list_length(sp->nodes);
4355 if (sp->closed) {
4356 n++;
4357 }
4359 //if the piece belongs to this subpath grab it
4360 //otherwise move onto the next subpath
4361 if (index < n) {
4362 e = sp->first;
4363 for (int i = 0; i < index; ++i) {
4364 e = e->n.other;
4365 }
4366 break;
4367 } else {
4368 if (sp->closed) {
4369 index -= (n+1);
4370 } else {
4371 index -= n;
4372 }
4373 }
4374 }
4376 return e;
4377 }
4379 /**
4380 * Returns plain text meaning of node type.
4381 */
4382 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4383 {
4384 unsigned retracted = 0;
4385 bool endnode = false;
4387 for (int which = -1; which <= 1; which += 2) {
4388 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4389 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4390 retracted ++;
4391 if (!side->other)
4392 endnode = true;
4393 }
4395 if (retracted == 0) {
4396 if (endnode) {
4397 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4398 return _("end node");
4399 } else {
4400 switch (node->type) {
4401 case Inkscape::NodePath::NODE_CUSP:
4402 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4403 return _("cusp");
4404 case Inkscape::NodePath::NODE_SMOOTH:
4405 // TRANSLATORS: "smooth" is an adjective here
4406 return _("smooth");
4407 case Inkscape::NodePath::NODE_SYMM:
4408 return _("symmetric");
4409 }
4410 }
4411 } else if (retracted == 1) {
4412 if (endnode) {
4413 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4414 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4415 } else {
4416 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4417 }
4418 } else {
4419 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4420 }
4422 return NULL;
4423 }
4425 /**
4426 * Handles content of statusbar as long as node tool is active.
4427 */
4428 void
4429 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4430 {
4431 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");
4432 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4434 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4435 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4436 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4437 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4439 SPDesktop *desktop = NULL;
4440 if (nodepath) {
4441 desktop = nodepath->desktop;
4442 } else {
4443 desktop = SP_ACTIVE_DESKTOP;
4444 }
4446 SPEventContext *ec = desktop->event_context;
4447 if (!ec) return;
4448 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4449 if (!mc) return;
4451 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4453 if (selected_nodes == 0) {
4454 Inkscape::Selection *sel = desktop->selection;
4455 if (!sel || sel->isEmpty()) {
4456 mc->setF(Inkscape::NORMAL_MESSAGE,
4457 _("Select a single object to edit its nodes or handles."));
4458 } else {
4459 if (nodepath) {
4460 mc->setF(Inkscape::NORMAL_MESSAGE,
4461 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.",
4462 "<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.",
4463 total_nodes),
4464 total_nodes);
4465 } else {
4466 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4467 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4468 } else {
4469 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4470 }
4471 }
4472 }
4473 } else if (nodepath && selected_nodes == 1) {
4474 mc->setF(Inkscape::NORMAL_MESSAGE,
4475 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4476 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4477 total_nodes),
4478 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4479 } else {
4480 if (selected_subpaths > 1) {
4481 mc->setF(Inkscape::NORMAL_MESSAGE,
4482 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4483 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4484 total_nodes),
4485 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4486 } else {
4487 mc->setF(Inkscape::NORMAL_MESSAGE,
4488 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4489 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4490 total_nodes),
4491 selected_nodes, total_nodes, when_selected);
4492 }
4493 }
4494 }
4496 /*
4497 * returns a *copy* of the curve of that object.
4498 */
4499 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4500 if (!object)
4501 return NULL;
4503 SPCurve *curve = NULL;
4504 if (SP_IS_PATH(object)) {
4505 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4506 curve = sp_curve_copy(curve_new);
4507 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4508 const gchar *svgd = object->repr->attribute(key);
4509 if (svgd) {
4510 NArtBpath *bpath = sp_svg_read_path(svgd);
4511 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4512 if (curve_new) {
4513 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4514 } else {
4515 g_free(bpath);
4516 }
4517 }
4518 }
4520 return curve;
4521 }
4523 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4524 if (!np || !np->object || !curve)
4525 return;
4527 if (SP_IS_PATH(np->object)) {
4528 if (SP_SHAPE(np->object)->path_effect_href) {
4529 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4530 } else {
4531 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4532 }
4533 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4534 // FIXME: this writing to string and then reading from string is bound to be slow.
4535 // create a method to convert from curve directly to 2geom...
4536 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4537 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4538 g_free(svgpath);
4540 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4541 }
4542 }
4544 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4545 np->show_helperpath = show;
4547 if (show) {
4548 SPCurve *helper_curve = sp_curve_copy(np->curve);
4549 sp_curve_transform(helper_curve, np->i2d );
4550 if (!np->helper_path) {
4551 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4552 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4553 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4554 sp_canvas_item_show(np->helper_path);
4555 } else {
4556 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4557 }
4558 sp_curve_unref(helper_curve);
4559 } else {
4560 if (np->helper_path) {
4561 GtkObject *temp = np->helper_path;
4562 np->helper_path = NULL;
4563 gtk_object_destroy(temp);
4564 }
4565 }
4566 }
4568 /* this function does not work yet */
4569 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4570 np->straight_path = true;
4571 np->show_handles = false;
4572 g_message("add code to make the path straight.");
4573 // do sp_nodepath_convert_node_type on all nodes?
4574 // search for this text !!! "Make selected segments lines"
4575 }
4578 /*
4579 Local Variables:
4580 mode:c++
4581 c-file-style:"stroustrup"
4582 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4583 indent-tabs-mode:nil
4584 fill-column:99
4585 End:
4586 */
4587 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :