ffc99f5789af92ed2552adcd4ef6023a9d61b967
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 "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include "libnr/n-art-bpath.h"
25 #include "libnr/nr-path.h"
26 #include "helper/units.h"
27 #include "knot.h"
28 #include "inkscape.h"
29 #include "document.h"
30 #include "sp-namedview.h"
31 #include "desktop.h"
32 #include "desktop-handles.h"
33 #include "snap.h"
34 #include "message-stack.h"
35 #include "message-context.h"
36 #include "node-context.h"
37 #include "shape-editor.h"
38 #include "selection-chemistry.h"
39 #include "selection.h"
40 #include "xml/repr.h"
41 #include "prefs-utils.h"
42 #include "sp-metrics.h"
43 #include "sp-path.h"
44 #include "libnr/nr-matrix-ops.h"
45 #include "splivarot.h"
46 #include "svg/svg.h"
47 #include "verbs.h"
48 #include "display/bezier-utils.h"
49 #include <vector>
50 #include <algorithm>
51 #include <cstring>
52 #include <string>
53 #include "live_effects/lpeobject.h"
54 #include "live_effects/parameter/parameter.h"
55 #include "util/mathfns.h"
56 #include "display/snap-indicator.h"
57 #include "snapped-point.h"
59 class NR::Matrix;
61 /// \todo
62 /// evil evil evil. FIXME: conflict of two different Path classes!
63 /// There is a conflict in the namespace between two classes named Path.
64 /// #include "sp-flowtext.h"
65 /// #include "sp-flowregion.h"
67 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
68 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
69 GType sp_flowregion_get_type (void);
70 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
71 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
72 GType sp_flowtext_get_type (void);
73 // end evil workaround
75 #include "helper/stlport.h"
78 /// \todo fixme: Implement these via preferences */
80 #define NODE_FILL 0xbfbfbf00
81 #define NODE_STROKE 0x000000ff
82 #define NODE_FILL_HI 0xff000000
83 #define NODE_STROKE_HI 0x000000ff
84 #define NODE_FILL_SEL 0x0000ffff
85 #define NODE_STROKE_SEL 0x000000ff
86 #define NODE_FILL_SEL_HI 0xff000000
87 #define NODE_STROKE_SEL_HI 0x000000ff
88 #define KNOT_FILL 0xffffffff
89 #define KNOT_STROKE 0x000000ff
90 #define KNOT_FILL_HI 0xff000000
91 #define KNOT_STROKE_HI 0x000000ff
93 static GMemChunk *nodechunk = NULL;
95 /* Creation from object */
97 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
98 static gchar *parse_nodetypes(gchar const *types, gint length);
100 /* Object updating */
102 static void stamp_repr(Inkscape::NodePath::Path *np);
103 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
104 static gchar *create_typestr(Inkscape::NodePath::Path *np);
106 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
108 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
110 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
112 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
114 /* Adjust handle placement, if the node or the other handle is moved */
115 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
116 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
118 /* Node event callbacks */
119 static void node_clicked(SPKnot *knot, guint state, gpointer data);
120 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
121 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
122 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
124 /* Handle event callbacks */
125 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
126 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
127 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
128 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
129 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
130 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
132 /* Constructors and destructors */
134 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
135 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
136 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
137 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
138 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
139 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
140 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
142 /* Helpers */
144 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
145 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
146 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
148 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
149 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
151 // active_node indicates mouseover node
152 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
154 /**
155 * \brief Creates new nodepath from item
156 */
157 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
158 {
159 Inkscape::XML::Node *repr = object->repr;
161 /** \todo
162 * FIXME: remove this. We don't want to edit paths inside flowtext.
163 * Instead we will build our flowtext with cloned paths, so that the
164 * real paths are outside the flowtext and thus editable as usual.
165 */
166 if (SP_IS_FLOWTEXT(object)) {
167 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
168 if SP_IS_FLOWREGION(child) {
169 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
170 if (grandchild && SP_IS_PATH(grandchild)) {
171 object = SP_ITEM(grandchild);
172 break;
173 }
174 }
175 }
176 }
178 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
180 if (curve == NULL)
181 return NULL;
183 NArtBpath *bpath = sp_curve_first_bpath(curve);
184 gint length = curve->end;
185 if (length == 0) {
186 sp_curve_unref(curve);
187 return NULL; // prevent crash for one-node paths
188 }
190 //Create new nodepath
191 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
192 if (!np) {
193 sp_curve_unref(curve);
194 return NULL;
195 }
197 // Set defaults
198 np->desktop = desktop;
199 np->object = object;
200 np->subpaths = NULL;
201 np->selected = NULL;
202 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
203 np->livarot_path = NULL;
204 np->local_change = 0;
205 np->show_handles = show_handles;
206 np->helper_path = NULL;
207 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
208 np->helperpath_width = 1.0;
209 np->curve = sp_curve_copy(curve);
210 np->show_helperpath = prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1;
211 np->straight_path = false;
212 if (IS_LIVEPATHEFFECT(object) && item) {
213 np->item = item;
214 } else {
215 np->item = SP_ITEM(object);
216 }
218 // we need to update item's transform from the repr here,
219 // because they may be out of sync when we respond
220 // to a change in repr by regenerating nodepath --bb
221 sp_object_read_attr(SP_OBJECT(np->item), "transform");
223 np->i2d = sp_item_i2d_affine(np->item);
224 np->d2i = np->i2d.inverse();
226 np->repr = repr;
227 if (repr_key_in) { // apparantly the object is an LPEObject
228 np->repr_key = g_strdup(repr_key_in);
229 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
230 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
231 if (lpeparam) {
232 lpeparam->param_setup_nodepath(np);
233 }
234 } else {
235 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
236 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
237 np->repr_key = g_strdup("inkscape:original-d");
239 LivePathEffectObject *lpeobj = sp_lpe_item_get_livepatheffectobject(SP_LPE_ITEM(np->object));
240 if (lpeobj && lpeobj->lpe) {
241 lpeobj->lpe->setup_nodepath(np);
242 }
243 } else {
244 np->repr_key = g_strdup("d");
245 }
246 }
248 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
249 gchar *typestr = parse_nodetypes(nodetypes, length);
251 // create the subpath(s) from the bpath
252 NArtBpath *b = bpath;
253 while (b->code != NR_END) {
254 b = subpath_from_bpath(np, b, typestr + (b - bpath));
255 }
257 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
258 np->subpaths = g_list_reverse(np->subpaths);
260 g_free(typestr);
261 sp_curve_unref(curve);
263 // create the livarot representation from the same item
264 sp_nodepath_ensure_livarot_path(np);
266 // Draw helper curve
267 if (np->show_helperpath) {
268 SPCurve *helper_curve = sp_curve_copy(np->curve);
269 sp_curve_transform(helper_curve, np->i2d );
270 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
271 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);
272 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
273 sp_canvas_item_move_to_z(np->helper_path, 0);
274 sp_canvas_item_show(np->helper_path);
275 sp_curve_unref(helper_curve);
276 }
278 return np;
279 }
281 /**
282 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
283 */
284 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
286 if (!np) //soft fail, like delete
287 return;
289 while (np->subpaths) {
290 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
291 }
293 //Inform the ShapeEditor that made me, if any, that I am gone.
294 if (np->shape_editor)
295 np->shape_editor->nodepath_destroyed();
297 g_assert(!np->selected);
299 if (np->livarot_path) {
300 delete np->livarot_path;
301 np->livarot_path = NULL;
302 }
304 if (np->helper_path) {
305 GtkObject *temp = np->helper_path;
306 np->helper_path = NULL;
307 gtk_object_destroy(temp);
308 }
309 if (np->curve) {
310 sp_curve_unref(np->curve);
311 np->curve = NULL;
312 }
314 if (np->repr_key) {
315 g_free(np->repr_key);
316 np->repr_key = NULL;
317 }
318 if (np->repr_nodetypes_key) {
319 g_free(np->repr_nodetypes_key);
320 np->repr_nodetypes_key = NULL;
321 }
323 np->desktop = NULL;
325 g_free(np);
326 }
329 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
330 {
331 if (np && np->livarot_path == NULL) {
332 SPCurve *curve = create_curve(np);
333 NArtBpath *bpath = SP_CURVE_BPATH(curve);
334 np->livarot_path = bpath_to_Path(bpath);
336 if (np->livarot_path)
337 np->livarot_path->ConvertWithBackData(0.01);
339 sp_curve_unref(curve);
340 }
341 }
344 /**
345 * Return the node count of a given NodeSubPath.
346 */
347 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
348 {
349 if (!subpath)
350 return 0;
351 gint nodeCount = g_list_length(subpath->nodes);
352 return nodeCount;
353 }
355 /**
356 * Return the node count of a given NodePath.
357 */
358 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
359 {
360 if (!np)
361 return 0;
362 gint nodeCount = 0;
363 for (GList *item = np->subpaths ; item ; item=item->next) {
364 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
365 nodeCount += g_list_length(subpath->nodes);
366 }
367 return nodeCount;
368 }
370 /**
371 * Return the subpath count of a given NodePath.
372 */
373 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
374 {
375 if (!np)
376 return 0;
377 return g_list_length (np->subpaths);
378 }
380 /**
381 * Return the selected node count of a given NodePath.
382 */
383 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
384 {
385 if (!np)
386 return 0;
387 return g_list_length (np->selected);
388 }
390 /**
391 * Return the number of subpaths where nodes are selected in a given NodePath.
392 */
393 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
394 {
395 if (!np)
396 return 0;
397 if (!np->selected)
398 return 0;
399 if (!np->selected->next)
400 return 1;
401 gint count = 0;
402 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
403 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
404 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
405 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
406 if (node->selected) {
407 count ++;
408 break;
409 }
410 }
411 }
412 return count;
413 }
415 /**
416 * Clean up a nodepath after editing.
417 *
418 * Currently we are deleting trivial subpaths.
419 */
420 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
421 {
422 GList *badSubPaths = NULL;
424 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
425 for (GList *l = nodepath->subpaths; l ; l=l->next) {
426 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
427 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
428 badSubPaths = g_list_append(badSubPaths, sp);
429 }
431 //Delete them. This second step is because sp_nodepath_subpath_destroy()
432 //also removes the subpath from nodepath->subpaths
433 for (GList *l = badSubPaths; l ; l=l->next) {
434 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
435 sp_nodepath_subpath_destroy(sp);
436 }
438 g_list_free(badSubPaths);
439 }
441 /**
442 * Create new nodepath from b, make it subpath of np.
443 * \param t The node type.
444 * \todo Fixme: t should be a proper type, rather than gchar
445 */
446 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
447 {
448 NR::Point ppos, pos, npos;
450 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
452 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
453 bool const closed = (b->code == NR_MOVETO);
455 pos = NR::Point(b->x3, b->y3) * np->i2d;
456 if (b[1].code == NR_CURVETO) {
457 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
458 } else {
459 npos = pos;
460 }
461 Inkscape::NodePath::Node *n;
462 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
463 g_assert(sp->first == n);
464 g_assert(sp->last == n);
466 b++;
467 t++;
468 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
469 pos = NR::Point(b->x3, b->y3) * np->i2d;
470 if (b->code == NR_CURVETO) {
471 ppos = NR::Point(b->x2, b->y2) * np->i2d;
472 } else {
473 ppos = pos;
474 }
475 if (b[1].code == NR_CURVETO) {
476 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
477 } else {
478 npos = pos;
479 }
480 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
481 b++;
482 t++;
483 }
485 if (closed) sp_nodepath_subpath_close(sp);
487 return b;
488 }
490 /**
491 * Convert from sodipodi:nodetypes to new style type string.
492 */
493 static gchar *parse_nodetypes(gchar const *types, gint length)
494 {
495 g_assert(length > 0);
497 gchar *typestr = g_new(gchar, length + 1);
499 gint pos = 0;
501 if (types) {
502 for (gint i = 0; types[i] && ( i < length ); i++) {
503 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
504 if (types[i] != '\0') {
505 switch (types[i]) {
506 case 's':
507 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
508 break;
509 case 'z':
510 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
511 break;
512 case 'c':
513 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
514 break;
515 default:
516 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
517 break;
518 }
519 }
520 }
521 }
523 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
525 return typestr;
526 }
528 /**
529 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
530 * updated but repr is not (for speed). Used during curve and node drag.
531 */
532 static void update_object(Inkscape::NodePath::Path *np)
533 {
534 g_assert(np);
536 sp_curve_unref(np->curve);
537 np->curve = create_curve(np);
539 sp_nodepath_set_curve(np, np->curve);
541 if (np->show_helperpath) {
542 SPCurve * helper_curve = sp_curve_copy(np->curve);
543 sp_curve_transform(helper_curve, np->i2d );
544 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
545 sp_curve_unref(helper_curve);
546 }
547 }
549 /**
550 * Update XML path node with data from path object.
551 */
552 static void update_repr_internal(Inkscape::NodePath::Path *np)
553 {
554 g_assert(np);
556 Inkscape::XML::Node *repr = np->object->repr;
558 sp_curve_unref(np->curve);
559 np->curve = create_curve(np);
561 gchar *typestr = create_typestr(np);
562 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
564 // determine if path has an effect applied and write to correct "d" attribute.
565 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
566 np->local_change++;
567 repr->setAttribute(np->repr_key, svgpath);
568 }
570 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
571 np->local_change++;
572 repr->setAttribute(np->repr_nodetypes_key, typestr);
573 }
575 g_free(svgpath);
576 g_free(typestr);
578 if (np->show_helperpath) {
579 SPCurve * helper_curve = sp_curve_copy(np->curve);
580 sp_curve_transform(helper_curve, np->i2d );
581 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
582 sp_curve_unref(helper_curve);
583 }
584 }
586 /**
587 * Update XML path node with data from path object, commit changes forever.
588 */
589 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
590 {
591 //fixme: np can be NULL, so check before proceeding
592 g_return_if_fail(np != NULL);
594 if (np->livarot_path) {
595 delete np->livarot_path;
596 np->livarot_path = NULL;
597 }
599 update_repr_internal(np);
600 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
602 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
603 annotation);
604 }
606 /**
607 * Update XML path node with data from path object, commit changes with undo.
608 */
609 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
610 {
611 if (np->livarot_path) {
612 delete np->livarot_path;
613 np->livarot_path = NULL;
614 }
616 update_repr_internal(np);
617 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
618 annotation);
619 }
621 /**
622 * Make duplicate of path, replace corresponding XML node in tree, commit.
623 */
624 static void stamp_repr(Inkscape::NodePath::Path *np)
625 {
626 g_assert(np);
628 Inkscape::XML::Node *old_repr = np->object->repr;
629 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
631 // remember the position of the item
632 gint pos = old_repr->position();
633 // remember parent
634 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
636 SPCurve *curve = create_curve(np);
637 gchar *typestr = create_typestr(np);
639 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
641 new_repr->setAttribute(np->repr_key, svgpath);
642 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
644 // add the new repr to the parent
645 parent->appendChild(new_repr);
646 // move to the saved position
647 new_repr->setPosition(pos > 0 ? pos : 0);
649 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
650 _("Stamp"));
652 Inkscape::GC::release(new_repr);
653 g_free(svgpath);
654 g_free(typestr);
655 sp_curve_unref(curve);
656 }
658 /**
659 * Create curve from path.
660 */
661 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
662 {
663 SPCurve *curve = sp_curve_new();
665 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
666 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
667 sp_curve_moveto(curve,
668 sp->first->pos * np->d2i);
669 Inkscape::NodePath::Node *n = sp->first->n.other;
670 while (n) {
671 NR::Point const end_pt = n->pos * np->d2i;
672 switch (n->code) {
673 case NR_LINETO:
674 sp_curve_lineto(curve, end_pt);
675 break;
676 case NR_CURVETO:
677 sp_curve_curveto(curve,
678 n->p.other->n.pos * np->d2i,
679 n->p.pos * np->d2i,
680 end_pt);
681 break;
682 default:
683 g_assert_not_reached();
684 break;
685 }
686 if (n != sp->last) {
687 n = n->n.other;
688 } else {
689 n = NULL;
690 }
691 }
692 if (sp->closed) {
693 sp_curve_closepath(curve);
694 }
695 }
697 return curve;
698 }
700 /**
701 * Convert path type string to sodipodi:nodetypes style.
702 */
703 static gchar *create_typestr(Inkscape::NodePath::Path *np)
704 {
705 gchar *typestr = g_new(gchar, 32);
706 gint len = 32;
707 gint pos = 0;
709 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
710 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
712 if (pos >= len) {
713 typestr = g_renew(gchar, typestr, len + 32);
714 len += 32;
715 }
717 typestr[pos++] = 'c';
719 Inkscape::NodePath::Node *n;
720 n = sp->first->n.other;
721 while (n) {
722 gchar code;
724 switch (n->type) {
725 case Inkscape::NodePath::NODE_CUSP:
726 code = 'c';
727 break;
728 case Inkscape::NodePath::NODE_SMOOTH:
729 code = 's';
730 break;
731 case Inkscape::NodePath::NODE_SYMM:
732 code = 'z';
733 break;
734 default:
735 g_assert_not_reached();
736 code = '\0';
737 break;
738 }
740 if (pos >= len) {
741 typestr = g_renew(gchar, typestr, len + 32);
742 len += 32;
743 }
745 typestr[pos++] = code;
747 if (n != sp->last) {
748 n = n->n.other;
749 } else {
750 n = NULL;
751 }
752 }
753 }
755 if (pos >= len) {
756 typestr = g_renew(gchar, typestr, len + 1);
757 len += 1;
758 }
760 typestr[pos++] = '\0';
762 return typestr;
763 }
765 /**
766 * Returns current path in context. // later eliminate this function at all!
767 */
768 static Inkscape::NodePath::Path *sp_nodepath_current()
769 {
770 if (!SP_ACTIVE_DESKTOP) {
771 return NULL;
772 }
774 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
776 if (!SP_IS_NODE_CONTEXT(event_context)) {
777 return NULL;
778 }
780 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
781 }
785 /**
786 \brief Fills node and handle positions for three nodes, splitting line
787 marked by end at distance t.
788 */
789 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
790 {
791 g_assert(new_path != NULL);
792 g_assert(end != NULL);
794 g_assert(end->p.other == new_path);
795 Inkscape::NodePath::Node *start = new_path->p.other;
796 g_assert(start);
798 if (end->code == NR_LINETO) {
799 new_path->type =Inkscape::NodePath::NODE_CUSP;
800 new_path->code = NR_LINETO;
801 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
802 } else {
803 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
804 new_path->code = NR_CURVETO;
805 gdouble s = 1 - t;
806 for (int dim = 0; dim < 2; dim++) {
807 NR::Coord const f000 = start->pos[dim];
808 NR::Coord const f001 = start->n.pos[dim];
809 NR::Coord const f011 = end->p.pos[dim];
810 NR::Coord const f111 = end->pos[dim];
811 NR::Coord const f00t = s * f000 + t * f001;
812 NR::Coord const f01t = s * f001 + t * f011;
813 NR::Coord const f11t = s * f011 + t * f111;
814 NR::Coord const f0tt = s * f00t + t * f01t;
815 NR::Coord const f1tt = s * f01t + t * f11t;
816 NR::Coord const fttt = s * f0tt + t * f1tt;
817 start->n.pos[dim] = f00t;
818 new_path->p.pos[dim] = f0tt;
819 new_path->pos[dim] = fttt;
820 new_path->n.pos[dim] = f1tt;
821 end->p.pos[dim] = f11t;
822 }
823 }
824 }
826 /**
827 * Adds new node on direct line between two nodes, activates handles of all
828 * three nodes.
829 */
830 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
831 {
832 g_assert(end);
833 g_assert(end->subpath);
834 g_assert(g_list_find(end->subpath->nodes, end));
836 Inkscape::NodePath::Node *start = end->p.other;
837 g_assert( start->n.other == end );
838 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
839 end,
840 (NRPathcode)end->code == NR_LINETO?
841 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
842 (NRPathcode)end->code,
843 &start->pos, &start->pos, &start->n.pos);
844 sp_nodepath_line_midpoint(newnode, end, t);
846 sp_node_adjust_handles(start);
847 sp_node_update_handles(start);
848 sp_node_update_handles(newnode);
849 sp_node_adjust_handles(end);
850 sp_node_update_handles(end);
852 return newnode;
853 }
855 /**
856 \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
857 */
858 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
859 {
860 g_assert(node);
861 g_assert(node->subpath);
862 g_assert(g_list_find(node->subpath->nodes, node));
864 Inkscape::NodePath::SubPath *sp = node->subpath;
865 Inkscape::NodePath::Path *np = sp->nodepath;
867 if (sp->closed) {
868 sp_nodepath_subpath_open(sp, node);
869 return sp->first;
870 } else {
871 // no break for end nodes
872 if (node == sp->first) return NULL;
873 if (node == sp->last ) return NULL;
875 // create a new subpath
876 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
878 // duplicate the break node as start of the new subpath
879 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
881 while (node->n.other) { // copy the remaining nodes into the new subpath
882 Inkscape::NodePath::Node *n = node->n.other;
883 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);
884 if (n->selected) {
885 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
886 }
887 sp_nodepath_node_destroy(n); // remove the point on the original subpath
888 }
890 return newnode;
891 }
892 }
894 /**
895 * Duplicate node and connect to neighbours.
896 */
897 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
898 {
899 g_assert(node);
900 g_assert(node->subpath);
901 g_assert(g_list_find(node->subpath->nodes, node));
903 Inkscape::NodePath::SubPath *sp = node->subpath;
905 NRPathcode code = (NRPathcode) node->code;
906 if (code == NR_MOVETO) { // if node is the endnode,
907 node->code = NR_LINETO; // new one is inserted before it, so change that to line
908 }
910 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
912 if (!node->n.other || !node->p.other) // if node is an endnode, select it
913 return node;
914 else
915 return newnode; // otherwise select the newly created node
916 }
918 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
919 {
920 node->p.pos = (node->pos + (node->pos - node->n.pos));
921 }
923 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
924 {
925 node->n.pos = (node->pos + (node->pos - node->p.pos));
926 }
928 /**
929 * Change line type at node, with side effects on neighbours.
930 */
931 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
932 {
933 g_assert(end);
934 g_assert(end->subpath);
935 g_assert(end->p.other);
937 if (end->code == static_cast< guint > ( code ) )
938 return;
940 Inkscape::NodePath::Node *start = end->p.other;
942 end->code = code;
944 if (code == NR_LINETO) {
945 if (start->code == NR_LINETO) {
946 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
947 }
948 if (end->n.other) {
949 if (end->n.other->code == NR_LINETO) {
950 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
951 }
952 }
953 } else {
954 NR::Point delta = end->pos - start->pos;
955 start->n.pos = start->pos + delta / 3;
956 end->p.pos = end->pos - delta / 3;
957 sp_node_adjust_handle(start, 1);
958 sp_node_adjust_handle(end, -1);
959 }
961 sp_node_update_handles(start);
962 sp_node_update_handles(end);
963 }
965 /**
966 * Change node type, and its handles accordingly.
967 */
968 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
969 {
970 g_assert(node);
971 g_assert(node->subpath);
973 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
974 return node;
976 if ((node->p.other != NULL) && (node->n.other != NULL)) {
977 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
978 type =Inkscape::NodePath::NODE_CUSP;
979 }
980 }
982 node->type = type;
984 if (node->type == Inkscape::NodePath::NODE_CUSP) {
985 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
986 node->knot->setSize (node->selected? 11 : 9);
987 sp_knot_update_ctrl(node->knot);
988 } else {
989 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
990 node->knot->setSize (node->selected? 9 : 7);
991 sp_knot_update_ctrl(node->knot);
992 }
994 // if one of handles is mouseovered, preserve its position
995 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
996 sp_node_adjust_handle(node, 1);
997 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
998 sp_node_adjust_handle(node, -1);
999 } else {
1000 sp_node_adjust_handles(node);
1001 }
1003 sp_node_update_handles(node);
1005 sp_nodepath_update_statusbar(node->subpath->nodepath);
1007 return node;
1008 }
1010 /**
1011 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
1012 * adjacent segments from lines to curves.
1013 */
1014 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1015 {
1016 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1017 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1019 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1020 if (p_line && n_line) {
1021 // only if both adjacent segments are lines,
1022 // convert both to curves:
1024 node->code = NR_CURVETO;
1025 node->n.other->code = NR_CURVETO;
1027 NR::Point leg_prev = node->pos - node->p.other->pos;
1028 NR::Point leg_next = node->pos - node->n.other->pos;
1030 double norm_leg_prev = L2(leg_prev);
1031 double norm_leg_next = L2(leg_next);
1033 // delta has length 1 and is orthogonal to bisecting line
1034 NR::Point delta;
1035 if (norm_leg_next > 0.0) {
1036 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1037 (&delta)->normalize();
1038 }
1040 if (type == Inkscape::NodePath::NODE_SYMM) {
1041 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1042 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1043 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1044 } else {
1045 // length of handle is proportional to distance to adjacent node
1046 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1047 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1048 }
1050 sp_node_update_handles(node);
1051 }
1052 }
1054 sp_nodepath_set_node_type (node, type);
1055 }
1057 /**
1058 * Move node to point, and adjust its and neighbouring handles.
1059 */
1060 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1061 {
1062 NR::Point delta = p - node->pos;
1063 node->pos = p;
1065 node->p.pos += delta;
1066 node->n.pos += delta;
1068 Inkscape::NodePath::Node *node_p = NULL;
1069 Inkscape::NodePath::Node *node_n = NULL;
1071 if (node->p.other) {
1072 if (node->code == NR_LINETO) {
1073 sp_node_adjust_handle(node, 1);
1074 sp_node_adjust_handle(node->p.other, -1);
1075 node_p = node->p.other;
1076 }
1077 }
1078 if (node->n.other) {
1079 if (node->n.other->code == NR_LINETO) {
1080 sp_node_adjust_handle(node, -1);
1081 sp_node_adjust_handle(node->n.other, 1);
1082 node_n = node->n.other;
1083 }
1084 }
1086 // this function is only called from batch movers that will update display at the end
1087 // themselves, so here we just move all the knots without emitting move signals, for speed
1088 sp_node_update_handles(node, false);
1089 if (node_n) {
1090 sp_node_update_handles(node_n, false);
1091 }
1092 if (node_p) {
1093 sp_node_update_handles(node_p, false);
1094 }
1095 }
1097 /**
1098 * Call sp_node_moveto() for node selection and handle possible snapping.
1099 */
1100 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1101 bool const snap = true)
1102 {
1103 NR::Coord best = NR_HUGE;
1104 NR::Point delta(dx, dy);
1105 NR::Point best_pt = delta;
1106 Inkscape::SnappedPoint best_abs;
1109 if (snap) {
1110 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1111 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1112 * must provide that information. */
1114 // Build a list of the unselected nodes to which the snapper should snap
1115 std::vector<NR::Point> unselected_nodes;
1116 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1117 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1118 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1119 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1120 if (!node->selected) {
1121 unselected_nodes.push_back(node->pos);
1122 }
1123 }
1124 }
1126 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1128 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1129 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1130 Inkscape::SnappedPoint s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1131 if (s.getDistance() < best) {
1132 best = s.getDistance();
1133 best_abs = s;
1134 best_pt = s.getPoint() - n->pos;
1135 }
1136 }
1138 if (best_abs.getSnapped()) {
1139 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1140 }
1141 }
1143 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1144 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1145 sp_node_moveto(n, n->pos + best_pt);
1146 }
1148 // do not update repr here so that node dragging is acceptably fast
1149 update_object(nodepath);
1150 }
1152 /**
1153 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1154 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1155 near x = 0.
1156 */
1157 double
1158 sculpt_profile (double x, double alpha, guint profile)
1159 {
1160 if (x >= 1)
1161 return 0;
1162 if (x <= 0)
1163 return 1;
1165 switch (profile) {
1166 case SCULPT_PROFILE_LINEAR:
1167 return 1 - x;
1168 case SCULPT_PROFILE_BELL:
1169 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1170 case SCULPT_PROFILE_ELLIPTIC:
1171 return sqrt(1 - x*x);
1172 }
1174 return 1;
1175 }
1177 double
1178 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1179 {
1180 // extremely primitive for now, don't have time to look for the real one
1181 double lower = NR::L2(b - a);
1182 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1183 return (lower + upper)/2;
1184 }
1186 void
1187 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1188 {
1189 n->pos = n->origin + delta;
1190 n->n.pos = n->n.origin + delta_n;
1191 n->p.pos = n->p.origin + delta_p;
1192 sp_node_adjust_handles(n);
1193 sp_node_update_handles(n, false);
1194 }
1196 /**
1197 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1198 * on how far they are from the dragged node n.
1199 */
1200 static void
1201 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1202 {
1203 g_assert (n);
1204 g_assert (nodepath);
1205 g_assert (n->subpath->nodepath == nodepath);
1207 double pressure = n->knot->pressure;
1208 if (pressure == 0)
1209 pressure = 0.5; // default
1210 pressure = CLAMP (pressure, 0.2, 0.8);
1212 // map pressure to alpha = 1/5 ... 5
1213 double alpha = 1 - 2 * fabs(pressure - 0.5);
1214 if (pressure > 0.5)
1215 alpha = 1/alpha;
1217 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1219 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1220 // Only one subpath has selected nodes:
1221 // use linear mode, where the distance from n to node being dragged is calculated along the path
1223 double n_sel_range = 0, p_sel_range = 0;
1224 guint n_nodes = 0, p_nodes = 0;
1225 guint n_sel_nodes = 0, p_sel_nodes = 0;
1227 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1228 {
1229 double n_range = 0, p_range = 0;
1230 bool n_going = true, p_going = true;
1231 Inkscape::NodePath::Node *n_node = n;
1232 Inkscape::NodePath::Node *p_node = n;
1233 do {
1234 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1235 if (n_node && n_going)
1236 n_node = n_node->n.other;
1237 if (n_node == NULL) {
1238 n_going = false;
1239 } else {
1240 n_nodes ++;
1241 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1242 if (n_node->selected) {
1243 n_sel_nodes ++;
1244 n_sel_range = n_range;
1245 }
1246 if (n_node == p_node) {
1247 n_going = false;
1248 p_going = false;
1249 }
1250 }
1251 if (p_node && p_going)
1252 p_node = p_node->p.other;
1253 if (p_node == NULL) {
1254 p_going = false;
1255 } else {
1256 p_nodes ++;
1257 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1258 if (p_node->selected) {
1259 p_sel_nodes ++;
1260 p_sel_range = p_range;
1261 }
1262 if (p_node == n_node) {
1263 n_going = false;
1264 p_going = false;
1265 }
1266 }
1267 } while (n_going || p_going);
1268 }
1270 // Second pass: actually move nodes in this subpath
1271 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1272 {
1273 double n_range = 0, p_range = 0;
1274 bool n_going = true, p_going = true;
1275 Inkscape::NodePath::Node *n_node = n;
1276 Inkscape::NodePath::Node *p_node = n;
1277 do {
1278 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1279 if (n_node && n_going)
1280 n_node = n_node->n.other;
1281 if (n_node == NULL) {
1282 n_going = false;
1283 } else {
1284 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1285 if (n_node->selected) {
1286 sp_nodepath_move_node_and_handles (n_node,
1287 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1288 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1289 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1290 }
1291 if (n_node == p_node) {
1292 n_going = false;
1293 p_going = false;
1294 }
1295 }
1296 if (p_node && p_going)
1297 p_node = p_node->p.other;
1298 if (p_node == NULL) {
1299 p_going = false;
1300 } else {
1301 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1302 if (p_node->selected) {
1303 sp_nodepath_move_node_and_handles (p_node,
1304 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1305 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1306 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1307 }
1308 if (p_node == n_node) {
1309 n_going = false;
1310 p_going = false;
1311 }
1312 }
1313 } while (n_going || p_going);
1314 }
1316 } else {
1317 // Multiple subpaths have selected nodes:
1318 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1319 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1320 // fix the pear-like shape when sculpting e.g. a ring
1322 // First pass: calculate range
1323 gdouble direct_range = 0;
1324 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1325 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1326 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1327 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1328 if (node->selected) {
1329 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1330 }
1331 }
1332 }
1334 // Second pass: actually move nodes
1335 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1336 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1337 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1338 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1339 if (node->selected) {
1340 if (direct_range > 1e-6) {
1341 sp_nodepath_move_node_and_handles (node,
1342 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1343 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1344 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1345 } else {
1346 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1347 }
1349 }
1350 }
1351 }
1352 }
1354 // do not update repr here so that node dragging is acceptably fast
1355 update_object(nodepath);
1356 }
1359 /**
1360 * Move node selection to point, adjust its and neighbouring handles,
1361 * handle possible snapping, and commit the change with possible undo.
1362 */
1363 void
1364 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1365 {
1366 if (!nodepath) return;
1368 sp_nodepath_selected_nodes_move(nodepath, dx, dy, 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 node selection off screen and commit the change.
1381 */
1382 void
1383 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1384 {
1385 // borrowed from sp_selection_move_screen in selection-chemistry.c
1386 // we find out the current zoom factor and divide deltas by it
1387 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1389 gdouble zoom = desktop->current_zoom();
1390 gdouble zdx = dx / zoom;
1391 gdouble zdy = dy / zoom;
1393 if (!nodepath) return;
1395 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1397 if (dx == 0) {
1398 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1399 } else if (dy == 0) {
1400 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1401 } else {
1402 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1403 }
1404 }
1406 /**
1407 * Move selected nodes to the absolute position given
1408 */
1409 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1410 {
1411 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1412 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1413 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1414 sp_node_moveto(n, npos);
1415 }
1417 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1418 }
1420 /**
1421 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1422 */
1423 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1424 {
1425 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1426 g_return_val_if_fail(nodepath->selected, no_coord);
1428 // determine coordinate of first selected node
1429 GList *nsel = nodepath->selected;
1430 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1431 NR::Coord coord = n->pos[axis];
1432 bool coincide = true;
1434 // compare it to the coordinates of all the other selected nodes
1435 for (GList *l = nsel->next; l != NULL; l = l->next) {
1436 n = (Inkscape::NodePath::Node *) l->data;
1437 if (n->pos[axis] != coord) {
1438 coincide = false;
1439 }
1440 }
1441 if (coincide) {
1442 return coord;
1443 } else {
1444 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1445 // currently we return the coordinate of the bounding box midpoint because I don't know how
1446 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1447 return bbox.midpoint()[axis];
1448 }
1449 }
1451 /** If they don't yet exist, creates knot and line for the given side of the node */
1452 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1453 {
1454 if (!side->knot) {
1455 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"));
1457 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1458 side->knot->setSize (7);
1459 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1460 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1461 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1462 sp_knot_update_ctrl(side->knot);
1464 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1465 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1466 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1467 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1468 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1469 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1470 }
1472 if (!side->line) {
1473 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1474 SP_TYPE_CTRLLINE, NULL);
1475 }
1476 }
1478 /**
1479 * Ensure the given handle of the node is visible/invisible, update its screen position
1480 */
1481 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1482 {
1483 g_assert(node != NULL);
1485 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1486 NRPathcode code = sp_node_path_code_from_side(node, side);
1488 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1490 if (show_handle) {
1491 if (!side->knot) { // No handle knot at all
1492 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1493 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1494 side->knot->pos = side->pos;
1495 if (side->knot->item)
1496 SP_CTRL(side->knot->item)->moveto(side->pos);
1497 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1498 sp_knot_show(side->knot);
1499 } else {
1500 if (side->knot->pos != side->pos) { // only if it's really moved
1501 if (fire_move_signals) {
1502 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1503 } else {
1504 sp_knot_moveto(side->knot, &side->pos);
1505 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1506 }
1507 }
1508 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1509 sp_knot_show(side->knot);
1510 }
1511 }
1512 sp_canvas_item_show(side->line);
1513 } else {
1514 if (side->knot) {
1515 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1516 sp_knot_hide(side->knot);
1517 }
1518 }
1519 if (side->line) {
1520 sp_canvas_item_hide(side->line);
1521 }
1522 }
1523 }
1525 /**
1526 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1527 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1528 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1529 * updated; otherwise, just move the knots silently (used in batch moves).
1530 */
1531 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1532 {
1533 g_assert(node != NULL);
1535 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1536 sp_knot_show(node->knot);
1537 }
1539 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1540 if (fire_move_signals)
1541 sp_knot_set_position(node->knot, &node->pos, 0);
1542 else
1543 sp_knot_moveto(node->knot, &node->pos);
1544 }
1546 gboolean show_handles = node->selected;
1547 if (node->p.other != NULL) {
1548 if (node->p.other->selected) show_handles = TRUE;
1549 }
1550 if (node->n.other != NULL) {
1551 if (node->n.other->selected) show_handles = TRUE;
1552 }
1554 if (node->subpath->nodepath->show_handles == false)
1555 show_handles = FALSE;
1557 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1558 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1559 }
1561 /**
1562 * Call sp_node_update_handles() for all nodes on subpath.
1563 */
1564 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1565 {
1566 g_assert(subpath != NULL);
1568 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1569 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1570 }
1571 }
1573 /**
1574 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1575 */
1576 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1577 {
1578 g_assert(nodepath != NULL);
1580 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1581 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1582 }
1583 }
1585 void
1586 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1587 {
1588 if (nodepath == NULL) return;
1590 nodepath->show_handles = show;
1591 sp_nodepath_update_handles(nodepath);
1592 }
1594 /**
1595 * Adds all selected nodes in nodepath to list.
1596 */
1597 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1598 {
1599 StlConv<Node *>::list(l, selected);
1600 /// \todo this adds a copying, rework when the selection becomes a stl list
1601 }
1603 /**
1604 * Align selected nodes on the specified axis.
1605 */
1606 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1607 {
1608 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1609 return;
1610 }
1612 if ( !nodepath->selected->next ) { // only one node selected
1613 return;
1614 }
1615 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1616 NR::Point dest(pNode->pos);
1617 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1618 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1619 if (pNode) {
1620 dest[axis] = pNode->pos[axis];
1621 sp_node_moveto(pNode, dest);
1622 }
1623 }
1625 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1626 }
1628 /// Helper struct.
1629 struct NodeSort
1630 {
1631 Inkscape::NodePath::Node *_node;
1632 NR::Coord _coord;
1633 /// \todo use vectorof pointers instead of calling copy ctor
1634 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1635 _node(node), _coord(node->pos[axis])
1636 {}
1638 };
1640 static bool operator<(NodeSort const &a, NodeSort const &b)
1641 {
1642 return (a._coord < b._coord);
1643 }
1645 /**
1646 * Distribute selected nodes on the specified axis.
1647 */
1648 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1649 {
1650 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1651 return;
1652 }
1654 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1655 return;
1656 }
1658 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1659 std::vector<NodeSort> sorted;
1660 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1661 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1662 if (pNode) {
1663 NodeSort n(pNode, axis);
1664 sorted.push_back(n);
1665 //dest[axis] = pNode->pos[axis];
1666 //sp_node_moveto(pNode, dest);
1667 }
1668 }
1669 std::sort(sorted.begin(), sorted.end());
1670 unsigned int len = sorted.size();
1671 //overall bboxes span
1672 float dist = (sorted.back()._coord -
1673 sorted.front()._coord);
1674 //new distance between each bbox
1675 float step = (dist) / (len - 1);
1676 float pos = sorted.front()._coord;
1677 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1678 it < sorted.end();
1679 it ++ )
1680 {
1681 NR::Point dest((*it)._node->pos);
1682 dest[axis] = pos;
1683 sp_node_moveto((*it)._node, dest);
1684 pos += step;
1685 }
1687 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1688 }
1691 /**
1692 * Call sp_nodepath_line_add_node() for all selected segments.
1693 */
1694 void
1695 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1696 {
1697 if (!nodepath) {
1698 return;
1699 }
1701 GList *nl = NULL;
1703 int n_added = 0;
1705 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1706 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1707 g_assert(t->selected);
1708 if (t->p.other && t->p.other->selected) {
1709 nl = g_list_prepend(nl, t);
1710 }
1711 }
1713 while (nl) {
1714 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1715 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1716 sp_nodepath_node_select(n, TRUE, FALSE);
1717 n_added ++;
1718 nl = g_list_remove(nl, t);
1719 }
1721 /** \todo fixme: adjust ? */
1722 sp_nodepath_update_handles(nodepath);
1724 if (n_added > 1) {
1725 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1726 } else if (n_added > 0) {
1727 sp_nodepath_update_repr(nodepath, _("Add node"));
1728 }
1730 sp_nodepath_update_statusbar(nodepath);
1731 }
1733 /**
1734 * Select segment nearest to point
1735 */
1736 void
1737 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1738 {
1739 if (!nodepath) {
1740 return;
1741 }
1743 sp_nodepath_ensure_livarot_path(nodepath);
1744 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1745 if (!maybe_position) {
1746 return;
1747 }
1748 Path::cut_position position = *maybe_position;
1750 //find segment to segment
1751 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1753 //fixme: this can return NULL, so check before proceeding.
1754 g_return_if_fail(e != NULL);
1756 gboolean force = FALSE;
1757 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1758 force = TRUE;
1759 }
1760 sp_nodepath_node_select(e, (gboolean) toggle, force);
1761 if (e->p.other)
1762 sp_nodepath_node_select(e->p.other, TRUE, force);
1764 sp_nodepath_update_handles(nodepath);
1766 sp_nodepath_update_statusbar(nodepath);
1767 }
1769 /**
1770 * Add a node nearest to point
1771 */
1772 void
1773 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1774 {
1775 if (!nodepath) {
1776 return;
1777 }
1779 sp_nodepath_ensure_livarot_path(nodepath);
1780 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1781 if (!maybe_position) {
1782 return;
1783 }
1784 Path::cut_position position = *maybe_position;
1786 //find segment to split
1787 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1789 //don't know why but t seems to flip for lines
1790 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1791 position.t = 1.0 - position.t;
1792 }
1793 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1794 sp_nodepath_node_select(n, FALSE, TRUE);
1796 /* fixme: adjust ? */
1797 sp_nodepath_update_handles(nodepath);
1799 sp_nodepath_update_repr(nodepath, _("Add node"));
1801 sp_nodepath_update_statusbar(nodepath);
1802 }
1804 /*
1805 * Adjusts a segment so that t moves by a certain delta for dragging
1806 * converts lines to curves
1807 *
1808 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1809 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1810 */
1811 void
1812 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1813 {
1814 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1816 //fixme: e and e->p can be NULL, so check for those before proceeding
1817 g_return_if_fail(e != NULL);
1818 g_return_if_fail(&e->p != NULL);
1820 /* feel good is an arbitrary parameter that distributes the delta between handles
1821 * if t of the drag point is less than 1/6 distance form the endpoint only
1822 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1823 */
1824 double feel_good;
1825 if (t <= 1.0 / 6.0)
1826 feel_good = 0;
1827 else if (t <= 0.5)
1828 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1829 else if (t <= 5.0 / 6.0)
1830 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1831 else
1832 feel_good = 1;
1834 //if we're dragging a line convert it to a curve
1835 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1836 sp_nodepath_set_line_type(e, NR_CURVETO);
1837 }
1839 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1840 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1841 e->p.other->n.pos += offsetcoord0;
1842 e->p.pos += offsetcoord1;
1844 // adjust handles of adjacent nodes where necessary
1845 sp_node_adjust_handle(e,1);
1846 sp_node_adjust_handle(e->p.other,-1);
1848 sp_nodepath_update_handles(e->subpath->nodepath);
1850 update_object(e->subpath->nodepath);
1852 sp_nodepath_update_statusbar(e->subpath->nodepath);
1853 }
1856 /**
1857 * Call sp_nodepath_break() for all selected segments.
1858 */
1859 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1860 {
1861 if (!nodepath) return;
1863 GList *temp = NULL;
1864 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1865 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1866 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1867 if (nn == NULL) continue; // no break, no new node
1868 temp = g_list_prepend(temp, nn);
1869 }
1871 if (temp) {
1872 sp_nodepath_deselect(nodepath);
1873 }
1874 for (GList *l = temp; l != NULL; l = l->next) {
1875 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1876 }
1878 sp_nodepath_update_handles(nodepath);
1880 sp_nodepath_update_repr(nodepath, _("Break path"));
1881 }
1883 /**
1884 * Duplicate the selected node(s).
1885 */
1886 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1887 {
1888 if (!nodepath) {
1889 return;
1890 }
1892 GList *temp = NULL;
1893 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1894 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1895 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1896 if (nn == NULL) continue; // could not duplicate
1897 temp = g_list_prepend(temp, nn);
1898 }
1900 if (temp) {
1901 sp_nodepath_deselect(nodepath);
1902 }
1903 for (GList *l = temp; l != NULL; l = l->next) {
1904 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1905 }
1907 sp_nodepath_update_handles(nodepath);
1909 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1910 }
1912 /**
1913 * Internal function to join two nodes by merging them into one.
1914 */
1915 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
1916 {
1917 /* a and b are endpoints */
1919 // if one of the two nodes is mouseovered, fix its position
1920 NR::Point c;
1921 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1922 c = a->pos;
1923 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1924 c = b->pos;
1925 } else {
1926 // otherwise, move joined node to the midpoint
1927 c = (a->pos + b->pos) / 2;
1928 }
1930 if (a->subpath == b->subpath) {
1931 Inkscape::NodePath::SubPath *sp = a->subpath;
1932 sp_nodepath_subpath_close(sp);
1933 sp_node_moveto (sp->first, c);
1935 sp_nodepath_update_handles(sp->nodepath);
1936 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1937 return;
1938 }
1940 /* a and b are separate subpaths */
1941 Inkscape::NodePath::SubPath *sa = a->subpath;
1942 Inkscape::NodePath::SubPath *sb = b->subpath;
1943 NR::Point p;
1944 Inkscape::NodePath::Node *n;
1945 NRPathcode code;
1946 if (a == sa->first) {
1947 // we will now reverse sa, so that a is its last node, not first, and drop that node
1948 p = sa->first->n.pos;
1949 code = (NRPathcode)sa->first->n.other->code;
1950 // create new subpath
1951 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1952 // create a first moveto node on it
1953 n = sa->last;
1954 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1955 n = n->p.other;
1956 if (n == sa->first) n = NULL;
1957 while (n) {
1958 // copy the rest of the nodes from sa to t, going backwards
1959 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1960 n = n->p.other;
1961 if (n == sa->first) n = NULL;
1962 }
1963 // replace sa with t
1964 sp_nodepath_subpath_destroy(sa);
1965 sa = t;
1966 } else if (a == sa->last) {
1967 // a is already last, just drop it
1968 p = sa->last->p.pos;
1969 code = (NRPathcode)sa->last->code;
1970 sp_nodepath_node_destroy(sa->last);
1971 } else {
1972 code = NR_END;
1973 g_assert_not_reached();
1974 }
1976 if (b == sb->first) {
1977 // copy all nodes from b to a, forward
1978 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1979 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1980 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1981 }
1982 } else if (b == sb->last) {
1983 // copy all nodes from b to a, backward
1984 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1985 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1986 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1987 }
1988 } else {
1989 g_assert_not_reached();
1990 }
1991 /* and now destroy sb */
1993 sp_nodepath_subpath_destroy(sb);
1995 sp_nodepath_update_handles(sa->nodepath);
1997 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1999 sp_nodepath_update_statusbar(nodepath);
2000 }
2002 /**
2003 * Internal function to join two nodes by adding a segment between them.
2004 */
2005 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2006 {
2007 if (a->subpath == b->subpath) {
2008 Inkscape::NodePath::SubPath *sp = a->subpath;
2010 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2011 sp->closed = TRUE;
2013 sp->first->p.other = sp->last;
2014 sp->last->n.other = sp->first;
2016 sp_node_handle_mirror_p_to_n(sp->last);
2017 sp_node_handle_mirror_n_to_p(sp->first);
2019 sp->first->code = sp->last->code;
2020 sp->first = sp->last;
2022 sp_nodepath_update_handles(sp->nodepath);
2024 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2026 return;
2027 }
2029 /* a and b are separate subpaths */
2030 Inkscape::NodePath::SubPath *sa = a->subpath;
2031 Inkscape::NodePath::SubPath *sb = b->subpath;
2033 Inkscape::NodePath::Node *n;
2034 NR::Point p;
2035 NRPathcode code;
2036 if (a == sa->first) {
2037 code = (NRPathcode) sa->first->n.other->code;
2038 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2039 n = sa->last;
2040 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2041 for (n = n->p.other; n != NULL; n = n->p.other) {
2042 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2043 }
2044 sp_nodepath_subpath_destroy(sa);
2045 sa = t;
2046 } else if (a == sa->last) {
2047 code = (NRPathcode)sa->last->code;
2048 } else {
2049 code = NR_END;
2050 g_assert_not_reached();
2051 }
2053 if (b == sb->first) {
2054 n = sb->first;
2055 sp_node_handle_mirror_p_to_n(sa->last);
2056 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2057 sp_node_handle_mirror_n_to_p(sa->last);
2058 for (n = n->n.other; n != NULL; n = n->n.other) {
2059 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2060 }
2061 } else if (b == sb->last) {
2062 n = sb->last;
2063 sp_node_handle_mirror_p_to_n(sa->last);
2064 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2065 sp_node_handle_mirror_n_to_p(sa->last);
2066 for (n = n->p.other; n != NULL; n = n->p.other) {
2067 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2068 }
2069 } else {
2070 g_assert_not_reached();
2071 }
2072 /* and now destroy sb */
2074 sp_nodepath_subpath_destroy(sb);
2076 sp_nodepath_update_handles(sa->nodepath);
2078 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2079 }
2081 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2083 /**
2084 * Internal function to handle joining two nodes.
2085 */
2086 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2087 {
2088 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2090 if (g_list_length(nodepath->selected) != 2) {
2091 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2092 return;
2093 }
2095 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2096 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2098 g_assert(a != b);
2099 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2100 // someone tried to join an orphan node (i.e. a single-node subpath).
2101 // this is not worth an error message, just fail silently.
2102 return;
2103 }
2105 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2106 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2107 return;
2108 }
2110 switch(mode) {
2111 case NODE_JOIN_ENDPOINTS:
2112 do_node_selected_join(nodepath, a, b);
2113 break;
2114 case NODE_JOIN_SEGMENT:
2115 do_node_selected_join_segment(nodepath, a, b);
2116 break;
2117 }
2118 }
2120 /**
2121 * Join two nodes by merging them into one.
2122 */
2123 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2124 {
2125 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2126 }
2128 /**
2129 * Join two nodes by adding a segment between them.
2130 */
2131 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2132 {
2133 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2134 }
2136 /**
2137 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2138 */
2139 void sp_node_delete_preserve(GList *nodes_to_delete)
2140 {
2141 GSList *nodepaths = NULL;
2143 while (nodes_to_delete) {
2144 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2145 Inkscape::NodePath::SubPath *sp = node->subpath;
2146 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2147 Inkscape::NodePath::Node *sample_cursor = NULL;
2148 Inkscape::NodePath::Node *sample_end = NULL;
2149 Inkscape::NodePath::Node *delete_cursor = node;
2150 bool just_delete = false;
2152 //find the start of this contiguous selection
2153 //move left to the first node that is not selected
2154 //or the start of the non-closed path
2155 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2156 delete_cursor = curr;
2157 }
2159 //just delete at the beginning of an open path
2160 if (!delete_cursor->p.other) {
2161 sample_cursor = delete_cursor;
2162 just_delete = true;
2163 } else {
2164 sample_cursor = delete_cursor->p.other;
2165 }
2167 //calculate points for each segment
2168 int rate = 5;
2169 float period = 1.0 / rate;
2170 std::vector<NR::Point> data;
2171 if (!just_delete) {
2172 data.push_back(sample_cursor->pos);
2173 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2174 //just delete at the end of an open path
2175 if (!sp->closed && curr == sp->last) {
2176 just_delete = true;
2177 break;
2178 }
2180 //sample points on the contiguous selected segment
2181 NR::Point *bez;
2182 bez = new NR::Point [4];
2183 bez[0] = curr->pos;
2184 bez[1] = curr->n.pos;
2185 bez[2] = curr->n.other->p.pos;
2186 bez[3] = curr->n.other->pos;
2187 for (int i=1; i<rate; i++) {
2188 gdouble t = i * period;
2189 NR::Point p = bezier_pt(3, bez, t);
2190 data.push_back(p);
2191 }
2192 data.push_back(curr->n.other->pos);
2194 sample_end = curr->n.other;
2195 //break if we've come full circle or hit the end of the selection
2196 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2197 break;
2198 }
2199 }
2200 }
2202 if (!just_delete) {
2203 //calculate the best fitting single segment and adjust the endpoints
2204 NR::Point *adata;
2205 adata = new NR::Point [data.size()];
2206 copy(data.begin(), data.end(), adata);
2208 NR::Point *bez;
2209 bez = new NR::Point [4];
2210 //would decreasing error create a better fitting approximation?
2211 gdouble error = 1.0;
2212 gint ret;
2213 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2215 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2216 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2217 //the resulting nodes behave as expected.
2218 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2219 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2221 //adjust endpoints
2222 sample_cursor->n.pos = bez[1];
2223 sample_end->p.pos = bez[2];
2224 }
2226 //destroy this contiguous selection
2227 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2228 Inkscape::NodePath::Node *temp = delete_cursor;
2229 if (delete_cursor->n.other == delete_cursor) {
2230 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2231 delete_cursor = NULL;
2232 } else {
2233 delete_cursor = delete_cursor->n.other;
2234 }
2235 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2236 sp_nodepath_node_destroy(temp);
2237 }
2239 sp_nodepath_update_handles(nodepath);
2241 if (!g_slist_find(nodepaths, nodepath))
2242 nodepaths = g_slist_prepend (nodepaths, nodepath);
2243 }
2245 for (GSList *i = nodepaths; i; i = i->next) {
2246 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2247 // different nodepaths will give us one undo event per nodepath
2248 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2250 // if the entire nodepath is removed, delete the selected object.
2251 if (nodepath->subpaths == NULL ||
2252 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2253 //at least 2
2254 sp_nodepath_get_node_count(nodepath) < 2) {
2255 SPDocument *document = sp_desktop_document (nodepath->desktop);
2256 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2257 //delete this nodepath's object, not the entire selection! (though at this time, this
2258 //does not matter)
2259 sp_selection_delete();
2260 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2261 _("Delete nodes"));
2262 } else {
2263 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2264 sp_nodepath_update_statusbar(nodepath);
2265 }
2266 }
2268 g_slist_free (nodepaths);
2269 }
2271 /**
2272 * Delete one or more selected nodes.
2273 */
2274 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2275 {
2276 if (!nodepath) return;
2277 if (!nodepath->selected) return;
2279 /** \todo fixme: do it the right way */
2280 while (nodepath->selected) {
2281 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2282 sp_nodepath_node_destroy(node);
2283 }
2286 //clean up the nodepath (such as for trivial subpaths)
2287 sp_nodepath_cleanup(nodepath);
2289 sp_nodepath_update_handles(nodepath);
2291 // if the entire nodepath is removed, delete the selected object.
2292 if (nodepath->subpaths == NULL ||
2293 sp_nodepath_get_node_count(nodepath) < 2) {
2294 SPDocument *document = sp_desktop_document (nodepath->desktop);
2295 sp_selection_delete();
2296 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2297 _("Delete nodes"));
2298 return;
2299 }
2301 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2303 sp_nodepath_update_statusbar(nodepath);
2304 }
2306 /**
2307 * Delete one or more segments between two selected nodes.
2308 * This is the code for 'split'.
2309 */
2310 void
2311 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2312 {
2313 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2314 Inkscape::NodePath::Node *curr, *next; //Iterators
2316 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2318 if (g_list_length(nodepath->selected) != 2) {
2319 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2320 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2321 return;
2322 }
2324 //Selected nodes, not inclusive
2325 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2326 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2328 if ( ( a==b) || //same node
2329 (a->subpath != b->subpath ) || //not the same path
2330 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2331 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2332 {
2333 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2334 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2335 return;
2336 }
2338 //###########################################
2339 //# BEGIN EDITS
2340 //###########################################
2341 //##################################
2342 //# CLOSED PATH
2343 //##################################
2344 if (a->subpath->closed) {
2347 gboolean reversed = FALSE;
2349 //Since we can go in a circle, we need to find the shorter distance.
2350 // a->b or b->a
2351 start = end = NULL;
2352 int distance = 0;
2353 int minDistance = 0;
2354 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2355 if (curr==b) {
2356 //printf("a to b:%d\n", distance);
2357 start = a;//go from a to b
2358 end = b;
2359 minDistance = distance;
2360 //printf("A to B :\n");
2361 break;
2362 }
2363 distance++;
2364 }
2366 //try again, the other direction
2367 distance = 0;
2368 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2369 if (curr==a) {
2370 //printf("b to a:%d\n", distance);
2371 if (distance < minDistance) {
2372 start = b; //we go from b to a
2373 end = a;
2374 reversed = TRUE;
2375 //printf("B to A\n");
2376 }
2377 break;
2378 }
2379 distance++;
2380 }
2383 //Copy everything from 'end' to 'start' to a new subpath
2384 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2385 for (curr=end ; curr ; curr=curr->n.other) {
2386 NRPathcode code = (NRPathcode) curr->code;
2387 if (curr == end)
2388 code = NR_MOVETO;
2389 sp_nodepath_node_new(t, NULL,
2390 (Inkscape::NodePath::NodeType)curr->type, code,
2391 &curr->p.pos, &curr->pos, &curr->n.pos);
2392 if (curr == start)
2393 break;
2394 }
2395 sp_nodepath_subpath_destroy(a->subpath);
2398 }
2402 //##################################
2403 //# OPEN PATH
2404 //##################################
2405 else {
2407 //We need to get the direction of the list between A and B
2408 //Can we walk from a to b?
2409 start = end = NULL;
2410 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2411 if (curr==b) {
2412 start = a; //did it! we go from a to b
2413 end = b;
2414 //printf("A to B\n");
2415 break;
2416 }
2417 }
2418 if (!start) {//didn't work? let's try the other direction
2419 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2420 if (curr==a) {
2421 start = b; //did it! we go from b to a
2422 end = a;
2423 //printf("B to A\n");
2424 break;
2425 }
2426 }
2427 }
2428 if (!start) {
2429 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2430 _("Cannot find path between nodes."));
2431 return;
2432 }
2436 //Copy everything after 'end' to a new subpath
2437 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2438 for (curr=end ; curr ; curr=curr->n.other) {
2439 NRPathcode code = (NRPathcode) curr->code;
2440 if (curr == end)
2441 code = NR_MOVETO;
2442 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2443 &curr->p.pos, &curr->pos, &curr->n.pos);
2444 }
2446 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2447 for (curr = start->n.other ; curr ; curr=next) {
2448 next = curr->n.other;
2449 sp_nodepath_node_destroy(curr);
2450 }
2452 }
2453 //###########################################
2454 //# END EDITS
2455 //###########################################
2457 //clean up the nodepath (such as for trivial subpaths)
2458 sp_nodepath_cleanup(nodepath);
2460 sp_nodepath_update_handles(nodepath);
2462 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2464 sp_nodepath_update_statusbar(nodepath);
2465 }
2467 /**
2468 * Call sp_nodepath_set_line() for all selected segments.
2469 */
2470 void
2471 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2472 {
2473 if (nodepath == NULL) return;
2475 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2476 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2477 g_assert(n->selected);
2478 if (n->p.other && n->p.other->selected) {
2479 sp_nodepath_set_line_type(n, code);
2480 }
2481 }
2483 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2484 }
2486 /**
2487 * Call sp_nodepath_convert_node_type() for all selected nodes.
2488 */
2489 void
2490 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2491 {
2492 if (nodepath == NULL) return;
2494 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2496 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2497 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2498 }
2500 sp_nodepath_update_repr(nodepath, _("Change node type"));
2501 }
2503 /**
2504 * Change select status of node, update its own and neighbour handles.
2505 */
2506 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2507 {
2508 node->selected = selected;
2510 if (selected) {
2511 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2512 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2513 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2514 sp_knot_update_ctrl(node->knot);
2515 } else {
2516 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2517 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2518 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2519 sp_knot_update_ctrl(node->knot);
2520 }
2522 sp_node_update_handles(node);
2523 if (node->n.other) sp_node_update_handles(node->n.other);
2524 if (node->p.other) sp_node_update_handles(node->p.other);
2525 }
2527 /**
2528 \brief Select a node
2529 \param node The node to select
2530 \param incremental If true, add to selection, otherwise deselect others
2531 \param override If true, always select this node, otherwise toggle selected status
2532 */
2533 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2534 {
2535 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2537 if (incremental) {
2538 if (override) {
2539 if (!g_list_find(nodepath->selected, node)) {
2540 nodepath->selected = g_list_prepend(nodepath->selected, node);
2541 }
2542 sp_node_set_selected(node, TRUE);
2543 } else { // toggle
2544 if (node->selected) {
2545 g_assert(g_list_find(nodepath->selected, node));
2546 nodepath->selected = g_list_remove(nodepath->selected, node);
2547 } else {
2548 g_assert(!g_list_find(nodepath->selected, node));
2549 nodepath->selected = g_list_prepend(nodepath->selected, node);
2550 }
2551 sp_node_set_selected(node, !node->selected);
2552 }
2553 } else {
2554 sp_nodepath_deselect(nodepath);
2555 nodepath->selected = g_list_prepend(nodepath->selected, node);
2556 sp_node_set_selected(node, TRUE);
2557 }
2559 sp_nodepath_update_statusbar(nodepath);
2560 }
2563 /**
2564 \brief Deselect all nodes in the nodepath
2565 */
2566 void
2567 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2568 {
2569 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2571 while (nodepath->selected) {
2572 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2573 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2574 }
2575 sp_nodepath_update_statusbar(nodepath);
2576 }
2578 /**
2579 \brief Select or invert selection of all nodes in the nodepath
2580 */
2581 void
2582 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2583 {
2584 if (!nodepath) return;
2586 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2587 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2588 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2589 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2590 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2591 }
2592 }
2593 }
2595 /**
2596 * If nothing selected, does the same as sp_nodepath_select_all();
2597 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2598 * (i.e., similar to "select all in layer", with the "selected" subpaths
2599 * being treated as "layers" in the path).
2600 */
2601 void
2602 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2603 {
2604 if (!nodepath) return;
2606 if (g_list_length (nodepath->selected) == 0) {
2607 sp_nodepath_select_all (nodepath, invert);
2608 return;
2609 }
2611 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2612 GSList *subpaths = NULL;
2614 for (GList *l = copy; l != NULL; l = l->next) {
2615 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2616 Inkscape::NodePath::SubPath *subpath = n->subpath;
2617 if (!g_slist_find (subpaths, subpath))
2618 subpaths = g_slist_prepend (subpaths, subpath);
2619 }
2621 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2622 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2623 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2624 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2625 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2626 }
2627 }
2629 g_slist_free (subpaths);
2630 g_list_free (copy);
2631 }
2633 /**
2634 * \brief Select the node after the last selected; if none is selected,
2635 * select the first within path.
2636 */
2637 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2638 {
2639 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2641 Inkscape::NodePath::Node *last = NULL;
2642 if (nodepath->selected) {
2643 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2644 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2645 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2646 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2647 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2648 if (node->selected) {
2649 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2650 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2651 if (spl->next) { // there's a next subpath
2652 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2653 last = subpath_next->first;
2654 } else if (spl->prev) { // there's a previous subpath
2655 last = NULL; // to be set later to the first node of first subpath
2656 } else {
2657 last = node->n.other;
2658 }
2659 } else {
2660 last = node->n.other;
2661 }
2662 } else {
2663 if (node->n.other) {
2664 last = node->n.other;
2665 } else {
2666 if (spl->next) { // there's a next subpath
2667 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2668 last = subpath_next->first;
2669 } else if (spl->prev) { // there's a previous subpath
2670 last = NULL; // to be set later to the first node of first subpath
2671 } else {
2672 last = (Inkscape::NodePath::Node *) subpath->first;
2673 }
2674 }
2675 }
2676 }
2677 }
2678 }
2679 sp_nodepath_deselect(nodepath);
2680 }
2682 if (last) { // there's at least one more node after selected
2683 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2684 } else { // no more nodes, select the first one in first subpath
2685 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2686 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2687 }
2688 }
2690 /**
2691 * \brief Select the node before the first selected; if none is selected,
2692 * select the last within path
2693 */
2694 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2695 {
2696 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2698 Inkscape::NodePath::Node *last = NULL;
2699 if (nodepath->selected) {
2700 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2701 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2702 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2703 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2704 if (node->selected) {
2705 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2706 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2707 if (spl->prev) { // there's a prev subpath
2708 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2709 last = subpath_prev->last;
2710 } else if (spl->next) { // there's a next subpath
2711 last = NULL; // to be set later to the last node of last subpath
2712 } else {
2713 last = node->p.other;
2714 }
2715 } else {
2716 last = node->p.other;
2717 }
2718 } else {
2719 if (node->p.other) {
2720 last = node->p.other;
2721 } else {
2722 if (spl->prev) { // there's a prev subpath
2723 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2724 last = subpath_prev->last;
2725 } else if (spl->next) { // there's a next subpath
2726 last = NULL; // to be set later to the last node of last subpath
2727 } else {
2728 last = (Inkscape::NodePath::Node *) subpath->last;
2729 }
2730 }
2731 }
2732 }
2733 }
2734 }
2735 sp_nodepath_deselect(nodepath);
2736 }
2738 if (last) { // there's at least one more node before selected
2739 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2740 } else { // no more nodes, select the last one in last subpath
2741 GList *spl = g_list_last(nodepath->subpaths);
2742 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2743 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2744 }
2745 }
2747 /**
2748 * \brief Select all nodes that are within the rectangle.
2749 */
2750 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2751 {
2752 if (!incremental) {
2753 sp_nodepath_deselect(nodepath);
2754 }
2756 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2757 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2758 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2759 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2761 if (b.contains(node->pos)) {
2762 sp_nodepath_node_select(node, TRUE, TRUE);
2763 }
2764 }
2765 }
2766 }
2769 void
2770 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2771 {
2772 g_assert (n);
2773 g_assert (nodepath);
2774 g_assert (n->subpath->nodepath == nodepath);
2776 if (g_list_length (nodepath->selected) == 0) {
2777 if (grow > 0) {
2778 sp_nodepath_node_select(n, TRUE, TRUE);
2779 }
2780 return;
2781 }
2783 if (g_list_length (nodepath->selected) == 1) {
2784 if (grow < 0) {
2785 sp_nodepath_deselect (nodepath);
2786 return;
2787 }
2788 }
2790 double n_sel_range = 0, p_sel_range = 0;
2791 Inkscape::NodePath::Node *farthest_n_node = n;
2792 Inkscape::NodePath::Node *farthest_p_node = n;
2794 // Calculate ranges
2795 {
2796 double n_range = 0, p_range = 0;
2797 bool n_going = true, p_going = true;
2798 Inkscape::NodePath::Node *n_node = n;
2799 Inkscape::NodePath::Node *p_node = n;
2800 do {
2801 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2802 if (n_node && n_going)
2803 n_node = n_node->n.other;
2804 if (n_node == NULL) {
2805 n_going = false;
2806 } else {
2807 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2808 if (n_node->selected) {
2809 n_sel_range = n_range;
2810 farthest_n_node = n_node;
2811 }
2812 if (n_node == p_node) {
2813 n_going = false;
2814 p_going = false;
2815 }
2816 }
2817 if (p_node && p_going)
2818 p_node = p_node->p.other;
2819 if (p_node == NULL) {
2820 p_going = false;
2821 } else {
2822 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2823 if (p_node->selected) {
2824 p_sel_range = p_range;
2825 farthest_p_node = p_node;
2826 }
2827 if (p_node == n_node) {
2828 n_going = false;
2829 p_going = false;
2830 }
2831 }
2832 } while (n_going || p_going);
2833 }
2835 if (grow > 0) {
2836 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2837 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2838 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2839 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2840 }
2841 } else {
2842 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2843 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2844 } else if (farthest_p_node && farthest_p_node->selected) {
2845 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2846 }
2847 }
2848 }
2850 void
2851 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2852 {
2853 g_assert (n);
2854 g_assert (nodepath);
2855 g_assert (n->subpath->nodepath == nodepath);
2857 if (g_list_length (nodepath->selected) == 0) {
2858 if (grow > 0) {
2859 sp_nodepath_node_select(n, TRUE, TRUE);
2860 }
2861 return;
2862 }
2864 if (g_list_length (nodepath->selected) == 1) {
2865 if (grow < 0) {
2866 sp_nodepath_deselect (nodepath);
2867 return;
2868 }
2869 }
2871 Inkscape::NodePath::Node *farthest_selected = NULL;
2872 double farthest_dist = 0;
2874 Inkscape::NodePath::Node *closest_unselected = NULL;
2875 double closest_dist = NR_HUGE;
2877 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2878 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2879 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2880 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2881 if (node == n)
2882 continue;
2883 if (node->selected) {
2884 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2885 farthest_dist = NR::L2(node->pos - n->pos);
2886 farthest_selected = node;
2887 }
2888 } else {
2889 if (NR::L2(node->pos - n->pos) < closest_dist) {
2890 closest_dist = NR::L2(node->pos - n->pos);
2891 closest_unselected = node;
2892 }
2893 }
2894 }
2895 }
2897 if (grow > 0) {
2898 if (closest_unselected) {
2899 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2900 }
2901 } else {
2902 if (farthest_selected) {
2903 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2904 }
2905 }
2906 }
2909 /**
2910 \brief Saves all nodes' and handles' current positions in their origin members
2911 */
2912 void
2913 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2914 {
2915 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2916 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2917 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2918 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2919 n->origin = n->pos;
2920 n->p.origin = n->p.pos;
2921 n->n.origin = n->n.pos;
2922 }
2923 }
2924 }
2926 /**
2927 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2928 */
2929 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2930 {
2931 if (!nodepath->selected) {
2932 return NULL;
2933 }
2935 GList *r = NULL;
2936 guint i = 0;
2937 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2938 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2939 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2940 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2941 i++;
2942 if (node->selected) {
2943 r = g_list_append(r, GINT_TO_POINTER(i));
2944 }
2945 }
2946 }
2947 return r;
2948 }
2950 /**
2951 \brief Restores selection by selecting nodes whose positions are in the list
2952 */
2953 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2954 {
2955 sp_nodepath_deselect(nodepath);
2957 guint i = 0;
2958 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2959 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2960 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2961 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2962 i++;
2963 if (g_list_find(r, GINT_TO_POINTER(i))) {
2964 sp_nodepath_node_select(node, TRUE, TRUE);
2965 }
2966 }
2967 }
2969 }
2971 /**
2972 \brief Adjusts handle according to node type and line code.
2973 */
2974 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2975 {
2976 double len, otherlen, linelen;
2978 g_assert(node);
2980 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2981 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2983 /** \todo fixme: */
2984 if (me->other == NULL) return;
2985 if (other->other == NULL) return;
2987 /* I have line */
2989 NRPathcode mecode, ocode;
2990 if (which_adjust == 1) {
2991 mecode = (NRPathcode)me->other->code;
2992 ocode = (NRPathcode)node->code;
2993 } else {
2994 mecode = (NRPathcode)node->code;
2995 ocode = (NRPathcode)other->other->code;
2996 }
2998 if (mecode == NR_LINETO) return;
3000 /* I am curve */
3002 if (other->other == NULL) return;
3004 /* Other has line */
3006 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3008 NR::Point delta;
3009 if (ocode == NR_LINETO) {
3010 /* other is lineto, we are either smooth or symm */
3011 Inkscape::NodePath::Node *othernode = other->other;
3012 len = NR::L2(me->pos - node->pos);
3013 delta = node->pos - othernode->pos;
3014 linelen = NR::L2(delta);
3015 if (linelen < 1e-18)
3016 return;
3017 me->pos = node->pos + (len / linelen)*delta;
3018 return;
3019 }
3021 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3023 me->pos = 2 * node->pos - other->pos;
3024 return;
3025 }
3027 /* We are smooth */
3029 len = NR::L2(me->pos - node->pos);
3030 delta = other->pos - node->pos;
3031 otherlen = NR::L2(delta);
3032 if (otherlen < 1e-18) return;
3034 me->pos = node->pos - (len / otherlen) * delta;
3035 }
3037 /**
3038 \brief Adjusts both handles according to node type and line code
3039 */
3040 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3041 {
3042 g_assert(node);
3044 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3046 /* we are either smooth or symm */
3048 if (node->p.other == NULL) return;
3050 if (node->n.other == NULL) return;
3052 if (node->code == NR_LINETO) {
3053 if (node->n.other->code == NR_LINETO) return;
3054 sp_node_adjust_handle(node, 1);
3055 return;
3056 }
3058 if (node->n.other->code == NR_LINETO) {
3059 if (node->code == NR_LINETO) return;
3060 sp_node_adjust_handle(node, -1);
3061 return;
3062 }
3064 /* both are curves */
3065 NR::Point const delta( node->n.pos - node->p.pos );
3067 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3068 node->p.pos = node->pos - delta / 2;
3069 node->n.pos = node->pos + delta / 2;
3070 return;
3071 }
3073 /* We are smooth */
3074 double plen = NR::L2(node->p.pos - node->pos);
3075 if (plen < 1e-18) return;
3076 double nlen = NR::L2(node->n.pos - node->pos);
3077 if (nlen < 1e-18) return;
3078 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3079 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3080 }
3082 /**
3083 * Node event callback.
3084 */
3085 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3086 {
3087 gboolean ret = FALSE;
3088 switch (event->type) {
3089 case GDK_ENTER_NOTIFY:
3090 Inkscape::NodePath::Path::active_node = n;
3091 break;
3092 case GDK_LEAVE_NOTIFY:
3093 Inkscape::NodePath::Path::active_node = NULL;
3094 break;
3095 case GDK_SCROLL:
3096 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3097 switch (event->scroll.direction) {
3098 case GDK_SCROLL_UP:
3099 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3100 break;
3101 case GDK_SCROLL_DOWN:
3102 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3103 break;
3104 default:
3105 break;
3106 }
3107 ret = TRUE;
3108 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3109 switch (event->scroll.direction) {
3110 case GDK_SCROLL_UP:
3111 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3112 break;
3113 case GDK_SCROLL_DOWN:
3114 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3115 break;
3116 default:
3117 break;
3118 }
3119 ret = TRUE;
3120 }
3121 break;
3122 case GDK_KEY_PRESS:
3123 switch (get_group0_keyval (&event->key)) {
3124 case GDK_space:
3125 if (event->key.state & GDK_BUTTON1_MASK) {
3126 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3127 stamp_repr(nodepath);
3128 ret = TRUE;
3129 }
3130 break;
3131 case GDK_Page_Up:
3132 if (event->key.state & GDK_CONTROL_MASK) {
3133 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3134 } else {
3135 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3136 }
3137 break;
3138 case GDK_Page_Down:
3139 if (event->key.state & GDK_CONTROL_MASK) {
3140 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3141 } else {
3142 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3143 }
3144 break;
3145 default:
3146 break;
3147 }
3148 break;
3149 default:
3150 break;
3151 }
3153 return ret;
3154 }
3156 /**
3157 * Handle keypress on node; directly called.
3158 */
3159 gboolean node_key(GdkEvent *event)
3160 {
3161 Inkscape::NodePath::Path *np;
3163 // there is no way to verify nodes so set active_node to nil when deleting!!
3164 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3166 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3167 gint ret = FALSE;
3168 switch (get_group0_keyval (&event->key)) {
3169 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3170 case GDK_BackSpace:
3171 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3172 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3173 sp_nodepath_update_repr(np, _("Delete node"));
3174 Inkscape::NodePath::Path::active_node = NULL;
3175 ret = TRUE;
3176 break;
3177 case GDK_c:
3178 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3179 ret = TRUE;
3180 break;
3181 case GDK_s:
3182 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3183 ret = TRUE;
3184 break;
3185 case GDK_y:
3186 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3187 ret = TRUE;
3188 break;
3189 case GDK_b:
3190 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3191 ret = TRUE;
3192 break;
3193 }
3194 return ret;
3195 }
3196 return FALSE;
3197 }
3199 /**
3200 * Mouseclick on node callback.
3201 */
3202 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3203 {
3204 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3206 if (state & GDK_CONTROL_MASK) {
3207 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3209 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3210 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3211 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3212 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3213 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3214 } else {
3215 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3216 }
3217 sp_nodepath_update_repr(nodepath, _("Change node type"));
3218 sp_nodepath_update_statusbar(nodepath);
3220 } else { //ctrl+alt+click: delete node
3221 GList *node_to_delete = NULL;
3222 node_to_delete = g_list_append(node_to_delete, n);
3223 sp_node_delete_preserve(node_to_delete);
3224 }
3226 } else {
3227 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3228 }
3229 }
3231 /**
3232 * Mouse grabbed node callback.
3233 */
3234 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3235 {
3236 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3238 if (!n->selected) {
3239 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3240 }
3242 n->is_dragging = true;
3243 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3245 sp_nodepath_remember_origins (n->subpath->nodepath);
3246 }
3248 /**
3249 * Mouse ungrabbed node callback.
3250 */
3251 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3252 {
3253 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3255 n->dragging_out = NULL;
3256 n->is_dragging = false;
3257 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3259 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3260 }
3262 /**
3263 * The point on a line, given by its angle, closest to the given point.
3264 * \param p A point.
3265 * \param a Angle of the line; it is assumed to go through coordinate origin.
3266 * \param closest Pointer to the point struct where the result is stored.
3267 * \todo FIXME: use dot product perhaps?
3268 */
3269 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3270 {
3271 if (a == HUGE_VAL) { // vertical
3272 *closest = NR::Point(0, (*p)[NR::Y]);
3273 } else {
3274 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3275 (*closest)[NR::Y] = a * (*closest)[NR::X];
3276 }
3277 }
3279 /**
3280 * Distance from the point to a line given by its angle.
3281 * \param p A point.
3282 * \param a Angle of the line; it is assumed to go through coordinate origin.
3283 */
3284 static double point_line_distance(NR::Point *p, double a)
3285 {
3286 NR::Point c;
3287 point_line_closest(p, a, &c);
3288 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]));
3289 }
3291 /**
3292 * Callback for node "request" signal.
3293 * \todo fixme: This goes to "moved" event? (lauris)
3294 */
3295 static gboolean
3296 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3297 {
3298 double yn, xn, yp, xp;
3299 double an, ap, na, pa;
3300 double d_an, d_ap, d_na, d_pa;
3301 gboolean collinear = FALSE;
3302 NR::Point c;
3303 NR::Point pr;
3305 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3307 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3309 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3310 if ( (!n->subpath->nodepath->straight_path) &&
3311 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3312 || n->dragging_out ) )
3313 {
3314 NR::Point mouse = (*p);
3316 if (!n->dragging_out) {
3317 // This is the first drag-out event; find out which handle to drag out
3318 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3319 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3321 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3322 return FALSE;
3324 Inkscape::NodePath::NodeSide *opposite;
3325 if (appr_p > appr_n) { // closer to p
3326 n->dragging_out = &n->p;
3327 opposite = &n->n;
3328 n->code = NR_CURVETO;
3329 } else if (appr_p < appr_n) { // closer to n
3330 n->dragging_out = &n->n;
3331 opposite = &n->p;
3332 n->n.other->code = NR_CURVETO;
3333 } else { // p and n nodes are the same
3334 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3335 n->dragging_out = &n->p;
3336 opposite = &n->n;
3337 n->code = NR_CURVETO;
3338 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3339 n->dragging_out = &n->n;
3340 opposite = &n->p;
3341 n->n.other->code = NR_CURVETO;
3342 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3343 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);
3344 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);
3345 if (appr_other_p > appr_other_n) { // closer to other's p handle
3346 n->dragging_out = &n->n;
3347 opposite = &n->p;
3348 n->n.other->code = NR_CURVETO;
3349 } else { // closer to other's n handle
3350 n->dragging_out = &n->p;
3351 opposite = &n->n;
3352 n->code = NR_CURVETO;
3353 }
3354 }
3355 }
3357 // if there's another handle, make sure the one we drag out starts parallel to it
3358 if (opposite->pos != n->pos) {
3359 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3360 }
3362 // knots might not be created yet!
3363 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3364 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3365 }
3367 // pass this on to the handle-moved callback
3368 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3369 sp_node_update_handles(n);
3370 return TRUE;
3371 }
3373 if (state & GDK_CONTROL_MASK) { // constrained motion
3375 // calculate relative distances of handles
3376 // n handle:
3377 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3378 xn = n->n.pos[NR::X] - n->pos[NR::X];
3379 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3380 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3381 if (n->n.other) { // if there is the next point
3382 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3383 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3384 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3385 }
3386 }
3387 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3388 if (yn < 0) { xn = -xn; yn = -yn; }
3390 // p handle:
3391 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3392 xp = n->p.pos[NR::X] - n->pos[NR::X];
3393 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3394 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3395 if (n->p.other) {
3396 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3397 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3398 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3399 }
3400 }
3401 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3402 if (yp < 0) { xp = -xp; yp = -yp; }
3404 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3405 // sliding on handles, only if at least one of the handles is non-vertical
3406 // (otherwise it's the same as ctrl+drag anyway)
3408 // calculate angles of the handles
3409 if (xn == 0) {
3410 if (yn == 0) { // no handle, consider it the continuation of the other one
3411 an = 0;
3412 collinear = TRUE;
3413 }
3414 else an = 0; // vertical; set the angle to horizontal
3415 } else an = yn/xn;
3417 if (xp == 0) {
3418 if (yp == 0) { // no handle, consider it the continuation of the other one
3419 ap = an;
3420 }
3421 else ap = 0; // vertical; set the angle to horizontal
3422 } else ap = yp/xp;
3424 if (collinear) an = ap;
3426 // angles of the perpendiculars; HUGE_VAL means vertical
3427 if (an == 0) na = HUGE_VAL; else na = -1/an;
3428 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3430 // mouse point relative to the node's original pos
3431 pr = (*p) - n->origin;
3433 // distances to the four lines (two handles and two perpendiculars)
3434 d_an = point_line_distance(&pr, an);
3435 d_na = point_line_distance(&pr, na);
3436 d_ap = point_line_distance(&pr, ap);
3437 d_pa = point_line_distance(&pr, pa);
3439 // find out which line is the closest, save its closest point in c
3440 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3441 point_line_closest(&pr, an, &c);
3442 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3443 point_line_closest(&pr, ap, &c);
3444 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3445 point_line_closest(&pr, na, &c);
3446 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3447 point_line_closest(&pr, pa, &c);
3448 }
3450 // move the node to the closest point
3451 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3452 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3453 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3455 } else { // constraining to hor/vert
3457 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3458 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3459 } else { // snap to vert
3460 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3461 }
3462 }
3463 } else { // move freely
3464 if (n->is_dragging) {
3465 if (state & GDK_MOD1_MASK) { // sculpt
3466 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3467 } else {
3468 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3469 (*p)[NR::X] - n->pos[NR::X],
3470 (*p)[NR::Y] - n->pos[NR::Y],
3471 (state & GDK_SHIFT_MASK) == 0);
3472 }
3473 }
3474 }
3476 n->subpath->nodepath->desktop->scroll_to_point(p);
3478 return TRUE;
3479 }
3481 /**
3482 * Node handle clicked callback.
3483 */
3484 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3485 {
3486 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3488 if (state & GDK_CONTROL_MASK) { // "delete" handle
3489 if (n->p.knot == knot) {
3490 n->p.pos = n->pos;
3491 } else if (n->n.knot == knot) {
3492 n->n.pos = n->pos;
3493 }
3494 sp_node_update_handles(n);
3495 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3496 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3497 sp_nodepath_update_statusbar(nodepath);
3499 } else { // just select or add to selection, depending in Shift
3500 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3501 }
3502 }
3504 /**
3505 * Node handle grabbed callback.
3506 */
3507 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3508 {
3509 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3511 if (!n->selected) {
3512 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3513 }
3515 // remember the origin point of the handle
3516 if (n->p.knot == knot) {
3517 n->p.origin_radial = n->p.pos - n->pos;
3518 } else if (n->n.knot == knot) {
3519 n->n.origin_radial = n->n.pos - n->pos;
3520 } else {
3521 g_assert_not_reached();
3522 }
3524 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3525 }
3527 /**
3528 * Node handle ungrabbed callback.
3529 */
3530 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3531 {
3532 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3534 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3535 if (n->p.knot == knot) {
3536 n->p.origin_radial.a = 0;
3537 sp_knot_set_position(knot, &n->p.pos, state);
3538 } else if (n->n.knot == knot) {
3539 n->n.origin_radial.a = 0;
3540 sp_knot_set_position(knot, &n->n.pos, state);
3541 } else {
3542 g_assert_not_reached();
3543 }
3545 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3546 }
3548 /**
3549 * Node handle "request" signal callback.
3550 */
3551 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint /*state*/, gpointer data)
3552 {
3553 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3555 Inkscape::NodePath::NodeSide *me, *opposite;
3556 gint which;
3557 if (n->p.knot == knot) {
3558 me = &n->p;
3559 opposite = &n->n;
3560 which = -1;
3561 } else if (n->n.knot == knot) {
3562 me = &n->n;
3563 opposite = &n->p;
3564 which = 1;
3565 } else {
3566 me = opposite = NULL;
3567 which = 0;
3568 g_assert_not_reached();
3569 }
3571 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3573 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3574 Inkscape::SnappedPoint s ;
3575 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3576 /* We are smooth node adjacent with line */
3577 NR::Point const delta = *p - n->pos;
3578 NR::Coord const len = NR::L2(delta);
3579 Inkscape::NodePath::Node *othernode = opposite->other;
3580 NR::Point const ndelta = n->pos - othernode->pos;
3581 NR::Coord const linelen = NR::L2(ndelta);
3582 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3583 NR::Coord const scal = dot(delta, ndelta) / linelen;
3584 (*p) = n->pos + (scal / linelen) * ndelta;
3585 }
3586 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item);
3587 } else {
3588 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item);
3589 }
3590 *p = s.getPoint();
3591 if (s.getSnapped()) {
3592 n->subpath->nodepath->desktop->snapindicator->set_new_snappoint(s);
3593 }
3595 sp_node_adjust_handle(n, -which);
3597 return FALSE;
3598 }
3600 /**
3601 * Node handle moved callback.
3602 */
3603 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3604 {
3605 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3607 Inkscape::NodePath::NodeSide *me;
3608 Inkscape::NodePath::NodeSide *other;
3609 if (n->p.knot == knot) {
3610 me = &n->p;
3611 other = &n->n;
3612 } else if (n->n.knot == knot) {
3613 me = &n->n;
3614 other = &n->p;
3615 } else {
3616 me = NULL;
3617 other = NULL;
3618 g_assert_not_reached();
3619 }
3621 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3622 Radial rme(me->pos - n->pos);
3623 Radial rother(other->pos - n->pos);
3624 Radial rnew(*p - n->pos);
3626 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3627 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3628 /* 0 interpreted as "no snapping". */
3630 // The closest PI/snaps angle, starting from zero.
3631 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3632 if (me->origin_radial.a == HUGE_VAL) {
3633 // ortho doesn't exist: original handle was zero length.
3634 rnew.a = a_snapped;
3635 } else {
3636 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3637 * its opposite and perpendiculars). */
3638 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3640 // Snap to the closest.
3641 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3642 ? a_snapped
3643 : a_ortho );
3644 }
3645 }
3647 if (state & GDK_MOD1_MASK) {
3648 // lock handle length
3649 rnew.r = me->origin_radial.r;
3650 }
3652 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3653 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3654 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3655 rother.a += rnew.a - rme.a;
3656 other->pos = NR::Point(rother) + n->pos;
3657 if (other->knot) {
3658 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3659 sp_knot_moveto(other->knot, &other->pos);
3660 }
3661 }
3663 me->pos = NR::Point(rnew) + n->pos;
3664 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3666 // move knot, but without emitting the signal:
3667 // we cannot emit a "moved" signal because we're now processing it
3668 sp_knot_moveto(me->knot, &(me->pos));
3670 update_object(n->subpath->nodepath);
3672 /* status text */
3673 SPDesktop *desktop = n->subpath->nodepath->desktop;
3674 if (!desktop) return;
3675 SPEventContext *ec = desktop->event_context;
3676 if (!ec) return;
3677 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3678 if (!mc) return;
3680 double degrees = 180 / M_PI * rnew.a;
3681 if (degrees > 180) degrees -= 360;
3682 if (degrees < -180) degrees += 360;
3683 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3684 degrees = angle_to_compass (degrees);
3686 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3688 mc->setF(Inkscape::NORMAL_MESSAGE,
3689 _("<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);
3691 g_string_free(length, TRUE);
3692 }
3694 /**
3695 * Node handle event callback.
3696 */
3697 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3698 {
3699 gboolean ret = FALSE;
3700 switch (event->type) {
3701 case GDK_KEY_PRESS:
3702 switch (get_group0_keyval (&event->key)) {
3703 case GDK_space:
3704 if (event->key.state & GDK_BUTTON1_MASK) {
3705 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3706 stamp_repr(nodepath);
3707 ret = TRUE;
3708 }
3709 break;
3710 default:
3711 break;
3712 }
3713 break;
3714 case GDK_ENTER_NOTIFY:
3715 // we use an experimentally determined threshold that seems to work fine
3716 if (NR::L2(n->pos - knot->pos) < 0.75)
3717 Inkscape::NodePath::Path::active_node = n;
3718 break;
3719 case GDK_LEAVE_NOTIFY:
3720 // we use an experimentally determined threshold that seems to work fine
3721 if (NR::L2(n->pos - knot->pos) < 0.75)
3722 Inkscape::NodePath::Path::active_node = NULL;
3723 break;
3724 default:
3725 break;
3726 }
3728 return ret;
3729 }
3731 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3732 Radial &rme, Radial &rother, gboolean const both)
3733 {
3734 rme.a += angle;
3735 if ( both
3736 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3737 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3738 {
3739 rother.a += angle;
3740 }
3741 }
3743 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3744 Radial &rme, Radial &rother, gboolean const both)
3745 {
3746 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3748 gdouble r;
3749 if ( both
3750 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3751 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3752 {
3753 r = MAX(rme.r, rother.r);
3754 } else {
3755 r = rme.r;
3756 }
3758 gdouble const weird_angle = atan2(norm_angle, r);
3759 /* Bulia says norm_angle is just the visible distance that the
3760 * object's end must travel on the screen. Left as 'angle' for want of
3761 * a better name.*/
3763 rme.a += weird_angle;
3764 if ( both
3765 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3766 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3767 {
3768 rother.a += weird_angle;
3769 }
3770 }
3772 /**
3773 * Rotate one node.
3774 */
3775 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3776 {
3777 Inkscape::NodePath::NodeSide *me, *other;
3778 bool both = false;
3780 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3781 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3783 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3784 me = &(n->p);
3785 other = &(n->n);
3786 } else if (!n->p.other) {
3787 me = &(n->n);
3788 other = &(n->p);
3789 } else {
3790 if (which > 0) { // right handle
3791 if (xn > xp) {
3792 me = &(n->n);
3793 other = &(n->p);
3794 } else {
3795 me = &(n->p);
3796 other = &(n->n);
3797 }
3798 } else if (which < 0){ // left handle
3799 if (xn <= xp) {
3800 me = &(n->n);
3801 other = &(n->p);
3802 } else {
3803 me = &(n->p);
3804 other = &(n->n);
3805 }
3806 } else { // both handles
3807 me = &(n->n);
3808 other = &(n->p);
3809 both = true;
3810 }
3811 }
3813 Radial rme(me->pos - n->pos);
3814 Radial rother(other->pos - n->pos);
3816 if (screen) {
3817 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3818 } else {
3819 node_rotate_one_internal (*n, angle, rme, rother, both);
3820 }
3822 me->pos = n->pos + NR::Point(rme);
3824 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3825 other->pos = n->pos + NR::Point(rother);
3826 }
3828 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3829 // so here we just move all the knots without emitting move signals, for speed
3830 sp_node_update_handles(n, false);
3831 }
3833 /**
3834 * Rotate selected nodes.
3835 */
3836 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3837 {
3838 if (!nodepath || !nodepath->selected) return;
3840 if (g_list_length(nodepath->selected) == 1) {
3841 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3842 node_rotate_one (n, angle, which, screen);
3843 } else {
3844 // rotate as an object:
3846 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3847 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3848 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3849 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3850 box.expandTo (n->pos); // contain all selected nodes
3851 }
3853 gdouble rot;
3854 if (screen) {
3855 gdouble const zoom = nodepath->desktop->current_zoom();
3856 gdouble const zmove = angle / zoom;
3857 gdouble const r = NR::L2(box.max() - box.midpoint());
3858 rot = atan2(zmove, r);
3859 } else {
3860 rot = angle;
3861 }
3863 NR::Point rot_center;
3864 if (Inkscape::NodePath::Path::active_node == NULL)
3865 rot_center = box.midpoint();
3866 else
3867 rot_center = Inkscape::NodePath::Path::active_node->pos;
3869 NR::Matrix t =
3870 NR::Matrix (NR::translate(-rot_center)) *
3871 NR::Matrix (NR::rotate(rot)) *
3872 NR::Matrix (NR::translate(rot_center));
3874 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3875 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3876 n->pos *= t;
3877 n->n.pos *= t;
3878 n->p.pos *= t;
3879 sp_node_update_handles(n, false);
3880 }
3881 }
3883 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3884 }
3886 /**
3887 * Scale one node.
3888 */
3889 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3890 {
3891 bool both = false;
3892 Inkscape::NodePath::NodeSide *me, *other;
3894 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3895 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3897 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3898 me = &(n->p);
3899 other = &(n->n);
3900 n->code = NR_CURVETO;
3901 } else if (!n->p.other) {
3902 me = &(n->n);
3903 other = &(n->p);
3904 if (n->n.other)
3905 n->n.other->code = NR_CURVETO;
3906 } else {
3907 if (which > 0) { // right handle
3908 if (xn > xp) {
3909 me = &(n->n);
3910 other = &(n->p);
3911 if (n->n.other)
3912 n->n.other->code = NR_CURVETO;
3913 } else {
3914 me = &(n->p);
3915 other = &(n->n);
3916 n->code = NR_CURVETO;
3917 }
3918 } else if (which < 0){ // left handle
3919 if (xn <= xp) {
3920 me = &(n->n);
3921 other = &(n->p);
3922 if (n->n.other)
3923 n->n.other->code = NR_CURVETO;
3924 } else {
3925 me = &(n->p);
3926 other = &(n->n);
3927 n->code = NR_CURVETO;
3928 }
3929 } else { // both handles
3930 me = &(n->n);
3931 other = &(n->p);
3932 both = true;
3933 n->code = NR_CURVETO;
3934 if (n->n.other)
3935 n->n.other->code = NR_CURVETO;
3936 }
3937 }
3939 Radial rme(me->pos - n->pos);
3940 Radial rother(other->pos - n->pos);
3942 rme.r += grow;
3943 if (rme.r < 0) rme.r = 0;
3944 if (rme.a == HUGE_VAL) {
3945 if (me->other) { // if direction is unknown, initialize it towards the next node
3946 Radial rme_next(me->other->pos - n->pos);
3947 rme.a = rme_next.a;
3948 } else { // if there's no next, initialize to 0
3949 rme.a = 0;
3950 }
3951 }
3952 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3953 rother.r += grow;
3954 if (rother.r < 0) rother.r = 0;
3955 if (rother.a == HUGE_VAL) {
3956 rother.a = rme.a + M_PI;
3957 }
3958 }
3960 me->pos = n->pos + NR::Point(rme);
3962 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3963 other->pos = n->pos + NR::Point(rother);
3964 }
3966 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3967 // so here we just move all the knots without emitting move signals, for speed
3968 sp_node_update_handles(n, false);
3969 }
3971 /**
3972 * Scale selected nodes.
3973 */
3974 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3975 {
3976 if (!nodepath || !nodepath->selected) return;
3978 if (g_list_length(nodepath->selected) == 1) {
3979 // scale handles of the single selected node
3980 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3981 node_scale_one (n, grow, which);
3982 } else {
3983 // scale nodes as an "object":
3985 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3986 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3987 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3988 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3989 box.expandTo (n->pos); // contain all selected nodes
3990 }
3992 double scale = (box.maxExtent() + grow)/box.maxExtent();
3994 NR::Point scale_center;
3995 if (Inkscape::NodePath::Path::active_node == NULL)
3996 scale_center = box.midpoint();
3997 else
3998 scale_center = Inkscape::NodePath::Path::active_node->pos;
4000 NR::Matrix t =
4001 NR::Matrix (NR::translate(-scale_center)) *
4002 NR::Matrix (NR::scale(scale, scale)) *
4003 NR::Matrix (NR::translate(scale_center));
4005 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4006 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4007 n->pos *= t;
4008 n->n.pos *= t;
4009 n->p.pos *= t;
4010 sp_node_update_handles(n, false);
4011 }
4012 }
4014 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4015 }
4017 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4018 {
4019 if (!nodepath) return;
4020 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4021 }
4023 /**
4024 * Flip selected nodes horizontally/vertically.
4025 */
4026 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4027 {
4028 if (!nodepath || !nodepath->selected) return;
4030 if (g_list_length(nodepath->selected) == 1 && !center) {
4031 // flip handles of the single selected node
4032 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4033 double temp = n->p.pos[axis];
4034 n->p.pos[axis] = n->n.pos[axis];
4035 n->n.pos[axis] = temp;
4036 sp_node_update_handles(n, false);
4037 } else {
4038 // scale nodes as an "object":
4040 NR::Rect box = sp_node_selected_bbox (nodepath);
4041 if (!center) {
4042 center = box.midpoint();
4043 }
4044 NR::Matrix t =
4045 NR::Matrix (NR::translate(- *center)) *
4046 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4047 NR::Matrix (NR::translate(*center));
4049 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4050 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4051 n->pos *= t;
4052 n->n.pos *= t;
4053 n->p.pos *= t;
4054 sp_node_update_handles(n, false);
4055 }
4056 }
4058 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4059 }
4061 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4062 {
4063 g_assert (nodepath->selected);
4065 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4066 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4067 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4068 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4069 box.expandTo (n->pos); // contain all selected nodes
4070 }
4071 return box;
4072 }
4074 //-----------------------------------------------
4075 /**
4076 * Return new subpath under given nodepath.
4077 */
4078 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4079 {
4080 g_assert(nodepath);
4081 g_assert(nodepath->desktop);
4083 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4085 s->nodepath = nodepath;
4086 s->closed = FALSE;
4087 s->nodes = NULL;
4088 s->first = NULL;
4089 s->last = NULL;
4091 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4092 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4093 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4095 return s;
4096 }
4098 /**
4099 * Destroy nodes in subpath, then subpath itself.
4100 */
4101 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4102 {
4103 g_assert(subpath);
4104 g_assert(subpath->nodepath);
4105 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4107 while (subpath->nodes) {
4108 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4109 }
4111 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4113 g_free(subpath);
4114 }
4116 /**
4117 * Link head to tail in subpath.
4118 */
4119 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4120 {
4121 g_assert(!sp->closed);
4122 g_assert(sp->last != sp->first);
4123 g_assert(sp->first->code == NR_MOVETO);
4125 sp->closed = TRUE;
4127 //Link the head to the tail
4128 sp->first->p.other = sp->last;
4129 sp->last->n.other = sp->first;
4130 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4131 sp->first = sp->last;
4133 //Remove the extra end node
4134 sp_nodepath_node_destroy(sp->last->n.other);
4135 }
4137 /**
4138 * Open closed (loopy) subpath at node.
4139 */
4140 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4141 {
4142 g_assert(sp->closed);
4143 g_assert(n->subpath == sp);
4144 g_assert(sp->first == sp->last);
4146 /* We create new startpoint, current node will become last one */
4148 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4149 &n->pos, &n->pos, &n->n.pos);
4152 sp->closed = FALSE;
4154 //Unlink to make a head and tail
4155 sp->first = new_path;
4156 sp->last = n;
4157 n->n.other = NULL;
4158 new_path->p.other = NULL;
4159 }
4161 /**
4162 * Return new node in subpath with given properties.
4163 * \param pos Position of node.
4164 * \param ppos Handle position in previous direction
4165 * \param npos Handle position in previous direction
4166 */
4167 Inkscape::NodePath::Node *
4168 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)
4169 {
4170 g_assert(sp);
4171 g_assert(sp->nodepath);
4172 g_assert(sp->nodepath->desktop);
4174 if (nodechunk == NULL)
4175 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4177 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4179 n->subpath = sp;
4181 if (type != Inkscape::NodePath::NODE_NONE) {
4182 // use the type from sodipodi:nodetypes
4183 n->type = type;
4184 } else {
4185 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4186 // points are (almost) collinear
4187 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4188 // endnode, or a node with a retracted handle
4189 n->type = Inkscape::NodePath::NODE_CUSP;
4190 } else {
4191 n->type = Inkscape::NodePath::NODE_SMOOTH;
4192 }
4193 } else {
4194 n->type = Inkscape::NodePath::NODE_CUSP;
4195 }
4196 }
4198 n->code = code;
4199 n->selected = FALSE;
4200 n->pos = *pos;
4201 n->p.pos = *ppos;
4202 n->n.pos = *npos;
4204 n->dragging_out = NULL;
4206 Inkscape::NodePath::Node *prev;
4207 if (next) {
4208 //g_assert(g_list_find(sp->nodes, next));
4209 prev = next->p.other;
4210 } else {
4211 prev = sp->last;
4212 }
4214 if (prev)
4215 prev->n.other = n;
4216 else
4217 sp->first = n;
4219 if (next)
4220 next->p.other = n;
4221 else
4222 sp->last = n;
4224 n->p.other = prev;
4225 n->n.other = next;
4227 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"));
4228 sp_knot_set_position(n->knot, pos, 0);
4230 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4231 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4232 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4233 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4234 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4235 sp_knot_update_ctrl(n->knot);
4237 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4238 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4239 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4240 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4241 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4242 sp_knot_show(n->knot);
4244 // We only create handle knots and lines on demand
4245 n->p.knot = NULL;
4246 n->p.line = NULL;
4247 n->n.knot = NULL;
4248 n->n.line = NULL;
4250 sp->nodes = g_list_prepend(sp->nodes, n);
4252 return n;
4253 }
4255 /**
4256 * Destroy node and its knots, link neighbors in subpath.
4257 */
4258 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4259 {
4260 g_assert(node);
4261 g_assert(node->subpath);
4262 g_assert(SP_IS_KNOT(node->knot));
4264 Inkscape::NodePath::SubPath *sp = node->subpath;
4266 if (node->selected) { // first, deselect
4267 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4268 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4269 }
4271 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4273 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4274 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4275 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4276 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4277 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4278 g_object_unref(G_OBJECT(node->knot));
4280 if (node->p.knot) {
4281 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4282 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4283 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4284 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4285 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4286 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4287 g_object_unref(G_OBJECT(node->p.knot));
4288 node->p.knot = NULL;
4289 }
4291 if (node->n.knot) {
4292 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4293 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4294 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4295 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4296 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4297 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4298 g_object_unref(G_OBJECT(node->n.knot));
4299 node->n.knot = NULL;
4300 }
4302 if (node->p.line)
4303 gtk_object_destroy(GTK_OBJECT(node->p.line));
4304 if (node->n.line)
4305 gtk_object_destroy(GTK_OBJECT(node->n.line));
4307 if (sp->nodes) { // there are others nodes on the subpath
4308 if (sp->closed) {
4309 if (sp->first == node) {
4310 g_assert(sp->last == node);
4311 sp->first = node->n.other;
4312 sp->last = sp->first;
4313 }
4314 node->p.other->n.other = node->n.other;
4315 node->n.other->p.other = node->p.other;
4316 } else {
4317 if (sp->first == node) {
4318 sp->first = node->n.other;
4319 sp->first->code = NR_MOVETO;
4320 }
4321 if (sp->last == node) sp->last = node->p.other;
4322 if (node->p.other) node->p.other->n.other = node->n.other;
4323 if (node->n.other) node->n.other->p.other = node->p.other;
4324 }
4325 } else { // this was the last node on subpath
4326 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4327 }
4329 g_mem_chunk_free(nodechunk, node);
4330 }
4332 /**
4333 * Returns one of the node's two sides.
4334 * \param which Indicates which side.
4335 * \return Pointer to previous node side if which==-1, next if which==1.
4336 */
4337 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4338 {
4339 g_assert(node);
4341 switch (which) {
4342 case -1:
4343 return &node->p;
4344 case 1:
4345 return &node->n;
4346 default:
4347 break;
4348 }
4350 g_assert_not_reached();
4352 return NULL;
4353 }
4355 /**
4356 * Return the other side of the node, given one of its sides.
4357 */
4358 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4359 {
4360 g_assert(node);
4362 if (me == &node->p) return &node->n;
4363 if (me == &node->n) return &node->p;
4365 g_assert_not_reached();
4367 return NULL;
4368 }
4370 /**
4371 * Return NRPathcode on the given side of the node.
4372 */
4373 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4374 {
4375 g_assert(node);
4377 if (me == &node->p) {
4378 if (node->p.other) return (NRPathcode)node->code;
4379 return NR_MOVETO;
4380 }
4382 if (me == &node->n) {
4383 if (node->n.other) return (NRPathcode)node->n.other->code;
4384 return NR_MOVETO;
4385 }
4387 g_assert_not_reached();
4389 return NR_END;
4390 }
4392 /**
4393 * Return node with the given index
4394 */
4395 Inkscape::NodePath::Node *
4396 sp_nodepath_get_node_by_index(int index)
4397 {
4398 Inkscape::NodePath::Node *e = NULL;
4400 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4401 if (!nodepath) {
4402 return e;
4403 }
4405 //find segment
4406 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4408 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4409 int n = g_list_length(sp->nodes);
4410 if (sp->closed) {
4411 n++;
4412 }
4414 //if the piece belongs to this subpath grab it
4415 //otherwise move onto the next subpath
4416 if (index < n) {
4417 e = sp->first;
4418 for (int i = 0; i < index; ++i) {
4419 e = e->n.other;
4420 }
4421 break;
4422 } else {
4423 if (sp->closed) {
4424 index -= (n+1);
4425 } else {
4426 index -= n;
4427 }
4428 }
4429 }
4431 return e;
4432 }
4434 /**
4435 * Returns plain text meaning of node type.
4436 */
4437 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4438 {
4439 unsigned retracted = 0;
4440 bool endnode = false;
4442 for (int which = -1; which <= 1; which += 2) {
4443 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4444 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4445 retracted ++;
4446 if (!side->other)
4447 endnode = true;
4448 }
4450 if (retracted == 0) {
4451 if (endnode) {
4452 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4453 return _("end node");
4454 } else {
4455 switch (node->type) {
4456 case Inkscape::NodePath::NODE_CUSP:
4457 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4458 return _("cusp");
4459 case Inkscape::NodePath::NODE_SMOOTH:
4460 // TRANSLATORS: "smooth" is an adjective here
4461 return _("smooth");
4462 case Inkscape::NodePath::NODE_SYMM:
4463 return _("symmetric");
4464 }
4465 }
4466 } else if (retracted == 1) {
4467 if (endnode) {
4468 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4469 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4470 } else {
4471 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4472 }
4473 } else {
4474 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4475 }
4477 return NULL;
4478 }
4480 /**
4481 * Handles content of statusbar as long as node tool is active.
4482 */
4483 void
4484 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4485 {
4486 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");
4487 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4489 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4490 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4491 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4492 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4494 SPDesktop *desktop = NULL;
4495 if (nodepath) {
4496 desktop = nodepath->desktop;
4497 } else {
4498 desktop = SP_ACTIVE_DESKTOP;
4499 }
4501 SPEventContext *ec = desktop->event_context;
4502 if (!ec) return;
4503 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4504 if (!mc) return;
4506 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4508 if (selected_nodes == 0) {
4509 Inkscape::Selection *sel = desktop->selection;
4510 if (!sel || sel->isEmpty()) {
4511 mc->setF(Inkscape::NORMAL_MESSAGE,
4512 _("Select a single object to edit its nodes or handles."));
4513 } else {
4514 if (nodepath) {
4515 mc->setF(Inkscape::NORMAL_MESSAGE,
4516 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.",
4517 "<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.",
4518 total_nodes),
4519 total_nodes);
4520 } else {
4521 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4522 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4523 } else {
4524 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4525 }
4526 }
4527 }
4528 } else if (nodepath && selected_nodes == 1) {
4529 mc->setF(Inkscape::NORMAL_MESSAGE,
4530 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4531 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4532 total_nodes),
4533 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4534 } else {
4535 if (selected_subpaths > 1) {
4536 mc->setF(Inkscape::NORMAL_MESSAGE,
4537 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4538 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4539 total_nodes),
4540 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4541 } else {
4542 mc->setF(Inkscape::NORMAL_MESSAGE,
4543 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4544 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4545 total_nodes),
4546 selected_nodes, total_nodes, when_selected);
4547 }
4548 }
4549 }
4551 /*
4552 * returns a *copy* of the curve of that object.
4553 */
4554 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4555 if (!object)
4556 return NULL;
4558 SPCurve *curve = NULL;
4559 if (SP_IS_PATH(object)) {
4560 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4561 curve = sp_curve_copy(curve_new);
4562 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4563 const gchar *svgd = object->repr->attribute(key);
4564 if (svgd) {
4565 NArtBpath *bpath = sp_svg_read_path(svgd);
4566 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4567 if (curve_new) {
4568 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4569 } else {
4570 g_free(bpath);
4571 }
4572 }
4573 }
4575 return curve;
4576 }
4578 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4579 if (!np || !np->object || !curve)
4580 return;
4582 if (SP_IS_PATH(np->object)) {
4583 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4584 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4585 } else {
4586 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4587 }
4588 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4589 // FIXME: this writing to string and then reading from string is bound to be slow.
4590 // create a method to convert from curve directly to 2geom...
4591 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4592 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4593 g_free(svgpath);
4595 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4596 }
4597 }
4599 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4600 np->show_helperpath = show;
4602 if (show) {
4603 SPCurve *helper_curve = sp_curve_copy(np->curve);
4604 sp_curve_transform(helper_curve, np->i2d );
4605 if (!np->helper_path) {
4606 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4607 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);
4608 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4609 sp_canvas_item_move_to_z(np->helper_path, 0);
4610 sp_canvas_item_show(np->helper_path);
4611 } else {
4612 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4613 }
4614 sp_curve_unref(helper_curve);
4615 } else {
4616 if (np->helper_path) {
4617 GtkObject *temp = np->helper_path;
4618 np->helper_path = NULL;
4619 gtk_object_destroy(temp);
4620 }
4621 }
4622 }
4624 /* this function does not work yet */
4625 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4626 np->straight_path = true;
4627 np->show_handles = false;
4628 g_message("add code to make the path straight.");
4629 // do sp_nodepath_convert_node_type on all nodes?
4630 // search for this text !!! "Make selected segments lines"
4631 }
4634 /*
4635 Local Variables:
4636 mode:c++
4637 c-file-style:"stroustrup"
4638 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4639 indent-tabs-mode:nil
4640 fill-column:99
4641 End:
4642 */
4643 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :