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 = 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;
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 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item).getPoint();
3544 } else {
3545 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item).getPoint();
3546 }
3548 sp_node_adjust_handle(n, -which);
3550 return FALSE;
3551 }
3553 /**
3554 * Node handle moved callback.
3555 */
3556 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3557 {
3558 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3560 Inkscape::NodePath::NodeSide *me;
3561 Inkscape::NodePath::NodeSide *other;
3562 if (n->p.knot == knot) {
3563 me = &n->p;
3564 other = &n->n;
3565 } else if (n->n.knot == knot) {
3566 me = &n->n;
3567 other = &n->p;
3568 } else {
3569 me = NULL;
3570 other = NULL;
3571 g_assert_not_reached();
3572 }
3574 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3575 Radial rme(me->pos - n->pos);
3576 Radial rother(other->pos - n->pos);
3577 Radial rnew(*p - n->pos);
3579 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3580 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3581 /* 0 interpreted as "no snapping". */
3583 // The closest PI/snaps angle, starting from zero.
3584 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3585 if (me->origin_radial.a == HUGE_VAL) {
3586 // ortho doesn't exist: original handle was zero length.
3587 rnew.a = a_snapped;
3588 } else {
3589 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3590 * its opposite and perpendiculars). */
3591 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3593 // Snap to the closest.
3594 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3595 ? a_snapped
3596 : a_ortho );
3597 }
3598 }
3600 if (state & GDK_MOD1_MASK) {
3601 // lock handle length
3602 rnew.r = me->origin_radial.r;
3603 }
3605 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3606 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3607 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3608 rother.a += rnew.a - rme.a;
3609 other->pos = NR::Point(rother) + n->pos;
3610 if (other->knot) {
3611 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3612 sp_knot_moveto(other->knot, &other->pos);
3613 }
3614 }
3616 me->pos = NR::Point(rnew) + n->pos;
3617 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3619 // move knot, but without emitting the signal:
3620 // we cannot emit a "moved" signal because we're now processing it
3621 sp_knot_moveto(me->knot, &(me->pos));
3623 update_object(n->subpath->nodepath);
3625 /* status text */
3626 SPDesktop *desktop = n->subpath->nodepath->desktop;
3627 if (!desktop) return;
3628 SPEventContext *ec = desktop->event_context;
3629 if (!ec) return;
3630 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3631 if (!mc) return;
3633 double degrees = 180 / M_PI * rnew.a;
3634 if (degrees > 180) degrees -= 360;
3635 if (degrees < -180) degrees += 360;
3636 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3637 degrees = angle_to_compass (degrees);
3639 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3641 mc->setF(Inkscape::NORMAL_MESSAGE,
3642 _("<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);
3644 g_string_free(length, TRUE);
3645 }
3647 /**
3648 * Node handle event callback.
3649 */
3650 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3651 {
3652 gboolean ret = FALSE;
3653 switch (event->type) {
3654 case GDK_KEY_PRESS:
3655 switch (get_group0_keyval (&event->key)) {
3656 case GDK_space:
3657 if (event->key.state & GDK_BUTTON1_MASK) {
3658 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3659 stamp_repr(nodepath);
3660 ret = TRUE;
3661 }
3662 break;
3663 default:
3664 break;
3665 }
3666 break;
3667 case GDK_ENTER_NOTIFY:
3668 // we use an experimentally determined threshold that seems to work fine
3669 if (NR::L2(n->pos - knot->pos) < 0.75)
3670 Inkscape::NodePath::Path::active_node = n;
3671 break;
3672 case GDK_LEAVE_NOTIFY:
3673 // we use an experimentally determined threshold that seems to work fine
3674 if (NR::L2(n->pos - knot->pos) < 0.75)
3675 Inkscape::NodePath::Path::active_node = NULL;
3676 break;
3677 default:
3678 break;
3679 }
3681 return ret;
3682 }
3684 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3685 Radial &rme, Radial &rother, gboolean const both)
3686 {
3687 rme.a += angle;
3688 if ( both
3689 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3690 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3691 {
3692 rother.a += angle;
3693 }
3694 }
3696 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3697 Radial &rme, Radial &rother, gboolean const both)
3698 {
3699 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3701 gdouble r;
3702 if ( both
3703 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3704 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3705 {
3706 r = MAX(rme.r, rother.r);
3707 } else {
3708 r = rme.r;
3709 }
3711 gdouble const weird_angle = atan2(norm_angle, r);
3712 /* Bulia says norm_angle is just the visible distance that the
3713 * object's end must travel on the screen. Left as 'angle' for want of
3714 * a better name.*/
3716 rme.a += weird_angle;
3717 if ( both
3718 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3719 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3720 {
3721 rother.a += weird_angle;
3722 }
3723 }
3725 /**
3726 * Rotate one node.
3727 */
3728 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3729 {
3730 Inkscape::NodePath::NodeSide *me, *other;
3731 bool both = false;
3733 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3734 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3736 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3737 me = &(n->p);
3738 other = &(n->n);
3739 } else if (!n->p.other) {
3740 me = &(n->n);
3741 other = &(n->p);
3742 } else {
3743 if (which > 0) { // right handle
3744 if (xn > xp) {
3745 me = &(n->n);
3746 other = &(n->p);
3747 } else {
3748 me = &(n->p);
3749 other = &(n->n);
3750 }
3751 } else if (which < 0){ // left handle
3752 if (xn <= xp) {
3753 me = &(n->n);
3754 other = &(n->p);
3755 } else {
3756 me = &(n->p);
3757 other = &(n->n);
3758 }
3759 } else { // both handles
3760 me = &(n->n);
3761 other = &(n->p);
3762 both = true;
3763 }
3764 }
3766 Radial rme(me->pos - n->pos);
3767 Radial rother(other->pos - n->pos);
3769 if (screen) {
3770 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3771 } else {
3772 node_rotate_one_internal (*n, angle, rme, rother, both);
3773 }
3775 me->pos = n->pos + NR::Point(rme);
3777 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3778 other->pos = n->pos + NR::Point(rother);
3779 }
3781 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3782 // so here we just move all the knots without emitting move signals, for speed
3783 sp_node_update_handles(n, false);
3784 }
3786 /**
3787 * Rotate selected nodes.
3788 */
3789 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3790 {
3791 if (!nodepath || !nodepath->selected) return;
3793 if (g_list_length(nodepath->selected) == 1) {
3794 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3795 node_rotate_one (n, angle, which, screen);
3796 } else {
3797 // rotate as an object:
3799 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3800 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3801 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3802 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3803 box.expandTo (n->pos); // contain all selected nodes
3804 }
3806 gdouble rot;
3807 if (screen) {
3808 gdouble const zoom = nodepath->desktop->current_zoom();
3809 gdouble const zmove = angle / zoom;
3810 gdouble const r = NR::L2(box.max() - box.midpoint());
3811 rot = atan2(zmove, r);
3812 } else {
3813 rot = angle;
3814 }
3816 NR::Point rot_center;
3817 if (Inkscape::NodePath::Path::active_node == NULL)
3818 rot_center = box.midpoint();
3819 else
3820 rot_center = Inkscape::NodePath::Path::active_node->pos;
3822 NR::Matrix t =
3823 NR::Matrix (NR::translate(-rot_center)) *
3824 NR::Matrix (NR::rotate(rot)) *
3825 NR::Matrix (NR::translate(rot_center));
3827 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3828 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3829 n->pos *= t;
3830 n->n.pos *= t;
3831 n->p.pos *= t;
3832 sp_node_update_handles(n, false);
3833 }
3834 }
3836 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3837 }
3839 /**
3840 * Scale one node.
3841 */
3842 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3843 {
3844 bool both = false;
3845 Inkscape::NodePath::NodeSide *me, *other;
3847 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3848 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3850 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3851 me = &(n->p);
3852 other = &(n->n);
3853 n->code = NR_CURVETO;
3854 } else if (!n->p.other) {
3855 me = &(n->n);
3856 other = &(n->p);
3857 if (n->n.other)
3858 n->n.other->code = NR_CURVETO;
3859 } else {
3860 if (which > 0) { // right handle
3861 if (xn > xp) {
3862 me = &(n->n);
3863 other = &(n->p);
3864 if (n->n.other)
3865 n->n.other->code = NR_CURVETO;
3866 } else {
3867 me = &(n->p);
3868 other = &(n->n);
3869 n->code = NR_CURVETO;
3870 }
3871 } else if (which < 0){ // left handle
3872 if (xn <= xp) {
3873 me = &(n->n);
3874 other = &(n->p);
3875 if (n->n.other)
3876 n->n.other->code = NR_CURVETO;
3877 } else {
3878 me = &(n->p);
3879 other = &(n->n);
3880 n->code = NR_CURVETO;
3881 }
3882 } else { // both handles
3883 me = &(n->n);
3884 other = &(n->p);
3885 both = true;
3886 n->code = NR_CURVETO;
3887 if (n->n.other)
3888 n->n.other->code = NR_CURVETO;
3889 }
3890 }
3892 Radial rme(me->pos - n->pos);
3893 Radial rother(other->pos - n->pos);
3895 rme.r += grow;
3896 if (rme.r < 0) rme.r = 0;
3897 if (rme.a == HUGE_VAL) {
3898 if (me->other) { // if direction is unknown, initialize it towards the next node
3899 Radial rme_next(me->other->pos - n->pos);
3900 rme.a = rme_next.a;
3901 } else { // if there's no next, initialize to 0
3902 rme.a = 0;
3903 }
3904 }
3905 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3906 rother.r += grow;
3907 if (rother.r < 0) rother.r = 0;
3908 if (rother.a == HUGE_VAL) {
3909 rother.a = rme.a + M_PI;
3910 }
3911 }
3913 me->pos = n->pos + NR::Point(rme);
3915 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3916 other->pos = n->pos + NR::Point(rother);
3917 }
3919 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3920 // so here we just move all the knots without emitting move signals, for speed
3921 sp_node_update_handles(n, false);
3922 }
3924 /**
3925 * Scale selected nodes.
3926 */
3927 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3928 {
3929 if (!nodepath || !nodepath->selected) return;
3931 if (g_list_length(nodepath->selected) == 1) {
3932 // scale handles of the single selected node
3933 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3934 node_scale_one (n, grow, which);
3935 } else {
3936 // scale nodes as an "object":
3938 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3939 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3940 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3941 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3942 box.expandTo (n->pos); // contain all selected nodes
3943 }
3945 double scale = (box.maxExtent() + grow)/box.maxExtent();
3947 NR::Point scale_center;
3948 if (Inkscape::NodePath::Path::active_node == NULL)
3949 scale_center = box.midpoint();
3950 else
3951 scale_center = Inkscape::NodePath::Path::active_node->pos;
3953 NR::Matrix t =
3954 NR::Matrix (NR::translate(-scale_center)) *
3955 NR::Matrix (NR::scale(scale, scale)) *
3956 NR::Matrix (NR::translate(scale_center));
3958 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3959 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3960 n->pos *= t;
3961 n->n.pos *= t;
3962 n->p.pos *= t;
3963 sp_node_update_handles(n, false);
3964 }
3965 }
3967 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3968 }
3970 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3971 {
3972 if (!nodepath) return;
3973 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3974 }
3976 /**
3977 * Flip selected nodes horizontally/vertically.
3978 */
3979 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3980 {
3981 if (!nodepath || !nodepath->selected) return;
3983 if (g_list_length(nodepath->selected) == 1 && !center) {
3984 // flip handles of the single selected node
3985 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3986 double temp = n->p.pos[axis];
3987 n->p.pos[axis] = n->n.pos[axis];
3988 n->n.pos[axis] = temp;
3989 sp_node_update_handles(n, false);
3990 } else {
3991 // scale nodes as an "object":
3993 NR::Rect box = sp_node_selected_bbox (nodepath);
3994 if (!center) {
3995 center = box.midpoint();
3996 }
3997 NR::Matrix t =
3998 NR::Matrix (NR::translate(- *center)) *
3999 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4000 NR::Matrix (NR::translate(*center));
4002 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4003 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4004 n->pos *= t;
4005 n->n.pos *= t;
4006 n->p.pos *= t;
4007 sp_node_update_handles(n, false);
4008 }
4009 }
4011 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4012 }
4014 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4015 {
4016 g_assert (nodepath->selected);
4018 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4019 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4020 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4021 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4022 box.expandTo (n->pos); // contain all selected nodes
4023 }
4024 return box;
4025 }
4027 //-----------------------------------------------
4028 /**
4029 * Return new subpath under given nodepath.
4030 */
4031 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4032 {
4033 g_assert(nodepath);
4034 g_assert(nodepath->desktop);
4036 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4038 s->nodepath = nodepath;
4039 s->closed = FALSE;
4040 s->nodes = NULL;
4041 s->first = NULL;
4042 s->last = NULL;
4044 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4045 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4046 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4048 return s;
4049 }
4051 /**
4052 * Destroy nodes in subpath, then subpath itself.
4053 */
4054 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4055 {
4056 g_assert(subpath);
4057 g_assert(subpath->nodepath);
4058 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4060 while (subpath->nodes) {
4061 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4062 }
4064 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4066 g_free(subpath);
4067 }
4069 /**
4070 * Link head to tail in subpath.
4071 */
4072 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4073 {
4074 g_assert(!sp->closed);
4075 g_assert(sp->last != sp->first);
4076 g_assert(sp->first->code == NR_MOVETO);
4078 sp->closed = TRUE;
4080 //Link the head to the tail
4081 sp->first->p.other = sp->last;
4082 sp->last->n.other = sp->first;
4083 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4084 sp->first = sp->last;
4086 //Remove the extra end node
4087 sp_nodepath_node_destroy(sp->last->n.other);
4088 }
4090 /**
4091 * Open closed (loopy) subpath at node.
4092 */
4093 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4094 {
4095 g_assert(sp->closed);
4096 g_assert(n->subpath == sp);
4097 g_assert(sp->first == sp->last);
4099 /* We create new startpoint, current node will become last one */
4101 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4102 &n->pos, &n->pos, &n->n.pos);
4105 sp->closed = FALSE;
4107 //Unlink to make a head and tail
4108 sp->first = new_path;
4109 sp->last = n;
4110 n->n.other = NULL;
4111 new_path->p.other = NULL;
4112 }
4114 /**
4115 * Return new node in subpath with given properties.
4116 * \param pos Position of node.
4117 * \param ppos Handle position in previous direction
4118 * \param npos Handle position in previous direction
4119 */
4120 Inkscape::NodePath::Node *
4121 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)
4122 {
4123 g_assert(sp);
4124 g_assert(sp->nodepath);
4125 g_assert(sp->nodepath->desktop);
4127 if (nodechunk == NULL)
4128 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4130 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4132 n->subpath = sp;
4134 if (type != Inkscape::NodePath::NODE_NONE) {
4135 // use the type from sodipodi:nodetypes
4136 n->type = type;
4137 } else {
4138 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4139 // points are (almost) collinear
4140 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4141 // endnode, or a node with a retracted handle
4142 n->type = Inkscape::NodePath::NODE_CUSP;
4143 } else {
4144 n->type = Inkscape::NodePath::NODE_SMOOTH;
4145 }
4146 } else {
4147 n->type = Inkscape::NodePath::NODE_CUSP;
4148 }
4149 }
4151 n->code = code;
4152 n->selected = FALSE;
4153 n->pos = *pos;
4154 n->p.pos = *ppos;
4155 n->n.pos = *npos;
4157 n->dragging_out = NULL;
4159 Inkscape::NodePath::Node *prev;
4160 if (next) {
4161 //g_assert(g_list_find(sp->nodes, next));
4162 prev = next->p.other;
4163 } else {
4164 prev = sp->last;
4165 }
4167 if (prev)
4168 prev->n.other = n;
4169 else
4170 sp->first = n;
4172 if (next)
4173 next->p.other = n;
4174 else
4175 sp->last = n;
4177 n->p.other = prev;
4178 n->n.other = next;
4180 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"));
4181 sp_knot_set_position(n->knot, pos, 0);
4183 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4184 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4185 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4186 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4187 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4188 sp_knot_update_ctrl(n->knot);
4190 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4191 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4192 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4193 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4194 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4195 sp_knot_show(n->knot);
4197 // We only create handle knots and lines on demand
4198 n->p.knot = NULL;
4199 n->p.line = NULL;
4200 n->n.knot = NULL;
4201 n->n.line = NULL;
4203 sp->nodes = g_list_prepend(sp->nodes, n);
4205 return n;
4206 }
4208 /**
4209 * Destroy node and its knots, link neighbors in subpath.
4210 */
4211 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4212 {
4213 g_assert(node);
4214 g_assert(node->subpath);
4215 g_assert(SP_IS_KNOT(node->knot));
4217 Inkscape::NodePath::SubPath *sp = node->subpath;
4219 if (node->selected) { // first, deselect
4220 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4221 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4222 }
4224 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4226 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4227 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4228 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4229 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4230 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4231 g_object_unref(G_OBJECT(node->knot));
4233 if (node->p.knot) {
4234 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4235 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4236 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4237 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4238 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4239 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4240 g_object_unref(G_OBJECT(node->p.knot));
4241 node->p.knot = NULL;
4242 }
4244 if (node->n.knot) {
4245 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4246 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4247 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4248 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4249 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4250 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4251 g_object_unref(G_OBJECT(node->n.knot));
4252 node->n.knot = NULL;
4253 }
4255 if (node->p.line)
4256 gtk_object_destroy(GTK_OBJECT(node->p.line));
4257 if (node->n.line)
4258 gtk_object_destroy(GTK_OBJECT(node->n.line));
4260 if (sp->nodes) { // there are others nodes on the subpath
4261 if (sp->closed) {
4262 if (sp->first == node) {
4263 g_assert(sp->last == node);
4264 sp->first = node->n.other;
4265 sp->last = sp->first;
4266 }
4267 node->p.other->n.other = node->n.other;
4268 node->n.other->p.other = node->p.other;
4269 } else {
4270 if (sp->first == node) {
4271 sp->first = node->n.other;
4272 sp->first->code = NR_MOVETO;
4273 }
4274 if (sp->last == node) sp->last = node->p.other;
4275 if (node->p.other) node->p.other->n.other = node->n.other;
4276 if (node->n.other) node->n.other->p.other = node->p.other;
4277 }
4278 } else { // this was the last node on subpath
4279 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4280 }
4282 g_mem_chunk_free(nodechunk, node);
4283 }
4285 /**
4286 * Returns one of the node's two sides.
4287 * \param which Indicates which side.
4288 * \return Pointer to previous node side if which==-1, next if which==1.
4289 */
4290 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4291 {
4292 g_assert(node);
4294 switch (which) {
4295 case -1:
4296 return &node->p;
4297 case 1:
4298 return &node->n;
4299 default:
4300 break;
4301 }
4303 g_assert_not_reached();
4305 return NULL;
4306 }
4308 /**
4309 * Return the other side of the node, given one of its sides.
4310 */
4311 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4312 {
4313 g_assert(node);
4315 if (me == &node->p) return &node->n;
4316 if (me == &node->n) return &node->p;
4318 g_assert_not_reached();
4320 return NULL;
4321 }
4323 /**
4324 * Return NRPathcode on the given side of the node.
4325 */
4326 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4327 {
4328 g_assert(node);
4330 if (me == &node->p) {
4331 if (node->p.other) return (NRPathcode)node->code;
4332 return NR_MOVETO;
4333 }
4335 if (me == &node->n) {
4336 if (node->n.other) return (NRPathcode)node->n.other->code;
4337 return NR_MOVETO;
4338 }
4340 g_assert_not_reached();
4342 return NR_END;
4343 }
4345 /**
4346 * Return node with the given index
4347 */
4348 Inkscape::NodePath::Node *
4349 sp_nodepath_get_node_by_index(int index)
4350 {
4351 Inkscape::NodePath::Node *e = NULL;
4353 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4354 if (!nodepath) {
4355 return e;
4356 }
4358 //find segment
4359 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4361 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4362 int n = g_list_length(sp->nodes);
4363 if (sp->closed) {
4364 n++;
4365 }
4367 //if the piece belongs to this subpath grab it
4368 //otherwise move onto the next subpath
4369 if (index < n) {
4370 e = sp->first;
4371 for (int i = 0; i < index; ++i) {
4372 e = e->n.other;
4373 }
4374 break;
4375 } else {
4376 if (sp->closed) {
4377 index -= (n+1);
4378 } else {
4379 index -= n;
4380 }
4381 }
4382 }
4384 return e;
4385 }
4387 /**
4388 * Returns plain text meaning of node type.
4389 */
4390 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4391 {
4392 unsigned retracted = 0;
4393 bool endnode = false;
4395 for (int which = -1; which <= 1; which += 2) {
4396 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4397 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4398 retracted ++;
4399 if (!side->other)
4400 endnode = true;
4401 }
4403 if (retracted == 0) {
4404 if (endnode) {
4405 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4406 return _("end node");
4407 } else {
4408 switch (node->type) {
4409 case Inkscape::NodePath::NODE_CUSP:
4410 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4411 return _("cusp");
4412 case Inkscape::NodePath::NODE_SMOOTH:
4413 // TRANSLATORS: "smooth" is an adjective here
4414 return _("smooth");
4415 case Inkscape::NodePath::NODE_SYMM:
4416 return _("symmetric");
4417 }
4418 }
4419 } else if (retracted == 1) {
4420 if (endnode) {
4421 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4422 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4423 } else {
4424 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4425 }
4426 } else {
4427 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4428 }
4430 return NULL;
4431 }
4433 /**
4434 * Handles content of statusbar as long as node tool is active.
4435 */
4436 void
4437 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4438 {
4439 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");
4440 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4442 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4443 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4444 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4445 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4447 SPDesktop *desktop = NULL;
4448 if (nodepath) {
4449 desktop = nodepath->desktop;
4450 } else {
4451 desktop = SP_ACTIVE_DESKTOP;
4452 }
4454 SPEventContext *ec = desktop->event_context;
4455 if (!ec) return;
4456 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4457 if (!mc) return;
4459 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4461 if (selected_nodes == 0) {
4462 Inkscape::Selection *sel = desktop->selection;
4463 if (!sel || sel->isEmpty()) {
4464 mc->setF(Inkscape::NORMAL_MESSAGE,
4465 _("Select a single object to edit its nodes or handles."));
4466 } else {
4467 if (nodepath) {
4468 mc->setF(Inkscape::NORMAL_MESSAGE,
4469 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.",
4470 "<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.",
4471 total_nodes),
4472 total_nodes);
4473 } else {
4474 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4475 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4476 } else {
4477 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4478 }
4479 }
4480 }
4481 } else if (nodepath && selected_nodes == 1) {
4482 mc->setF(Inkscape::NORMAL_MESSAGE,
4483 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4484 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4485 total_nodes),
4486 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4487 } else {
4488 if (selected_subpaths > 1) {
4489 mc->setF(Inkscape::NORMAL_MESSAGE,
4490 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4491 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4492 total_nodes),
4493 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4494 } else {
4495 mc->setF(Inkscape::NORMAL_MESSAGE,
4496 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4497 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4498 total_nodes),
4499 selected_nodes, total_nodes, when_selected);
4500 }
4501 }
4502 }
4504 /*
4505 * returns a *copy* of the curve of that object.
4506 */
4507 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4508 if (!object)
4509 return NULL;
4511 SPCurve *curve = NULL;
4512 if (SP_IS_PATH(object)) {
4513 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4514 curve = sp_curve_copy(curve_new);
4515 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4516 const gchar *svgd = object->repr->attribute(key);
4517 if (svgd) {
4518 NArtBpath *bpath = sp_svg_read_path(svgd);
4519 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4520 if (curve_new) {
4521 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4522 } else {
4523 g_free(bpath);
4524 }
4525 }
4526 }
4528 return curve;
4529 }
4531 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4532 if (!np || !np->object || !curve)
4533 return;
4535 if (SP_IS_PATH(np->object)) {
4536 if (SP_SHAPE(np->object)->path_effect_href) {
4537 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4538 } else {
4539 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4540 }
4541 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4542 // FIXME: this writing to string and then reading from string is bound to be slow.
4543 // create a method to convert from curve directly to 2geom...
4544 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4545 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4546 g_free(svgpath);
4548 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4549 }
4550 }
4552 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4553 np->show_helperpath = show;
4555 if (show) {
4556 SPCurve *helper_curve = sp_curve_copy(np->curve);
4557 sp_curve_transform(helper_curve, np->i2d );
4558 if (!np->helper_path) {
4559 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4560 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);
4561 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4562 sp_canvas_item_show(np->helper_path);
4563 } else {
4564 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4565 }
4566 sp_curve_unref(helper_curve);
4567 } else {
4568 if (np->helper_path) {
4569 GtkObject *temp = np->helper_path;
4570 np->helper_path = NULL;
4571 gtk_object_destroy(temp);
4572 }
4573 }
4574 }
4576 /* this function does not work yet */
4577 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4578 np->straight_path = true;
4579 np->show_handles = false;
4580 g_message("add code to make the path straight.");
4581 // do sp_nodepath_convert_node_type on all nodes?
4582 // search for this text !!! "Make selected segments lines"
4583 }
4586 /*
4587 Local Variables:
4588 mode:c++
4589 c-file-style:"stroustrup"
4590 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4591 indent-tabs-mode:nil
4592 fill-column:99
4593 End:
4594 */
4595 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :