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/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "shape-editor.h"
35 #include "selection-chemistry.h"
36 #include "selection.h"
37 #include "xml/repr.h"
38 #include "prefs-utils.h"
39 #include "sp-metrics.h"
40 #include "sp-path.h"
41 #include "libnr/nr-matrix-ops.h"
42 #include "splivarot.h"
43 #include "svg/svg.h"
44 #include "verbs.h"
45 #include "display/bezier-utils.h"
46 #include <vector>
47 #include <algorithm>
49 class NR::Matrix;
51 /// \todo
52 /// evil evil evil. FIXME: conflict of two different Path classes!
53 /// There is a conflict in the namespace between two classes named Path.
54 /// #include "sp-flowtext.h"
55 /// #include "sp-flowregion.h"
57 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
58 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
59 GType sp_flowregion_get_type (void);
60 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
61 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
62 GType sp_flowtext_get_type (void);
63 // end evil workaround
65 #include "helper/stlport.h"
68 /// \todo fixme: Implement these via preferences */
70 #define NODE_FILL 0xbfbfbf00
71 #define NODE_STROKE 0x000000ff
72 #define NODE_FILL_HI 0xff000000
73 #define NODE_STROKE_HI 0x000000ff
74 #define NODE_FILL_SEL 0x0000ffff
75 #define NODE_STROKE_SEL 0x000000ff
76 #define NODE_FILL_SEL_HI 0xff000000
77 #define NODE_STROKE_SEL_HI 0x000000ff
78 #define KNOT_FILL 0xffffffff
79 #define KNOT_STROKE 0x000000ff
80 #define KNOT_FILL_HI 0xff000000
81 #define KNOT_STROKE_HI 0x000000ff
83 static GMemChunk *nodechunk = NULL;
85 /* Creation from object */
87 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
88 static gchar *parse_nodetypes(gchar const *types, gint length);
90 /* Object updating */
92 static void stamp_repr(Inkscape::NodePath::Path *np);
93 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
94 static gchar *create_typestr(Inkscape::NodePath::Path *np);
96 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
98 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
100 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
102 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
104 /* Adjust handle placement, if the node or the other handle is moved */
105 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
106 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
108 /* Node event callbacks */
109 static void node_clicked(SPKnot *knot, guint state, gpointer data);
110 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
111 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
112 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
114 /* Handle event callbacks */
115 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
116 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
117 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
118 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
119 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
120 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
122 /* Constructors and destructors */
124 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
125 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
126 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
127 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
128 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
129 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
130 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
132 /* Helpers */
134 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
135 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
136 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
138 // active_node indicates mouseover node
139 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
141 /**
142 * \brief Creates new nodepath from item
143 */
144 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
145 {
146 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
148 /** \todo
149 * FIXME: remove this. We don't want to edit paths inside flowtext.
150 * Instead we will build our flowtext with cloned paths, so that the
151 * real paths are outside the flowtext and thus editable as usual.
152 */
153 if (SP_IS_FLOWTEXT(item)) {
154 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
155 if SP_IS_FLOWREGION(child) {
156 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
157 if (grandchild && SP_IS_PATH(grandchild)) {
158 item = SP_ITEM(grandchild);
159 break;
160 }
161 }
162 }
163 }
165 if (!SP_IS_PATH(item))
166 return NULL;
167 SPPath *path = SP_PATH(item);
168 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
169 if (curve == NULL)
170 return NULL;
172 NArtBpath *bpath = sp_curve_first_bpath(curve);
173 gint length = curve->end;
174 if (length == 0)
175 return NULL; // prevent crash for one-node paths
177 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
178 gchar *typestr = parse_nodetypes(nodetypes, length);
180 //Create new nodepath
181 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
182 if (!np)
183 return NULL;
185 // Set defaults
186 np->desktop = desktop;
187 np->path = path;
188 np->subpaths = NULL;
189 np->selected = NULL;
190 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
191 np->livarot_path = NULL;
192 np->local_change = 0;
193 np->show_handles = show_handles;
195 // we need to update item's transform from the repr here,
196 // because they may be out of sync when we respond
197 // to a change in repr by regenerating nodepath --bb
198 sp_object_read_attr(SP_OBJECT(item), "transform");
200 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
201 np->d2i = np->i2d.inverse();
202 np->repr = repr;
204 // create the subpath(s) from the bpath
205 NArtBpath *b = bpath;
206 while (b->code != NR_END) {
207 b = subpath_from_bpath(np, b, typestr + (b - bpath));
208 }
210 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
211 np->subpaths = g_list_reverse(np->subpaths);
213 g_free(typestr);
214 sp_curve_unref(curve);
216 // create the livarot representation from the same item
217 sp_nodepath_ensure_livarot_path(np);
219 return np;
220 }
222 /**
223 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
224 */
225 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
227 if (!np) //soft fail, like delete
228 return;
230 while (np->subpaths) {
231 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
232 }
234 //Inform the ShapeEditor that made me, if any, that I am gone.
235 if (np->shape_editor)
236 np->shape_editor->nodepath_destroyed();
238 g_assert(!np->selected);
240 if (np->livarot_path) {
241 delete np->livarot_path;
242 np->livarot_path = NULL;
243 }
245 np->desktop = NULL;
247 g_free(np);
248 }
251 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
252 {
253 if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
254 np->livarot_path = Path_for_item (np->path, true, true);
255 if (np->livarot_path)
256 np->livarot_path->ConvertWithBackData(0.01);
257 }
258 }
261 /**
262 * Return the node count of a given NodeSubPath.
263 */
264 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
265 {
266 if (!subpath)
267 return 0;
268 gint nodeCount = g_list_length(subpath->nodes);
269 return nodeCount;
270 }
272 /**
273 * Return the node count of a given NodePath.
274 */
275 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
276 {
277 if (!np)
278 return 0;
279 gint nodeCount = 0;
280 for (GList *item = np->subpaths ; item ; item=item->next) {
281 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
282 nodeCount += g_list_length(subpath->nodes);
283 }
284 return nodeCount;
285 }
287 /**
288 * Return the subpath count of a given NodePath.
289 */
290 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
291 {
292 if (!np)
293 return 0;
294 return g_list_length (np->subpaths);
295 }
297 /**
298 * Return the selected node count of a given NodePath.
299 */
300 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
301 {
302 if (!np)
303 return 0;
304 return g_list_length (np->selected);
305 }
307 /**
308 * Return the number of subpaths where nodes are selected in a given NodePath.
309 */
310 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
311 {
312 if (!np)
313 return 0;
314 if (!np->selected)
315 return 0;
316 if (!np->selected->next)
317 return 1;
318 gint count = 0;
319 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
320 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
321 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
322 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
323 if (node->selected) {
324 count ++;
325 break;
326 }
327 }
328 }
329 return count;
330 }
332 /**
333 * Clean up a nodepath after editing.
334 *
335 * Currently we are deleting trivial subpaths.
336 */
337 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
338 {
339 GList *badSubPaths = NULL;
341 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
342 for (GList *l = nodepath->subpaths; l ; l=l->next) {
343 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
344 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
345 badSubPaths = g_list_append(badSubPaths, sp);
346 }
348 //Delete them. This second step is because sp_nodepath_subpath_destroy()
349 //also removes the subpath from nodepath->subpaths
350 for (GList *l = badSubPaths; l ; l=l->next) {
351 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
352 sp_nodepath_subpath_destroy(sp);
353 }
355 g_list_free(badSubPaths);
356 }
358 /**
359 * Create new nodepath from b, make it subpath of np.
360 * \param t The node type.
361 * \todo Fixme: t should be a proper type, rather than gchar
362 */
363 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
364 {
365 NR::Point ppos, pos, npos;
367 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
369 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
370 bool const closed = (b->code == NR_MOVETO);
372 pos = NR::Point(b->x3, b->y3) * np->i2d;
373 if (b[1].code == NR_CURVETO) {
374 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
375 } else {
376 npos = pos;
377 }
378 Inkscape::NodePath::Node *n;
379 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
380 g_assert(sp->first == n);
381 g_assert(sp->last == n);
383 b++;
384 t++;
385 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
386 pos = NR::Point(b->x3, b->y3) * np->i2d;
387 if (b->code == NR_CURVETO) {
388 ppos = NR::Point(b->x2, b->y2) * np->i2d;
389 } else {
390 ppos = pos;
391 }
392 if (b[1].code == NR_CURVETO) {
393 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
394 } else {
395 npos = pos;
396 }
397 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
398 b++;
399 t++;
400 }
402 if (closed) sp_nodepath_subpath_close(sp);
404 return b;
405 }
407 /**
408 * Convert from sodipodi:nodetypes to new style type string.
409 */
410 static gchar *parse_nodetypes(gchar const *types, gint length)
411 {
412 g_assert(length > 0);
414 gchar *typestr = g_new(gchar, length + 1);
416 gint pos = 0;
418 if (types) {
419 for (gint i = 0; types[i] && ( i < length ); i++) {
420 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
421 if (types[i] != '\0') {
422 switch (types[i]) {
423 case 's':
424 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
425 break;
426 case 'z':
427 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
428 break;
429 case 'c':
430 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
431 break;
432 default:
433 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
434 break;
435 }
436 }
437 }
438 }
440 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
442 return typestr;
443 }
445 /**
446 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
447 * updated but repr is not (for speed). Used during curve and node drag.
448 */
449 static void update_object(Inkscape::NodePath::Path *np)
450 {
451 g_assert(np);
453 SPCurve *curve = create_curve(np);
455 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
457 sp_curve_unref(curve);
458 }
460 /**
461 * Update XML path node with data from path object.
462 */
463 static void update_repr_internal(Inkscape::NodePath::Path *np)
464 {
465 g_assert(np);
467 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
469 SPCurve *curve = create_curve(np);
470 gchar *typestr = create_typestr(np);
471 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
473 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
474 np->local_change++;
475 repr->setAttribute("d", svgpath);
476 }
478 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
479 np->local_change++;
480 repr->setAttribute("sodipodi:nodetypes", typestr);
481 }
483 g_free(svgpath);
484 g_free(typestr);
485 sp_curve_unref(curve);
486 }
488 /**
489 * Update XML path node with data from path object, commit changes forever.
490 */
491 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
492 {
493 //fixme: np can be NULL, so check before proceeding
494 g_return_if_fail(np != NULL);
496 if (np->livarot_path) {
497 delete np->livarot_path;
498 np->livarot_path = NULL;
499 }
501 update_repr_internal(np);
502 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
504 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
505 annotation);
506 }
508 /**
509 * Update XML path node with data from path object, commit changes with undo.
510 */
511 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
512 {
513 if (np->livarot_path) {
514 delete np->livarot_path;
515 np->livarot_path = NULL;
516 }
518 update_repr_internal(np);
519 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
520 annotation);
521 }
523 /**
524 * Make duplicate of path, replace corresponding XML node in tree, commit.
525 */
526 static void stamp_repr(Inkscape::NodePath::Path *np)
527 {
528 g_assert(np);
530 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
531 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
533 // remember the position of the item
534 gint pos = old_repr->position();
535 // remember parent
536 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
538 SPCurve *curve = create_curve(np);
539 gchar *typestr = create_typestr(np);
541 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
543 new_repr->setAttribute("d", svgpath);
544 new_repr->setAttribute("sodipodi:nodetypes", typestr);
546 // add the new repr to the parent
547 parent->appendChild(new_repr);
548 // move to the saved position
549 new_repr->setPosition(pos > 0 ? pos : 0);
551 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
552 _("Stamp"));
554 Inkscape::GC::release(new_repr);
555 g_free(svgpath);
556 g_free(typestr);
557 sp_curve_unref(curve);
558 }
560 /**
561 * Create curve from path.
562 */
563 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
564 {
565 SPCurve *curve = sp_curve_new();
567 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
568 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
569 sp_curve_moveto(curve,
570 sp->first->pos * np->d2i);
571 Inkscape::NodePath::Node *n = sp->first->n.other;
572 while (n) {
573 NR::Point const end_pt = n->pos * np->d2i;
574 switch (n->code) {
575 case NR_LINETO:
576 sp_curve_lineto(curve, end_pt);
577 break;
578 case NR_CURVETO:
579 sp_curve_curveto(curve,
580 n->p.other->n.pos * np->d2i,
581 n->p.pos * np->d2i,
582 end_pt);
583 break;
584 default:
585 g_assert_not_reached();
586 break;
587 }
588 if (n != sp->last) {
589 n = n->n.other;
590 } else {
591 n = NULL;
592 }
593 }
594 if (sp->closed) {
595 sp_curve_closepath(curve);
596 }
597 }
599 return curve;
600 }
602 /**
603 * Convert path type string to sodipodi:nodetypes style.
604 */
605 static gchar *create_typestr(Inkscape::NodePath::Path *np)
606 {
607 gchar *typestr = g_new(gchar, 32);
608 gint len = 32;
609 gint pos = 0;
611 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
612 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
614 if (pos >= len) {
615 typestr = g_renew(gchar, typestr, len + 32);
616 len += 32;
617 }
619 typestr[pos++] = 'c';
621 Inkscape::NodePath::Node *n;
622 n = sp->first->n.other;
623 while (n) {
624 gchar code;
626 switch (n->type) {
627 case Inkscape::NodePath::NODE_CUSP:
628 code = 'c';
629 break;
630 case Inkscape::NodePath::NODE_SMOOTH:
631 code = 's';
632 break;
633 case Inkscape::NodePath::NODE_SYMM:
634 code = 'z';
635 break;
636 default:
637 g_assert_not_reached();
638 code = '\0';
639 break;
640 }
642 if (pos >= len) {
643 typestr = g_renew(gchar, typestr, len + 32);
644 len += 32;
645 }
647 typestr[pos++] = code;
649 if (n != sp->last) {
650 n = n->n.other;
651 } else {
652 n = NULL;
653 }
654 }
655 }
657 if (pos >= len) {
658 typestr = g_renew(gchar, typestr, len + 1);
659 len += 1;
660 }
662 typestr[pos++] = '\0';
664 return typestr;
665 }
667 /**
668 * Returns current path in context. // later eliminate this function at all!
669 */
670 static Inkscape::NodePath::Path *sp_nodepath_current()
671 {
672 if (!SP_ACTIVE_DESKTOP) {
673 return NULL;
674 }
676 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
678 if (!SP_IS_NODE_CONTEXT(event_context)) {
679 return NULL;
680 }
682 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
683 }
687 /**
688 \brief Fills node and handle positions for three nodes, splitting line
689 marked by end at distance t.
690 */
691 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
692 {
693 g_assert(new_path != NULL);
694 g_assert(end != NULL);
696 g_assert(end->p.other == new_path);
697 Inkscape::NodePath::Node *start = new_path->p.other;
698 g_assert(start);
700 if (end->code == NR_LINETO) {
701 new_path->type =Inkscape::NodePath::NODE_CUSP;
702 new_path->code = NR_LINETO;
703 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
704 } else {
705 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
706 new_path->code = NR_CURVETO;
707 gdouble s = 1 - t;
708 for (int dim = 0; dim < 2; dim++) {
709 NR::Coord const f000 = start->pos[dim];
710 NR::Coord const f001 = start->n.pos[dim];
711 NR::Coord const f011 = end->p.pos[dim];
712 NR::Coord const f111 = end->pos[dim];
713 NR::Coord const f00t = s * f000 + t * f001;
714 NR::Coord const f01t = s * f001 + t * f011;
715 NR::Coord const f11t = s * f011 + t * f111;
716 NR::Coord const f0tt = s * f00t + t * f01t;
717 NR::Coord const f1tt = s * f01t + t * f11t;
718 NR::Coord const fttt = s * f0tt + t * f1tt;
719 start->n.pos[dim] = f00t;
720 new_path->p.pos[dim] = f0tt;
721 new_path->pos[dim] = fttt;
722 new_path->n.pos[dim] = f1tt;
723 end->p.pos[dim] = f11t;
724 }
725 }
726 }
728 /**
729 * Adds new node on direct line between two nodes, activates handles of all
730 * three nodes.
731 */
732 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
733 {
734 g_assert(end);
735 g_assert(end->subpath);
736 g_assert(g_list_find(end->subpath->nodes, end));
738 Inkscape::NodePath::Node *start = end->p.other;
739 g_assert( start->n.other == end );
740 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
741 end,
742 (NRPathcode)end->code == NR_LINETO?
743 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
744 (NRPathcode)end->code,
745 &start->pos, &start->pos, &start->n.pos);
746 sp_nodepath_line_midpoint(newnode, end, t);
748 sp_node_adjust_handles(start);
749 sp_node_update_handles(start);
750 sp_node_update_handles(newnode);
751 sp_node_adjust_handles(end);
752 sp_node_update_handles(end);
754 return newnode;
755 }
757 /**
758 \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
759 */
760 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
761 {
762 g_assert(node);
763 g_assert(node->subpath);
764 g_assert(g_list_find(node->subpath->nodes, node));
766 Inkscape::NodePath::SubPath *sp = node->subpath;
767 Inkscape::NodePath::Path *np = sp->nodepath;
769 if (sp->closed) {
770 sp_nodepath_subpath_open(sp, node);
771 return sp->first;
772 } else {
773 // no break for end nodes
774 if (node == sp->first) return NULL;
775 if (node == sp->last ) return NULL;
777 // create a new subpath
778 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
780 // duplicate the break node as start of the new subpath
781 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
783 while (node->n.other) { // copy the remaining nodes into the new subpath
784 Inkscape::NodePath::Node *n = node->n.other;
785 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);
786 if (n->selected) {
787 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
788 }
789 sp_nodepath_node_destroy(n); // remove the point on the original subpath
790 }
792 return newnode;
793 }
794 }
796 /**
797 * Duplicate node and connect to neighbours.
798 */
799 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
800 {
801 g_assert(node);
802 g_assert(node->subpath);
803 g_assert(g_list_find(node->subpath->nodes, node));
805 Inkscape::NodePath::SubPath *sp = node->subpath;
807 NRPathcode code = (NRPathcode) node->code;
808 if (code == NR_MOVETO) { // if node is the endnode,
809 node->code = NR_LINETO; // new one is inserted before it, so change that to line
810 }
812 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
814 if (!node->n.other || !node->p.other) // if node is an endnode, select it
815 return node;
816 else
817 return newnode; // otherwise select the newly created node
818 }
820 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
821 {
822 node->p.pos = (node->pos + (node->pos - node->n.pos));
823 }
825 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
826 {
827 node->n.pos = (node->pos + (node->pos - node->p.pos));
828 }
830 /**
831 * Change line type at node, with side effects on neighbours.
832 */
833 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
834 {
835 g_assert(end);
836 g_assert(end->subpath);
837 g_assert(end->p.other);
839 if (end->code == static_cast< guint > ( code ) )
840 return;
842 Inkscape::NodePath::Node *start = end->p.other;
844 end->code = code;
846 if (code == NR_LINETO) {
847 if (start->code == NR_LINETO) {
848 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
849 }
850 if (end->n.other) {
851 if (end->n.other->code == NR_LINETO) {
852 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
853 }
854 }
855 } else {
856 NR::Point delta = end->pos - start->pos;
857 start->n.pos = start->pos + delta / 3;
858 end->p.pos = end->pos - delta / 3;
859 sp_node_adjust_handle(start, 1);
860 sp_node_adjust_handle(end, -1);
861 }
863 sp_node_update_handles(start);
864 sp_node_update_handles(end);
865 }
867 /**
868 * Change node type, and its handles accordingly.
869 */
870 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
871 {
872 g_assert(node);
873 g_assert(node->subpath);
875 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
876 return node;
878 if ((node->p.other != NULL) && (node->n.other != NULL)) {
879 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
880 type =Inkscape::NodePath::NODE_CUSP;
881 }
882 }
884 node->type = type;
886 if (node->type == Inkscape::NodePath::NODE_CUSP) {
887 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
888 node->knot->setSize (node->selected? 11 : 9);
889 sp_knot_update_ctrl(node->knot);
890 } else {
891 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
892 node->knot->setSize (node->selected? 9 : 7);
893 sp_knot_update_ctrl(node->knot);
894 }
896 // if one of handles is mouseovered, preserve its position
897 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
898 sp_node_adjust_handle(node, 1);
899 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
900 sp_node_adjust_handle(node, -1);
901 } else {
902 sp_node_adjust_handles(node);
903 }
905 sp_node_update_handles(node);
907 sp_nodepath_update_statusbar(node->subpath->nodepath);
909 return node;
910 }
912 /**
913 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
914 * adjacent segments from lines to curves.
915 */
916 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
917 {
918 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
919 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
921 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
922 if (p_line && n_line) {
923 // only if both adjacent segments are lines,
924 // convert both to curves:
926 // BEFORE:
927 {
928 node->code = NR_CURVETO;
929 NR::Point delta = node->n.other->pos - node->p.other->pos;
930 node->p.pos = node->pos - delta / 4;
931 }
933 // AFTER:
934 {
935 node->n.other->code = NR_CURVETO;
936 NR::Point delta = node->p.other->pos - node->n.other->pos;
937 node->n.pos = node->pos - delta / 4;
938 }
940 sp_node_update_handles(node);
941 }
942 }
944 sp_nodepath_set_node_type (node, type);
945 }
947 /**
948 * Move node to point, and adjust its and neighbouring handles.
949 */
950 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
951 {
952 NR::Point delta = p - node->pos;
953 node->pos = p;
955 node->p.pos += delta;
956 node->n.pos += delta;
958 Inkscape::NodePath::Node *node_p = NULL;
959 Inkscape::NodePath::Node *node_n = NULL;
961 if (node->p.other) {
962 if (node->code == NR_LINETO) {
963 sp_node_adjust_handle(node, 1);
964 sp_node_adjust_handle(node->p.other, -1);
965 node_p = node->p.other;
966 }
967 }
968 if (node->n.other) {
969 if (node->n.other->code == NR_LINETO) {
970 sp_node_adjust_handle(node, -1);
971 sp_node_adjust_handle(node->n.other, 1);
972 node_n = node->n.other;
973 }
974 }
976 // this function is only called from batch movers that will update display at the end
977 // themselves, so here we just move all the knots without emitting move signals, for speed
978 sp_node_update_handles(node, false);
979 if (node_n) {
980 sp_node_update_handles(node_n, false);
981 }
982 if (node_p) {
983 sp_node_update_handles(node_p, false);
984 }
985 }
987 /**
988 * Call sp_node_moveto() for node selection and handle possible snapping.
989 */
990 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
991 bool const snap = true)
992 {
993 NR::Coord best = NR_HUGE;
994 NR::Point delta(dx, dy);
995 NR::Point best_pt = delta;
997 if (snap) {
998 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
1000 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1001 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1002 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, n->subpath->nodepath->path);
1003 if (s.getDistance() < best) {
1004 best = s.getDistance();
1005 best_pt = s.getPoint() - n->pos;
1006 }
1007 }
1008 }
1010 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1011 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1012 sp_node_moveto(n, n->pos + best_pt);
1013 }
1015 // do not update repr here so that node dragging is acceptably fast
1016 update_object(nodepath);
1017 }
1019 /**
1020 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1021 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1022 near x = 0.
1023 */
1024 double
1025 sculpt_profile (double x, double alpha, guint profile)
1026 {
1027 if (x >= 1)
1028 return 0;
1029 if (x <= 0)
1030 return 1;
1032 switch (profile) {
1033 case SCULPT_PROFILE_LINEAR:
1034 return 1 - x;
1035 case SCULPT_PROFILE_BELL:
1036 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1037 case SCULPT_PROFILE_ELLIPTIC:
1038 return sqrt(1 - x*x);
1039 }
1041 return 1;
1042 }
1044 double
1045 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1046 {
1047 // extremely primitive for now, don't have time to look for the real one
1048 double lower = NR::L2(b - a);
1049 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1050 return (lower + upper)/2;
1051 }
1053 void
1054 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1055 {
1056 n->pos = n->origin + delta;
1057 n->n.pos = n->n.origin + delta_n;
1058 n->p.pos = n->p.origin + delta_p;
1059 sp_node_adjust_handles(n);
1060 sp_node_update_handles(n, false);
1061 }
1063 /**
1064 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1065 * on how far they are from the dragged node n.
1066 */
1067 static void
1068 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1069 {
1070 g_assert (n);
1071 g_assert (nodepath);
1072 g_assert (n->subpath->nodepath == nodepath);
1074 double pressure = n->knot->pressure;
1075 if (pressure == 0)
1076 pressure = 0.5; // default
1077 pressure = CLAMP (pressure, 0.2, 0.8);
1079 // map pressure to alpha = 1/5 ... 5
1080 double alpha = 1 - 2 * fabs(pressure - 0.5);
1081 if (pressure > 0.5)
1082 alpha = 1/alpha;
1084 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1086 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1087 // Only one subpath has selected nodes:
1088 // use linear mode, where the distance from n to node being dragged is calculated along the path
1090 double n_sel_range = 0, p_sel_range = 0;
1091 guint n_nodes = 0, p_nodes = 0;
1092 guint n_sel_nodes = 0, p_sel_nodes = 0;
1094 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1095 {
1096 double n_range = 0, p_range = 0;
1097 bool n_going = true, p_going = true;
1098 Inkscape::NodePath::Node *n_node = n;
1099 Inkscape::NodePath::Node *p_node = n;
1100 do {
1101 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1102 if (n_node && n_going)
1103 n_node = n_node->n.other;
1104 if (n_node == NULL) {
1105 n_going = false;
1106 } else {
1107 n_nodes ++;
1108 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1109 if (n_node->selected) {
1110 n_sel_nodes ++;
1111 n_sel_range = n_range;
1112 }
1113 if (n_node == p_node) {
1114 n_going = false;
1115 p_going = false;
1116 }
1117 }
1118 if (p_node && p_going)
1119 p_node = p_node->p.other;
1120 if (p_node == NULL) {
1121 p_going = false;
1122 } else {
1123 p_nodes ++;
1124 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1125 if (p_node->selected) {
1126 p_sel_nodes ++;
1127 p_sel_range = p_range;
1128 }
1129 if (p_node == n_node) {
1130 n_going = false;
1131 p_going = false;
1132 }
1133 }
1134 } while (n_going || p_going);
1135 }
1137 // Second pass: actually move nodes in this subpath
1138 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1139 {
1140 double n_range = 0, p_range = 0;
1141 bool n_going = true, p_going = true;
1142 Inkscape::NodePath::Node *n_node = n;
1143 Inkscape::NodePath::Node *p_node = n;
1144 do {
1145 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1146 if (n_node && n_going)
1147 n_node = n_node->n.other;
1148 if (n_node == NULL) {
1149 n_going = false;
1150 } else {
1151 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1152 if (n_node->selected) {
1153 sp_nodepath_move_node_and_handles (n_node,
1154 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1155 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1156 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1157 }
1158 if (n_node == p_node) {
1159 n_going = false;
1160 p_going = false;
1161 }
1162 }
1163 if (p_node && p_going)
1164 p_node = p_node->p.other;
1165 if (p_node == NULL) {
1166 p_going = false;
1167 } else {
1168 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1169 if (p_node->selected) {
1170 sp_nodepath_move_node_and_handles (p_node,
1171 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1172 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1173 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1174 }
1175 if (p_node == n_node) {
1176 n_going = false;
1177 p_going = false;
1178 }
1179 }
1180 } while (n_going || p_going);
1181 }
1183 } else {
1184 // Multiple subpaths have selected nodes:
1185 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1186 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1187 // fix the pear-like shape when sculpting e.g. a ring
1189 // First pass: calculate range
1190 gdouble direct_range = 0;
1191 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1192 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1193 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1194 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1195 if (node->selected) {
1196 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1197 }
1198 }
1199 }
1201 // Second pass: actually move nodes
1202 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1203 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1204 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1205 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1206 if (node->selected) {
1207 if (direct_range > 1e-6) {
1208 sp_nodepath_move_node_and_handles (node,
1209 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1210 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1211 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1212 } else {
1213 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1214 }
1216 }
1217 }
1218 }
1219 }
1221 // do not update repr here so that node dragging is acceptably fast
1222 update_object(nodepath);
1223 }
1226 /**
1227 * Move node selection to point, adjust its and neighbouring handles,
1228 * handle possible snapping, and commit the change with possible undo.
1229 */
1230 void
1231 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1232 {
1233 if (!nodepath) return;
1235 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1237 if (dx == 0) {
1238 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1239 } else if (dy == 0) {
1240 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1241 } else {
1242 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1243 }
1244 }
1246 /**
1247 * Move node selection off screen and commit the change.
1248 */
1249 void
1250 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1251 {
1252 // borrowed from sp_selection_move_screen in selection-chemistry.c
1253 // we find out the current zoom factor and divide deltas by it
1254 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1256 gdouble zoom = desktop->current_zoom();
1257 gdouble zdx = dx / zoom;
1258 gdouble zdy = dy / zoom;
1260 if (!nodepath) return;
1262 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1264 if (dx == 0) {
1265 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1266 } else if (dy == 0) {
1267 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1268 } else {
1269 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1270 }
1271 }
1273 /** If they don't yet exist, creates knot and line for the given side of the node */
1274 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1275 {
1276 if (!side->knot) {
1277 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"));
1279 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1280 side->knot->setSize (7);
1281 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1282 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1283 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1284 sp_knot_update_ctrl(side->knot);
1286 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1287 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1288 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1289 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1290 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1291 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1292 }
1294 if (!side->line) {
1295 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1296 SP_TYPE_CTRLLINE, NULL);
1297 }
1298 }
1300 /**
1301 * Ensure the given handle of the node is visible/invisible, update its screen position
1302 */
1303 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1304 {
1305 g_assert(node != NULL);
1307 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1308 NRPathcode code = sp_node_path_code_from_side(node, side);
1310 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1312 if (show_handle) {
1313 if (!side->knot) { // No handle knot at all
1314 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1315 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1316 side->knot->pos = side->pos;
1317 if (side->knot->item)
1318 SP_CTRL(side->knot->item)->moveto(side->pos);
1319 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1320 sp_knot_show(side->knot);
1321 } else {
1322 if (side->knot->pos != side->pos) { // only if it's really moved
1323 if (fire_move_signals) {
1324 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1325 } else {
1326 sp_knot_moveto(side->knot, &side->pos);
1327 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1328 }
1329 }
1330 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1331 sp_knot_show(side->knot);
1332 }
1333 }
1334 sp_canvas_item_show(side->line);
1335 } else {
1336 if (side->knot) {
1337 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1338 sp_knot_hide(side->knot);
1339 }
1340 }
1341 if (side->line) {
1342 sp_canvas_item_hide(side->line);
1343 }
1344 }
1345 }
1347 /**
1348 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1349 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1350 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1351 * updated; otherwise, just move the knots silently (used in batch moves).
1352 */
1353 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1354 {
1355 g_assert(node != NULL);
1357 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1358 sp_knot_show(node->knot);
1359 }
1361 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1362 if (fire_move_signals)
1363 sp_knot_set_position(node->knot, &node->pos, 0);
1364 else
1365 sp_knot_moveto(node->knot, &node->pos);
1366 }
1368 gboolean show_handles = node->selected;
1369 if (node->p.other != NULL) {
1370 if (node->p.other->selected) show_handles = TRUE;
1371 }
1372 if (node->n.other != NULL) {
1373 if (node->n.other->selected) show_handles = TRUE;
1374 }
1376 if (node->subpath->nodepath->show_handles == false)
1377 show_handles = FALSE;
1379 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1380 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1381 }
1383 /**
1384 * Call sp_node_update_handles() for all nodes on subpath.
1385 */
1386 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1387 {
1388 g_assert(subpath != NULL);
1390 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1391 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1392 }
1393 }
1395 /**
1396 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1397 */
1398 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1399 {
1400 g_assert(nodepath != NULL);
1402 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1403 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1404 }
1405 }
1407 void
1408 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1409 {
1410 if (nodepath == NULL) return;
1412 nodepath->show_handles = show;
1413 sp_nodepath_update_handles(nodepath);
1414 }
1416 /**
1417 * Adds all selected nodes in nodepath to list.
1418 */
1419 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1420 {
1421 StlConv<Node *>::list(l, selected);
1422 /// \todo this adds a copying, rework when the selection becomes a stl list
1423 }
1425 /**
1426 * Align selected nodes on the specified axis.
1427 */
1428 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1429 {
1430 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1431 return;
1432 }
1434 if ( !nodepath->selected->next ) { // only one node selected
1435 return;
1436 }
1437 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1438 NR::Point dest(pNode->pos);
1439 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1440 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1441 if (pNode) {
1442 dest[axis] = pNode->pos[axis];
1443 sp_node_moveto(pNode, dest);
1444 }
1445 }
1447 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1448 }
1450 /// Helper struct.
1451 struct NodeSort
1452 {
1453 Inkscape::NodePath::Node *_node;
1454 NR::Coord _coord;
1455 /// \todo use vectorof pointers instead of calling copy ctor
1456 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1457 _node(node), _coord(node->pos[axis])
1458 {}
1460 };
1462 static bool operator<(NodeSort const &a, NodeSort const &b)
1463 {
1464 return (a._coord < b._coord);
1465 }
1467 /**
1468 * Distribute selected nodes on the specified axis.
1469 */
1470 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1471 {
1472 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1473 return;
1474 }
1476 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1477 return;
1478 }
1480 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1481 std::vector<NodeSort> sorted;
1482 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1483 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1484 if (pNode) {
1485 NodeSort n(pNode, axis);
1486 sorted.push_back(n);
1487 //dest[axis] = pNode->pos[axis];
1488 //sp_node_moveto(pNode, dest);
1489 }
1490 }
1491 std::sort(sorted.begin(), sorted.end());
1492 unsigned int len = sorted.size();
1493 //overall bboxes span
1494 float dist = (sorted.back()._coord -
1495 sorted.front()._coord);
1496 //new distance between each bbox
1497 float step = (dist) / (len - 1);
1498 float pos = sorted.front()._coord;
1499 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1500 it < sorted.end();
1501 it ++ )
1502 {
1503 NR::Point dest((*it)._node->pos);
1504 dest[axis] = pos;
1505 sp_node_moveto((*it)._node, dest);
1506 pos += step;
1507 }
1509 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1510 }
1513 /**
1514 * Call sp_nodepath_line_add_node() for all selected segments.
1515 */
1516 void
1517 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1518 {
1519 if (!nodepath) {
1520 return;
1521 }
1523 GList *nl = NULL;
1525 int n_added = 0;
1527 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1528 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1529 g_assert(t->selected);
1530 if (t->p.other && t->p.other->selected) {
1531 nl = g_list_prepend(nl, t);
1532 }
1533 }
1535 while (nl) {
1536 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1537 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1538 sp_nodepath_node_select(n, TRUE, FALSE);
1539 n_added ++;
1540 nl = g_list_remove(nl, t);
1541 }
1543 /** \todo fixme: adjust ? */
1544 sp_nodepath_update_handles(nodepath);
1546 if (n_added > 1) {
1547 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1548 } else if (n_added > 0) {
1549 sp_nodepath_update_repr(nodepath, _("Add node"));
1550 }
1552 sp_nodepath_update_statusbar(nodepath);
1553 }
1555 /**
1556 * Select segment nearest to point
1557 */
1558 void
1559 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1560 {
1561 if (!nodepath) {
1562 return;
1563 }
1565 sp_nodepath_ensure_livarot_path(nodepath);
1566 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1567 if (!maybe_position) {
1568 return;
1569 }
1570 Path::cut_position position = *maybe_position;
1572 //find segment to segment
1573 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1575 //fixme: this can return NULL, so check before proceeding.
1576 g_return_if_fail(e != NULL);
1578 gboolean force = FALSE;
1579 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1580 force = TRUE;
1581 }
1582 sp_nodepath_node_select(e, (gboolean) toggle, force);
1583 if (e->p.other)
1584 sp_nodepath_node_select(e->p.other, TRUE, force);
1586 sp_nodepath_update_handles(nodepath);
1588 sp_nodepath_update_statusbar(nodepath);
1589 }
1591 /**
1592 * Add a node nearest to point
1593 */
1594 void
1595 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1596 {
1597 if (!nodepath) {
1598 return;
1599 }
1601 sp_nodepath_ensure_livarot_path(nodepath);
1602 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1603 if (!maybe_position) {
1604 return;
1605 }
1606 Path::cut_position position = *maybe_position;
1608 //find segment to split
1609 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1611 //don't know why but t seems to flip for lines
1612 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1613 position.t = 1.0 - position.t;
1614 }
1615 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1616 sp_nodepath_node_select(n, FALSE, TRUE);
1618 /* fixme: adjust ? */
1619 sp_nodepath_update_handles(nodepath);
1621 sp_nodepath_update_repr(nodepath, _("Add node"));
1623 sp_nodepath_update_statusbar(nodepath);
1624 }
1626 /*
1627 * Adjusts a segment so that t moves by a certain delta for dragging
1628 * converts lines to curves
1629 *
1630 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1631 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1632 */
1633 void
1634 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1635 {
1636 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1638 //fixme: e and e->p can be NULL, so check for those before proceeding
1639 g_return_if_fail(e != NULL);
1640 g_return_if_fail(&e->p != NULL);
1642 /* feel good is an arbitrary parameter that distributes the delta between handles
1643 * if t of the drag point is less than 1/6 distance form the endpoint only
1644 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1645 */
1646 double feel_good;
1647 if (t <= 1.0 / 6.0)
1648 feel_good = 0;
1649 else if (t <= 0.5)
1650 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1651 else if (t <= 5.0 / 6.0)
1652 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1653 else
1654 feel_good = 1;
1656 //if we're dragging a line convert it to a curve
1657 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1658 sp_nodepath_set_line_type(e, NR_CURVETO);
1659 }
1661 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1662 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1663 e->p.other->n.pos += offsetcoord0;
1664 e->p.pos += offsetcoord1;
1666 // adjust handles of adjacent nodes where necessary
1667 sp_node_adjust_handle(e,1);
1668 sp_node_adjust_handle(e->p.other,-1);
1670 sp_nodepath_update_handles(e->subpath->nodepath);
1672 update_object(e->subpath->nodepath);
1674 sp_nodepath_update_statusbar(e->subpath->nodepath);
1675 }
1678 /**
1679 * Call sp_nodepath_break() for all selected segments.
1680 */
1681 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
1682 {
1683 if (!nodepath) return;
1685 GList *temp = NULL;
1686 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1687 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1688 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1689 if (nn == NULL) continue; // no break, no new node
1690 temp = g_list_prepend(temp, nn);
1691 }
1693 if (temp) {
1694 sp_nodepath_deselect(nodepath);
1695 }
1696 for (GList *l = temp; l != NULL; l = l->next) {
1697 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1698 }
1700 sp_nodepath_update_handles(nodepath);
1702 sp_nodepath_update_repr(nodepath, _("Break path"));
1703 }
1705 /**
1706 * Duplicate the selected node(s).
1707 */
1708 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
1709 {
1710 if (!nodepath) {
1711 return;
1712 }
1714 GList *temp = NULL;
1715 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1716 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1717 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1718 if (nn == NULL) continue; // could not duplicate
1719 temp = g_list_prepend(temp, nn);
1720 }
1722 if (temp) {
1723 sp_nodepath_deselect(nodepath);
1724 }
1725 for (GList *l = temp; l != NULL; l = l->next) {
1726 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1727 }
1729 sp_nodepath_update_handles(nodepath);
1731 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1732 }
1734 /**
1735 * Join two nodes by merging them into one.
1736 */
1737 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
1738 {
1739 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1741 if (g_list_length(nodepath->selected) != 2) {
1742 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1743 return;
1744 }
1746 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1747 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1749 g_assert(a != b);
1750 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1751 // someone tried to join an orphan node (i.e. a single-node subpath).
1752 // this is not worth an error message, just fail silently.
1753 return;
1754 }
1756 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1757 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1758 return;
1759 }
1761 /* a and b are endpoints */
1763 NR::Point c;
1764 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1765 c = a->pos;
1766 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1767 c = b->pos;
1768 } else {
1769 c = (a->pos + b->pos) / 2;
1770 }
1772 if (a->subpath == b->subpath) {
1773 Inkscape::NodePath::SubPath *sp = a->subpath;
1774 sp_nodepath_subpath_close(sp);
1775 sp_node_moveto (sp->first, c);
1777 sp_nodepath_update_handles(sp->nodepath);
1778 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1779 return;
1780 }
1782 /* a and b are separate subpaths */
1783 Inkscape::NodePath::SubPath *sa = a->subpath;
1784 Inkscape::NodePath::SubPath *sb = b->subpath;
1785 NR::Point p;
1786 Inkscape::NodePath::Node *n;
1787 NRPathcode code;
1788 if (a == sa->first) {
1789 p = sa->first->n.pos;
1790 code = (NRPathcode)sa->first->n.other->code;
1791 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1792 n = sa->last;
1793 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1794 n = n->p.other;
1795 while (n) {
1796 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1797 n = n->p.other;
1798 if (n == sa->first) n = NULL;
1799 }
1800 sp_nodepath_subpath_destroy(sa);
1801 sa = t;
1802 } else if (a == sa->last) {
1803 p = sa->last->p.pos;
1804 code = (NRPathcode)sa->last->code;
1805 sp_nodepath_node_destroy(sa->last);
1806 } else {
1807 code = NR_END;
1808 g_assert_not_reached();
1809 }
1811 if (b == sb->first) {
1812 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1813 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1814 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1815 }
1816 } else if (b == sb->last) {
1817 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1818 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1819 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1820 }
1821 } else {
1822 g_assert_not_reached();
1823 }
1824 /* and now destroy sb */
1826 sp_nodepath_subpath_destroy(sb);
1828 sp_nodepath_update_handles(sa->nodepath);
1830 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1832 sp_nodepath_update_statusbar(nodepath);
1833 }
1835 /**
1836 * Join two nodes by adding a segment between them.
1837 */
1838 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
1839 {
1840 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1842 if (g_list_length(nodepath->selected) != 2) {
1843 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1844 return;
1845 }
1847 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1848 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1850 g_assert(a != b);
1851 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1852 // someone tried to join an orphan node (i.e. a single-node subpath).
1853 // this is not worth an error message, just fail silently.
1854 return;
1855 }
1857 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1858 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1859 return;
1860 }
1862 if (a->subpath == b->subpath) {
1863 Inkscape::NodePath::SubPath *sp = a->subpath;
1865 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1866 sp->closed = TRUE;
1868 sp->first->p.other = sp->last;
1869 sp->last->n.other = sp->first;
1871 sp_node_handle_mirror_p_to_n(sp->last);
1872 sp_node_handle_mirror_n_to_p(sp->first);
1874 sp->first->code = sp->last->code;
1875 sp->first = sp->last;
1877 sp_nodepath_update_handles(sp->nodepath);
1879 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1881 return;
1882 }
1884 /* a and b are separate subpaths */
1885 Inkscape::NodePath::SubPath *sa = a->subpath;
1886 Inkscape::NodePath::SubPath *sb = b->subpath;
1888 Inkscape::NodePath::Node *n;
1889 NR::Point p;
1890 NRPathcode code;
1891 if (a == sa->first) {
1892 code = (NRPathcode) sa->first->n.other->code;
1893 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1894 n = sa->last;
1895 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1896 for (n = n->p.other; n != NULL; n = n->p.other) {
1897 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1898 }
1899 sp_nodepath_subpath_destroy(sa);
1900 sa = t;
1901 } else if (a == sa->last) {
1902 code = (NRPathcode)sa->last->code;
1903 } else {
1904 code = NR_END;
1905 g_assert_not_reached();
1906 }
1908 if (b == sb->first) {
1909 n = sb->first;
1910 sp_node_handle_mirror_p_to_n(sa->last);
1911 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1912 sp_node_handle_mirror_n_to_p(sa->last);
1913 for (n = n->n.other; n != NULL; n = n->n.other) {
1914 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1915 }
1916 } else if (b == sb->last) {
1917 n = sb->last;
1918 sp_node_handle_mirror_p_to_n(sa->last);
1919 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1920 sp_node_handle_mirror_n_to_p(sa->last);
1921 for (n = n->p.other; n != NULL; n = n->p.other) {
1922 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1923 }
1924 } else {
1925 g_assert_not_reached();
1926 }
1927 /* and now destroy sb */
1929 sp_nodepath_subpath_destroy(sb);
1931 sp_nodepath_update_handles(sa->nodepath);
1933 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1934 }
1936 /**
1937 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1938 */
1939 void sp_node_delete_preserve(GList *nodes_to_delete)
1940 {
1941 GSList *nodepaths = NULL;
1943 while (nodes_to_delete) {
1944 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1945 Inkscape::NodePath::SubPath *sp = node->subpath;
1946 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1947 Inkscape::NodePath::Node *sample_cursor = NULL;
1948 Inkscape::NodePath::Node *sample_end = NULL;
1949 Inkscape::NodePath::Node *delete_cursor = node;
1950 bool just_delete = false;
1952 //find the start of this contiguous selection
1953 //move left to the first node that is not selected
1954 //or the start of the non-closed path
1955 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1956 delete_cursor = curr;
1957 }
1959 //just delete at the beginning of an open path
1960 if (!delete_cursor->p.other) {
1961 sample_cursor = delete_cursor;
1962 just_delete = true;
1963 } else {
1964 sample_cursor = delete_cursor->p.other;
1965 }
1967 //calculate points for each segment
1968 int rate = 5;
1969 float period = 1.0 / rate;
1970 std::vector<NR::Point> data;
1971 if (!just_delete) {
1972 data.push_back(sample_cursor->pos);
1973 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1974 //just delete at the end of an open path
1975 if (!sp->closed && curr == sp->last) {
1976 just_delete = true;
1977 break;
1978 }
1980 //sample points on the contiguous selected segment
1981 NR::Point *bez;
1982 bez = new NR::Point [4];
1983 bez[0] = curr->pos;
1984 bez[1] = curr->n.pos;
1985 bez[2] = curr->n.other->p.pos;
1986 bez[3] = curr->n.other->pos;
1987 for (int i=1; i<rate; i++) {
1988 gdouble t = i * period;
1989 NR::Point p = bezier_pt(3, bez, t);
1990 data.push_back(p);
1991 }
1992 data.push_back(curr->n.other->pos);
1994 sample_end = curr->n.other;
1995 //break if we've come full circle or hit the end of the selection
1996 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1997 break;
1998 }
1999 }
2000 }
2002 if (!just_delete) {
2003 //calculate the best fitting single segment and adjust the endpoints
2004 NR::Point *adata;
2005 adata = new NR::Point [data.size()];
2006 copy(data.begin(), data.end(), adata);
2008 NR::Point *bez;
2009 bez = new NR::Point [4];
2010 //would decreasing error create a better fitting approximation?
2011 gdouble error = 1.0;
2012 gint ret;
2013 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2015 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2016 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2017 //the resulting nodes behave as expected.
2018 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2019 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2021 //adjust endpoints
2022 sample_cursor->n.pos = bez[1];
2023 sample_end->p.pos = bez[2];
2024 }
2026 //destroy this contiguous selection
2027 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2028 Inkscape::NodePath::Node *temp = delete_cursor;
2029 if (delete_cursor->n.other == delete_cursor) {
2030 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2031 delete_cursor = NULL;
2032 } else {
2033 delete_cursor = delete_cursor->n.other;
2034 }
2035 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2036 sp_nodepath_node_destroy(temp);
2037 }
2039 sp_nodepath_update_handles(nodepath);
2041 if (!g_slist_find(nodepaths, nodepath))
2042 nodepaths = g_slist_prepend (nodepaths, nodepath);
2043 }
2045 for (GSList *i = nodepaths; i; i = i->next) {
2046 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2047 // different nodepaths will give us one undo event per nodepath
2048 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2050 // if the entire nodepath is removed, delete the selected object.
2051 if (nodepath->subpaths == NULL ||
2052 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2053 //at least 2
2054 sp_nodepath_get_node_count(nodepath) < 2) {
2055 SPDocument *document = sp_desktop_document (nodepath->desktop);
2056 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2057 //delete this nodepath's object, not the entire selection! (though at this time, this
2058 //does not matter)
2059 sp_selection_delete();
2060 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2061 _("Delete nodes"));
2062 } else {
2063 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2064 sp_nodepath_update_statusbar(nodepath);
2065 }
2066 }
2068 g_slist_free (nodepaths);
2069 }
2071 /**
2072 * Delete one or more selected nodes.
2073 */
2074 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2075 {
2076 if (!nodepath) return;
2077 if (!nodepath->selected) return;
2079 /** \todo fixme: do it the right way */
2080 while (nodepath->selected) {
2081 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2082 sp_nodepath_node_destroy(node);
2083 }
2086 //clean up the nodepath (such as for trivial subpaths)
2087 sp_nodepath_cleanup(nodepath);
2089 sp_nodepath_update_handles(nodepath);
2091 // if the entire nodepath is removed, delete the selected object.
2092 if (nodepath->subpaths == NULL ||
2093 sp_nodepath_get_node_count(nodepath) < 2) {
2094 SPDocument *document = sp_desktop_document (nodepath->desktop);
2095 sp_selection_delete();
2096 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2097 _("Delete nodes"));
2098 return;
2099 }
2101 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2103 sp_nodepath_update_statusbar(nodepath);
2104 }
2106 /**
2107 * Delete one or more segments between two selected nodes.
2108 * This is the code for 'split'.
2109 */
2110 void
2111 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2112 {
2113 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2114 Inkscape::NodePath::Node *curr, *next; //Iterators
2116 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2118 if (g_list_length(nodepath->selected) != 2) {
2119 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2120 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2121 return;
2122 }
2124 //Selected nodes, not inclusive
2125 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2126 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2128 if ( ( a==b) || //same node
2129 (a->subpath != b->subpath ) || //not the same path
2130 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2131 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2132 {
2133 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2134 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2135 return;
2136 }
2138 //###########################################
2139 //# BEGIN EDITS
2140 //###########################################
2141 //##################################
2142 //# CLOSED PATH
2143 //##################################
2144 if (a->subpath->closed) {
2147 gboolean reversed = FALSE;
2149 //Since we can go in a circle, we need to find the shorter distance.
2150 // a->b or b->a
2151 start = end = NULL;
2152 int distance = 0;
2153 int minDistance = 0;
2154 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2155 if (curr==b) {
2156 //printf("a to b:%d\n", distance);
2157 start = a;//go from a to b
2158 end = b;
2159 minDistance = distance;
2160 //printf("A to B :\n");
2161 break;
2162 }
2163 distance++;
2164 }
2166 //try again, the other direction
2167 distance = 0;
2168 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2169 if (curr==a) {
2170 //printf("b to a:%d\n", distance);
2171 if (distance < minDistance) {
2172 start = b; //we go from b to a
2173 end = a;
2174 reversed = TRUE;
2175 //printf("B to A\n");
2176 }
2177 break;
2178 }
2179 distance++;
2180 }
2183 //Copy everything from 'end' to 'start' to a new subpath
2184 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2185 for (curr=end ; curr ; curr=curr->n.other) {
2186 NRPathcode code = (NRPathcode) curr->code;
2187 if (curr == end)
2188 code = NR_MOVETO;
2189 sp_nodepath_node_new(t, NULL,
2190 (Inkscape::NodePath::NodeType)curr->type, code,
2191 &curr->p.pos, &curr->pos, &curr->n.pos);
2192 if (curr == start)
2193 break;
2194 }
2195 sp_nodepath_subpath_destroy(a->subpath);
2198 }
2202 //##################################
2203 //# OPEN PATH
2204 //##################################
2205 else {
2207 //We need to get the direction of the list between A and B
2208 //Can we walk from a to b?
2209 start = end = NULL;
2210 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2211 if (curr==b) {
2212 start = a; //did it! we go from a to b
2213 end = b;
2214 //printf("A to B\n");
2215 break;
2216 }
2217 }
2218 if (!start) {//didn't work? let's try the other direction
2219 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2220 if (curr==a) {
2221 start = b; //did it! we go from b to a
2222 end = a;
2223 //printf("B to A\n");
2224 break;
2225 }
2226 }
2227 }
2228 if (!start) {
2229 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2230 _("Cannot find path between nodes."));
2231 return;
2232 }
2236 //Copy everything after 'end' to a new subpath
2237 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2238 for (curr=end ; curr ; curr=curr->n.other) {
2239 NRPathcode code = (NRPathcode) curr->code;
2240 if (curr == end)
2241 code = NR_MOVETO;
2242 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2243 &curr->p.pos, &curr->pos, &curr->n.pos);
2244 }
2246 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2247 for (curr = start->n.other ; curr ; curr=next) {
2248 next = curr->n.other;
2249 sp_nodepath_node_destroy(curr);
2250 }
2252 }
2253 //###########################################
2254 //# END EDITS
2255 //###########################################
2257 //clean up the nodepath (such as for trivial subpaths)
2258 sp_nodepath_cleanup(nodepath);
2260 sp_nodepath_update_handles(nodepath);
2262 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2264 sp_nodepath_update_statusbar(nodepath);
2265 }
2267 /**
2268 * Call sp_nodepath_set_line() for all selected segments.
2269 */
2270 void
2271 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2272 {
2273 if (nodepath == NULL) return;
2275 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2276 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2277 g_assert(n->selected);
2278 if (n->p.other && n->p.other->selected) {
2279 sp_nodepath_set_line_type(n, code);
2280 }
2281 }
2283 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2284 }
2286 /**
2287 * Call sp_nodepath_convert_node_type() for all selected nodes.
2288 */
2289 void
2290 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2291 {
2292 if (nodepath == NULL) return;
2294 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2295 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2296 }
2298 sp_nodepath_update_repr(nodepath, _("Change node type"));
2299 }
2301 /**
2302 * Change select status of node, update its own and neighbour handles.
2303 */
2304 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2305 {
2306 node->selected = selected;
2308 if (selected) {
2309 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2310 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2311 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2312 sp_knot_update_ctrl(node->knot);
2313 } else {
2314 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2315 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2316 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2317 sp_knot_update_ctrl(node->knot);
2318 }
2320 sp_node_update_handles(node);
2321 if (node->n.other) sp_node_update_handles(node->n.other);
2322 if (node->p.other) sp_node_update_handles(node->p.other);
2323 }
2325 /**
2326 \brief Select a node
2327 \param node The node to select
2328 \param incremental If true, add to selection, otherwise deselect others
2329 \param override If true, always select this node, otherwise toggle selected status
2330 */
2331 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2332 {
2333 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2335 if (incremental) {
2336 if (override) {
2337 if (!g_list_find(nodepath->selected, node)) {
2338 nodepath->selected = g_list_prepend(nodepath->selected, node);
2339 }
2340 sp_node_set_selected(node, TRUE);
2341 } else { // toggle
2342 if (node->selected) {
2343 g_assert(g_list_find(nodepath->selected, node));
2344 nodepath->selected = g_list_remove(nodepath->selected, node);
2345 } else {
2346 g_assert(!g_list_find(nodepath->selected, node));
2347 nodepath->selected = g_list_prepend(nodepath->selected, node);
2348 }
2349 sp_node_set_selected(node, !node->selected);
2350 }
2351 } else {
2352 sp_nodepath_deselect(nodepath);
2353 nodepath->selected = g_list_prepend(nodepath->selected, node);
2354 sp_node_set_selected(node, TRUE);
2355 }
2357 sp_nodepath_update_statusbar(nodepath);
2358 }
2361 /**
2362 \brief Deselect all nodes in the nodepath
2363 */
2364 void
2365 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2366 {
2367 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2369 while (nodepath->selected) {
2370 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2371 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2372 }
2373 sp_nodepath_update_statusbar(nodepath);
2374 }
2376 /**
2377 \brief Select or invert selection of all nodes in the nodepath
2378 */
2379 void
2380 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2381 {
2382 if (!nodepath) return;
2384 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2385 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2386 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2387 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2388 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2389 }
2390 }
2391 }
2393 /**
2394 * If nothing selected, does the same as sp_nodepath_select_all();
2395 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2396 * (i.e., similar to "select all in layer", with the "selected" subpaths
2397 * being treated as "layers" in the path).
2398 */
2399 void
2400 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2401 {
2402 if (!nodepath) return;
2404 if (g_list_length (nodepath->selected) == 0) {
2405 sp_nodepath_select_all (nodepath, invert);
2406 return;
2407 }
2409 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2410 GSList *subpaths = NULL;
2412 for (GList *l = copy; l != NULL; l = l->next) {
2413 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2414 Inkscape::NodePath::SubPath *subpath = n->subpath;
2415 if (!g_slist_find (subpaths, subpath))
2416 subpaths = g_slist_prepend (subpaths, subpath);
2417 }
2419 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2420 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2421 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2422 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2423 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2424 }
2425 }
2427 g_slist_free (subpaths);
2428 g_list_free (copy);
2429 }
2431 /**
2432 * \brief Select the node after the last selected; if none is selected,
2433 * select the first within path.
2434 */
2435 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2436 {
2437 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2439 Inkscape::NodePath::Node *last = NULL;
2440 if (nodepath->selected) {
2441 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2442 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2443 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2444 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2445 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2446 if (node->selected) {
2447 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2448 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2449 if (spl->next) { // there's a next subpath
2450 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2451 last = subpath_next->first;
2452 } else if (spl->prev) { // there's a previous subpath
2453 last = NULL; // to be set later to the first node of first subpath
2454 } else {
2455 last = node->n.other;
2456 }
2457 } else {
2458 last = node->n.other;
2459 }
2460 } else {
2461 if (node->n.other) {
2462 last = node->n.other;
2463 } else {
2464 if (spl->next) { // there's a next subpath
2465 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2466 last = subpath_next->first;
2467 } else if (spl->prev) { // there's a previous subpath
2468 last = NULL; // to be set later to the first node of first subpath
2469 } else {
2470 last = (Inkscape::NodePath::Node *) subpath->first;
2471 }
2472 }
2473 }
2474 }
2475 }
2476 }
2477 sp_nodepath_deselect(nodepath);
2478 }
2480 if (last) { // there's at least one more node after selected
2481 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2482 } else { // no more nodes, select the first one in first subpath
2483 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2484 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2485 }
2486 }
2488 /**
2489 * \brief Select the node before the first selected; if none is selected,
2490 * select the last within path
2491 */
2492 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2493 {
2494 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2496 Inkscape::NodePath::Node *last = NULL;
2497 if (nodepath->selected) {
2498 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2499 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2500 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2501 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2502 if (node->selected) {
2503 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2504 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2505 if (spl->prev) { // there's a prev subpath
2506 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2507 last = subpath_prev->last;
2508 } else if (spl->next) { // there's a next subpath
2509 last = NULL; // to be set later to the last node of last subpath
2510 } else {
2511 last = node->p.other;
2512 }
2513 } else {
2514 last = node->p.other;
2515 }
2516 } else {
2517 if (node->p.other) {
2518 last = node->p.other;
2519 } else {
2520 if (spl->prev) { // there's a prev subpath
2521 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2522 last = subpath_prev->last;
2523 } else if (spl->next) { // there's a next subpath
2524 last = NULL; // to be set later to the last node of last subpath
2525 } else {
2526 last = (Inkscape::NodePath::Node *) subpath->last;
2527 }
2528 }
2529 }
2530 }
2531 }
2532 }
2533 sp_nodepath_deselect(nodepath);
2534 }
2536 if (last) { // there's at least one more node before selected
2537 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2538 } else { // no more nodes, select the last one in last subpath
2539 GList *spl = g_list_last(nodepath->subpaths);
2540 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2541 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2542 }
2543 }
2545 /**
2546 * \brief Select all nodes that are within the rectangle.
2547 */
2548 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2549 {
2550 if (!incremental) {
2551 sp_nodepath_deselect(nodepath);
2552 }
2554 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2555 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2556 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2557 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2559 if (b.contains(node->pos)) {
2560 sp_nodepath_node_select(node, TRUE, TRUE);
2561 }
2562 }
2563 }
2564 }
2567 void
2568 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2569 {
2570 g_assert (n);
2571 g_assert (nodepath);
2572 g_assert (n->subpath->nodepath == nodepath);
2574 if (g_list_length (nodepath->selected) == 0) {
2575 if (grow > 0) {
2576 sp_nodepath_node_select(n, TRUE, TRUE);
2577 }
2578 return;
2579 }
2581 if (g_list_length (nodepath->selected) == 1) {
2582 if (grow < 0) {
2583 sp_nodepath_deselect (nodepath);
2584 return;
2585 }
2586 }
2588 double n_sel_range = 0, p_sel_range = 0;
2589 Inkscape::NodePath::Node *farthest_n_node = n;
2590 Inkscape::NodePath::Node *farthest_p_node = n;
2592 // Calculate ranges
2593 {
2594 double n_range = 0, p_range = 0;
2595 bool n_going = true, p_going = true;
2596 Inkscape::NodePath::Node *n_node = n;
2597 Inkscape::NodePath::Node *p_node = n;
2598 do {
2599 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2600 if (n_node && n_going)
2601 n_node = n_node->n.other;
2602 if (n_node == NULL) {
2603 n_going = false;
2604 } else {
2605 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2606 if (n_node->selected) {
2607 n_sel_range = n_range;
2608 farthest_n_node = n_node;
2609 }
2610 if (n_node == p_node) {
2611 n_going = false;
2612 p_going = false;
2613 }
2614 }
2615 if (p_node && p_going)
2616 p_node = p_node->p.other;
2617 if (p_node == NULL) {
2618 p_going = false;
2619 } else {
2620 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2621 if (p_node->selected) {
2622 p_sel_range = p_range;
2623 farthest_p_node = p_node;
2624 }
2625 if (p_node == n_node) {
2626 n_going = false;
2627 p_going = false;
2628 }
2629 }
2630 } while (n_going || p_going);
2631 }
2633 if (grow > 0) {
2634 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2635 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2636 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2637 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2638 }
2639 } else {
2640 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2641 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2642 } else if (farthest_p_node && farthest_p_node->selected) {
2643 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2644 }
2645 }
2646 }
2648 void
2649 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2650 {
2651 g_assert (n);
2652 g_assert (nodepath);
2653 g_assert (n->subpath->nodepath == nodepath);
2655 if (g_list_length (nodepath->selected) == 0) {
2656 if (grow > 0) {
2657 sp_nodepath_node_select(n, TRUE, TRUE);
2658 }
2659 return;
2660 }
2662 if (g_list_length (nodepath->selected) == 1) {
2663 if (grow < 0) {
2664 sp_nodepath_deselect (nodepath);
2665 return;
2666 }
2667 }
2669 Inkscape::NodePath::Node *farthest_selected = NULL;
2670 double farthest_dist = 0;
2672 Inkscape::NodePath::Node *closest_unselected = NULL;
2673 double closest_dist = NR_HUGE;
2675 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2676 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2677 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2678 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2679 if (node == n)
2680 continue;
2681 if (node->selected) {
2682 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2683 farthest_dist = NR::L2(node->pos - n->pos);
2684 farthest_selected = node;
2685 }
2686 } else {
2687 if (NR::L2(node->pos - n->pos) < closest_dist) {
2688 closest_dist = NR::L2(node->pos - n->pos);
2689 closest_unselected = node;
2690 }
2691 }
2692 }
2693 }
2695 if (grow > 0) {
2696 if (closest_unselected) {
2697 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2698 }
2699 } else {
2700 if (farthest_selected) {
2701 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2702 }
2703 }
2704 }
2707 /**
2708 \brief Saves all nodes' and handles' current positions in their origin members
2709 */
2710 void
2711 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2712 {
2713 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2714 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2715 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2716 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2717 n->origin = n->pos;
2718 n->p.origin = n->p.pos;
2719 n->n.origin = n->n.pos;
2720 }
2721 }
2722 }
2724 /**
2725 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2726 */
2727 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2728 {
2729 if (!nodepath->selected) {
2730 return NULL;
2731 }
2733 GList *r = NULL;
2734 guint i = 0;
2735 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2736 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2737 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2738 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2739 i++;
2740 if (node->selected) {
2741 r = g_list_append(r, GINT_TO_POINTER(i));
2742 }
2743 }
2744 }
2745 return r;
2746 }
2748 /**
2749 \brief Restores selection by selecting nodes whose positions are in the list
2750 */
2751 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2752 {
2753 sp_nodepath_deselect(nodepath);
2755 guint i = 0;
2756 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2757 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2758 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2759 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2760 i++;
2761 if (g_list_find(r, GINT_TO_POINTER(i))) {
2762 sp_nodepath_node_select(node, TRUE, TRUE);
2763 }
2764 }
2765 }
2767 }
2769 /**
2770 \brief Adjusts handle according to node type and line code.
2771 */
2772 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2773 {
2774 double len, otherlen, linelen;
2776 g_assert(node);
2778 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2779 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2781 /** \todo fixme: */
2782 if (me->other == NULL) return;
2783 if (other->other == NULL) return;
2785 /* I have line */
2787 NRPathcode mecode, ocode;
2788 if (which_adjust == 1) {
2789 mecode = (NRPathcode)me->other->code;
2790 ocode = (NRPathcode)node->code;
2791 } else {
2792 mecode = (NRPathcode)node->code;
2793 ocode = (NRPathcode)other->other->code;
2794 }
2796 if (mecode == NR_LINETO) return;
2798 /* I am curve */
2800 if (other->other == NULL) return;
2802 /* Other has line */
2804 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2806 NR::Point delta;
2807 if (ocode == NR_LINETO) {
2808 /* other is lineto, we are either smooth or symm */
2809 Inkscape::NodePath::Node *othernode = other->other;
2810 len = NR::L2(me->pos - node->pos);
2811 delta = node->pos - othernode->pos;
2812 linelen = NR::L2(delta);
2813 if (linelen < 1e-18)
2814 return;
2815 me->pos = node->pos + (len / linelen)*delta;
2816 return;
2817 }
2819 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2821 me->pos = 2 * node->pos - other->pos;
2822 return;
2823 }
2825 /* We are smooth */
2827 len = NR::L2(me->pos - node->pos);
2828 delta = other->pos - node->pos;
2829 otherlen = NR::L2(delta);
2830 if (otherlen < 1e-18) return;
2832 me->pos = node->pos - (len / otherlen) * delta;
2833 }
2835 /**
2836 \brief Adjusts both handles according to node type and line code
2837 */
2838 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2839 {
2840 g_assert(node);
2842 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2844 /* we are either smooth or symm */
2846 if (node->p.other == NULL) return;
2848 if (node->n.other == NULL) return;
2850 if (node->code == NR_LINETO) {
2851 if (node->n.other->code == NR_LINETO) return;
2852 sp_node_adjust_handle(node, 1);
2853 return;
2854 }
2856 if (node->n.other->code == NR_LINETO) {
2857 if (node->code == NR_LINETO) return;
2858 sp_node_adjust_handle(node, -1);
2859 return;
2860 }
2862 /* both are curves */
2863 NR::Point const delta( node->n.pos - node->p.pos );
2865 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2866 node->p.pos = node->pos - delta / 2;
2867 node->n.pos = node->pos + delta / 2;
2868 return;
2869 }
2871 /* We are smooth */
2872 double plen = NR::L2(node->p.pos - node->pos);
2873 if (plen < 1e-18) return;
2874 double nlen = NR::L2(node->n.pos - node->pos);
2875 if (nlen < 1e-18) return;
2876 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2877 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2878 }
2880 /**
2881 * Node event callback.
2882 */
2883 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2884 {
2885 gboolean ret = FALSE;
2886 switch (event->type) {
2887 case GDK_ENTER_NOTIFY:
2888 Inkscape::NodePath::Path::active_node = n;
2889 break;
2890 case GDK_LEAVE_NOTIFY:
2891 Inkscape::NodePath::Path::active_node = NULL;
2892 break;
2893 case GDK_SCROLL:
2894 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2895 switch (event->scroll.direction) {
2896 case GDK_SCROLL_UP:
2897 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2898 break;
2899 case GDK_SCROLL_DOWN:
2900 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2901 break;
2902 default:
2903 break;
2904 }
2905 ret = TRUE;
2906 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2907 switch (event->scroll.direction) {
2908 case GDK_SCROLL_UP:
2909 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2910 break;
2911 case GDK_SCROLL_DOWN:
2912 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2913 break;
2914 default:
2915 break;
2916 }
2917 ret = TRUE;
2918 }
2919 break;
2920 case GDK_KEY_PRESS:
2921 switch (get_group0_keyval (&event->key)) {
2922 case GDK_space:
2923 if (event->key.state & GDK_BUTTON1_MASK) {
2924 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2925 stamp_repr(nodepath);
2926 ret = TRUE;
2927 }
2928 break;
2929 case GDK_Page_Up:
2930 if (event->key.state & GDK_CONTROL_MASK) {
2931 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2932 } else {
2933 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2934 }
2935 break;
2936 case GDK_Page_Down:
2937 if (event->key.state & GDK_CONTROL_MASK) {
2938 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2939 } else {
2940 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2941 }
2942 break;
2943 default:
2944 break;
2945 }
2946 break;
2947 default:
2948 break;
2949 }
2951 return ret;
2952 }
2954 /**
2955 * Handle keypress on node; directly called.
2956 */
2957 gboolean node_key(GdkEvent *event)
2958 {
2959 Inkscape::NodePath::Path *np;
2961 // there is no way to verify nodes so set active_node to nil when deleting!!
2962 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
2964 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2965 gint ret = FALSE;
2966 switch (get_group0_keyval (&event->key)) {
2967 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2968 case GDK_BackSpace:
2969 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
2970 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
2971 sp_nodepath_update_repr(np, _("Delete node"));
2972 Inkscape::NodePath::Path::active_node = NULL;
2973 ret = TRUE;
2974 break;
2975 case GDK_c:
2976 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
2977 ret = TRUE;
2978 break;
2979 case GDK_s:
2980 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
2981 ret = TRUE;
2982 break;
2983 case GDK_y:
2984 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
2985 ret = TRUE;
2986 break;
2987 case GDK_b:
2988 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
2989 ret = TRUE;
2990 break;
2991 }
2992 return ret;
2993 }
2994 return FALSE;
2995 }
2997 /**
2998 * Mouseclick on node callback.
2999 */
3000 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3001 {
3002 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3004 if (state & GDK_CONTROL_MASK) {
3005 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3007 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3008 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3009 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3010 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3011 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3012 } else {
3013 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3014 }
3015 sp_nodepath_update_repr(nodepath, _("Change node type"));
3016 sp_nodepath_update_statusbar(nodepath);
3018 } else { //ctrl+alt+click: delete node
3019 GList *node_to_delete = NULL;
3020 node_to_delete = g_list_append(node_to_delete, n);
3021 sp_node_delete_preserve(node_to_delete);
3022 }
3024 } else {
3025 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3026 }
3027 }
3029 /**
3030 * Mouse grabbed node callback.
3031 */
3032 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3033 {
3034 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3036 if (!n->selected) {
3037 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3038 }
3040 n->is_dragging = true;
3041 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3043 sp_nodepath_remember_origins (n->subpath->nodepath);
3044 }
3046 /**
3047 * Mouse ungrabbed node callback.
3048 */
3049 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3050 {
3051 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3053 n->dragging_out = NULL;
3054 n->is_dragging = false;
3055 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3057 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3058 }
3060 /**
3061 * The point on a line, given by its angle, closest to the given point.
3062 * \param p A point.
3063 * \param a Angle of the line; it is assumed to go through coordinate origin.
3064 * \param closest Pointer to the point struct where the result is stored.
3065 * \todo FIXME: use dot product perhaps?
3066 */
3067 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3068 {
3069 if (a == HUGE_VAL) { // vertical
3070 *closest = NR::Point(0, (*p)[NR::Y]);
3071 } else {
3072 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3073 (*closest)[NR::Y] = a * (*closest)[NR::X];
3074 }
3075 }
3077 /**
3078 * Distance from the point to a line given by its angle.
3079 * \param p A point.
3080 * \param a Angle of the line; it is assumed to go through coordinate origin.
3081 */
3082 static double point_line_distance(NR::Point *p, double a)
3083 {
3084 NR::Point c;
3085 point_line_closest(p, a, &c);
3086 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]));
3087 }
3089 /**
3090 * Callback for node "request" signal.
3091 * \todo fixme: This goes to "moved" event? (lauris)
3092 */
3093 static gboolean
3094 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3095 {
3096 double yn, xn, yp, xp;
3097 double an, ap, na, pa;
3098 double d_an, d_ap, d_na, d_pa;
3099 gboolean collinear = FALSE;
3100 NR::Point c;
3101 NR::Point pr;
3103 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3105 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3106 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3108 NR::Point mouse = (*p);
3110 if (!n->dragging_out) {
3111 // This is the first drag-out event; find out which handle to drag out
3112 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3113 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3115 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3116 return FALSE;
3118 Inkscape::NodePath::NodeSide *opposite;
3119 if (appr_p > appr_n) { // closer to p
3120 n->dragging_out = &n->p;
3121 opposite = &n->n;
3122 n->code = NR_CURVETO;
3123 } else if (appr_p < appr_n) { // closer to n
3124 n->dragging_out = &n->n;
3125 opposite = &n->p;
3126 n->n.other->code = NR_CURVETO;
3127 } else { // p and n nodes are the same
3128 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3129 n->dragging_out = &n->p;
3130 opposite = &n->n;
3131 n->code = NR_CURVETO;
3132 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3133 n->dragging_out = &n->n;
3134 opposite = &n->p;
3135 n->n.other->code = NR_CURVETO;
3136 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3137 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);
3138 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);
3139 if (appr_other_p > appr_other_n) { // closer to other's p handle
3140 n->dragging_out = &n->n;
3141 opposite = &n->p;
3142 n->n.other->code = NR_CURVETO;
3143 } else { // closer to other's n handle
3144 n->dragging_out = &n->p;
3145 opposite = &n->n;
3146 n->code = NR_CURVETO;
3147 }
3148 }
3149 }
3151 // if there's another handle, make sure the one we drag out starts parallel to it
3152 if (opposite->pos != n->pos) {
3153 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3154 }
3156 // knots might not be created yet!
3157 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3158 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3159 }
3161 // pass this on to the handle-moved callback
3162 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3163 sp_node_update_handles(n);
3164 return TRUE;
3165 }
3167 if (state & GDK_CONTROL_MASK) { // constrained motion
3169 // calculate relative distances of handles
3170 // n handle:
3171 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3172 xn = n->n.pos[NR::X] - n->pos[NR::X];
3173 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3174 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3175 if (n->n.other) { // if there is the next point
3176 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3177 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3178 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3179 }
3180 }
3181 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3182 if (yn < 0) { xn = -xn; yn = -yn; }
3184 // p handle:
3185 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3186 xp = n->p.pos[NR::X] - n->pos[NR::X];
3187 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3188 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3189 if (n->p.other) {
3190 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3191 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3192 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3193 }
3194 }
3195 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3196 if (yp < 0) { xp = -xp; yp = -yp; }
3198 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3199 // sliding on handles, only if at least one of the handles is non-vertical
3200 // (otherwise it's the same as ctrl+drag anyway)
3202 // calculate angles of the handles
3203 if (xn == 0) {
3204 if (yn == 0) { // no handle, consider it the continuation of the other one
3205 an = 0;
3206 collinear = TRUE;
3207 }
3208 else an = 0; // vertical; set the angle to horizontal
3209 } else an = yn/xn;
3211 if (xp == 0) {
3212 if (yp == 0) { // no handle, consider it the continuation of the other one
3213 ap = an;
3214 }
3215 else ap = 0; // vertical; set the angle to horizontal
3216 } else ap = yp/xp;
3218 if (collinear) an = ap;
3220 // angles of the perpendiculars; HUGE_VAL means vertical
3221 if (an == 0) na = HUGE_VAL; else na = -1/an;
3222 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3224 // mouse point relative to the node's original pos
3225 pr = (*p) - n->origin;
3227 // distances to the four lines (two handles and two perpendiculars)
3228 d_an = point_line_distance(&pr, an);
3229 d_na = point_line_distance(&pr, na);
3230 d_ap = point_line_distance(&pr, ap);
3231 d_pa = point_line_distance(&pr, pa);
3233 // find out which line is the closest, save its closest point in c
3234 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3235 point_line_closest(&pr, an, &c);
3236 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3237 point_line_closest(&pr, ap, &c);
3238 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3239 point_line_closest(&pr, na, &c);
3240 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3241 point_line_closest(&pr, pa, &c);
3242 }
3244 // move the node to the closest point
3245 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3246 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3247 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3249 } else { // constraining to hor/vert
3251 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3252 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3253 } else { // snap to vert
3254 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3255 }
3256 }
3257 } else { // move freely
3258 if (n->is_dragging) {
3259 if (state & GDK_MOD1_MASK) { // sculpt
3260 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3261 } else {
3262 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3263 (*p)[NR::X] - n->pos[NR::X],
3264 (*p)[NR::Y] - n->pos[NR::Y],
3265 (state & GDK_SHIFT_MASK) == 0);
3266 }
3267 }
3268 }
3270 n->subpath->nodepath->desktop->scroll_to_point(p);
3272 return TRUE;
3273 }
3275 /**
3276 * Node handle clicked callback.
3277 */
3278 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3279 {
3280 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3282 if (state & GDK_CONTROL_MASK) { // "delete" handle
3283 if (n->p.knot == knot) {
3284 n->p.pos = n->pos;
3285 } else if (n->n.knot == knot) {
3286 n->n.pos = n->pos;
3287 }
3288 sp_node_update_handles(n);
3289 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3290 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3291 sp_nodepath_update_statusbar(nodepath);
3293 } else { // just select or add to selection, depending in Shift
3294 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3295 }
3296 }
3298 /**
3299 * Node handle grabbed callback.
3300 */
3301 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3302 {
3303 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3305 if (!n->selected) {
3306 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3307 }
3309 // remember the origin point of the handle
3310 if (n->p.knot == knot) {
3311 n->p.origin_radial = n->p.pos - n->pos;
3312 } else if (n->n.knot == knot) {
3313 n->n.origin_radial = n->n.pos - n->pos;
3314 } else {
3315 g_assert_not_reached();
3316 }
3318 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3319 }
3321 /**
3322 * Node handle ungrabbed callback.
3323 */
3324 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3325 {
3326 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3328 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3329 if (n->p.knot == knot) {
3330 n->p.origin_radial.a = 0;
3331 sp_knot_set_position(knot, &n->p.pos, state);
3332 } else if (n->n.knot == knot) {
3333 n->n.origin_radial.a = 0;
3334 sp_knot_set_position(knot, &n->n.pos, state);
3335 } else {
3336 g_assert_not_reached();
3337 }
3339 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3340 }
3342 /**
3343 * Node handle "request" signal callback.
3344 */
3345 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3346 {
3347 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3349 Inkscape::NodePath::NodeSide *me, *opposite;
3350 gint which;
3351 if (n->p.knot == knot) {
3352 me = &n->p;
3353 opposite = &n->n;
3354 which = -1;
3355 } else if (n->n.knot == knot) {
3356 me = &n->n;
3357 opposite = &n->p;
3358 which = 1;
3359 } else {
3360 me = opposite = NULL;
3361 which = 0;
3362 g_assert_not_reached();
3363 }
3365 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3367 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3369 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3370 /* We are smooth node adjacent with line */
3371 NR::Point const delta = *p - n->pos;
3372 NR::Coord const len = NR::L2(delta);
3373 Inkscape::NodePath::Node *othernode = opposite->other;
3374 NR::Point const ndelta = n->pos - othernode->pos;
3375 NR::Coord const linelen = NR::L2(ndelta);
3376 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3377 NR::Coord const scal = dot(delta, ndelta) / linelen;
3378 (*p) = n->pos + (scal / linelen) * ndelta;
3379 }
3380 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3381 } else {
3382 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3383 }
3385 sp_node_adjust_handle(n, -which);
3387 return FALSE;
3388 }
3390 /**
3391 * Node handle moved callback.
3392 */
3393 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3394 {
3395 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3397 Inkscape::NodePath::NodeSide *me;
3398 Inkscape::NodePath::NodeSide *other;
3399 if (n->p.knot == knot) {
3400 me = &n->p;
3401 other = &n->n;
3402 } else if (n->n.knot == knot) {
3403 me = &n->n;
3404 other = &n->p;
3405 } else {
3406 me = NULL;
3407 other = NULL;
3408 g_assert_not_reached();
3409 }
3411 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3412 Radial rme(me->pos - n->pos);
3413 Radial rother(other->pos - n->pos);
3414 Radial rnew(*p - n->pos);
3416 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3417 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3418 /* 0 interpreted as "no snapping". */
3420 // The closest PI/snaps angle, starting from zero.
3421 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3422 if (me->origin_radial.a == HUGE_VAL) {
3423 // ortho doesn't exist: original handle was zero length.
3424 rnew.a = a_snapped;
3425 } else {
3426 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3427 * its opposite and perpendiculars). */
3428 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3430 // Snap to the closest.
3431 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3432 ? a_snapped
3433 : a_ortho );
3434 }
3435 }
3437 if (state & GDK_MOD1_MASK) {
3438 // lock handle length
3439 rnew.r = me->origin_radial.r;
3440 }
3442 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3443 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3444 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3445 rother.a += rnew.a - rme.a;
3446 other->pos = NR::Point(rother) + n->pos;
3447 if (other->knot) {
3448 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3449 sp_knot_moveto(other->knot, &other->pos);
3450 }
3451 }
3453 me->pos = NR::Point(rnew) + n->pos;
3454 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3456 // move knot, but without emitting the signal:
3457 // we cannot emit a "moved" signal because we're now processing it
3458 sp_knot_moveto(me->knot, &(me->pos));
3460 update_object(n->subpath->nodepath);
3462 /* status text */
3463 SPDesktop *desktop = n->subpath->nodepath->desktop;
3464 if (!desktop) return;
3465 SPEventContext *ec = desktop->event_context;
3466 if (!ec) return;
3467 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3468 if (!mc) return;
3470 double degrees = 180 / M_PI * rnew.a;
3471 if (degrees > 180) degrees -= 360;
3472 if (degrees < -180) degrees += 360;
3473 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3474 degrees = angle_to_compass (degrees);
3476 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3478 mc->setF(Inkscape::NORMAL_MESSAGE,
3479 _("<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);
3481 g_string_free(length, TRUE);
3482 }
3484 /**
3485 * Node handle event callback.
3486 */
3487 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3488 {
3489 gboolean ret = FALSE;
3490 switch (event->type) {
3491 case GDK_KEY_PRESS:
3492 switch (get_group0_keyval (&event->key)) {
3493 case GDK_space:
3494 if (event->key.state & GDK_BUTTON1_MASK) {
3495 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3496 stamp_repr(nodepath);
3497 ret = TRUE;
3498 }
3499 break;
3500 default:
3501 break;
3502 }
3503 break;
3504 case GDK_ENTER_NOTIFY:
3505 // we use an experimentally determined threshold that seems to work fine
3506 if (NR::L2(n->pos - knot->pos) < 0.75)
3507 Inkscape::NodePath::Path::active_node = n;
3508 break;
3509 case GDK_LEAVE_NOTIFY:
3510 // we use an experimentally determined threshold that seems to work fine
3511 if (NR::L2(n->pos - knot->pos) < 0.75)
3512 Inkscape::NodePath::Path::active_node = NULL;
3513 break;
3514 default:
3515 break;
3516 }
3518 return ret;
3519 }
3521 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3522 Radial &rme, Radial &rother, gboolean const both)
3523 {
3524 rme.a += angle;
3525 if ( both
3526 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3527 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3528 {
3529 rother.a += angle;
3530 }
3531 }
3533 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3534 Radial &rme, Radial &rother, gboolean const both)
3535 {
3536 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3538 gdouble r;
3539 if ( both
3540 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3541 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3542 {
3543 r = MAX(rme.r, rother.r);
3544 } else {
3545 r = rme.r;
3546 }
3548 gdouble const weird_angle = atan2(norm_angle, r);
3549 /* Bulia says norm_angle is just the visible distance that the
3550 * object's end must travel on the screen. Left as 'angle' for want of
3551 * a better name.*/
3553 rme.a += weird_angle;
3554 if ( both
3555 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3556 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3557 {
3558 rother.a += weird_angle;
3559 }
3560 }
3562 /**
3563 * Rotate one node.
3564 */
3565 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3566 {
3567 Inkscape::NodePath::NodeSide *me, *other;
3568 bool both = false;
3570 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3571 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3573 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3574 me = &(n->p);
3575 other = &(n->n);
3576 } else if (!n->p.other) {
3577 me = &(n->n);
3578 other = &(n->p);
3579 } else {
3580 if (which > 0) { // right handle
3581 if (xn > xp) {
3582 me = &(n->n);
3583 other = &(n->p);
3584 } else {
3585 me = &(n->p);
3586 other = &(n->n);
3587 }
3588 } else if (which < 0){ // left handle
3589 if (xn <= xp) {
3590 me = &(n->n);
3591 other = &(n->p);
3592 } else {
3593 me = &(n->p);
3594 other = &(n->n);
3595 }
3596 } else { // both handles
3597 me = &(n->n);
3598 other = &(n->p);
3599 both = true;
3600 }
3601 }
3603 Radial rme(me->pos - n->pos);
3604 Radial rother(other->pos - n->pos);
3606 if (screen) {
3607 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3608 } else {
3609 node_rotate_one_internal (*n, angle, rme, rother, both);
3610 }
3612 me->pos = n->pos + NR::Point(rme);
3614 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3615 other->pos = n->pos + NR::Point(rother);
3616 }
3618 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3619 // so here we just move all the knots without emitting move signals, for speed
3620 sp_node_update_handles(n, false);
3621 }
3623 /**
3624 * Rotate selected nodes.
3625 */
3626 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3627 {
3628 if (!nodepath || !nodepath->selected) return;
3630 if (g_list_length(nodepath->selected) == 1) {
3631 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3632 node_rotate_one (n, angle, which, screen);
3633 } else {
3634 // rotate as an object:
3636 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3637 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3638 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3639 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3640 box.expandTo (n->pos); // contain all selected nodes
3641 }
3643 gdouble rot;
3644 if (screen) {
3645 gdouble const zoom = nodepath->desktop->current_zoom();
3646 gdouble const zmove = angle / zoom;
3647 gdouble const r = NR::L2(box.max() - box.midpoint());
3648 rot = atan2(zmove, r);
3649 } else {
3650 rot = angle;
3651 }
3653 NR::Point rot_center;
3654 if (Inkscape::NodePath::Path::active_node == NULL)
3655 rot_center = box.midpoint();
3656 else
3657 rot_center = Inkscape::NodePath::Path::active_node->pos;
3659 NR::Matrix t =
3660 NR::Matrix (NR::translate(-rot_center)) *
3661 NR::Matrix (NR::rotate(rot)) *
3662 NR::Matrix (NR::translate(rot_center));
3664 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3665 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3666 n->pos *= t;
3667 n->n.pos *= t;
3668 n->p.pos *= t;
3669 sp_node_update_handles(n, false);
3670 }
3671 }
3673 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3674 }
3676 /**
3677 * Scale one node.
3678 */
3679 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3680 {
3681 bool both = false;
3682 Inkscape::NodePath::NodeSide *me, *other;
3684 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3685 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3687 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3688 me = &(n->p);
3689 other = &(n->n);
3690 n->code = NR_CURVETO;
3691 } else if (!n->p.other) {
3692 me = &(n->n);
3693 other = &(n->p);
3694 if (n->n.other)
3695 n->n.other->code = NR_CURVETO;
3696 } else {
3697 if (which > 0) { // right handle
3698 if (xn > xp) {
3699 me = &(n->n);
3700 other = &(n->p);
3701 if (n->n.other)
3702 n->n.other->code = NR_CURVETO;
3703 } else {
3704 me = &(n->p);
3705 other = &(n->n);
3706 n->code = NR_CURVETO;
3707 }
3708 } else if (which < 0){ // left handle
3709 if (xn <= xp) {
3710 me = &(n->n);
3711 other = &(n->p);
3712 if (n->n.other)
3713 n->n.other->code = NR_CURVETO;
3714 } else {
3715 me = &(n->p);
3716 other = &(n->n);
3717 n->code = NR_CURVETO;
3718 }
3719 } else { // both handles
3720 me = &(n->n);
3721 other = &(n->p);
3722 both = true;
3723 n->code = NR_CURVETO;
3724 if (n->n.other)
3725 n->n.other->code = NR_CURVETO;
3726 }
3727 }
3729 Radial rme(me->pos - n->pos);
3730 Radial rother(other->pos - n->pos);
3732 rme.r += grow;
3733 if (rme.r < 0) rme.r = 0;
3734 if (rme.a == HUGE_VAL) {
3735 if (me->other) { // if direction is unknown, initialize it towards the next node
3736 Radial rme_next(me->other->pos - n->pos);
3737 rme.a = rme_next.a;
3738 } else { // if there's no next, initialize to 0
3739 rme.a = 0;
3740 }
3741 }
3742 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3743 rother.r += grow;
3744 if (rother.r < 0) rother.r = 0;
3745 if (rother.a == HUGE_VAL) {
3746 rother.a = rme.a + M_PI;
3747 }
3748 }
3750 me->pos = n->pos + NR::Point(rme);
3752 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3753 other->pos = n->pos + NR::Point(rother);
3754 }
3756 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3757 // so here we just move all the knots without emitting move signals, for speed
3758 sp_node_update_handles(n, false);
3759 }
3761 /**
3762 * Scale selected nodes.
3763 */
3764 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3765 {
3766 if (!nodepath || !nodepath->selected) return;
3768 if (g_list_length(nodepath->selected) == 1) {
3769 // scale handles of the single selected node
3770 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3771 node_scale_one (n, grow, which);
3772 } else {
3773 // scale nodes as an "object":
3775 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3776 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3777 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3778 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3779 box.expandTo (n->pos); // contain all selected nodes
3780 }
3782 double scale = (box.maxExtent() + grow)/box.maxExtent();
3784 NR::Point scale_center;
3785 if (Inkscape::NodePath::Path::active_node == NULL)
3786 scale_center = box.midpoint();
3787 else
3788 scale_center = Inkscape::NodePath::Path::active_node->pos;
3790 NR::Matrix t =
3791 NR::Matrix (NR::translate(-scale_center)) *
3792 NR::Matrix (NR::scale(scale, scale)) *
3793 NR::Matrix (NR::translate(scale_center));
3795 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3796 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3797 n->pos *= t;
3798 n->n.pos *= t;
3799 n->p.pos *= t;
3800 sp_node_update_handles(n, false);
3801 }
3802 }
3804 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3805 }
3807 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3808 {
3809 if (!nodepath) return;
3810 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3811 }
3813 /**
3814 * Flip selected nodes horizontally/vertically.
3815 */
3816 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
3817 {
3818 if (!nodepath || !nodepath->selected) return;
3820 if (g_list_length(nodepath->selected) == 1 && !center) {
3821 // flip handles of the single selected node
3822 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3823 double temp = n->p.pos[axis];
3824 n->p.pos[axis] = n->n.pos[axis];
3825 n->n.pos[axis] = temp;
3826 sp_node_update_handles(n, false);
3827 } else {
3828 // scale nodes as an "object":
3830 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3831 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3832 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3833 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3834 box.expandTo (n->pos); // contain all selected nodes
3835 }
3837 if (!center) {
3838 center = box.midpoint();
3839 }
3840 NR::Matrix t =
3841 NR::Matrix (NR::translate(- *center)) *
3842 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3843 NR::Matrix (NR::translate(*center));
3845 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3846 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3847 n->pos *= t;
3848 n->n.pos *= t;
3849 n->p.pos *= t;
3850 sp_node_update_handles(n, false);
3851 }
3852 }
3854 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3855 }
3857 //-----------------------------------------------
3858 /**
3859 * Return new subpath under given nodepath.
3860 */
3861 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3862 {
3863 g_assert(nodepath);
3864 g_assert(nodepath->desktop);
3866 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3868 s->nodepath = nodepath;
3869 s->closed = FALSE;
3870 s->nodes = NULL;
3871 s->first = NULL;
3872 s->last = NULL;
3874 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3875 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3876 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3878 return s;
3879 }
3881 /**
3882 * Destroy nodes in subpath, then subpath itself.
3883 */
3884 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3885 {
3886 g_assert(subpath);
3887 g_assert(subpath->nodepath);
3888 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3890 while (subpath->nodes) {
3891 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3892 }
3894 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3896 g_free(subpath);
3897 }
3899 /**
3900 * Link head to tail in subpath.
3901 */
3902 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3903 {
3904 g_assert(!sp->closed);
3905 g_assert(sp->last != sp->first);
3906 g_assert(sp->first->code == NR_MOVETO);
3908 sp->closed = TRUE;
3910 //Link the head to the tail
3911 sp->first->p.other = sp->last;
3912 sp->last->n.other = sp->first;
3913 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3914 sp->first = sp->last;
3916 //Remove the extra end node
3917 sp_nodepath_node_destroy(sp->last->n.other);
3918 }
3920 /**
3921 * Open closed (loopy) subpath at node.
3922 */
3923 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3924 {
3925 g_assert(sp->closed);
3926 g_assert(n->subpath == sp);
3927 g_assert(sp->first == sp->last);
3929 /* We create new startpoint, current node will become last one */
3931 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3932 &n->pos, &n->pos, &n->n.pos);
3935 sp->closed = FALSE;
3937 //Unlink to make a head and tail
3938 sp->first = new_path;
3939 sp->last = n;
3940 n->n.other = NULL;
3941 new_path->p.other = NULL;
3942 }
3944 /**
3945 * Returns area in triangle given by points; may be negative.
3946 */
3947 inline double
3948 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3949 {
3950 return (p1[NR::X]*p2[NR::Y] + p1[NR::Y]*p3[NR::X] + p2[NR::X]*p3[NR::Y] - p2[NR::Y]*p3[NR::X] - p1[NR::Y]*p2[NR::X] - p1[NR::X]*p3[NR::Y]);
3951 }
3953 /**
3954 * Return new node in subpath with given properties.
3955 * \param pos Position of node.
3956 * \param ppos Handle position in previous direction
3957 * \param npos Handle position in previous direction
3958 */
3959 Inkscape::NodePath::Node *
3960 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)
3961 {
3962 g_assert(sp);
3963 g_assert(sp->nodepath);
3964 g_assert(sp->nodepath->desktop);
3966 if (nodechunk == NULL)
3967 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3969 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3971 n->subpath = sp;
3973 if (type != Inkscape::NodePath::NODE_NONE) {
3974 // use the type from sodipodi:nodetypes
3975 n->type = type;
3976 } else {
3977 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3978 // points are (almost) collinear
3979 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3980 // endnode, or a node with a retracted handle
3981 n->type = Inkscape::NodePath::NODE_CUSP;
3982 } else {
3983 n->type = Inkscape::NodePath::NODE_SMOOTH;
3984 }
3985 } else {
3986 n->type = Inkscape::NodePath::NODE_CUSP;
3987 }
3988 }
3990 n->code = code;
3991 n->selected = FALSE;
3992 n->pos = *pos;
3993 n->p.pos = *ppos;
3994 n->n.pos = *npos;
3996 n->dragging_out = NULL;
3998 Inkscape::NodePath::Node *prev;
3999 if (next) {
4000 //g_assert(g_list_find(sp->nodes, next));
4001 prev = next->p.other;
4002 } else {
4003 prev = sp->last;
4004 }
4006 if (prev)
4007 prev->n.other = n;
4008 else
4009 sp->first = n;
4011 if (next)
4012 next->p.other = n;
4013 else
4014 sp->last = n;
4016 n->p.other = prev;
4017 n->n.other = next;
4019 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"));
4020 sp_knot_set_position(n->knot, pos, 0);
4022 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4023 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4024 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4025 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4026 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4027 sp_knot_update_ctrl(n->knot);
4029 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4030 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4031 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4032 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4033 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4034 sp_knot_show(n->knot);
4036 // We only create handle knots and lines on demand
4037 n->p.knot = NULL;
4038 n->p.line = NULL;
4039 n->n.knot = NULL;
4040 n->n.line = NULL;
4042 sp->nodes = g_list_prepend(sp->nodes, n);
4044 return n;
4045 }
4047 /**
4048 * Destroy node and its knots, link neighbors in subpath.
4049 */
4050 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4051 {
4052 g_assert(node);
4053 g_assert(node->subpath);
4054 g_assert(SP_IS_KNOT(node->knot));
4056 Inkscape::NodePath::SubPath *sp = node->subpath;
4058 if (node->selected) { // first, deselect
4059 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4060 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4061 }
4063 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4065 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4066 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4067 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4068 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4069 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4070 g_object_unref(G_OBJECT(node->knot));
4072 if (node->p.knot) {
4073 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4074 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4075 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4076 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4077 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4078 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4079 g_object_unref(G_OBJECT(node->p.knot));
4080 node->p.knot = NULL;
4081 }
4083 if (node->n.knot) {
4084 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4085 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4086 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4087 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4088 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4089 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4090 g_object_unref(G_OBJECT(node->n.knot));
4091 node->n.knot = NULL;
4092 }
4094 if (node->p.line)
4095 gtk_object_destroy(GTK_OBJECT(node->p.line));
4096 if (node->n.line)
4097 gtk_object_destroy(GTK_OBJECT(node->n.line));
4099 if (sp->nodes) { // there are others nodes on the subpath
4100 if (sp->closed) {
4101 if (sp->first == node) {
4102 g_assert(sp->last == node);
4103 sp->first = node->n.other;
4104 sp->last = sp->first;
4105 }
4106 node->p.other->n.other = node->n.other;
4107 node->n.other->p.other = node->p.other;
4108 } else {
4109 if (sp->first == node) {
4110 sp->first = node->n.other;
4111 sp->first->code = NR_MOVETO;
4112 }
4113 if (sp->last == node) sp->last = node->p.other;
4114 if (node->p.other) node->p.other->n.other = node->n.other;
4115 if (node->n.other) node->n.other->p.other = node->p.other;
4116 }
4117 } else { // this was the last node on subpath
4118 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4119 }
4121 g_mem_chunk_free(nodechunk, node);
4122 }
4124 /**
4125 * Returns one of the node's two sides.
4126 * \param which Indicates which side.
4127 * \return Pointer to previous node side if which==-1, next if which==1.
4128 */
4129 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4130 {
4131 g_assert(node);
4133 switch (which) {
4134 case -1:
4135 return &node->p;
4136 case 1:
4137 return &node->n;
4138 default:
4139 break;
4140 }
4142 g_assert_not_reached();
4144 return NULL;
4145 }
4147 /**
4148 * Return the other side of the node, given one of its sides.
4149 */
4150 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4151 {
4152 g_assert(node);
4154 if (me == &node->p) return &node->n;
4155 if (me == &node->n) return &node->p;
4157 g_assert_not_reached();
4159 return NULL;
4160 }
4162 /**
4163 * Return NRPathcode on the given side of the node.
4164 */
4165 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4166 {
4167 g_assert(node);
4169 if (me == &node->p) {
4170 if (node->p.other) return (NRPathcode)node->code;
4171 return NR_MOVETO;
4172 }
4174 if (me == &node->n) {
4175 if (node->n.other) return (NRPathcode)node->n.other->code;
4176 return NR_MOVETO;
4177 }
4179 g_assert_not_reached();
4181 return NR_END;
4182 }
4184 /**
4185 * Return node with the given index
4186 */
4187 Inkscape::NodePath::Node *
4188 sp_nodepath_get_node_by_index(int index)
4189 {
4190 Inkscape::NodePath::Node *e = NULL;
4192 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4193 if (!nodepath) {
4194 return e;
4195 }
4197 //find segment
4198 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4200 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4201 int n = g_list_length(sp->nodes);
4202 if (sp->closed) {
4203 n++;
4204 }
4206 //if the piece belongs to this subpath grab it
4207 //otherwise move onto the next subpath
4208 if (index < n) {
4209 e = sp->first;
4210 for (int i = 0; i < index; ++i) {
4211 e = e->n.other;
4212 }
4213 break;
4214 } else {
4215 if (sp->closed) {
4216 index -= (n+1);
4217 } else {
4218 index -= n;
4219 }
4220 }
4221 }
4223 return e;
4224 }
4226 /**
4227 * Returns plain text meaning of node type.
4228 */
4229 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4230 {
4231 unsigned retracted = 0;
4232 bool endnode = false;
4234 for (int which = -1; which <= 1; which += 2) {
4235 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4236 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4237 retracted ++;
4238 if (!side->other)
4239 endnode = true;
4240 }
4242 if (retracted == 0) {
4243 if (endnode) {
4244 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4245 return _("end node");
4246 } else {
4247 switch (node->type) {
4248 case Inkscape::NodePath::NODE_CUSP:
4249 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4250 return _("cusp");
4251 case Inkscape::NodePath::NODE_SMOOTH:
4252 // TRANSLATORS: "smooth" is an adjective here
4253 return _("smooth");
4254 case Inkscape::NodePath::NODE_SYMM:
4255 return _("symmetric");
4256 }
4257 }
4258 } else if (retracted == 1) {
4259 if (endnode) {
4260 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4261 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4262 } else {
4263 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4264 }
4265 } else {
4266 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4267 }
4269 return NULL;
4270 }
4272 /**
4273 * Handles content of statusbar as long as node tool is active.
4274 */
4275 void
4276 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4277 {
4278 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");
4279 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4281 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4282 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4283 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4284 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4286 SPDesktop *desktop = NULL;
4287 if (nodepath) {
4288 desktop = nodepath->desktop;
4289 } else {
4290 desktop = SP_ACTIVE_DESKTOP;
4291 }
4293 SPEventContext *ec = desktop->event_context;
4294 if (!ec) return;
4295 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4296 if (!mc) return;
4298 if (selected_nodes == 0) {
4299 Inkscape::Selection *sel = desktop->selection;
4300 if (!sel || sel->isEmpty()) {
4301 mc->setF(Inkscape::NORMAL_MESSAGE,
4302 _("Select a single object to edit its nodes or handles."));
4303 } else {
4304 if (nodepath) {
4305 mc->setF(Inkscape::NORMAL_MESSAGE,
4306 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.",
4307 "<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.",
4308 total_nodes),
4309 total_nodes);
4310 } else {
4311 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4312 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4313 } else {
4314 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4315 }
4316 }
4317 }
4318 } else if (nodepath && selected_nodes == 1) {
4319 mc->setF(Inkscape::NORMAL_MESSAGE,
4320 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4321 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4322 total_nodes),
4323 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4324 } else {
4325 if (selected_subpaths > 1) {
4326 mc->setF(Inkscape::NORMAL_MESSAGE,
4327 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4328 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4329 total_nodes),
4330 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4331 } else {
4332 mc->setF(Inkscape::NORMAL_MESSAGE,
4333 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4334 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4335 total_nodes),
4336 selected_nodes, total_nodes, when_selected);
4337 }
4338 }
4339 }
4342 /*
4343 Local Variables:
4344 mode:c++
4345 c-file-style:"stroustrup"
4346 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4347 indent-tabs-mode:nil
4348 fill-column:99
4349 End:
4350 */
4351 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :