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