fa80f3baad0b579078c32c1168e854c95751230b
1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include <glibmm/i18n.h>
23 #include "libnr/n-art-bpath.h"
24 #include "libnr/nr-path.h"
25 #include "helper/units.h"
26 #include "knot.h"
27 #include "inkscape.h"
28 #include "document.h"
29 #include "sp-namedview.h"
30 #include "desktop.h"
31 #include "desktop-handles.h"
32 #include "snap.h"
33 #include "message-stack.h"
34 #include "message-context.h"
35 #include "node-context.h"
36 #include "shape-editor.h"
37 #include "selection-chemistry.h"
38 #include "selection.h"
39 #include "xml/repr.h"
40 #include "prefs-utils.h"
41 #include "sp-metrics.h"
42 #include "sp-path.h"
43 #include "libnr/nr-matrix-ops.h"
44 #include "splivarot.h"
45 #include "svg/svg.h"
46 #include "verbs.h"
47 #include "display/bezier-utils.h"
48 #include <vector>
49 #include <algorithm>
50 #include "live_effects/lpeobject.h"
52 class NR::Matrix;
54 /// \todo
55 /// evil evil evil. FIXME: conflict of two different Path classes!
56 /// There is a conflict in the namespace between two classes named Path.
57 /// #include "sp-flowtext.h"
58 /// #include "sp-flowregion.h"
60 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
61 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
62 GType sp_flowregion_get_type (void);
63 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
64 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
65 GType sp_flowtext_get_type (void);
66 // end evil workaround
68 #include "helper/stlport.h"
71 /// \todo fixme: Implement these via preferences */
73 #define NODE_FILL 0xbfbfbf00
74 #define NODE_STROKE 0x000000ff
75 #define NODE_FILL_HI 0xff000000
76 #define NODE_STROKE_HI 0x000000ff
77 #define NODE_FILL_SEL 0x0000ffff
78 #define NODE_STROKE_SEL 0x000000ff
79 #define NODE_FILL_SEL_HI 0xff000000
80 #define NODE_STROKE_SEL_HI 0x000000ff
81 #define KNOT_FILL 0xffffffff
82 #define KNOT_STROKE 0x000000ff
83 #define KNOT_FILL_HI 0xff000000
84 #define KNOT_STROKE_HI 0x000000ff
86 static GMemChunk *nodechunk = NULL;
88 /* Creation from object */
90 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
91 static gchar *parse_nodetypes(gchar const *types, gint length);
93 /* Object updating */
95 static void stamp_repr(Inkscape::NodePath::Path *np);
96 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
97 static gchar *create_typestr(Inkscape::NodePath::Path *np);
99 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
101 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
103 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
105 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
107 /* Adjust handle placement, if the node or the other handle is moved */
108 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
109 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
111 /* Node event callbacks */
112 static void node_clicked(SPKnot *knot, guint state, gpointer data);
113 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
114 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
115 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 /* Handle event callbacks */
118 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
119 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
120 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
121 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
122 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
123 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
125 /* Constructors and destructors */
127 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
128 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
129 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
130 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
131 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
132 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
133 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
135 /* Helpers */
137 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
138 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
139 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
141 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
142 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
144 // active_node indicates mouseover node
145 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
147 /**
148 * \brief Creates new nodepath from item
149 */
150 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
151 {
152 Inkscape::XML::Node *repr = object->repr;
154 /** \todo
155 * FIXME: remove this. We don't want to edit paths inside flowtext.
156 * Instead we will build our flowtext with cloned paths, so that the
157 * real paths are outside the flowtext and thus editable as usual.
158 */
159 if (SP_IS_FLOWTEXT(object)) {
160 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
161 if SP_IS_FLOWREGION(child) {
162 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
163 if (grandchild && SP_IS_PATH(grandchild)) {
164 object = SP_ITEM(grandchild);
165 break;
166 }
167 }
168 }
169 }
171 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
173 if (curve == NULL)
174 return NULL;
176 NArtBpath *bpath = sp_curve_first_bpath(curve);
177 gint length = curve->end;
178 if (length == 0) {
179 sp_curve_unref(curve);
180 return NULL; // prevent crash for one-node paths
181 }
183 //Create new nodepath
184 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
185 if (!np) {
186 sp_curve_unref(curve);
187 return NULL;
188 }
190 // Set defaults
191 np->desktop = desktop;
192 np->object = object;
193 np->subpaths = NULL;
194 np->selected = NULL;
195 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
196 np->livarot_path = NULL;
197 np->local_change = 0;
198 np->show_handles = show_handles;
199 np->helper_path = NULL;
200 np->curve = sp_curve_copy(curve);
201 np->show_helperpath = false;
202 np->straight_path = false;
203 if (IS_LIVEPATHEFFECT(object) && item) {
204 np->item = item;
205 } else {
206 np->item = SP_ITEM(object);
207 }
209 // we need to update item's transform from the repr here,
210 // because they may be out of sync when we respond
211 // to a change in repr by regenerating nodepath --bb
212 sp_object_read_attr(SP_OBJECT(np->item), "transform");
214 np->i2d = sp_item_i2d_affine(np->item);
215 np->d2i = np->i2d.inverse();
217 np->repr = repr;
218 if (repr_key_in) { // apparantly the object is an LPEObject
219 np->repr_key = g_strdup(repr_key_in);
220 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
221 np->show_helperpath = true;
222 } else {
223 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
224 if ( SP_SHAPE(np->object)->path_effect_href ) {
225 np->repr_key = g_strdup("inkscape:original-d");
226 np->show_helperpath = true;
227 } else {
228 np->repr_key = g_strdup("d");
229 }
230 }
232 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
233 gchar *typestr = parse_nodetypes(nodetypes, length);
235 // create the subpath(s) from the bpath
236 NArtBpath *b = bpath;
237 while (b->code != NR_END) {
238 b = subpath_from_bpath(np, b, typestr + (b - bpath));
239 }
241 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
242 np->subpaths = g_list_reverse(np->subpaths);
244 g_free(typestr);
245 sp_curve_unref(curve);
247 // create the livarot representation from the same item
248 sp_nodepath_ensure_livarot_path(np);
250 // Draw helper curve
251 if (np->show_helperpath) {
252 SPCurve *helper_curve = sp_curve_copy(np->curve);
253 sp_curve_transform(helper_curve, np->i2d );
254 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
255 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), 0xff0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
256 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
257 sp_canvas_item_show(np->helper_path);
258 sp_curve_unref(helper_curve);
259 }
261 return np;
262 }
264 /**
265 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
266 */
267 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
269 if (!np) //soft fail, like delete
270 return;
272 while (np->subpaths) {
273 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
274 }
276 //Inform the ShapeEditor that made me, if any, that I am gone.
277 if (np->shape_editor)
278 np->shape_editor->nodepath_destroyed();
280 g_assert(!np->selected);
282 if (np->livarot_path) {
283 delete np->livarot_path;
284 np->livarot_path = NULL;
285 }
287 if (np->helper_path) {
288 GtkObject *temp = np->helper_path;
289 np->helper_path = NULL;
290 gtk_object_destroy(temp);
291 }
292 if (np->curve) {
293 sp_curve_unref(np->curve);
294 np->curve = NULL;
295 }
297 if (np->repr_key) {
298 g_free(np->repr_key);
299 np->repr_key = NULL;
300 }
301 if (np->repr_nodetypes_key) {
302 g_free(np->repr_nodetypes_key);
303 np->repr_nodetypes_key = NULL;
304 }
306 np->desktop = NULL;
308 g_free(np);
309 }
312 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
313 {
314 if (np && np->livarot_path == NULL) {
315 SPCurve *curve = create_curve(np);
316 NArtBpath *bpath = SP_CURVE_BPATH(curve);
317 np->livarot_path = bpath_to_Path(bpath);
319 if (np->livarot_path)
320 np->livarot_path->ConvertWithBackData(0.01);
322 sp_curve_unref(curve);
323 }
324 }
327 /**
328 * Return the node count of a given NodeSubPath.
329 */
330 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
331 {
332 if (!subpath)
333 return 0;
334 gint nodeCount = g_list_length(subpath->nodes);
335 return nodeCount;
336 }
338 /**
339 * Return the node count of a given NodePath.
340 */
341 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
342 {
343 if (!np)
344 return 0;
345 gint nodeCount = 0;
346 for (GList *item = np->subpaths ; item ; item=item->next) {
347 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
348 nodeCount += g_list_length(subpath->nodes);
349 }
350 return nodeCount;
351 }
353 /**
354 * Return the subpath count of a given NodePath.
355 */
356 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
357 {
358 if (!np)
359 return 0;
360 return g_list_length (np->subpaths);
361 }
363 /**
364 * Return the selected node count of a given NodePath.
365 */
366 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
367 {
368 if (!np)
369 return 0;
370 return g_list_length (np->selected);
371 }
373 /**
374 * Return the number of subpaths where nodes are selected in a given NodePath.
375 */
376 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
377 {
378 if (!np)
379 return 0;
380 if (!np->selected)
381 return 0;
382 if (!np->selected->next)
383 return 1;
384 gint count = 0;
385 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
386 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
387 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
388 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
389 if (node->selected) {
390 count ++;
391 break;
392 }
393 }
394 }
395 return count;
396 }
398 /**
399 * Clean up a nodepath after editing.
400 *
401 * Currently we are deleting trivial subpaths.
402 */
403 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
404 {
405 GList *badSubPaths = NULL;
407 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
408 for (GList *l = nodepath->subpaths; l ; l=l->next) {
409 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
410 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
411 badSubPaths = g_list_append(badSubPaths, sp);
412 }
414 //Delete them. This second step is because sp_nodepath_subpath_destroy()
415 //also removes the subpath from nodepath->subpaths
416 for (GList *l = badSubPaths; l ; l=l->next) {
417 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
418 sp_nodepath_subpath_destroy(sp);
419 }
421 g_list_free(badSubPaths);
422 }
424 /**
425 * Create new nodepath from b, make it subpath of np.
426 * \param t The node type.
427 * \todo Fixme: t should be a proper type, rather than gchar
428 */
429 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
430 {
431 NR::Point ppos, pos, npos;
433 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
435 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
436 bool const closed = (b->code == NR_MOVETO);
438 pos = NR::Point(b->x3, b->y3) * np->i2d;
439 if (b[1].code == NR_CURVETO) {
440 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
441 } else {
442 npos = pos;
443 }
444 Inkscape::NodePath::Node *n;
445 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
446 g_assert(sp->first == n);
447 g_assert(sp->last == n);
449 b++;
450 t++;
451 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
452 pos = NR::Point(b->x3, b->y3) * np->i2d;
453 if (b->code == NR_CURVETO) {
454 ppos = NR::Point(b->x2, b->y2) * np->i2d;
455 } else {
456 ppos = pos;
457 }
458 if (b[1].code == NR_CURVETO) {
459 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
460 } else {
461 npos = pos;
462 }
463 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
464 b++;
465 t++;
466 }
468 if (closed) sp_nodepath_subpath_close(sp);
470 return b;
471 }
473 /**
474 * Convert from sodipodi:nodetypes to new style type string.
475 */
476 static gchar *parse_nodetypes(gchar const *types, gint length)
477 {
478 g_assert(length > 0);
480 gchar *typestr = g_new(gchar, length + 1);
482 gint pos = 0;
484 if (types) {
485 for (gint i = 0; types[i] && ( i < length ); i++) {
486 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
487 if (types[i] != '\0') {
488 switch (types[i]) {
489 case 's':
490 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
491 break;
492 case 'z':
493 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
494 break;
495 case 'c':
496 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
497 break;
498 default:
499 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
500 break;
501 }
502 }
503 }
504 }
506 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
508 return typestr;
509 }
511 /**
512 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
513 * updated but repr is not (for speed). Used during curve and node drag.
514 */
515 static void update_object(Inkscape::NodePath::Path *np)
516 {
517 g_assert(np);
519 sp_curve_unref(np->curve);
520 np->curve = create_curve(np);
522 sp_nodepath_set_curve(np, np->curve);
524 if (np->show_helperpath) {
525 SPCurve * helper_curve = sp_curve_copy(np->curve);
526 sp_curve_transform(helper_curve, np->i2d );
527 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
528 sp_curve_unref(helper_curve);
529 }
530 }
532 /**
533 * Update XML path node with data from path object.
534 */
535 static void update_repr_internal(Inkscape::NodePath::Path *np)
536 {
537 g_assert(np);
539 Inkscape::XML::Node *repr = np->object->repr;
541 sp_curve_unref(np->curve);
542 np->curve = create_curve(np);
544 gchar *typestr = create_typestr(np);
545 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
547 // determine if path has an effect applied and write to correct "d" attribute.
548 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
549 np->local_change++;
550 repr->setAttribute(np->repr_key, svgpath);
551 }
553 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
554 np->local_change++;
555 repr->setAttribute(np->repr_nodetypes_key, typestr);
556 }
558 g_free(svgpath);
559 g_free(typestr);
561 if (np->show_helperpath) {
562 SPCurve * helper_curve = sp_curve_copy(np->curve);
563 sp_curve_transform(helper_curve, np->i2d );
564 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
565 sp_curve_unref(helper_curve);
566 }
567 }
569 /**
570 * Update XML path node with data from path object, commit changes forever.
571 */
572 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
573 {
574 //fixme: np can be NULL, so check before proceeding
575 g_return_if_fail(np != NULL);
577 if (np->livarot_path) {
578 delete np->livarot_path;
579 np->livarot_path = NULL;
580 }
582 update_repr_internal(np);
583 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
585 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
586 annotation);
587 }
589 /**
590 * Update XML path node with data from path object, commit changes with undo.
591 */
592 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
593 {
594 if (np->livarot_path) {
595 delete np->livarot_path;
596 np->livarot_path = NULL;
597 }
599 update_repr_internal(np);
600 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
601 annotation);
602 }
604 /**
605 * Make duplicate of path, replace corresponding XML node in tree, commit.
606 */
607 static void stamp_repr(Inkscape::NodePath::Path *np)
608 {
609 g_assert(np);
611 Inkscape::XML::Node *old_repr = np->object->repr;
612 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
614 // remember the position of the item
615 gint pos = old_repr->position();
616 // remember parent
617 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
619 SPCurve *curve = create_curve(np);
620 gchar *typestr = create_typestr(np);
622 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
624 new_repr->setAttribute(np->repr_key, svgpath);
625 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
627 // add the new repr to the parent
628 parent->appendChild(new_repr);
629 // move to the saved position
630 new_repr->setPosition(pos > 0 ? pos : 0);
632 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
633 _("Stamp"));
635 Inkscape::GC::release(new_repr);
636 g_free(svgpath);
637 g_free(typestr);
638 sp_curve_unref(curve);
639 }
641 /**
642 * Create curve from path.
643 */
644 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
645 {
646 SPCurve *curve = sp_curve_new();
648 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
649 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
650 sp_curve_moveto(curve,
651 sp->first->pos * np->d2i);
652 Inkscape::NodePath::Node *n = sp->first->n.other;
653 while (n) {
654 NR::Point const end_pt = n->pos * np->d2i;
655 switch (n->code) {
656 case NR_LINETO:
657 sp_curve_lineto(curve, end_pt);
658 break;
659 case NR_CURVETO:
660 sp_curve_curveto(curve,
661 n->p.other->n.pos * np->d2i,
662 n->p.pos * np->d2i,
663 end_pt);
664 break;
665 default:
666 g_assert_not_reached();
667 break;
668 }
669 if (n != sp->last) {
670 n = n->n.other;
671 } else {
672 n = NULL;
673 }
674 }
675 if (sp->closed) {
676 sp_curve_closepath(curve);
677 }
678 }
680 return curve;
681 }
683 /**
684 * Convert path type string to sodipodi:nodetypes style.
685 */
686 static gchar *create_typestr(Inkscape::NodePath::Path *np)
687 {
688 gchar *typestr = g_new(gchar, 32);
689 gint len = 32;
690 gint pos = 0;
692 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
693 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
695 if (pos >= len) {
696 typestr = g_renew(gchar, typestr, len + 32);
697 len += 32;
698 }
700 typestr[pos++] = 'c';
702 Inkscape::NodePath::Node *n;
703 n = sp->first->n.other;
704 while (n) {
705 gchar code;
707 switch (n->type) {
708 case Inkscape::NodePath::NODE_CUSP:
709 code = 'c';
710 break;
711 case Inkscape::NodePath::NODE_SMOOTH:
712 code = 's';
713 break;
714 case Inkscape::NodePath::NODE_SYMM:
715 code = 'z';
716 break;
717 default:
718 g_assert_not_reached();
719 code = '\0';
720 break;
721 }
723 if (pos >= len) {
724 typestr = g_renew(gchar, typestr, len + 32);
725 len += 32;
726 }
728 typestr[pos++] = code;
730 if (n != sp->last) {
731 n = n->n.other;
732 } else {
733 n = NULL;
734 }
735 }
736 }
738 if (pos >= len) {
739 typestr = g_renew(gchar, typestr, len + 1);
740 len += 1;
741 }
743 typestr[pos++] = '\0';
745 return typestr;
746 }
748 /**
749 * Returns current path in context. // later eliminate this function at all!
750 */
751 static Inkscape::NodePath::Path *sp_nodepath_current()
752 {
753 if (!SP_ACTIVE_DESKTOP) {
754 return NULL;
755 }
757 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
759 if (!SP_IS_NODE_CONTEXT(event_context)) {
760 return NULL;
761 }
763 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
764 }
768 /**
769 \brief Fills node and handle positions for three nodes, splitting line
770 marked by end at distance t.
771 */
772 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
773 {
774 g_assert(new_path != NULL);
775 g_assert(end != NULL);
777 g_assert(end->p.other == new_path);
778 Inkscape::NodePath::Node *start = new_path->p.other;
779 g_assert(start);
781 if (end->code == NR_LINETO) {
782 new_path->type =Inkscape::NodePath::NODE_CUSP;
783 new_path->code = NR_LINETO;
784 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
785 } else {
786 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
787 new_path->code = NR_CURVETO;
788 gdouble s = 1 - t;
789 for (int dim = 0; dim < 2; dim++) {
790 NR::Coord const f000 = start->pos[dim];
791 NR::Coord const f001 = start->n.pos[dim];
792 NR::Coord const f011 = end->p.pos[dim];
793 NR::Coord const f111 = end->pos[dim];
794 NR::Coord const f00t = s * f000 + t * f001;
795 NR::Coord const f01t = s * f001 + t * f011;
796 NR::Coord const f11t = s * f011 + t * f111;
797 NR::Coord const f0tt = s * f00t + t * f01t;
798 NR::Coord const f1tt = s * f01t + t * f11t;
799 NR::Coord const fttt = s * f0tt + t * f1tt;
800 start->n.pos[dim] = f00t;
801 new_path->p.pos[dim] = f0tt;
802 new_path->pos[dim] = fttt;
803 new_path->n.pos[dim] = f1tt;
804 end->p.pos[dim] = f11t;
805 }
806 }
807 }
809 /**
810 * Adds new node on direct line between two nodes, activates handles of all
811 * three nodes.
812 */
813 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
814 {
815 g_assert(end);
816 g_assert(end->subpath);
817 g_assert(g_list_find(end->subpath->nodes, end));
819 Inkscape::NodePath::Node *start = end->p.other;
820 g_assert( start->n.other == end );
821 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
822 end,
823 (NRPathcode)end->code == NR_LINETO?
824 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
825 (NRPathcode)end->code,
826 &start->pos, &start->pos, &start->n.pos);
827 sp_nodepath_line_midpoint(newnode, end, t);
829 sp_node_adjust_handles(start);
830 sp_node_update_handles(start);
831 sp_node_update_handles(newnode);
832 sp_node_adjust_handles(end);
833 sp_node_update_handles(end);
835 return newnode;
836 }
838 /**
839 \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
840 */
841 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
842 {
843 g_assert(node);
844 g_assert(node->subpath);
845 g_assert(g_list_find(node->subpath->nodes, node));
847 Inkscape::NodePath::SubPath *sp = node->subpath;
848 Inkscape::NodePath::Path *np = sp->nodepath;
850 if (sp->closed) {
851 sp_nodepath_subpath_open(sp, node);
852 return sp->first;
853 } else {
854 // no break for end nodes
855 if (node == sp->first) return NULL;
856 if (node == sp->last ) return NULL;
858 // create a new subpath
859 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
861 // duplicate the break node as start of the new subpath
862 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
864 while (node->n.other) { // copy the remaining nodes into the new subpath
865 Inkscape::NodePath::Node *n = node->n.other;
866 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);
867 if (n->selected) {
868 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
869 }
870 sp_nodepath_node_destroy(n); // remove the point on the original subpath
871 }
873 return newnode;
874 }
875 }
877 /**
878 * Duplicate node and connect to neighbours.
879 */
880 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
881 {
882 g_assert(node);
883 g_assert(node->subpath);
884 g_assert(g_list_find(node->subpath->nodes, node));
886 Inkscape::NodePath::SubPath *sp = node->subpath;
888 NRPathcode code = (NRPathcode) node->code;
889 if (code == NR_MOVETO) { // if node is the endnode,
890 node->code = NR_LINETO; // new one is inserted before it, so change that to line
891 }
893 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
895 if (!node->n.other || !node->p.other) // if node is an endnode, select it
896 return node;
897 else
898 return newnode; // otherwise select the newly created node
899 }
901 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
902 {
903 node->p.pos = (node->pos + (node->pos - node->n.pos));
904 }
906 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
907 {
908 node->n.pos = (node->pos + (node->pos - node->p.pos));
909 }
911 /**
912 * Change line type at node, with side effects on neighbours.
913 */
914 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
915 {
916 g_assert(end);
917 g_assert(end->subpath);
918 g_assert(end->p.other);
920 if (end->code == static_cast< guint > ( code ) )
921 return;
923 Inkscape::NodePath::Node *start = end->p.other;
925 end->code = code;
927 if (code == NR_LINETO) {
928 if (start->code == NR_LINETO) {
929 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
930 }
931 if (end->n.other) {
932 if (end->n.other->code == NR_LINETO) {
933 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
934 }
935 }
936 } else {
937 NR::Point delta = end->pos - start->pos;
938 start->n.pos = start->pos + delta / 3;
939 end->p.pos = end->pos - delta / 3;
940 sp_node_adjust_handle(start, 1);
941 sp_node_adjust_handle(end, -1);
942 }
944 sp_node_update_handles(start);
945 sp_node_update_handles(end);
946 }
948 /**
949 * Change node type, and its handles accordingly.
950 */
951 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
952 {
953 g_assert(node);
954 g_assert(node->subpath);
956 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
957 return node;
959 if ((node->p.other != NULL) && (node->n.other != NULL)) {
960 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
961 type =Inkscape::NodePath::NODE_CUSP;
962 }
963 }
965 node->type = type;
967 if (node->type == Inkscape::NodePath::NODE_CUSP) {
968 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
969 node->knot->setSize (node->selected? 11 : 9);
970 sp_knot_update_ctrl(node->knot);
971 } else {
972 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
973 node->knot->setSize (node->selected? 9 : 7);
974 sp_knot_update_ctrl(node->knot);
975 }
977 // if one of handles is mouseovered, preserve its position
978 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
979 sp_node_adjust_handle(node, 1);
980 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
981 sp_node_adjust_handle(node, -1);
982 } else {
983 sp_node_adjust_handles(node);
984 }
986 sp_node_update_handles(node);
988 sp_nodepath_update_statusbar(node->subpath->nodepath);
990 return node;
991 }
993 /**
994 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
995 * adjacent segments from lines to curves.
996 */
997 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
998 {
999 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
1000 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
1002 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1003 if (p_line && n_line) {
1004 // only if both adjacent segments are lines,
1005 // convert both to curves:
1007 // BEFORE:
1008 {
1009 node->code = NR_CURVETO;
1010 NR::Point delta = node->n.other->pos - node->p.other->pos;
1011 node->p.pos = node->pos - delta / 4;
1012 }
1014 // AFTER:
1015 {
1016 node->n.other->code = NR_CURVETO;
1017 NR::Point delta = node->p.other->pos - node->n.other->pos;
1018 node->n.pos = node->pos - delta / 4;
1019 }
1021 sp_node_update_handles(node);
1022 }
1023 }
1025 sp_nodepath_set_node_type (node, type);
1026 }
1028 /**
1029 * Move node to point, and adjust its and neighbouring handles.
1030 */
1031 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1032 {
1033 NR::Point delta = p - node->pos;
1034 node->pos = p;
1036 node->p.pos += delta;
1037 node->n.pos += delta;
1039 Inkscape::NodePath::Node *node_p = NULL;
1040 Inkscape::NodePath::Node *node_n = NULL;
1042 if (node->p.other) {
1043 if (node->code == NR_LINETO) {
1044 sp_node_adjust_handle(node, 1);
1045 sp_node_adjust_handle(node->p.other, -1);
1046 node_p = node->p.other;
1047 }
1048 }
1049 if (node->n.other) {
1050 if (node->n.other->code == NR_LINETO) {
1051 sp_node_adjust_handle(node, -1);
1052 sp_node_adjust_handle(node->n.other, 1);
1053 node_n = node->n.other;
1054 }
1055 }
1057 // this function is only called from batch movers that will update display at the end
1058 // themselves, so here we just move all the knots without emitting move signals, for speed
1059 sp_node_update_handles(node, false);
1060 if (node_n) {
1061 sp_node_update_handles(node_n, false);
1062 }
1063 if (node_p) {
1064 sp_node_update_handles(node_p, false);
1065 }
1066 }
1068 /**
1069 * Call sp_node_moveto() for node selection and handle possible snapping.
1070 */
1071 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1072 bool const snap = true)
1073 {
1074 NR::Coord best = NR_HUGE;
1075 NR::Point delta(dx, dy);
1076 NR::Point best_pt = delta;
1078 if (snap) {
1079 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1081 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1082 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1083 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->item));
1084 if (s.getDistance() < best) {
1085 best = s.getDistance();
1086 best_pt = s.getPoint() - n->pos;
1087 }
1088 }
1089 }
1091 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1092 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1093 sp_node_moveto(n, n->pos + best_pt);
1094 }
1096 // do not update repr here so that node dragging is acceptably fast
1097 update_object(nodepath);
1098 }
1100 /**
1101 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1102 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1103 near x = 0.
1104 */
1105 double
1106 sculpt_profile (double x, double alpha, guint profile)
1107 {
1108 if (x >= 1)
1109 return 0;
1110 if (x <= 0)
1111 return 1;
1113 switch (profile) {
1114 case SCULPT_PROFILE_LINEAR:
1115 return 1 - x;
1116 case SCULPT_PROFILE_BELL:
1117 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1118 case SCULPT_PROFILE_ELLIPTIC:
1119 return sqrt(1 - x*x);
1120 }
1122 return 1;
1123 }
1125 double
1126 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1127 {
1128 // extremely primitive for now, don't have time to look for the real one
1129 double lower = NR::L2(b - a);
1130 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1131 return (lower + upper)/2;
1132 }
1134 void
1135 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1136 {
1137 n->pos = n->origin + delta;
1138 n->n.pos = n->n.origin + delta_n;
1139 n->p.pos = n->p.origin + delta_p;
1140 sp_node_adjust_handles(n);
1141 sp_node_update_handles(n, false);
1142 }
1144 /**
1145 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1146 * on how far they are from the dragged node n.
1147 */
1148 static void
1149 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1150 {
1151 g_assert (n);
1152 g_assert (nodepath);
1153 g_assert (n->subpath->nodepath == nodepath);
1155 double pressure = n->knot->pressure;
1156 if (pressure == 0)
1157 pressure = 0.5; // default
1158 pressure = CLAMP (pressure, 0.2, 0.8);
1160 // map pressure to alpha = 1/5 ... 5
1161 double alpha = 1 - 2 * fabs(pressure - 0.5);
1162 if (pressure > 0.5)
1163 alpha = 1/alpha;
1165 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1167 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1168 // Only one subpath has selected nodes:
1169 // use linear mode, where the distance from n to node being dragged is calculated along the path
1171 double n_sel_range = 0, p_sel_range = 0;
1172 guint n_nodes = 0, p_nodes = 0;
1173 guint n_sel_nodes = 0, p_sel_nodes = 0;
1175 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1176 {
1177 double n_range = 0, p_range = 0;
1178 bool n_going = true, p_going = true;
1179 Inkscape::NodePath::Node *n_node = n;
1180 Inkscape::NodePath::Node *p_node = n;
1181 do {
1182 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1183 if (n_node && n_going)
1184 n_node = n_node->n.other;
1185 if (n_node == NULL) {
1186 n_going = false;
1187 } else {
1188 n_nodes ++;
1189 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1190 if (n_node->selected) {
1191 n_sel_nodes ++;
1192 n_sel_range = n_range;
1193 }
1194 if (n_node == p_node) {
1195 n_going = false;
1196 p_going = false;
1197 }
1198 }
1199 if (p_node && p_going)
1200 p_node = p_node->p.other;
1201 if (p_node == NULL) {
1202 p_going = false;
1203 } else {
1204 p_nodes ++;
1205 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1206 if (p_node->selected) {
1207 p_sel_nodes ++;
1208 p_sel_range = p_range;
1209 }
1210 if (p_node == n_node) {
1211 n_going = false;
1212 p_going = false;
1213 }
1214 }
1215 } while (n_going || p_going);
1216 }
1218 // Second pass: actually move nodes in this subpath
1219 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1220 {
1221 double n_range = 0, p_range = 0;
1222 bool n_going = true, p_going = true;
1223 Inkscape::NodePath::Node *n_node = n;
1224 Inkscape::NodePath::Node *p_node = n;
1225 do {
1226 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1227 if (n_node && n_going)
1228 n_node = n_node->n.other;
1229 if (n_node == NULL) {
1230 n_going = false;
1231 } else {
1232 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1233 if (n_node->selected) {
1234 sp_nodepath_move_node_and_handles (n_node,
1235 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1236 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1237 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1238 }
1239 if (n_node == p_node) {
1240 n_going = false;
1241 p_going = false;
1242 }
1243 }
1244 if (p_node && p_going)
1245 p_node = p_node->p.other;
1246 if (p_node == NULL) {
1247 p_going = false;
1248 } else {
1249 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1250 if (p_node->selected) {
1251 sp_nodepath_move_node_and_handles (p_node,
1252 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1253 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1254 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1255 }
1256 if (p_node == n_node) {
1257 n_going = false;
1258 p_going = false;
1259 }
1260 }
1261 } while (n_going || p_going);
1262 }
1264 } else {
1265 // Multiple subpaths have selected nodes:
1266 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1267 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1268 // fix the pear-like shape when sculpting e.g. a ring
1270 // First pass: calculate range
1271 gdouble direct_range = 0;
1272 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1273 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1274 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1275 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1276 if (node->selected) {
1277 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1278 }
1279 }
1280 }
1282 // Second pass: actually move nodes
1283 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1284 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1285 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1286 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1287 if (node->selected) {
1288 if (direct_range > 1e-6) {
1289 sp_nodepath_move_node_and_handles (node,
1290 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1291 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1292 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1293 } else {
1294 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1295 }
1297 }
1298 }
1299 }
1300 }
1302 // do not update repr here so that node dragging is acceptably fast
1303 update_object(nodepath);
1304 }
1307 /**
1308 * Move node selection to point, adjust its and neighbouring handles,
1309 * handle possible snapping, and commit the change with possible undo.
1310 */
1311 void
1312 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1313 {
1314 if (!nodepath) return;
1316 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1318 if (dx == 0) {
1319 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1320 } else if (dy == 0) {
1321 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1322 } else {
1323 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1324 }
1325 }
1327 /**
1328 * Move node selection off screen and commit the change.
1329 */
1330 void
1331 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1332 {
1333 // borrowed from sp_selection_move_screen in selection-chemistry.c
1334 // we find out the current zoom factor and divide deltas by it
1335 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1337 gdouble zoom = desktop->current_zoom();
1338 gdouble zdx = dx / zoom;
1339 gdouble zdy = dy / zoom;
1341 if (!nodepath) return;
1343 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1345 if (dx == 0) {
1346 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1347 } else if (dy == 0) {
1348 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1349 } else {
1350 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1351 }
1352 }
1354 /** If they don't yet exist, creates knot and line for the given side of the node */
1355 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1356 {
1357 if (!side->knot) {
1358 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"));
1360 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1361 side->knot->setSize (7);
1362 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1363 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1364 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1365 sp_knot_update_ctrl(side->knot);
1367 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1368 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1369 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1370 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1371 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1372 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1373 }
1375 if (!side->line) {
1376 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1377 SP_TYPE_CTRLLINE, NULL);
1378 }
1379 }
1381 /**
1382 * Ensure the given handle of the node is visible/invisible, update its screen position
1383 */
1384 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1385 {
1386 g_assert(node != NULL);
1388 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1389 NRPathcode code = sp_node_path_code_from_side(node, side);
1391 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1393 if (show_handle) {
1394 if (!side->knot) { // No handle knot at all
1395 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1396 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1397 side->knot->pos = side->pos;
1398 if (side->knot->item)
1399 SP_CTRL(side->knot->item)->moveto(side->pos);
1400 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1401 sp_knot_show(side->knot);
1402 } else {
1403 if (side->knot->pos != side->pos) { // only if it's really moved
1404 if (fire_move_signals) {
1405 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1406 } else {
1407 sp_knot_moveto(side->knot, &side->pos);
1408 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1409 }
1410 }
1411 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1412 sp_knot_show(side->knot);
1413 }
1414 }
1415 sp_canvas_item_show(side->line);
1416 } else {
1417 if (side->knot) {
1418 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1419 sp_knot_hide(side->knot);
1420 }
1421 }
1422 if (side->line) {
1423 sp_canvas_item_hide(side->line);
1424 }
1425 }
1426 }
1428 /**
1429 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1430 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1431 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1432 * updated; otherwise, just move the knots silently (used in batch moves).
1433 */
1434 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1435 {
1436 g_assert(node != NULL);
1438 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1439 sp_knot_show(node->knot);
1440 }
1442 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1443 if (fire_move_signals)
1444 sp_knot_set_position(node->knot, &node->pos, 0);
1445 else
1446 sp_knot_moveto(node->knot, &node->pos);
1447 }
1449 gboolean show_handles = node->selected;
1450 if (node->p.other != NULL) {
1451 if (node->p.other->selected) show_handles = TRUE;
1452 }
1453 if (node->n.other != NULL) {
1454 if (node->n.other->selected) show_handles = TRUE;
1455 }
1457 if (node->subpath->nodepath->show_handles == false)
1458 show_handles = FALSE;
1460 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1461 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1462 }
1464 /**
1465 * Call sp_node_update_handles() for all nodes on subpath.
1466 */
1467 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1468 {
1469 g_assert(subpath != NULL);
1471 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1472 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1473 }
1474 }
1476 /**
1477 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1478 */
1479 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1480 {
1481 g_assert(nodepath != NULL);
1483 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1484 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1485 }
1486 }
1488 void
1489 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1490 {
1491 if (nodepath == NULL) return;
1493 nodepath->show_handles = show;
1494 sp_nodepath_update_handles(nodepath);
1495 }
1497 /**
1498 * Adds all selected nodes in nodepath to list.
1499 */
1500 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1501 {
1502 StlConv<Node *>::list(l, selected);
1503 /// \todo this adds a copying, rework when the selection becomes a stl list
1504 }
1506 /**
1507 * Align selected nodes on the specified axis.
1508 */
1509 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1510 {
1511 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1512 return;
1513 }
1515 if ( !nodepath->selected->next ) { // only one node selected
1516 return;
1517 }
1518 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1519 NR::Point dest(pNode->pos);
1520 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1521 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1522 if (pNode) {
1523 dest[axis] = pNode->pos[axis];
1524 sp_node_moveto(pNode, dest);
1525 }
1526 }
1528 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1529 }
1531 /// Helper struct.
1532 struct NodeSort
1533 {
1534 Inkscape::NodePath::Node *_node;
1535 NR::Coord _coord;
1536 /// \todo use vectorof pointers instead of calling copy ctor
1537 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1538 _node(node), _coord(node->pos[axis])
1539 {}
1541 };
1543 static bool operator<(NodeSort const &a, NodeSort const &b)
1544 {
1545 return (a._coord < b._coord);
1546 }
1548 /**
1549 * Distribute selected nodes on the specified axis.
1550 */
1551 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1552 {
1553 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1554 return;
1555 }
1557 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1558 return;
1559 }
1561 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1562 std::vector<NodeSort> sorted;
1563 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1564 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1565 if (pNode) {
1566 NodeSort n(pNode, axis);
1567 sorted.push_back(n);
1568 //dest[axis] = pNode->pos[axis];
1569 //sp_node_moveto(pNode, dest);
1570 }
1571 }
1572 std::sort(sorted.begin(), sorted.end());
1573 unsigned int len = sorted.size();
1574 //overall bboxes span
1575 float dist = (sorted.back()._coord -
1576 sorted.front()._coord);
1577 //new distance between each bbox
1578 float step = (dist) / (len - 1);
1579 float pos = sorted.front()._coord;
1580 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1581 it < sorted.end();
1582 it ++ )
1583 {
1584 NR::Point dest((*it)._node->pos);
1585 dest[axis] = pos;
1586 sp_node_moveto((*it)._node, dest);
1587 pos += step;
1588 }
1590 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1591 }
1594 /**
1595 * Call sp_nodepath_line_add_node() for all selected segments.
1596 */
1597 void
1598 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1599 {
1600 if (!nodepath) {
1601 return;
1602 }
1604 GList *nl = NULL;
1606 int n_added = 0;
1608 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1609 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1610 g_assert(t->selected);
1611 if (t->p.other && t->p.other->selected) {
1612 nl = g_list_prepend(nl, t);
1613 }
1614 }
1616 while (nl) {
1617 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1618 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1619 sp_nodepath_node_select(n, TRUE, FALSE);
1620 n_added ++;
1621 nl = g_list_remove(nl, t);
1622 }
1624 /** \todo fixme: adjust ? */
1625 sp_nodepath_update_handles(nodepath);
1627 if (n_added > 1) {
1628 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1629 } else if (n_added > 0) {
1630 sp_nodepath_update_repr(nodepath, _("Add node"));
1631 }
1633 sp_nodepath_update_statusbar(nodepath);
1634 }
1636 /**
1637 * Select segment nearest to point
1638 */
1639 void
1640 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1641 {
1642 if (!nodepath) {
1643 return;
1644 }
1646 sp_nodepath_ensure_livarot_path(nodepath);
1647 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1648 if (!maybe_position) {
1649 return;
1650 }
1651 Path::cut_position position = *maybe_position;
1653 //find segment to segment
1654 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1656 //fixme: this can return NULL, so check before proceeding.
1657 g_return_if_fail(e != NULL);
1659 gboolean force = FALSE;
1660 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1661 force = TRUE;
1662 }
1663 sp_nodepath_node_select(e, (gboolean) toggle, force);
1664 if (e->p.other)
1665 sp_nodepath_node_select(e->p.other, TRUE, force);
1667 sp_nodepath_update_handles(nodepath);
1669 sp_nodepath_update_statusbar(nodepath);
1670 }
1672 /**
1673 * Add a node nearest to point
1674 */
1675 void
1676 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1677 {
1678 if (!nodepath) {
1679 return;
1680 }
1682 sp_nodepath_ensure_livarot_path(nodepath);
1683 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1684 if (!maybe_position) {
1685 return;
1686 }
1687 Path::cut_position position = *maybe_position;
1689 //find segment to split
1690 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1692 //don't know why but t seems to flip for lines
1693 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1694 position.t = 1.0 - position.t;
1695 }
1696 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1697 sp_nodepath_node_select(n, FALSE, TRUE);
1699 /* fixme: adjust ? */
1700 sp_nodepath_update_handles(nodepath);
1702 sp_nodepath_update_repr(nodepath, _("Add node"));
1704 sp_nodepath_update_statusbar(nodepath);
1705 }
1707 /*
1708 * Adjusts a segment so that t moves by a certain delta for dragging
1709 * converts lines to curves
1710 *
1711 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1712 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1713 */
1714 void
1715 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1716 {
1717 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1719 //fixme: e and e->p can be NULL, so check for those before proceeding
1720 g_return_if_fail(e != NULL);
1721 g_return_if_fail(&e->p != NULL);
1723 /* feel good is an arbitrary parameter that distributes the delta between handles
1724 * if t of the drag point is less than 1/6 distance form the endpoint only
1725 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1726 */
1727 double feel_good;
1728 if (t <= 1.0 / 6.0)
1729 feel_good = 0;
1730 else if (t <= 0.5)
1731 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1732 else if (t <= 5.0 / 6.0)
1733 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1734 else
1735 feel_good = 1;
1737 //if we're dragging a line convert it to a curve
1738 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1739 sp_nodepath_set_line_type(e, NR_CURVETO);
1740 }
1742 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1743 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1744 e->p.other->n.pos += offsetcoord0;
1745 e->p.pos += offsetcoord1;
1747 // adjust handles of adjacent nodes where necessary
1748 sp_node_adjust_handle(e,1);
1749 sp_node_adjust_handle(e->p.other,-1);
1751 sp_nodepath_update_handles(e->subpath->nodepath);
1753 update_object(e->subpath->nodepath);
1755 sp_nodepath_update_statusbar(e->subpath->nodepath);
1756 }
1759 /**
1760 * Call sp_nodepath_break() for all selected segments.
1761 */
1762 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1763 {
1764 if (!nodepath) return;
1766 GList *temp = NULL;
1767 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1768 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1769 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1770 if (nn == NULL) continue; // no break, no new node
1771 temp = g_list_prepend(temp, nn);
1772 }
1774 if (temp) {
1775 sp_nodepath_deselect(nodepath);
1776 }
1777 for (GList *l = temp; l != NULL; l = l->next) {
1778 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1779 }
1781 sp_nodepath_update_handles(nodepath);
1783 sp_nodepath_update_repr(nodepath, _("Break path"));
1784 }
1786 /**
1787 * Duplicate the selected node(s).
1788 */
1789 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1790 {
1791 if (!nodepath) {
1792 return;
1793 }
1795 GList *temp = NULL;
1796 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1797 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1798 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1799 if (nn == NULL) continue; // could not duplicate
1800 temp = g_list_prepend(temp, nn);
1801 }
1803 if (temp) {
1804 sp_nodepath_deselect(nodepath);
1805 }
1806 for (GList *l = temp; l != NULL; l = l->next) {
1807 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1808 }
1810 sp_nodepath_update_handles(nodepath);
1812 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1813 }
1815 /**
1816 * Join two nodes by merging them into one.
1817 */
1818 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1819 {
1820 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1822 if (g_list_length(nodepath->selected) != 2) {
1823 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1824 return;
1825 }
1827 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1828 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1830 g_assert(a != b);
1831 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1832 // someone tried to join an orphan node (i.e. a single-node subpath).
1833 // this is not worth an error message, just fail silently.
1834 return;
1835 }
1837 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1838 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1839 return;
1840 }
1842 /* a and b are endpoints */
1844 NR::Point c;
1845 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1846 c = a->pos;
1847 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1848 c = b->pos;
1849 } else {
1850 c = (a->pos + b->pos) / 2;
1851 }
1853 if (a->subpath == b->subpath) {
1854 Inkscape::NodePath::SubPath *sp = a->subpath;
1855 sp_nodepath_subpath_close(sp);
1856 sp_node_moveto (sp->first, c);
1858 sp_nodepath_update_handles(sp->nodepath);
1859 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1860 return;
1861 }
1863 /* a and b are separate subpaths */
1864 Inkscape::NodePath::SubPath *sa = a->subpath;
1865 Inkscape::NodePath::SubPath *sb = b->subpath;
1866 NR::Point p;
1867 Inkscape::NodePath::Node *n;
1868 NRPathcode code;
1869 if (a == sa->first) {
1870 p = sa->first->n.pos;
1871 code = (NRPathcode)sa->first->n.other->code;
1872 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1873 n = sa->last;
1874 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1875 n = n->p.other;
1876 while (n) {
1877 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1878 n = n->p.other;
1879 if (n == sa->first) n = NULL;
1880 }
1881 sp_nodepath_subpath_destroy(sa);
1882 sa = t;
1883 } else if (a == sa->last) {
1884 p = sa->last->p.pos;
1885 code = (NRPathcode)sa->last->code;
1886 sp_nodepath_node_destroy(sa->last);
1887 } else {
1888 code = NR_END;
1889 g_assert_not_reached();
1890 }
1892 if (b == sb->first) {
1893 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1894 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1895 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1896 }
1897 } else if (b == sb->last) {
1898 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1899 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1900 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1901 }
1902 } else {
1903 g_assert_not_reached();
1904 }
1905 /* and now destroy sb */
1907 sp_nodepath_subpath_destroy(sb);
1909 sp_nodepath_update_handles(sa->nodepath);
1911 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1913 sp_nodepath_update_statusbar(nodepath);
1914 }
1916 /**
1917 * Join two nodes by adding a segment between them.
1918 */
1919 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1920 {
1921 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1923 if (g_list_length(nodepath->selected) != 2) {
1924 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1925 return;
1926 }
1928 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1929 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1931 g_assert(a != b);
1932 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1933 // someone tried to join an orphan node (i.e. a single-node subpath).
1934 // this is not worth an error message, just fail silently.
1935 return;
1936 }
1938 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1939 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1940 return;
1941 }
1943 if (a->subpath == b->subpath) {
1944 Inkscape::NodePath::SubPath *sp = a->subpath;
1946 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1947 sp->closed = TRUE;
1949 sp->first->p.other = sp->last;
1950 sp->last->n.other = sp->first;
1952 sp_node_handle_mirror_p_to_n(sp->last);
1953 sp_node_handle_mirror_n_to_p(sp->first);
1955 sp->first->code = sp->last->code;
1956 sp->first = sp->last;
1958 sp_nodepath_update_handles(sp->nodepath);
1960 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1962 return;
1963 }
1965 /* a and b are separate subpaths */
1966 Inkscape::NodePath::SubPath *sa = a->subpath;
1967 Inkscape::NodePath::SubPath *sb = b->subpath;
1969 Inkscape::NodePath::Node *n;
1970 NR::Point p;
1971 NRPathcode code;
1972 if (a == sa->first) {
1973 code = (NRPathcode) sa->first->n.other->code;
1974 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1975 n = sa->last;
1976 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1977 for (n = n->p.other; n != NULL; n = n->p.other) {
1978 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1979 }
1980 sp_nodepath_subpath_destroy(sa);
1981 sa = t;
1982 } else if (a == sa->last) {
1983 code = (NRPathcode)sa->last->code;
1984 } else {
1985 code = NR_END;
1986 g_assert_not_reached();
1987 }
1989 if (b == sb->first) {
1990 n = sb->first;
1991 sp_node_handle_mirror_p_to_n(sa->last);
1992 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1993 sp_node_handle_mirror_n_to_p(sa->last);
1994 for (n = n->n.other; n != NULL; n = n->n.other) {
1995 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1996 }
1997 } else if (b == sb->last) {
1998 n = sb->last;
1999 sp_node_handle_mirror_p_to_n(sa->last);
2000 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2001 sp_node_handle_mirror_n_to_p(sa->last);
2002 for (n = n->p.other; n != NULL; n = n->p.other) {
2003 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2004 }
2005 } else {
2006 g_assert_not_reached();
2007 }
2008 /* and now destroy sb */
2010 sp_nodepath_subpath_destroy(sb);
2012 sp_nodepath_update_handles(sa->nodepath);
2014 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2015 }
2017 /**
2018 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2019 */
2020 void sp_node_delete_preserve(GList *nodes_to_delete)
2021 {
2022 GSList *nodepaths = NULL;
2024 while (nodes_to_delete) {
2025 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2026 Inkscape::NodePath::SubPath *sp = node->subpath;
2027 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2028 Inkscape::NodePath::Node *sample_cursor = NULL;
2029 Inkscape::NodePath::Node *sample_end = NULL;
2030 Inkscape::NodePath::Node *delete_cursor = node;
2031 bool just_delete = false;
2033 //find the start of this contiguous selection
2034 //move left to the first node that is not selected
2035 //or the start of the non-closed path
2036 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2037 delete_cursor = curr;
2038 }
2040 //just delete at the beginning of an open path
2041 if (!delete_cursor->p.other) {
2042 sample_cursor = delete_cursor;
2043 just_delete = true;
2044 } else {
2045 sample_cursor = delete_cursor->p.other;
2046 }
2048 //calculate points for each segment
2049 int rate = 5;
2050 float period = 1.0 / rate;
2051 std::vector<NR::Point> data;
2052 if (!just_delete) {
2053 data.push_back(sample_cursor->pos);
2054 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2055 //just delete at the end of an open path
2056 if (!sp->closed && curr == sp->last) {
2057 just_delete = true;
2058 break;
2059 }
2061 //sample points on the contiguous selected segment
2062 NR::Point *bez;
2063 bez = new NR::Point [4];
2064 bez[0] = curr->pos;
2065 bez[1] = curr->n.pos;
2066 bez[2] = curr->n.other->p.pos;
2067 bez[3] = curr->n.other->pos;
2068 for (int i=1; i<rate; i++) {
2069 gdouble t = i * period;
2070 NR::Point p = bezier_pt(3, bez, t);
2071 data.push_back(p);
2072 }
2073 data.push_back(curr->n.other->pos);
2075 sample_end = curr->n.other;
2076 //break if we've come full circle or hit the end of the selection
2077 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2078 break;
2079 }
2080 }
2081 }
2083 if (!just_delete) {
2084 //calculate the best fitting single segment and adjust the endpoints
2085 NR::Point *adata;
2086 adata = new NR::Point [data.size()];
2087 copy(data.begin(), data.end(), adata);
2089 NR::Point *bez;
2090 bez = new NR::Point [4];
2091 //would decreasing error create a better fitting approximation?
2092 gdouble error = 1.0;
2093 gint ret;
2094 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2096 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2097 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2098 //the resulting nodes behave as expected.
2099 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2100 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2102 //adjust endpoints
2103 sample_cursor->n.pos = bez[1];
2104 sample_end->p.pos = bez[2];
2105 }
2107 //destroy this contiguous selection
2108 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2109 Inkscape::NodePath::Node *temp = delete_cursor;
2110 if (delete_cursor->n.other == delete_cursor) {
2111 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2112 delete_cursor = NULL;
2113 } else {
2114 delete_cursor = delete_cursor->n.other;
2115 }
2116 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2117 sp_nodepath_node_destroy(temp);
2118 }
2120 sp_nodepath_update_handles(nodepath);
2122 if (!g_slist_find(nodepaths, nodepath))
2123 nodepaths = g_slist_prepend (nodepaths, nodepath);
2124 }
2126 for (GSList *i = nodepaths; i; i = i->next) {
2127 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2128 // different nodepaths will give us one undo event per nodepath
2129 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2131 // if the entire nodepath is removed, delete the selected object.
2132 if (nodepath->subpaths == NULL ||
2133 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2134 //at least 2
2135 sp_nodepath_get_node_count(nodepath) < 2) {
2136 SPDocument *document = sp_desktop_document (nodepath->desktop);
2137 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2138 //delete this nodepath's object, not the entire selection! (though at this time, this
2139 //does not matter)
2140 sp_selection_delete();
2141 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2142 _("Delete nodes"));
2143 } else {
2144 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2145 sp_nodepath_update_statusbar(nodepath);
2146 }
2147 }
2149 g_slist_free (nodepaths);
2150 }
2152 /**
2153 * Delete one or more selected nodes.
2154 */
2155 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2156 {
2157 if (!nodepath) return;
2158 if (!nodepath->selected) return;
2160 /** \todo fixme: do it the right way */
2161 while (nodepath->selected) {
2162 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2163 sp_nodepath_node_destroy(node);
2164 }
2167 //clean up the nodepath (such as for trivial subpaths)
2168 sp_nodepath_cleanup(nodepath);
2170 sp_nodepath_update_handles(nodepath);
2172 // if the entire nodepath is removed, delete the selected object.
2173 if (nodepath->subpaths == NULL ||
2174 sp_nodepath_get_node_count(nodepath) < 2) {
2175 SPDocument *document = sp_desktop_document (nodepath->desktop);
2176 sp_selection_delete();
2177 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2178 _("Delete nodes"));
2179 return;
2180 }
2182 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2184 sp_nodepath_update_statusbar(nodepath);
2185 }
2187 /**
2188 * Delete one or more segments between two selected nodes.
2189 * This is the code for 'split'.
2190 */
2191 void
2192 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2193 {
2194 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2195 Inkscape::NodePath::Node *curr, *next; //Iterators
2197 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2199 if (g_list_length(nodepath->selected) != 2) {
2200 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2201 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2202 return;
2203 }
2205 //Selected nodes, not inclusive
2206 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2207 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2209 if ( ( a==b) || //same node
2210 (a->subpath != b->subpath ) || //not the same path
2211 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2212 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2213 {
2214 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2215 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2216 return;
2217 }
2219 //###########################################
2220 //# BEGIN EDITS
2221 //###########################################
2222 //##################################
2223 //# CLOSED PATH
2224 //##################################
2225 if (a->subpath->closed) {
2228 gboolean reversed = FALSE;
2230 //Since we can go in a circle, we need to find the shorter distance.
2231 // a->b or b->a
2232 start = end = NULL;
2233 int distance = 0;
2234 int minDistance = 0;
2235 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2236 if (curr==b) {
2237 //printf("a to b:%d\n", distance);
2238 start = a;//go from a to b
2239 end = b;
2240 minDistance = distance;
2241 //printf("A to B :\n");
2242 break;
2243 }
2244 distance++;
2245 }
2247 //try again, the other direction
2248 distance = 0;
2249 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2250 if (curr==a) {
2251 //printf("b to a:%d\n", distance);
2252 if (distance < minDistance) {
2253 start = b; //we go from b to a
2254 end = a;
2255 reversed = TRUE;
2256 //printf("B to A\n");
2257 }
2258 break;
2259 }
2260 distance++;
2261 }
2264 //Copy everything from 'end' to 'start' to a new subpath
2265 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2266 for (curr=end ; curr ; curr=curr->n.other) {
2267 NRPathcode code = (NRPathcode) curr->code;
2268 if (curr == end)
2269 code = NR_MOVETO;
2270 sp_nodepath_node_new(t, NULL,
2271 (Inkscape::NodePath::NodeType)curr->type, code,
2272 &curr->p.pos, &curr->pos, &curr->n.pos);
2273 if (curr == start)
2274 break;
2275 }
2276 sp_nodepath_subpath_destroy(a->subpath);
2279 }
2283 //##################################
2284 //# OPEN PATH
2285 //##################################
2286 else {
2288 //We need to get the direction of the list between A and B
2289 //Can we walk from a to b?
2290 start = end = NULL;
2291 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2292 if (curr==b) {
2293 start = a; //did it! we go from a to b
2294 end = b;
2295 //printf("A to B\n");
2296 break;
2297 }
2298 }
2299 if (!start) {//didn't work? let's try the other direction
2300 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2301 if (curr==a) {
2302 start = b; //did it! we go from b to a
2303 end = a;
2304 //printf("B to A\n");
2305 break;
2306 }
2307 }
2308 }
2309 if (!start) {
2310 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2311 _("Cannot find path between nodes."));
2312 return;
2313 }
2317 //Copy everything after 'end' to a new subpath
2318 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2319 for (curr=end ; curr ; curr=curr->n.other) {
2320 NRPathcode code = (NRPathcode) curr->code;
2321 if (curr == end)
2322 code = NR_MOVETO;
2323 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2324 &curr->p.pos, &curr->pos, &curr->n.pos);
2325 }
2327 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2328 for (curr = start->n.other ; curr ; curr=next) {
2329 next = curr->n.other;
2330 sp_nodepath_node_destroy(curr);
2331 }
2333 }
2334 //###########################################
2335 //# END EDITS
2336 //###########################################
2338 //clean up the nodepath (such as for trivial subpaths)
2339 sp_nodepath_cleanup(nodepath);
2341 sp_nodepath_update_handles(nodepath);
2343 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2345 sp_nodepath_update_statusbar(nodepath);
2346 }
2348 /**
2349 * Call sp_nodepath_set_line() for all selected segments.
2350 */
2351 void
2352 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2353 {
2354 if (nodepath == NULL) return;
2356 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2357 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2358 g_assert(n->selected);
2359 if (n->p.other && n->p.other->selected) {
2360 sp_nodepath_set_line_type(n, code);
2361 }
2362 }
2364 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2365 }
2367 /**
2368 * Call sp_nodepath_convert_node_type() for all selected nodes.
2369 */
2370 void
2371 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2372 {
2373 if (nodepath == NULL) return;
2375 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2376 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2377 }
2379 sp_nodepath_update_repr(nodepath, _("Change node type"));
2380 }
2382 /**
2383 * Change select status of node, update its own and neighbour handles.
2384 */
2385 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2386 {
2387 node->selected = selected;
2389 if (selected) {
2390 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2391 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2392 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2393 sp_knot_update_ctrl(node->knot);
2394 } else {
2395 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2396 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2397 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2398 sp_knot_update_ctrl(node->knot);
2399 }
2401 sp_node_update_handles(node);
2402 if (node->n.other) sp_node_update_handles(node->n.other);
2403 if (node->p.other) sp_node_update_handles(node->p.other);
2404 }
2406 /**
2407 \brief Select a node
2408 \param node The node to select
2409 \param incremental If true, add to selection, otherwise deselect others
2410 \param override If true, always select this node, otherwise toggle selected status
2411 */
2412 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2413 {
2414 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2416 if (incremental) {
2417 if (override) {
2418 if (!g_list_find(nodepath->selected, node)) {
2419 nodepath->selected = g_list_prepend(nodepath->selected, node);
2420 }
2421 sp_node_set_selected(node, TRUE);
2422 } else { // toggle
2423 if (node->selected) {
2424 g_assert(g_list_find(nodepath->selected, node));
2425 nodepath->selected = g_list_remove(nodepath->selected, node);
2426 } else {
2427 g_assert(!g_list_find(nodepath->selected, node));
2428 nodepath->selected = g_list_prepend(nodepath->selected, node);
2429 }
2430 sp_node_set_selected(node, !node->selected);
2431 }
2432 } else {
2433 sp_nodepath_deselect(nodepath);
2434 nodepath->selected = g_list_prepend(nodepath->selected, node);
2435 sp_node_set_selected(node, TRUE);
2436 }
2438 sp_nodepath_update_statusbar(nodepath);
2439 }
2442 /**
2443 \brief Deselect all nodes in the nodepath
2444 */
2445 void
2446 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2447 {
2448 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2450 while (nodepath->selected) {
2451 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2452 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2453 }
2454 sp_nodepath_update_statusbar(nodepath);
2455 }
2457 /**
2458 \brief Select or invert selection of all nodes in the nodepath
2459 */
2460 void
2461 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2462 {
2463 if (!nodepath) return;
2465 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2466 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2467 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2468 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2469 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2470 }
2471 }
2472 }
2474 /**
2475 * If nothing selected, does the same as sp_nodepath_select_all();
2476 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2477 * (i.e., similar to "select all in layer", with the "selected" subpaths
2478 * being treated as "layers" in the path).
2479 */
2480 void
2481 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2482 {
2483 if (!nodepath) return;
2485 if (g_list_length (nodepath->selected) == 0) {
2486 sp_nodepath_select_all (nodepath, invert);
2487 return;
2488 }
2490 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2491 GSList *subpaths = NULL;
2493 for (GList *l = copy; l != NULL; l = l->next) {
2494 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2495 Inkscape::NodePath::SubPath *subpath = n->subpath;
2496 if (!g_slist_find (subpaths, subpath))
2497 subpaths = g_slist_prepend (subpaths, subpath);
2498 }
2500 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2501 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2502 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2503 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2504 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2505 }
2506 }
2508 g_slist_free (subpaths);
2509 g_list_free (copy);
2510 }
2512 /**
2513 * \brief Select the node after the last selected; if none is selected,
2514 * select the first within path.
2515 */
2516 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2517 {
2518 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2520 Inkscape::NodePath::Node *last = NULL;
2521 if (nodepath->selected) {
2522 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2523 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2524 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2525 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2526 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2527 if (node->selected) {
2528 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2529 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2530 if (spl->next) { // there's a next subpath
2531 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2532 last = subpath_next->first;
2533 } else if (spl->prev) { // there's a previous subpath
2534 last = NULL; // to be set later to the first node of first subpath
2535 } else {
2536 last = node->n.other;
2537 }
2538 } else {
2539 last = node->n.other;
2540 }
2541 } else {
2542 if (node->n.other) {
2543 last = node->n.other;
2544 } else {
2545 if (spl->next) { // there's a next subpath
2546 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2547 last = subpath_next->first;
2548 } else if (spl->prev) { // there's a previous subpath
2549 last = NULL; // to be set later to the first node of first subpath
2550 } else {
2551 last = (Inkscape::NodePath::Node *) subpath->first;
2552 }
2553 }
2554 }
2555 }
2556 }
2557 }
2558 sp_nodepath_deselect(nodepath);
2559 }
2561 if (last) { // there's at least one more node after selected
2562 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2563 } else { // no more nodes, select the first one in first subpath
2564 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2565 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2566 }
2567 }
2569 /**
2570 * \brief Select the node before the first selected; if none is selected,
2571 * select the last within path
2572 */
2573 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2574 {
2575 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2577 Inkscape::NodePath::Node *last = NULL;
2578 if (nodepath->selected) {
2579 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2580 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2581 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2582 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2583 if (node->selected) {
2584 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2585 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2586 if (spl->prev) { // there's a prev subpath
2587 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2588 last = subpath_prev->last;
2589 } else if (spl->next) { // there's a next subpath
2590 last = NULL; // to be set later to the last node of last subpath
2591 } else {
2592 last = node->p.other;
2593 }
2594 } else {
2595 last = node->p.other;
2596 }
2597 } else {
2598 if (node->p.other) {
2599 last = node->p.other;
2600 } else {
2601 if (spl->prev) { // there's a prev subpath
2602 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2603 last = subpath_prev->last;
2604 } else if (spl->next) { // there's a next subpath
2605 last = NULL; // to be set later to the last node of last subpath
2606 } else {
2607 last = (Inkscape::NodePath::Node *) subpath->last;
2608 }
2609 }
2610 }
2611 }
2612 }
2613 }
2614 sp_nodepath_deselect(nodepath);
2615 }
2617 if (last) { // there's at least one more node before selected
2618 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2619 } else { // no more nodes, select the last one in last subpath
2620 GList *spl = g_list_last(nodepath->subpaths);
2621 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2622 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2623 }
2624 }
2626 /**
2627 * \brief Select all nodes that are within the rectangle.
2628 */
2629 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2630 {
2631 if (!incremental) {
2632 sp_nodepath_deselect(nodepath);
2633 }
2635 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2636 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2637 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2638 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2640 if (b.contains(node->pos)) {
2641 sp_nodepath_node_select(node, TRUE, TRUE);
2642 }
2643 }
2644 }
2645 }
2648 void
2649 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2650 {
2651 g_assert (n);
2652 g_assert (nodepath);
2653 g_assert (n->subpath->nodepath == nodepath);
2655 if (g_list_length (nodepath->selected) == 0) {
2656 if (grow > 0) {
2657 sp_nodepath_node_select(n, TRUE, TRUE);
2658 }
2659 return;
2660 }
2662 if (g_list_length (nodepath->selected) == 1) {
2663 if (grow < 0) {
2664 sp_nodepath_deselect (nodepath);
2665 return;
2666 }
2667 }
2669 double n_sel_range = 0, p_sel_range = 0;
2670 Inkscape::NodePath::Node *farthest_n_node = n;
2671 Inkscape::NodePath::Node *farthest_p_node = n;
2673 // Calculate ranges
2674 {
2675 double n_range = 0, p_range = 0;
2676 bool n_going = true, p_going = true;
2677 Inkscape::NodePath::Node *n_node = n;
2678 Inkscape::NodePath::Node *p_node = n;
2679 do {
2680 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2681 if (n_node && n_going)
2682 n_node = n_node->n.other;
2683 if (n_node == NULL) {
2684 n_going = false;
2685 } else {
2686 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2687 if (n_node->selected) {
2688 n_sel_range = n_range;
2689 farthest_n_node = n_node;
2690 }
2691 if (n_node == p_node) {
2692 n_going = false;
2693 p_going = false;
2694 }
2695 }
2696 if (p_node && p_going)
2697 p_node = p_node->p.other;
2698 if (p_node == NULL) {
2699 p_going = false;
2700 } else {
2701 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2702 if (p_node->selected) {
2703 p_sel_range = p_range;
2704 farthest_p_node = p_node;
2705 }
2706 if (p_node == n_node) {
2707 n_going = false;
2708 p_going = false;
2709 }
2710 }
2711 } while (n_going || p_going);
2712 }
2714 if (grow > 0) {
2715 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2716 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2717 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2718 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2719 }
2720 } else {
2721 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2722 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2723 } else if (farthest_p_node && farthest_p_node->selected) {
2724 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2725 }
2726 }
2727 }
2729 void
2730 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2731 {
2732 g_assert (n);
2733 g_assert (nodepath);
2734 g_assert (n->subpath->nodepath == nodepath);
2736 if (g_list_length (nodepath->selected) == 0) {
2737 if (grow > 0) {
2738 sp_nodepath_node_select(n, TRUE, TRUE);
2739 }
2740 return;
2741 }
2743 if (g_list_length (nodepath->selected) == 1) {
2744 if (grow < 0) {
2745 sp_nodepath_deselect (nodepath);
2746 return;
2747 }
2748 }
2750 Inkscape::NodePath::Node *farthest_selected = NULL;
2751 double farthest_dist = 0;
2753 Inkscape::NodePath::Node *closest_unselected = NULL;
2754 double closest_dist = NR_HUGE;
2756 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2757 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2758 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2759 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2760 if (node == n)
2761 continue;
2762 if (node->selected) {
2763 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2764 farthest_dist = NR::L2(node->pos - n->pos);
2765 farthest_selected = node;
2766 }
2767 } else {
2768 if (NR::L2(node->pos - n->pos) < closest_dist) {
2769 closest_dist = NR::L2(node->pos - n->pos);
2770 closest_unselected = node;
2771 }
2772 }
2773 }
2774 }
2776 if (grow > 0) {
2777 if (closest_unselected) {
2778 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2779 }
2780 } else {
2781 if (farthest_selected) {
2782 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2783 }
2784 }
2785 }
2788 /**
2789 \brief Saves all nodes' and handles' current positions in their origin members
2790 */
2791 void
2792 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2793 {
2794 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2795 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2796 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2797 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2798 n->origin = n->pos;
2799 n->p.origin = n->p.pos;
2800 n->n.origin = n->n.pos;
2801 }
2802 }
2803 }
2805 /**
2806 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2807 */
2808 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2809 {
2810 if (!nodepath->selected) {
2811 return NULL;
2812 }
2814 GList *r = NULL;
2815 guint i = 0;
2816 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2817 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2818 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2819 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2820 i++;
2821 if (node->selected) {
2822 r = g_list_append(r, GINT_TO_POINTER(i));
2823 }
2824 }
2825 }
2826 return r;
2827 }
2829 /**
2830 \brief Restores selection by selecting nodes whose positions are in the list
2831 */
2832 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2833 {
2834 sp_nodepath_deselect(nodepath);
2836 guint i = 0;
2837 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2838 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2839 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2840 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2841 i++;
2842 if (g_list_find(r, GINT_TO_POINTER(i))) {
2843 sp_nodepath_node_select(node, TRUE, TRUE);
2844 }
2845 }
2846 }
2848 }
2850 /**
2851 \brief Adjusts handle according to node type and line code.
2852 */
2853 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2854 {
2855 double len, otherlen, linelen;
2857 g_assert(node);
2859 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2860 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2862 /** \todo fixme: */
2863 if (me->other == NULL) return;
2864 if (other->other == NULL) return;
2866 /* I have line */
2868 NRPathcode mecode, ocode;
2869 if (which_adjust == 1) {
2870 mecode = (NRPathcode)me->other->code;
2871 ocode = (NRPathcode)node->code;
2872 } else {
2873 mecode = (NRPathcode)node->code;
2874 ocode = (NRPathcode)other->other->code;
2875 }
2877 if (mecode == NR_LINETO) return;
2879 /* I am curve */
2881 if (other->other == NULL) return;
2883 /* Other has line */
2885 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2887 NR::Point delta;
2888 if (ocode == NR_LINETO) {
2889 /* other is lineto, we are either smooth or symm */
2890 Inkscape::NodePath::Node *othernode = other->other;
2891 len = NR::L2(me->pos - node->pos);
2892 delta = node->pos - othernode->pos;
2893 linelen = NR::L2(delta);
2894 if (linelen < 1e-18)
2895 return;
2896 me->pos = node->pos + (len / linelen)*delta;
2897 return;
2898 }
2900 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2902 me->pos = 2 * node->pos - other->pos;
2903 return;
2904 }
2906 /* We are smooth */
2908 len = NR::L2(me->pos - node->pos);
2909 delta = other->pos - node->pos;
2910 otherlen = NR::L2(delta);
2911 if (otherlen < 1e-18) return;
2913 me->pos = node->pos - (len / otherlen) * delta;
2914 }
2916 /**
2917 \brief Adjusts both handles according to node type and line code
2918 */
2919 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2920 {
2921 g_assert(node);
2923 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2925 /* we are either smooth or symm */
2927 if (node->p.other == NULL) return;
2929 if (node->n.other == NULL) return;
2931 if (node->code == NR_LINETO) {
2932 if (node->n.other->code == NR_LINETO) return;
2933 sp_node_adjust_handle(node, 1);
2934 return;
2935 }
2937 if (node->n.other->code == NR_LINETO) {
2938 if (node->code == NR_LINETO) return;
2939 sp_node_adjust_handle(node, -1);
2940 return;
2941 }
2943 /* both are curves */
2944 NR::Point const delta( node->n.pos - node->p.pos );
2946 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2947 node->p.pos = node->pos - delta / 2;
2948 node->n.pos = node->pos + delta / 2;
2949 return;
2950 }
2952 /* We are smooth */
2953 double plen = NR::L2(node->p.pos - node->pos);
2954 if (plen < 1e-18) return;
2955 double nlen = NR::L2(node->n.pos - node->pos);
2956 if (nlen < 1e-18) return;
2957 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2958 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2959 }
2961 /**
2962 * Node event callback.
2963 */
2964 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2965 {
2966 gboolean ret = FALSE;
2967 switch (event->type) {
2968 case GDK_ENTER_NOTIFY:
2969 Inkscape::NodePath::Path::active_node = n;
2970 break;
2971 case GDK_LEAVE_NOTIFY:
2972 Inkscape::NodePath::Path::active_node = NULL;
2973 break;
2974 case GDK_SCROLL:
2975 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2976 switch (event->scroll.direction) {
2977 case GDK_SCROLL_UP:
2978 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2979 break;
2980 case GDK_SCROLL_DOWN:
2981 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2982 break;
2983 default:
2984 break;
2985 }
2986 ret = TRUE;
2987 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2988 switch (event->scroll.direction) {
2989 case GDK_SCROLL_UP:
2990 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2991 break;
2992 case GDK_SCROLL_DOWN:
2993 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2994 break;
2995 default:
2996 break;
2997 }
2998 ret = TRUE;
2999 }
3000 break;
3001 case GDK_KEY_PRESS:
3002 switch (get_group0_keyval (&event->key)) {
3003 case GDK_space:
3004 if (event->key.state & GDK_BUTTON1_MASK) {
3005 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3006 stamp_repr(nodepath);
3007 ret = TRUE;
3008 }
3009 break;
3010 case GDK_Page_Up:
3011 if (event->key.state & GDK_CONTROL_MASK) {
3012 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3013 } else {
3014 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3015 }
3016 break;
3017 case GDK_Page_Down:
3018 if (event->key.state & GDK_CONTROL_MASK) {
3019 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3020 } else {
3021 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3022 }
3023 break;
3024 default:
3025 break;
3026 }
3027 break;
3028 default:
3029 break;
3030 }
3032 return ret;
3033 }
3035 /**
3036 * Handle keypress on node; directly called.
3037 */
3038 gboolean node_key(GdkEvent *event)
3039 {
3040 Inkscape::NodePath::Path *np;
3042 // there is no way to verify nodes so set active_node to nil when deleting!!
3043 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3045 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3046 gint ret = FALSE;
3047 switch (get_group0_keyval (&event->key)) {
3048 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3049 case GDK_BackSpace:
3050 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3051 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3052 sp_nodepath_update_repr(np, _("Delete node"));
3053 Inkscape::NodePath::Path::active_node = NULL;
3054 ret = TRUE;
3055 break;
3056 case GDK_c:
3057 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3058 ret = TRUE;
3059 break;
3060 case GDK_s:
3061 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3062 ret = TRUE;
3063 break;
3064 case GDK_y:
3065 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3066 ret = TRUE;
3067 break;
3068 case GDK_b:
3069 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3070 ret = TRUE;
3071 break;
3072 }
3073 return ret;
3074 }
3075 return FALSE;
3076 }
3078 /**
3079 * Mouseclick on node callback.
3080 */
3081 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3082 {
3083 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3085 if (state & GDK_CONTROL_MASK) {
3086 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3088 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3089 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3090 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3091 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3092 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3093 } else {
3094 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3095 }
3096 sp_nodepath_update_repr(nodepath, _("Change node type"));
3097 sp_nodepath_update_statusbar(nodepath);
3099 } else { //ctrl+alt+click: delete node
3100 GList *node_to_delete = NULL;
3101 node_to_delete = g_list_append(node_to_delete, n);
3102 sp_node_delete_preserve(node_to_delete);
3103 }
3105 } else {
3106 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3107 }
3108 }
3110 /**
3111 * Mouse grabbed node callback.
3112 */
3113 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3114 {
3115 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3117 if (!n->selected) {
3118 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3119 }
3121 n->is_dragging = true;
3122 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3124 sp_nodepath_remember_origins (n->subpath->nodepath);
3125 }
3127 /**
3128 * Mouse ungrabbed node callback.
3129 */
3130 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3131 {
3132 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3134 n->dragging_out = NULL;
3135 n->is_dragging = false;
3136 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3138 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3139 }
3141 /**
3142 * The point on a line, given by its angle, closest to the given point.
3143 * \param p A point.
3144 * \param a Angle of the line; it is assumed to go through coordinate origin.
3145 * \param closest Pointer to the point struct where the result is stored.
3146 * \todo FIXME: use dot product perhaps?
3147 */
3148 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3149 {
3150 if (a == HUGE_VAL) { // vertical
3151 *closest = NR::Point(0, (*p)[NR::Y]);
3152 } else {
3153 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3154 (*closest)[NR::Y] = a * (*closest)[NR::X];
3155 }
3156 }
3158 /**
3159 * Distance from the point to a line given by its angle.
3160 * \param p A point.
3161 * \param a Angle of the line; it is assumed to go through coordinate origin.
3162 */
3163 static double point_line_distance(NR::Point *p, double a)
3164 {
3165 NR::Point c;
3166 point_line_closest(p, a, &c);
3167 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]));
3168 }
3170 /**
3171 * Callback for node "request" signal.
3172 * \todo fixme: This goes to "moved" event? (lauris)
3173 */
3174 static gboolean
3175 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3176 {
3177 double yn, xn, yp, xp;
3178 double an, ap, na, pa;
3179 double d_an, d_ap, d_na, d_pa;
3180 gboolean collinear = FALSE;
3181 NR::Point c;
3182 NR::Point pr;
3184 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3186 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3187 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3189 NR::Point mouse = (*p);
3191 if (!n->dragging_out) {
3192 // This is the first drag-out event; find out which handle to drag out
3193 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3194 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3196 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3197 return FALSE;
3199 Inkscape::NodePath::NodeSide *opposite;
3200 if (appr_p > appr_n) { // closer to p
3201 n->dragging_out = &n->p;
3202 opposite = &n->n;
3203 n->code = NR_CURVETO;
3204 } else if (appr_p < appr_n) { // closer to n
3205 n->dragging_out = &n->n;
3206 opposite = &n->p;
3207 n->n.other->code = NR_CURVETO;
3208 } else { // p and n nodes are the same
3209 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3210 n->dragging_out = &n->p;
3211 opposite = &n->n;
3212 n->code = NR_CURVETO;
3213 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3214 n->dragging_out = &n->n;
3215 opposite = &n->p;
3216 n->n.other->code = NR_CURVETO;
3217 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3218 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);
3219 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);
3220 if (appr_other_p > appr_other_n) { // closer to other's p handle
3221 n->dragging_out = &n->n;
3222 opposite = &n->p;
3223 n->n.other->code = NR_CURVETO;
3224 } else { // closer to other's n handle
3225 n->dragging_out = &n->p;
3226 opposite = &n->n;
3227 n->code = NR_CURVETO;
3228 }
3229 }
3230 }
3232 // if there's another handle, make sure the one we drag out starts parallel to it
3233 if (opposite->pos != n->pos) {
3234 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3235 }
3237 // knots might not be created yet!
3238 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3239 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3240 }
3242 // pass this on to the handle-moved callback
3243 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3244 sp_node_update_handles(n);
3245 return TRUE;
3246 }
3248 if (state & GDK_CONTROL_MASK) { // constrained motion
3250 // calculate relative distances of handles
3251 // n handle:
3252 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3253 xn = n->n.pos[NR::X] - n->pos[NR::X];
3254 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3255 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3256 if (n->n.other) { // if there is the next point
3257 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3258 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3259 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3260 }
3261 }
3262 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3263 if (yn < 0) { xn = -xn; yn = -yn; }
3265 // p handle:
3266 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3267 xp = n->p.pos[NR::X] - n->pos[NR::X];
3268 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3269 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3270 if (n->p.other) {
3271 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3272 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3273 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3274 }
3275 }
3276 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3277 if (yp < 0) { xp = -xp; yp = -yp; }
3279 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3280 // sliding on handles, only if at least one of the handles is non-vertical
3281 // (otherwise it's the same as ctrl+drag anyway)
3283 // calculate angles of the handles
3284 if (xn == 0) {
3285 if (yn == 0) { // no handle, consider it the continuation of the other one
3286 an = 0;
3287 collinear = TRUE;
3288 }
3289 else an = 0; // vertical; set the angle to horizontal
3290 } else an = yn/xn;
3292 if (xp == 0) {
3293 if (yp == 0) { // no handle, consider it the continuation of the other one
3294 ap = an;
3295 }
3296 else ap = 0; // vertical; set the angle to horizontal
3297 } else ap = yp/xp;
3299 if (collinear) an = ap;
3301 // angles of the perpendiculars; HUGE_VAL means vertical
3302 if (an == 0) na = HUGE_VAL; else na = -1/an;
3303 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3305 // mouse point relative to the node's original pos
3306 pr = (*p) - n->origin;
3308 // distances to the four lines (two handles and two perpendiculars)
3309 d_an = point_line_distance(&pr, an);
3310 d_na = point_line_distance(&pr, na);
3311 d_ap = point_line_distance(&pr, ap);
3312 d_pa = point_line_distance(&pr, pa);
3314 // find out which line is the closest, save its closest point in c
3315 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3316 point_line_closest(&pr, an, &c);
3317 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3318 point_line_closest(&pr, ap, &c);
3319 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3320 point_line_closest(&pr, na, &c);
3321 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3322 point_line_closest(&pr, pa, &c);
3323 }
3325 // move the node to the closest point
3326 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3327 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3328 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3330 } else { // constraining to hor/vert
3332 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3333 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3334 } else { // snap to vert
3335 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3336 }
3337 }
3338 } else { // move freely
3339 if (n->is_dragging) {
3340 if (state & GDK_MOD1_MASK) { // sculpt
3341 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3342 } else {
3343 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3344 (*p)[NR::X] - n->pos[NR::X],
3345 (*p)[NR::Y] - n->pos[NR::Y],
3346 (state & GDK_SHIFT_MASK) == 0);
3347 }
3348 }
3349 }
3351 n->subpath->nodepath->desktop->scroll_to_point(p);
3353 return TRUE;
3354 }
3356 /**
3357 * Node handle clicked callback.
3358 */
3359 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3360 {
3361 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3363 if (state & GDK_CONTROL_MASK) { // "delete" handle
3364 if (n->p.knot == knot) {
3365 n->p.pos = n->pos;
3366 } else if (n->n.knot == knot) {
3367 n->n.pos = n->pos;
3368 }
3369 sp_node_update_handles(n);
3370 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3371 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3372 sp_nodepath_update_statusbar(nodepath);
3374 } else { // just select or add to selection, depending in Shift
3375 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3376 }
3377 }
3379 /**
3380 * Node handle grabbed callback.
3381 */
3382 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3383 {
3384 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3386 if (!n->selected) {
3387 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3388 }
3390 // remember the origin point of the handle
3391 if (n->p.knot == knot) {
3392 n->p.origin_radial = n->p.pos - n->pos;
3393 } else if (n->n.knot == knot) {
3394 n->n.origin_radial = n->n.pos - n->pos;
3395 } else {
3396 g_assert_not_reached();
3397 }
3399 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3400 }
3402 /**
3403 * Node handle ungrabbed callback.
3404 */
3405 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3406 {
3407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3409 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3410 if (n->p.knot == knot) {
3411 n->p.origin_radial.a = 0;
3412 sp_knot_set_position(knot, &n->p.pos, state);
3413 } else if (n->n.knot == knot) {
3414 n->n.origin_radial.a = 0;
3415 sp_knot_set_position(knot, &n->n.pos, state);
3416 } else {
3417 g_assert_not_reached();
3418 }
3420 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3421 }
3423 /**
3424 * Node handle "request" signal callback.
3425 */
3426 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3427 {
3428 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3430 Inkscape::NodePath::NodeSide *me, *opposite;
3431 gint which;
3432 if (n->p.knot == knot) {
3433 me = &n->p;
3434 opposite = &n->n;
3435 which = -1;
3436 } else if (n->n.knot == knot) {
3437 me = &n->n;
3438 opposite = &n->p;
3439 which = 1;
3440 } else {
3441 me = opposite = NULL;
3442 which = 0;
3443 g_assert_not_reached();
3444 }
3446 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3448 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3450 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3451 /* We are smooth node adjacent with line */
3452 NR::Point const delta = *p - n->pos;
3453 NR::Coord const len = NR::L2(delta);
3454 Inkscape::NodePath::Node *othernode = opposite->other;
3455 NR::Point const ndelta = n->pos - othernode->pos;
3456 NR::Coord const linelen = NR::L2(ndelta);
3457 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3458 NR::Coord const scal = dot(delta, ndelta) / linelen;
3459 (*p) = n->pos + (scal / linelen) * ndelta;
3460 }
3461 *p = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), n->subpath->nodepath->item).getPoint();
3462 } else {
3463 *p = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, n->subpath->nodepath->item).getPoint();
3464 }
3466 sp_node_adjust_handle(n, -which);
3468 return FALSE;
3469 }
3471 /**
3472 * Node handle moved callback.
3473 */
3474 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3475 {
3476 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3478 Inkscape::NodePath::NodeSide *me;
3479 Inkscape::NodePath::NodeSide *other;
3480 if (n->p.knot == knot) {
3481 me = &n->p;
3482 other = &n->n;
3483 } else if (n->n.knot == knot) {
3484 me = &n->n;
3485 other = &n->p;
3486 } else {
3487 me = NULL;
3488 other = NULL;
3489 g_assert_not_reached();
3490 }
3492 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3493 Radial rme(me->pos - n->pos);
3494 Radial rother(other->pos - n->pos);
3495 Radial rnew(*p - n->pos);
3497 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3498 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3499 /* 0 interpreted as "no snapping". */
3501 // The closest PI/snaps angle, starting from zero.
3502 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3503 if (me->origin_radial.a == HUGE_VAL) {
3504 // ortho doesn't exist: original handle was zero length.
3505 rnew.a = a_snapped;
3506 } else {
3507 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3508 * its opposite and perpendiculars). */
3509 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3511 // Snap to the closest.
3512 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3513 ? a_snapped
3514 : a_ortho );
3515 }
3516 }
3518 if (state & GDK_MOD1_MASK) {
3519 // lock handle length
3520 rnew.r = me->origin_radial.r;
3521 }
3523 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3524 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3525 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3526 rother.a += rnew.a - rme.a;
3527 other->pos = NR::Point(rother) + n->pos;
3528 if (other->knot) {
3529 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3530 sp_knot_moveto(other->knot, &other->pos);
3531 }
3532 }
3534 me->pos = NR::Point(rnew) + n->pos;
3535 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3537 // move knot, but without emitting the signal:
3538 // we cannot emit a "moved" signal because we're now processing it
3539 sp_knot_moveto(me->knot, &(me->pos));
3541 update_object(n->subpath->nodepath);
3543 /* status text */
3544 SPDesktop *desktop = n->subpath->nodepath->desktop;
3545 if (!desktop) return;
3546 SPEventContext *ec = desktop->event_context;
3547 if (!ec) return;
3548 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3549 if (!mc) return;
3551 double degrees = 180 / M_PI * rnew.a;
3552 if (degrees > 180) degrees -= 360;
3553 if (degrees < -180) degrees += 360;
3554 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3555 degrees = angle_to_compass (degrees);
3557 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3559 mc->setF(Inkscape::NORMAL_MESSAGE,
3560 _("<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);
3562 g_string_free(length, TRUE);
3563 }
3565 /**
3566 * Node handle event callback.
3567 */
3568 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3569 {
3570 gboolean ret = FALSE;
3571 switch (event->type) {
3572 case GDK_KEY_PRESS:
3573 switch (get_group0_keyval (&event->key)) {
3574 case GDK_space:
3575 if (event->key.state & GDK_BUTTON1_MASK) {
3576 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3577 stamp_repr(nodepath);
3578 ret = TRUE;
3579 }
3580 break;
3581 default:
3582 break;
3583 }
3584 break;
3585 case GDK_ENTER_NOTIFY:
3586 // we use an experimentally determined threshold that seems to work fine
3587 if (NR::L2(n->pos - knot->pos) < 0.75)
3588 Inkscape::NodePath::Path::active_node = n;
3589 break;
3590 case GDK_LEAVE_NOTIFY:
3591 // we use an experimentally determined threshold that seems to work fine
3592 if (NR::L2(n->pos - knot->pos) < 0.75)
3593 Inkscape::NodePath::Path::active_node = NULL;
3594 break;
3595 default:
3596 break;
3597 }
3599 return ret;
3600 }
3602 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3603 Radial &rme, Radial &rother, gboolean const both)
3604 {
3605 rme.a += angle;
3606 if ( both
3607 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3608 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3609 {
3610 rother.a += angle;
3611 }
3612 }
3614 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3615 Radial &rme, Radial &rother, gboolean const both)
3616 {
3617 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3619 gdouble r;
3620 if ( both
3621 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3622 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3623 {
3624 r = MAX(rme.r, rother.r);
3625 } else {
3626 r = rme.r;
3627 }
3629 gdouble const weird_angle = atan2(norm_angle, r);
3630 /* Bulia says norm_angle is just the visible distance that the
3631 * object's end must travel on the screen. Left as 'angle' for want of
3632 * a better name.*/
3634 rme.a += weird_angle;
3635 if ( both
3636 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3637 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3638 {
3639 rother.a += weird_angle;
3640 }
3641 }
3643 /**
3644 * Rotate one node.
3645 */
3646 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3647 {
3648 Inkscape::NodePath::NodeSide *me, *other;
3649 bool both = false;
3651 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3652 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3654 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3655 me = &(n->p);
3656 other = &(n->n);
3657 } else if (!n->p.other) {
3658 me = &(n->n);
3659 other = &(n->p);
3660 } else {
3661 if (which > 0) { // right handle
3662 if (xn > xp) {
3663 me = &(n->n);
3664 other = &(n->p);
3665 } else {
3666 me = &(n->p);
3667 other = &(n->n);
3668 }
3669 } else if (which < 0){ // left handle
3670 if (xn <= xp) {
3671 me = &(n->n);
3672 other = &(n->p);
3673 } else {
3674 me = &(n->p);
3675 other = &(n->n);
3676 }
3677 } else { // both handles
3678 me = &(n->n);
3679 other = &(n->p);
3680 both = true;
3681 }
3682 }
3684 Radial rme(me->pos - n->pos);
3685 Radial rother(other->pos - n->pos);
3687 if (screen) {
3688 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3689 } else {
3690 node_rotate_one_internal (*n, angle, rme, rother, both);
3691 }
3693 me->pos = n->pos + NR::Point(rme);
3695 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3696 other->pos = n->pos + NR::Point(rother);
3697 }
3699 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3700 // so here we just move all the knots without emitting move signals, for speed
3701 sp_node_update_handles(n, false);
3702 }
3704 /**
3705 * Rotate selected nodes.
3706 */
3707 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3708 {
3709 if (!nodepath || !nodepath->selected) return;
3711 if (g_list_length(nodepath->selected) == 1) {
3712 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3713 node_rotate_one (n, angle, which, screen);
3714 } else {
3715 // rotate as an object:
3717 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3718 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3719 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3720 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3721 box.expandTo (n->pos); // contain all selected nodes
3722 }
3724 gdouble rot;
3725 if (screen) {
3726 gdouble const zoom = nodepath->desktop->current_zoom();
3727 gdouble const zmove = angle / zoom;
3728 gdouble const r = NR::L2(box.max() - box.midpoint());
3729 rot = atan2(zmove, r);
3730 } else {
3731 rot = angle;
3732 }
3734 NR::Point rot_center;
3735 if (Inkscape::NodePath::Path::active_node == NULL)
3736 rot_center = box.midpoint();
3737 else
3738 rot_center = Inkscape::NodePath::Path::active_node->pos;
3740 NR::Matrix t =
3741 NR::Matrix (NR::translate(-rot_center)) *
3742 NR::Matrix (NR::rotate(rot)) *
3743 NR::Matrix (NR::translate(rot_center));
3745 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3746 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3747 n->pos *= t;
3748 n->n.pos *= t;
3749 n->p.pos *= t;
3750 sp_node_update_handles(n, false);
3751 }
3752 }
3754 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3755 }
3757 /**
3758 * Scale one node.
3759 */
3760 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3761 {
3762 bool both = false;
3763 Inkscape::NodePath::NodeSide *me, *other;
3765 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3766 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3768 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3769 me = &(n->p);
3770 other = &(n->n);
3771 n->code = NR_CURVETO;
3772 } else if (!n->p.other) {
3773 me = &(n->n);
3774 other = &(n->p);
3775 if (n->n.other)
3776 n->n.other->code = NR_CURVETO;
3777 } else {
3778 if (which > 0) { // right handle
3779 if (xn > xp) {
3780 me = &(n->n);
3781 other = &(n->p);
3782 if (n->n.other)
3783 n->n.other->code = NR_CURVETO;
3784 } else {
3785 me = &(n->p);
3786 other = &(n->n);
3787 n->code = NR_CURVETO;
3788 }
3789 } else if (which < 0){ // left handle
3790 if (xn <= xp) {
3791 me = &(n->n);
3792 other = &(n->p);
3793 if (n->n.other)
3794 n->n.other->code = NR_CURVETO;
3795 } else {
3796 me = &(n->p);
3797 other = &(n->n);
3798 n->code = NR_CURVETO;
3799 }
3800 } else { // both handles
3801 me = &(n->n);
3802 other = &(n->p);
3803 both = true;
3804 n->code = NR_CURVETO;
3805 if (n->n.other)
3806 n->n.other->code = NR_CURVETO;
3807 }
3808 }
3810 Radial rme(me->pos - n->pos);
3811 Radial rother(other->pos - n->pos);
3813 rme.r += grow;
3814 if (rme.r < 0) rme.r = 0;
3815 if (rme.a == HUGE_VAL) {
3816 if (me->other) { // if direction is unknown, initialize it towards the next node
3817 Radial rme_next(me->other->pos - n->pos);
3818 rme.a = rme_next.a;
3819 } else { // if there's no next, initialize to 0
3820 rme.a = 0;
3821 }
3822 }
3823 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3824 rother.r += grow;
3825 if (rother.r < 0) rother.r = 0;
3826 if (rother.a == HUGE_VAL) {
3827 rother.a = rme.a + M_PI;
3828 }
3829 }
3831 me->pos = n->pos + NR::Point(rme);
3833 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3834 other->pos = n->pos + NR::Point(rother);
3835 }
3837 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3838 // so here we just move all the knots without emitting move signals, for speed
3839 sp_node_update_handles(n, false);
3840 }
3842 /**
3843 * Scale selected nodes.
3844 */
3845 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3846 {
3847 if (!nodepath || !nodepath->selected) return;
3849 if (g_list_length(nodepath->selected) == 1) {
3850 // scale handles of the single selected node
3851 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3852 node_scale_one (n, grow, which);
3853 } else {
3854 // scale nodes as an "object":
3856 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3857 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3858 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3859 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3860 box.expandTo (n->pos); // contain all selected nodes
3861 }
3863 double scale = (box.maxExtent() + grow)/box.maxExtent();
3865 NR::Point scale_center;
3866 if (Inkscape::NodePath::Path::active_node == NULL)
3867 scale_center = box.midpoint();
3868 else
3869 scale_center = Inkscape::NodePath::Path::active_node->pos;
3871 NR::Matrix t =
3872 NR::Matrix (NR::translate(-scale_center)) *
3873 NR::Matrix (NR::scale(scale, scale)) *
3874 NR::Matrix (NR::translate(scale_center));
3876 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3877 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3878 n->pos *= t;
3879 n->n.pos *= t;
3880 n->p.pos *= t;
3881 sp_node_update_handles(n, false);
3882 }
3883 }
3885 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3886 }
3888 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3889 {
3890 if (!nodepath) return;
3891 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3892 }
3894 /**
3895 * Flip selected nodes horizontally/vertically.
3896 */
3897 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3898 {
3899 if (!nodepath || !nodepath->selected) return;
3901 if (g_list_length(nodepath->selected) == 1 && !center) {
3902 // flip handles of the single selected node
3903 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3904 double temp = n->p.pos[axis];
3905 n->p.pos[axis] = n->n.pos[axis];
3906 n->n.pos[axis] = temp;
3907 sp_node_update_handles(n, false);
3908 } else {
3909 // scale nodes as an "object":
3911 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3912 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3913 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3914 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3915 box.expandTo (n->pos); // contain all selected nodes
3916 }
3918 if (!center) {
3919 center = box.midpoint();
3920 }
3921 NR::Matrix t =
3922 NR::Matrix (NR::translate(- *center)) *
3923 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3924 NR::Matrix (NR::translate(*center));
3926 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3927 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3928 n->pos *= t;
3929 n->n.pos *= t;
3930 n->p.pos *= t;
3931 sp_node_update_handles(n, false);
3932 }
3933 }
3935 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3936 }
3938 //-----------------------------------------------
3939 /**
3940 * Return new subpath under given nodepath.
3941 */
3942 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3943 {
3944 g_assert(nodepath);
3945 g_assert(nodepath->desktop);
3947 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3949 s->nodepath = nodepath;
3950 s->closed = FALSE;
3951 s->nodes = NULL;
3952 s->first = NULL;
3953 s->last = NULL;
3955 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3956 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3957 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3959 return s;
3960 }
3962 /**
3963 * Destroy nodes in subpath, then subpath itself.
3964 */
3965 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3966 {
3967 g_assert(subpath);
3968 g_assert(subpath->nodepath);
3969 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3971 while (subpath->nodes) {
3972 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3973 }
3975 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3977 g_free(subpath);
3978 }
3980 /**
3981 * Link head to tail in subpath.
3982 */
3983 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3984 {
3985 g_assert(!sp->closed);
3986 g_assert(sp->last != sp->first);
3987 g_assert(sp->first->code == NR_MOVETO);
3989 sp->closed = TRUE;
3991 //Link the head to the tail
3992 sp->first->p.other = sp->last;
3993 sp->last->n.other = sp->first;
3994 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3995 sp->first = sp->last;
3997 //Remove the extra end node
3998 sp_nodepath_node_destroy(sp->last->n.other);
3999 }
4001 /**
4002 * Open closed (loopy) subpath at node.
4003 */
4004 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4005 {
4006 g_assert(sp->closed);
4007 g_assert(n->subpath == sp);
4008 g_assert(sp->first == sp->last);
4010 /* We create new startpoint, current node will become last one */
4012 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4013 &n->pos, &n->pos, &n->n.pos);
4016 sp->closed = FALSE;
4018 //Unlink to make a head and tail
4019 sp->first = new_path;
4020 sp->last = n;
4021 n->n.other = NULL;
4022 new_path->p.other = NULL;
4023 }
4025 /**
4026 * Returns area in triangle given by points; may be negative.
4027 */
4028 inline double
4029 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
4030 {
4031 return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
4032 }
4034 /**
4035 * Return new node in subpath with given properties.
4036 * \param pos Position of node.
4037 * \param ppos Handle position in previous direction
4038 * \param npos Handle position in previous direction
4039 */
4040 Inkscape::NodePath::Node *
4041 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)
4042 {
4043 g_assert(sp);
4044 g_assert(sp->nodepath);
4045 g_assert(sp->nodepath->desktop);
4047 if (nodechunk == NULL)
4048 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4050 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4052 n->subpath = sp;
4054 if (type != Inkscape::NodePath::NODE_NONE) {
4055 // use the type from sodipodi:nodetypes
4056 n->type = type;
4057 } else {
4058 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4059 // points are (almost) collinear
4060 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4061 // endnode, or a node with a retracted handle
4062 n->type = Inkscape::NodePath::NODE_CUSP;
4063 } else {
4064 n->type = Inkscape::NodePath::NODE_SMOOTH;
4065 }
4066 } else {
4067 n->type = Inkscape::NodePath::NODE_CUSP;
4068 }
4069 }
4071 n->code = code;
4072 n->selected = FALSE;
4073 n->pos = *pos;
4074 n->p.pos = *ppos;
4075 n->n.pos = *npos;
4077 n->dragging_out = NULL;
4079 Inkscape::NodePath::Node *prev;
4080 if (next) {
4081 //g_assert(g_list_find(sp->nodes, next));
4082 prev = next->p.other;
4083 } else {
4084 prev = sp->last;
4085 }
4087 if (prev)
4088 prev->n.other = n;
4089 else
4090 sp->first = n;
4092 if (next)
4093 next->p.other = n;
4094 else
4095 sp->last = n;
4097 n->p.other = prev;
4098 n->n.other = next;
4100 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"));
4101 sp_knot_set_position(n->knot, pos, 0);
4103 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4104 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4105 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4106 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4107 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4108 sp_knot_update_ctrl(n->knot);
4110 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4111 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4112 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4113 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4114 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4115 sp_knot_show(n->knot);
4117 // We only create handle knots and lines on demand
4118 n->p.knot = NULL;
4119 n->p.line = NULL;
4120 n->n.knot = NULL;
4121 n->n.line = NULL;
4123 sp->nodes = g_list_prepend(sp->nodes, n);
4125 return n;
4126 }
4128 /**
4129 * Destroy node and its knots, link neighbors in subpath.
4130 */
4131 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4132 {
4133 g_assert(node);
4134 g_assert(node->subpath);
4135 g_assert(SP_IS_KNOT(node->knot));
4137 Inkscape::NodePath::SubPath *sp = node->subpath;
4139 if (node->selected) { // first, deselect
4140 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4141 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4142 }
4144 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4146 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4147 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4148 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4149 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4150 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4151 g_object_unref(G_OBJECT(node->knot));
4153 if (node->p.knot) {
4154 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4155 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4156 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4157 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4158 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4159 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4160 g_object_unref(G_OBJECT(node->p.knot));
4161 node->p.knot = NULL;
4162 }
4164 if (node->n.knot) {
4165 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4166 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4167 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4168 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4169 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4170 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4171 g_object_unref(G_OBJECT(node->n.knot));
4172 node->n.knot = NULL;
4173 }
4175 if (node->p.line)
4176 gtk_object_destroy(GTK_OBJECT(node->p.line));
4177 if (node->n.line)
4178 gtk_object_destroy(GTK_OBJECT(node->n.line));
4180 if (sp->nodes) { // there are others nodes on the subpath
4181 if (sp->closed) {
4182 if (sp->first == node) {
4183 g_assert(sp->last == node);
4184 sp->first = node->n.other;
4185 sp->last = sp->first;
4186 }
4187 node->p.other->n.other = node->n.other;
4188 node->n.other->p.other = node->p.other;
4189 } else {
4190 if (sp->first == node) {
4191 sp->first = node->n.other;
4192 sp->first->code = NR_MOVETO;
4193 }
4194 if (sp->last == node) sp->last = node->p.other;
4195 if (node->p.other) node->p.other->n.other = node->n.other;
4196 if (node->n.other) node->n.other->p.other = node->p.other;
4197 }
4198 } else { // this was the last node on subpath
4199 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4200 }
4202 g_mem_chunk_free(nodechunk, node);
4203 }
4205 /**
4206 * Returns one of the node's two sides.
4207 * \param which Indicates which side.
4208 * \return Pointer to previous node side if which==-1, next if which==1.
4209 */
4210 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4211 {
4212 g_assert(node);
4214 switch (which) {
4215 case -1:
4216 return &node->p;
4217 case 1:
4218 return &node->n;
4219 default:
4220 break;
4221 }
4223 g_assert_not_reached();
4225 return NULL;
4226 }
4228 /**
4229 * Return the other side of the node, given one of its sides.
4230 */
4231 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4232 {
4233 g_assert(node);
4235 if (me == &node->p) return &node->n;
4236 if (me == &node->n) return &node->p;
4238 g_assert_not_reached();
4240 return NULL;
4241 }
4243 /**
4244 * Return NRPathcode on the given side of the node.
4245 */
4246 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4247 {
4248 g_assert(node);
4250 if (me == &node->p) {
4251 if (node->p.other) return (NRPathcode)node->code;
4252 return NR_MOVETO;
4253 }
4255 if (me == &node->n) {
4256 if (node->n.other) return (NRPathcode)node->n.other->code;
4257 return NR_MOVETO;
4258 }
4260 g_assert_not_reached();
4262 return NR_END;
4263 }
4265 /**
4266 * Return node with the given index
4267 */
4268 Inkscape::NodePath::Node *
4269 sp_nodepath_get_node_by_index(int index)
4270 {
4271 Inkscape::NodePath::Node *e = NULL;
4273 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4274 if (!nodepath) {
4275 return e;
4276 }
4278 //find segment
4279 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4281 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4282 int n = g_list_length(sp->nodes);
4283 if (sp->closed) {
4284 n++;
4285 }
4287 //if the piece belongs to this subpath grab it
4288 //otherwise move onto the next subpath
4289 if (index < n) {
4290 e = sp->first;
4291 for (int i = 0; i < index; ++i) {
4292 e = e->n.other;
4293 }
4294 break;
4295 } else {
4296 if (sp->closed) {
4297 index -= (n+1);
4298 } else {
4299 index -= n;
4300 }
4301 }
4302 }
4304 return e;
4305 }
4307 /**
4308 * Returns plain text meaning of node type.
4309 */
4310 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4311 {
4312 unsigned retracted = 0;
4313 bool endnode = false;
4315 for (int which = -1; which <= 1; which += 2) {
4316 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4317 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4318 retracted ++;
4319 if (!side->other)
4320 endnode = true;
4321 }
4323 if (retracted == 0) {
4324 if (endnode) {
4325 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4326 return _("end node");
4327 } else {
4328 switch (node->type) {
4329 case Inkscape::NodePath::NODE_CUSP:
4330 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4331 return _("cusp");
4332 case Inkscape::NodePath::NODE_SMOOTH:
4333 // TRANSLATORS: "smooth" is an adjective here
4334 return _("smooth");
4335 case Inkscape::NodePath::NODE_SYMM:
4336 return _("symmetric");
4337 }
4338 }
4339 } else if (retracted == 1) {
4340 if (endnode) {
4341 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4342 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4343 } else {
4344 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4345 }
4346 } else {
4347 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4348 }
4350 return NULL;
4351 }
4353 /**
4354 * Handles content of statusbar as long as node tool is active.
4355 */
4356 void
4357 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4358 {
4359 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");
4360 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4362 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4363 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4364 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4365 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4367 SPDesktop *desktop = NULL;
4368 if (nodepath) {
4369 desktop = nodepath->desktop;
4370 } else {
4371 desktop = SP_ACTIVE_DESKTOP;
4372 }
4374 SPEventContext *ec = desktop->event_context;
4375 if (!ec) return;
4376 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4377 if (!mc) return;
4379 if (selected_nodes == 0) {
4380 Inkscape::Selection *sel = desktop->selection;
4381 if (!sel || sel->isEmpty()) {
4382 mc->setF(Inkscape::NORMAL_MESSAGE,
4383 _("Select a single object to edit its nodes or handles."));
4384 } else {
4385 if (nodepath) {
4386 mc->setF(Inkscape::NORMAL_MESSAGE,
4387 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.",
4388 "<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.",
4389 total_nodes),
4390 total_nodes);
4391 } else {
4392 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4393 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4394 } else {
4395 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4396 }
4397 }
4398 }
4399 } else if (nodepath && selected_nodes == 1) {
4400 mc->setF(Inkscape::NORMAL_MESSAGE,
4401 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4402 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4403 total_nodes),
4404 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4405 } else {
4406 if (selected_subpaths > 1) {
4407 mc->setF(Inkscape::NORMAL_MESSAGE,
4408 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4409 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4410 total_nodes),
4411 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4412 } else {
4413 mc->setF(Inkscape::NORMAL_MESSAGE,
4414 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4415 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4416 total_nodes),
4417 selected_nodes, total_nodes, when_selected);
4418 }
4419 }
4420 }
4422 /*
4423 * returns a *copy* of the curve of that object.
4424 */
4425 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4426 if (!object)
4427 return NULL;
4429 SPCurve *curve = NULL;
4430 if (SP_IS_PATH(object)) {
4431 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4432 curve = sp_curve_copy(curve_new);
4433 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4434 const gchar *svgd = object->repr->attribute(key);
4435 if (svgd) {
4436 NArtBpath *bpath = sp_svg_read_path(svgd);
4437 SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
4438 if (curve_new) {
4439 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4440 } else {
4441 g_free(bpath);
4442 }
4443 }
4444 }
4446 return curve;
4447 }
4449 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4450 if (!np || !np->object || !curve)
4451 return;
4453 if (SP_IS_PATH(np->object)) {
4454 if (SP_SHAPE(np->object)->path_effect_href) {
4455 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4456 } else {
4457 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4458 }
4459 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4460 // FIXME: this writing to string and then reading from string is bound to be slow.
4461 // create a method to convert from curve directly to 2geom...
4462 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
4463 LIVEPATHEFFECT(np->object)->lpe->setParameter(np->repr_key, svgpath);
4464 g_free(svgpath);
4466 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4467 }
4468 }
4470 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4471 np->show_helperpath = show;
4472 }
4474 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4475 np->straight_path = true;
4476 np->show_handles = false;
4477 g_message("add code to make the path straight.");
4478 // do sp_nodepath_convert_node_type on all nodes?
4479 // search for this text !!! "Make selected segments lines"
4480 }
4483 /*
4484 Local Variables:
4485 mode:c++
4486 c-file-style:"stroustrup"
4487 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4488 indent-tabs-mode:nil
4489 fill-column:99
4490 End:
4491 */
4492 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :