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