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(gdouble dx, gdouble dy)
1232 {
1233 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1234 if (!nodepath) return;
1236 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1238 if (dx == 0) {
1239 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1240 } else if (dy == 0) {
1241 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1242 } else {
1243 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1244 }
1245 }
1247 /**
1248 * Move node selection off screen and commit the change.
1249 */
1250 void
1251 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1252 {
1253 // borrowed from sp_selection_move_screen in selection-chemistry.c
1254 // we find out the current zoom factor and divide deltas by it
1255 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1257 gdouble zoom = desktop->current_zoom();
1258 gdouble zdx = dx / zoom;
1259 gdouble zdy = dy / zoom;
1261 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1262 if (!nodepath) return;
1264 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1266 if (dx == 0) {
1267 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1268 } else if (dy == 0) {
1269 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1270 } else {
1271 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1272 }
1273 }
1275 /** If they don't yet exist, creates knot and line for the given side of the node */
1276 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1277 {
1278 if (!side->knot) {
1279 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"));
1281 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1282 side->knot->setSize (7);
1283 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1284 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1285 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1286 sp_knot_update_ctrl(side->knot);
1288 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1289 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1290 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1291 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1292 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1293 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1294 }
1296 if (!side->line) {
1297 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1298 SP_TYPE_CTRLLINE, NULL);
1299 }
1300 }
1302 /**
1303 * Ensure the given handle of the node is visible/invisible, update its screen position
1304 */
1305 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1306 {
1307 g_assert(node != NULL);
1309 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1310 NRPathcode code = sp_node_path_code_from_side(node, side);
1312 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1314 if (show_handle) {
1315 if (!side->knot) { // No handle knot at all
1316 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1317 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1318 side->knot->pos = side->pos;
1319 if (side->knot->item)
1320 SP_CTRL(side->knot->item)->moveto(side->pos);
1321 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1322 sp_knot_show(side->knot);
1323 } else {
1324 if (side->knot->pos != side->pos) { // only if it's really moved
1325 if (fire_move_signals) {
1326 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1327 } else {
1328 sp_knot_moveto(side->knot, &side->pos);
1329 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1330 }
1331 }
1332 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1333 sp_knot_show(side->knot);
1334 }
1335 }
1336 sp_canvas_item_show(side->line);
1337 } else {
1338 if (side->knot) {
1339 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1340 sp_knot_hide(side->knot);
1341 }
1342 }
1343 if (side->line) {
1344 sp_canvas_item_hide(side->line);
1345 }
1346 }
1347 }
1349 /**
1350 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1351 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1352 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1353 * updated; otherwise, just move the knots silently (used in batch moves).
1354 */
1355 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1356 {
1357 g_assert(node != NULL);
1359 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1360 sp_knot_show(node->knot);
1361 }
1363 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1364 if (fire_move_signals)
1365 sp_knot_set_position(node->knot, &node->pos, 0);
1366 else
1367 sp_knot_moveto(node->knot, &node->pos);
1368 }
1370 gboolean show_handles = node->selected;
1371 if (node->p.other != NULL) {
1372 if (node->p.other->selected) show_handles = TRUE;
1373 }
1374 if (node->n.other != NULL) {
1375 if (node->n.other->selected) show_handles = TRUE;
1376 }
1378 if (node->subpath->nodepath->show_handles == false)
1379 show_handles = FALSE;
1381 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1382 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1383 }
1385 /**
1386 * Call sp_node_update_handles() for all nodes on subpath.
1387 */
1388 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1389 {
1390 g_assert(subpath != NULL);
1392 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1393 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1394 }
1395 }
1397 /**
1398 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1399 */
1400 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1401 {
1402 g_assert(nodepath != NULL);
1404 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1405 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1406 }
1407 }
1409 void
1410 sp_nodepath_show_handles(bool show)
1411 {
1412 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1413 if (nodepath == NULL) return;
1415 nodepath->show_handles = show;
1416 sp_nodepath_update_handles(nodepath);
1417 }
1419 /**
1420 * Adds all selected nodes in nodepath to list.
1421 */
1422 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1423 {
1424 StlConv<Node *>::list(l, selected);
1425 /// \todo this adds a copying, rework when the selection becomes a stl list
1426 }
1428 /**
1429 * Align selected nodes on the specified axis.
1430 */
1431 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1432 {
1433 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1434 return;
1435 }
1437 if ( !nodepath->selected->next ) { // only one node selected
1438 return;
1439 }
1440 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1441 NR::Point dest(pNode->pos);
1442 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1443 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1444 if (pNode) {
1445 dest[axis] = pNode->pos[axis];
1446 sp_node_moveto(pNode, dest);
1447 }
1448 }
1450 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1451 }
1453 /// Helper struct.
1454 struct NodeSort
1455 {
1456 Inkscape::NodePath::Node *_node;
1457 NR::Coord _coord;
1458 /// \todo use vectorof pointers instead of calling copy ctor
1459 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1460 _node(node), _coord(node->pos[axis])
1461 {}
1463 };
1465 static bool operator<(NodeSort const &a, NodeSort const &b)
1466 {
1467 return (a._coord < b._coord);
1468 }
1470 /**
1471 * Distribute selected nodes on the specified axis.
1472 */
1473 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1474 {
1475 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1476 return;
1477 }
1479 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1480 return;
1481 }
1483 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1484 std::vector<NodeSort> sorted;
1485 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1486 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1487 if (pNode) {
1488 NodeSort n(pNode, axis);
1489 sorted.push_back(n);
1490 //dest[axis] = pNode->pos[axis];
1491 //sp_node_moveto(pNode, dest);
1492 }
1493 }
1494 std::sort(sorted.begin(), sorted.end());
1495 unsigned int len = sorted.size();
1496 //overall bboxes span
1497 float dist = (sorted.back()._coord -
1498 sorted.front()._coord);
1499 //new distance between each bbox
1500 float step = (dist) / (len - 1);
1501 float pos = sorted.front()._coord;
1502 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1503 it < sorted.end();
1504 it ++ )
1505 {
1506 NR::Point dest((*it)._node->pos);
1507 dest[axis] = pos;
1508 sp_node_moveto((*it)._node, dest);
1509 pos += step;
1510 }
1512 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1513 }
1516 /**
1517 * Call sp_nodepath_line_add_node() for all selected segments.
1518 */
1519 void
1520 sp_node_selected_add_node(void)
1521 {
1522 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1523 if (!nodepath) {
1524 return;
1525 }
1527 GList *nl = NULL;
1529 int n_added = 0;
1531 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1532 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1533 g_assert(t->selected);
1534 if (t->p.other && t->p.other->selected) {
1535 nl = g_list_prepend(nl, t);
1536 }
1537 }
1539 while (nl) {
1540 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1541 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1542 sp_nodepath_node_select(n, TRUE, FALSE);
1543 n_added ++;
1544 nl = g_list_remove(nl, t);
1545 }
1547 /** \todo fixme: adjust ? */
1548 sp_nodepath_update_handles(nodepath);
1550 if (n_added > 1) {
1551 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1552 } else if (n_added > 0) {
1553 sp_nodepath_update_repr(nodepath, _("Add node"));
1554 }
1556 sp_nodepath_update_statusbar(nodepath);
1557 }
1559 /**
1560 * Select segment nearest to point
1561 */
1562 void
1563 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1564 {
1565 if (!nodepath) {
1566 return;
1567 }
1569 sp_nodepath_ensure_livarot_path(nodepath);
1570 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1571 if (!maybe_position) {
1572 return;
1573 }
1574 Path::cut_position position = *maybe_position;
1576 //find segment to segment
1577 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1579 //fixme: this can return NULL, so check before proceeding.
1580 g_return_if_fail(e != NULL);
1582 gboolean force = FALSE;
1583 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1584 force = TRUE;
1585 }
1586 sp_nodepath_node_select(e, (gboolean) toggle, force);
1587 if (e->p.other)
1588 sp_nodepath_node_select(e->p.other, TRUE, force);
1590 sp_nodepath_update_handles(nodepath);
1592 sp_nodepath_update_statusbar(nodepath);
1593 }
1595 /**
1596 * Add a node nearest to point
1597 */
1598 void
1599 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1600 {
1601 if (!nodepath) {
1602 return;
1603 }
1605 sp_nodepath_ensure_livarot_path(nodepath);
1606 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1607 if (!maybe_position) {
1608 return;
1609 }
1610 Path::cut_position position = *maybe_position;
1612 //find segment to split
1613 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1615 //don't know why but t seems to flip for lines
1616 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1617 position.t = 1.0 - position.t;
1618 }
1619 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1620 sp_nodepath_node_select(n, FALSE, TRUE);
1622 /* fixme: adjust ? */
1623 sp_nodepath_update_handles(nodepath);
1625 sp_nodepath_update_repr(nodepath, _("Add node"));
1627 sp_nodepath_update_statusbar(nodepath);
1628 }
1630 /*
1631 * Adjusts a segment so that t moves by a certain delta for dragging
1632 * converts lines to curves
1633 *
1634 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1635 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1636 */
1637 void
1638 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1639 {
1640 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1642 //fixme: e and e->p can be NULL, so check for those before proceeding
1643 g_return_if_fail(e != NULL);
1644 g_return_if_fail(&e->p != NULL);
1646 /* feel good is an arbitrary parameter that distributes the delta between handles
1647 * if t of the drag point is less than 1/6 distance form the endpoint only
1648 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1649 */
1650 double feel_good;
1651 if (t <= 1.0 / 6.0)
1652 feel_good = 0;
1653 else if (t <= 0.5)
1654 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1655 else if (t <= 5.0 / 6.0)
1656 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1657 else
1658 feel_good = 1;
1660 //if we're dragging a line convert it to a curve
1661 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1662 sp_nodepath_set_line_type(e, NR_CURVETO);
1663 }
1665 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1666 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1667 e->p.other->n.pos += offsetcoord0;
1668 e->p.pos += offsetcoord1;
1670 // adjust handles of adjacent nodes where necessary
1671 sp_node_adjust_handle(e,1);
1672 sp_node_adjust_handle(e->p.other,-1);
1674 sp_nodepath_update_handles(e->subpath->nodepath);
1676 update_object(e->subpath->nodepath);
1678 sp_nodepath_update_statusbar(e->subpath->nodepath);
1679 }
1682 /**
1683 * Call sp_nodepath_break() for all selected segments.
1684 */
1685 void sp_node_selected_break()
1686 {
1687 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1688 if (!nodepath) return;
1690 GList *temp = NULL;
1691 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1692 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1693 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1694 if (nn == NULL) continue; // no break, no new node
1695 temp = g_list_prepend(temp, nn);
1696 }
1698 if (temp) {
1699 sp_nodepath_deselect(nodepath);
1700 }
1701 for (GList *l = temp; l != NULL; l = l->next) {
1702 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1703 }
1705 sp_nodepath_update_handles(nodepath);
1707 sp_nodepath_update_repr(nodepath, _("Break path"));
1708 }
1710 /**
1711 * Duplicate the selected node(s).
1712 */
1713 void sp_node_selected_duplicate()
1714 {
1715 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1716 if (!nodepath) {
1717 return;
1718 }
1720 GList *temp = NULL;
1721 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1722 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1723 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1724 if (nn == NULL) continue; // could not duplicate
1725 temp = g_list_prepend(temp, nn);
1726 }
1728 if (temp) {
1729 sp_nodepath_deselect(nodepath);
1730 }
1731 for (GList *l = temp; l != NULL; l = l->next) {
1732 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1733 }
1735 sp_nodepath_update_handles(nodepath);
1737 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1738 }
1740 /**
1741 * Join two nodes by merging them into one.
1742 */
1743 void sp_node_selected_join()
1744 {
1745 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1746 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1748 if (g_list_length(nodepath->selected) != 2) {
1749 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1750 return;
1751 }
1753 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1754 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1756 g_assert(a != b);
1757 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1758 // someone tried to join an orphan node (i.e. a single-node subpath).
1759 // this is not worth an error message, just fail silently.
1760 return;
1761 }
1763 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1764 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1765 return;
1766 }
1768 /* a and b are endpoints */
1770 NR::Point c;
1771 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1772 c = a->pos;
1773 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1774 c = b->pos;
1775 } else {
1776 c = (a->pos + b->pos) / 2;
1777 }
1779 if (a->subpath == b->subpath) {
1780 Inkscape::NodePath::SubPath *sp = a->subpath;
1781 sp_nodepath_subpath_close(sp);
1782 sp_node_moveto (sp->first, c);
1784 sp_nodepath_update_handles(sp->nodepath);
1785 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1786 return;
1787 }
1789 /* a and b are separate subpaths */
1790 Inkscape::NodePath::SubPath *sa = a->subpath;
1791 Inkscape::NodePath::SubPath *sb = b->subpath;
1792 NR::Point p;
1793 Inkscape::NodePath::Node *n;
1794 NRPathcode code;
1795 if (a == sa->first) {
1796 p = sa->first->n.pos;
1797 code = (NRPathcode)sa->first->n.other->code;
1798 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1799 n = sa->last;
1800 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1801 n = n->p.other;
1802 while (n) {
1803 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1804 n = n->p.other;
1805 if (n == sa->first) n = NULL;
1806 }
1807 sp_nodepath_subpath_destroy(sa);
1808 sa = t;
1809 } else if (a == sa->last) {
1810 p = sa->last->p.pos;
1811 code = (NRPathcode)sa->last->code;
1812 sp_nodepath_node_destroy(sa->last);
1813 } else {
1814 code = NR_END;
1815 g_assert_not_reached();
1816 }
1818 if (b == sb->first) {
1819 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1820 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1821 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1822 }
1823 } else if (b == sb->last) {
1824 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1825 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1826 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1827 }
1828 } else {
1829 g_assert_not_reached();
1830 }
1831 /* and now destroy sb */
1833 sp_nodepath_subpath_destroy(sb);
1835 sp_nodepath_update_handles(sa->nodepath);
1837 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1839 sp_nodepath_update_statusbar(nodepath);
1840 }
1842 /**
1843 * Join two nodes by adding a segment between them.
1844 */
1845 void sp_node_selected_join_segment()
1846 {
1847 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1848 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1850 if (g_list_length(nodepath->selected) != 2) {
1851 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1852 return;
1853 }
1855 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1856 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1858 g_assert(a != b);
1859 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1860 // someone tried to join an orphan node (i.e. a single-node subpath).
1861 // this is not worth an error message, just fail silently.
1862 return;
1863 }
1865 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1866 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1867 return;
1868 }
1870 if (a->subpath == b->subpath) {
1871 Inkscape::NodePath::SubPath *sp = a->subpath;
1873 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1874 sp->closed = TRUE;
1876 sp->first->p.other = sp->last;
1877 sp->last->n.other = sp->first;
1879 sp_node_handle_mirror_p_to_n(sp->last);
1880 sp_node_handle_mirror_n_to_p(sp->first);
1882 sp->first->code = sp->last->code;
1883 sp->first = sp->last;
1885 sp_nodepath_update_handles(sp->nodepath);
1887 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1889 return;
1890 }
1892 /* a and b are separate subpaths */
1893 Inkscape::NodePath::SubPath *sa = a->subpath;
1894 Inkscape::NodePath::SubPath *sb = b->subpath;
1896 Inkscape::NodePath::Node *n;
1897 NR::Point p;
1898 NRPathcode code;
1899 if (a == sa->first) {
1900 code = (NRPathcode) sa->first->n.other->code;
1901 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1902 n = sa->last;
1903 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1904 for (n = n->p.other; n != NULL; n = n->p.other) {
1905 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1906 }
1907 sp_nodepath_subpath_destroy(sa);
1908 sa = t;
1909 } else if (a == sa->last) {
1910 code = (NRPathcode)sa->last->code;
1911 } else {
1912 code = NR_END;
1913 g_assert_not_reached();
1914 }
1916 if (b == sb->first) {
1917 n = sb->first;
1918 sp_node_handle_mirror_p_to_n(sa->last);
1919 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1920 sp_node_handle_mirror_n_to_p(sa->last);
1921 for (n = n->n.other; n != NULL; n = n->n.other) {
1922 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1923 }
1924 } else if (b == sb->last) {
1925 n = sb->last;
1926 sp_node_handle_mirror_p_to_n(sa->last);
1927 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1928 sp_node_handle_mirror_n_to_p(sa->last);
1929 for (n = n->p.other; n != NULL; n = n->p.other) {
1930 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1931 }
1932 } else {
1933 g_assert_not_reached();
1934 }
1935 /* and now destroy sb */
1937 sp_nodepath_subpath_destroy(sb);
1939 sp_nodepath_update_handles(sa->nodepath);
1941 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1942 }
1944 /**
1945 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1946 */
1947 void sp_node_delete_preserve(GList *nodes_to_delete)
1948 {
1949 GSList *nodepaths = NULL;
1951 while (nodes_to_delete) {
1952 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1953 Inkscape::NodePath::SubPath *sp = node->subpath;
1954 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1955 Inkscape::NodePath::Node *sample_cursor = NULL;
1956 Inkscape::NodePath::Node *sample_end = NULL;
1957 Inkscape::NodePath::Node *delete_cursor = node;
1958 bool just_delete = false;
1960 //find the start of this contiguous selection
1961 //move left to the first node that is not selected
1962 //or the start of the non-closed path
1963 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1964 delete_cursor = curr;
1965 }
1967 //just delete at the beginning of an open path
1968 if (!delete_cursor->p.other) {
1969 sample_cursor = delete_cursor;
1970 just_delete = true;
1971 } else {
1972 sample_cursor = delete_cursor->p.other;
1973 }
1975 //calculate points for each segment
1976 int rate = 5;
1977 float period = 1.0 / rate;
1978 std::vector<NR::Point> data;
1979 if (!just_delete) {
1980 data.push_back(sample_cursor->pos);
1981 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1982 //just delete at the end of an open path
1983 if (!sp->closed && curr == sp->last) {
1984 just_delete = true;
1985 break;
1986 }
1988 //sample points on the contiguous selected segment
1989 NR::Point *bez;
1990 bez = new NR::Point [4];
1991 bez[0] = curr->pos;
1992 bez[1] = curr->n.pos;
1993 bez[2] = curr->n.other->p.pos;
1994 bez[3] = curr->n.other->pos;
1995 for (int i=1; i<rate; i++) {
1996 gdouble t = i * period;
1997 NR::Point p = bezier_pt(3, bez, t);
1998 data.push_back(p);
1999 }
2000 data.push_back(curr->n.other->pos);
2002 sample_end = curr->n.other;
2003 //break if we've come full circle or hit the end of the selection
2004 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2005 break;
2006 }
2007 }
2008 }
2010 if (!just_delete) {
2011 //calculate the best fitting single segment and adjust the endpoints
2012 NR::Point *adata;
2013 adata = new NR::Point [data.size()];
2014 copy(data.begin(), data.end(), adata);
2016 NR::Point *bez;
2017 bez = new NR::Point [4];
2018 //would decreasing error create a better fitting approximation?
2019 gdouble error = 1.0;
2020 gint ret;
2021 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2023 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2024 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2025 //the resulting nodes behave as expected.
2026 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2027 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2029 //adjust endpoints
2030 sample_cursor->n.pos = bez[1];
2031 sample_end->p.pos = bez[2];
2032 }
2034 //destroy this contiguous selection
2035 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2036 Inkscape::NodePath::Node *temp = delete_cursor;
2037 if (delete_cursor->n.other == delete_cursor) {
2038 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2039 delete_cursor = NULL;
2040 } else {
2041 delete_cursor = delete_cursor->n.other;
2042 }
2043 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2044 sp_nodepath_node_destroy(temp);
2045 }
2047 sp_nodepath_update_handles(nodepath);
2049 if (!g_slist_find(nodepaths, nodepath))
2050 nodepaths = g_slist_prepend (nodepaths, nodepath);
2051 }
2053 for (GSList *i = nodepaths; i; i = i->next) {
2054 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2055 // different nodepaths will give us one undo event per nodepath
2056 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2058 // if the entire nodepath is removed, delete the selected object.
2059 if (nodepath->subpaths == NULL ||
2060 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2061 //at least 2
2062 sp_nodepath_get_node_count(nodepath) < 2) {
2063 SPDocument *document = sp_desktop_document (nodepath->desktop);
2064 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2065 //delete this nodepath's object, not the entire selection! (though at this time, this
2066 //does not matter)
2067 sp_selection_delete();
2068 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2069 _("Delete nodes"));
2070 } else {
2071 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2072 sp_nodepath_update_statusbar(nodepath);
2073 }
2074 }
2076 g_slist_free (nodepaths);
2077 }
2079 /**
2080 * Delete one or more selected nodes.
2081 */
2082 void sp_node_selected_delete()
2083 {
2084 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2085 if (!nodepath) return;
2086 if (!nodepath->selected) return;
2088 /** \todo fixme: do it the right way */
2089 while (nodepath->selected) {
2090 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2091 sp_nodepath_node_destroy(node);
2092 }
2095 //clean up the nodepath (such as for trivial subpaths)
2096 sp_nodepath_cleanup(nodepath);
2098 sp_nodepath_update_handles(nodepath);
2100 // if the entire nodepath is removed, delete the selected object.
2101 if (nodepath->subpaths == NULL ||
2102 sp_nodepath_get_node_count(nodepath) < 2) {
2103 SPDocument *document = sp_desktop_document (nodepath->desktop);
2104 sp_selection_delete();
2105 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2106 _("Delete nodes"));
2107 return;
2108 }
2110 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2112 sp_nodepath_update_statusbar(nodepath);
2113 }
2115 /**
2116 * Delete one or more segments between two selected nodes.
2117 * This is the code for 'split'.
2118 */
2119 void
2120 sp_node_selected_delete_segment(void)
2121 {
2122 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2123 Inkscape::NodePath::Node *curr, *next; //Iterators
2125 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2126 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2128 if (g_list_length(nodepath->selected) != 2) {
2129 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2130 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2131 return;
2132 }
2134 //Selected nodes, not inclusive
2135 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2136 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2138 if ( ( a==b) || //same node
2139 (a->subpath != b->subpath ) || //not the same path
2140 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2141 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2142 {
2143 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2144 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2145 return;
2146 }
2148 //###########################################
2149 //# BEGIN EDITS
2150 //###########################################
2151 //##################################
2152 //# CLOSED PATH
2153 //##################################
2154 if (a->subpath->closed) {
2157 gboolean reversed = FALSE;
2159 //Since we can go in a circle, we need to find the shorter distance.
2160 // a->b or b->a
2161 start = end = NULL;
2162 int distance = 0;
2163 int minDistance = 0;
2164 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2165 if (curr==b) {
2166 //printf("a to b:%d\n", distance);
2167 start = a;//go from a to b
2168 end = b;
2169 minDistance = distance;
2170 //printf("A to B :\n");
2171 break;
2172 }
2173 distance++;
2174 }
2176 //try again, the other direction
2177 distance = 0;
2178 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2179 if (curr==a) {
2180 //printf("b to a:%d\n", distance);
2181 if (distance < minDistance) {
2182 start = b; //we go from b to a
2183 end = a;
2184 reversed = TRUE;
2185 //printf("B to A\n");
2186 }
2187 break;
2188 }
2189 distance++;
2190 }
2193 //Copy everything from 'end' to 'start' to a new subpath
2194 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2195 for (curr=end ; curr ; curr=curr->n.other) {
2196 NRPathcode code = (NRPathcode) curr->code;
2197 if (curr == end)
2198 code = NR_MOVETO;
2199 sp_nodepath_node_new(t, NULL,
2200 (Inkscape::NodePath::NodeType)curr->type, code,
2201 &curr->p.pos, &curr->pos, &curr->n.pos);
2202 if (curr == start)
2203 break;
2204 }
2205 sp_nodepath_subpath_destroy(a->subpath);
2208 }
2212 //##################################
2213 //# OPEN PATH
2214 //##################################
2215 else {
2217 //We need to get the direction of the list between A and B
2218 //Can we walk from a to b?
2219 start = end = NULL;
2220 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2221 if (curr==b) {
2222 start = a; //did it! we go from a to b
2223 end = b;
2224 //printf("A to B\n");
2225 break;
2226 }
2227 }
2228 if (!start) {//didn't work? let's try the other direction
2229 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2230 if (curr==a) {
2231 start = b; //did it! we go from b to a
2232 end = a;
2233 //printf("B to A\n");
2234 break;
2235 }
2236 }
2237 }
2238 if (!start) {
2239 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2240 _("Cannot find path between nodes."));
2241 return;
2242 }
2246 //Copy everything after 'end' to a new subpath
2247 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2248 for (curr=end ; curr ; curr=curr->n.other) {
2249 NRPathcode code = (NRPathcode) curr->code;
2250 if (curr == end)
2251 code = NR_MOVETO;
2252 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2253 &curr->p.pos, &curr->pos, &curr->n.pos);
2254 }
2256 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2257 for (curr = start->n.other ; curr ; curr=next) {
2258 next = curr->n.other;
2259 sp_nodepath_node_destroy(curr);
2260 }
2262 }
2263 //###########################################
2264 //# END EDITS
2265 //###########################################
2267 //clean up the nodepath (such as for trivial subpaths)
2268 sp_nodepath_cleanup(nodepath);
2270 sp_nodepath_update_handles(nodepath);
2272 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2274 sp_nodepath_update_statusbar(nodepath);
2275 }
2277 /**
2278 * Call sp_nodepath_set_line() for all selected segments.
2279 */
2280 void
2281 sp_node_selected_set_line_type(NRPathcode code)
2282 {
2283 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2284 if (nodepath == NULL) return;
2286 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2287 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2288 g_assert(n->selected);
2289 if (n->p.other && n->p.other->selected) {
2290 sp_nodepath_set_line_type(n, code);
2291 }
2292 }
2294 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2295 }
2297 /**
2298 * Call sp_nodepath_convert_node_type() for all selected nodes.
2299 */
2300 void
2301 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2302 {
2303 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2304 if (nodepath == NULL) return;
2306 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2307 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2308 }
2310 sp_nodepath_update_repr(nodepath, _("Change node type"));
2311 }
2313 /**
2314 * Change select status of node, update its own and neighbour handles.
2315 */
2316 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2317 {
2318 node->selected = selected;
2320 if (selected) {
2321 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2322 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2323 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2324 sp_knot_update_ctrl(node->knot);
2325 } else {
2326 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2327 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2328 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2329 sp_knot_update_ctrl(node->knot);
2330 }
2332 sp_node_update_handles(node);
2333 if (node->n.other) sp_node_update_handles(node->n.other);
2334 if (node->p.other) sp_node_update_handles(node->p.other);
2335 }
2337 /**
2338 \brief Select a node
2339 \param node The node to select
2340 \param incremental If true, add to selection, otherwise deselect others
2341 \param override If true, always select this node, otherwise toggle selected status
2342 */
2343 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2344 {
2345 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2347 if (incremental) {
2348 if (override) {
2349 if (!g_list_find(nodepath->selected, node)) {
2350 nodepath->selected = g_list_prepend(nodepath->selected, node);
2351 }
2352 sp_node_set_selected(node, TRUE);
2353 } else { // toggle
2354 if (node->selected) {
2355 g_assert(g_list_find(nodepath->selected, node));
2356 nodepath->selected = g_list_remove(nodepath->selected, node);
2357 } else {
2358 g_assert(!g_list_find(nodepath->selected, node));
2359 nodepath->selected = g_list_prepend(nodepath->selected, node);
2360 }
2361 sp_node_set_selected(node, !node->selected);
2362 }
2363 } else {
2364 sp_nodepath_deselect(nodepath);
2365 nodepath->selected = g_list_prepend(nodepath->selected, node);
2366 sp_node_set_selected(node, TRUE);
2367 }
2369 sp_nodepath_update_statusbar(nodepath);
2370 }
2373 /**
2374 \brief Deselect all nodes in the nodepath
2375 */
2376 void
2377 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2378 {
2379 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2381 while (nodepath->selected) {
2382 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2383 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2384 }
2385 sp_nodepath_update_statusbar(nodepath);
2386 }
2388 /**
2389 \brief Select or invert selection of all nodes in the nodepath
2390 */
2391 void
2392 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2393 {
2394 if (!nodepath) return;
2396 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2397 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2398 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2399 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2400 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2401 }
2402 }
2403 }
2405 /**
2406 * If nothing selected, does the same as sp_nodepath_select_all();
2407 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2408 * (i.e., similar to "select all in layer", with the "selected" subpaths
2409 * being treated as "layers" in the path).
2410 */
2411 void
2412 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2413 {
2414 if (!nodepath) return;
2416 if (g_list_length (nodepath->selected) == 0) {
2417 sp_nodepath_select_all (nodepath, invert);
2418 return;
2419 }
2421 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2422 GSList *subpaths = NULL;
2424 for (GList *l = copy; l != NULL; l = l->next) {
2425 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2426 Inkscape::NodePath::SubPath *subpath = n->subpath;
2427 if (!g_slist_find (subpaths, subpath))
2428 subpaths = g_slist_prepend (subpaths, subpath);
2429 }
2431 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2432 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2433 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2434 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2435 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2436 }
2437 }
2439 g_slist_free (subpaths);
2440 g_list_free (copy);
2441 }
2443 /**
2444 * \brief Select the node after the last selected; if none is selected,
2445 * select the first within path.
2446 */
2447 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2448 {
2449 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2451 Inkscape::NodePath::Node *last = NULL;
2452 if (nodepath->selected) {
2453 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2454 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2455 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2456 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2457 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2458 if (node->selected) {
2459 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2460 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2461 if (spl->next) { // there's a next subpath
2462 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2463 last = subpath_next->first;
2464 } else if (spl->prev) { // there's a previous subpath
2465 last = NULL; // to be set later to the first node of first subpath
2466 } else {
2467 last = node->n.other;
2468 }
2469 } else {
2470 last = node->n.other;
2471 }
2472 } else {
2473 if (node->n.other) {
2474 last = node->n.other;
2475 } else {
2476 if (spl->next) { // there's a next subpath
2477 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2478 last = subpath_next->first;
2479 } else if (spl->prev) { // there's a previous subpath
2480 last = NULL; // to be set later to the first node of first subpath
2481 } else {
2482 last = (Inkscape::NodePath::Node *) subpath->first;
2483 }
2484 }
2485 }
2486 }
2487 }
2488 }
2489 sp_nodepath_deselect(nodepath);
2490 }
2492 if (last) { // there's at least one more node after selected
2493 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2494 } else { // no more nodes, select the first one in first subpath
2495 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2496 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2497 }
2498 }
2500 /**
2501 * \brief Select the node before the first selected; if none is selected,
2502 * select the last within path
2503 */
2504 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2505 {
2506 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2508 Inkscape::NodePath::Node *last = NULL;
2509 if (nodepath->selected) {
2510 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2511 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2512 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2513 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2514 if (node->selected) {
2515 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2516 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2517 if (spl->prev) { // there's a prev subpath
2518 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2519 last = subpath_prev->last;
2520 } else if (spl->next) { // there's a next subpath
2521 last = NULL; // to be set later to the last node of last subpath
2522 } else {
2523 last = node->p.other;
2524 }
2525 } else {
2526 last = node->p.other;
2527 }
2528 } else {
2529 if (node->p.other) {
2530 last = node->p.other;
2531 } else {
2532 if (spl->prev) { // there's a prev subpath
2533 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2534 last = subpath_prev->last;
2535 } else if (spl->next) { // there's a next subpath
2536 last = NULL; // to be set later to the last node of last subpath
2537 } else {
2538 last = (Inkscape::NodePath::Node *) subpath->last;
2539 }
2540 }
2541 }
2542 }
2543 }
2544 }
2545 sp_nodepath_deselect(nodepath);
2546 }
2548 if (last) { // there's at least one more node before selected
2549 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2550 } else { // no more nodes, select the last one in last subpath
2551 GList *spl = g_list_last(nodepath->subpaths);
2552 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2553 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2554 }
2555 }
2557 /**
2558 * \brief Select all nodes that are within the rectangle.
2559 */
2560 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2561 {
2562 if (!incremental) {
2563 sp_nodepath_deselect(nodepath);
2564 }
2566 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2567 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2568 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2569 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2571 if (b.contains(node->pos)) {
2572 sp_nodepath_node_select(node, TRUE, TRUE);
2573 }
2574 }
2575 }
2576 }
2579 void
2580 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2581 {
2582 g_assert (n);
2583 g_assert (nodepath);
2584 g_assert (n->subpath->nodepath == nodepath);
2586 if (g_list_length (nodepath->selected) == 0) {
2587 if (grow > 0) {
2588 sp_nodepath_node_select(n, TRUE, TRUE);
2589 }
2590 return;
2591 }
2593 if (g_list_length (nodepath->selected) == 1) {
2594 if (grow < 0) {
2595 sp_nodepath_deselect (nodepath);
2596 return;
2597 }
2598 }
2600 double n_sel_range = 0, p_sel_range = 0;
2601 Inkscape::NodePath::Node *farthest_n_node = n;
2602 Inkscape::NodePath::Node *farthest_p_node = n;
2604 // Calculate ranges
2605 {
2606 double n_range = 0, p_range = 0;
2607 bool n_going = true, p_going = true;
2608 Inkscape::NodePath::Node *n_node = n;
2609 Inkscape::NodePath::Node *p_node = n;
2610 do {
2611 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2612 if (n_node && n_going)
2613 n_node = n_node->n.other;
2614 if (n_node == NULL) {
2615 n_going = false;
2616 } else {
2617 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2618 if (n_node->selected) {
2619 n_sel_range = n_range;
2620 farthest_n_node = n_node;
2621 }
2622 if (n_node == p_node) {
2623 n_going = false;
2624 p_going = false;
2625 }
2626 }
2627 if (p_node && p_going)
2628 p_node = p_node->p.other;
2629 if (p_node == NULL) {
2630 p_going = false;
2631 } else {
2632 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2633 if (p_node->selected) {
2634 p_sel_range = p_range;
2635 farthest_p_node = p_node;
2636 }
2637 if (p_node == n_node) {
2638 n_going = false;
2639 p_going = false;
2640 }
2641 }
2642 } while (n_going || p_going);
2643 }
2645 if (grow > 0) {
2646 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2647 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2648 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2649 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2650 }
2651 } else {
2652 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2653 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2654 } else if (farthest_p_node && farthest_p_node->selected) {
2655 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2656 }
2657 }
2658 }
2660 void
2661 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2662 {
2663 g_assert (n);
2664 g_assert (nodepath);
2665 g_assert (n->subpath->nodepath == nodepath);
2667 if (g_list_length (nodepath->selected) == 0) {
2668 if (grow > 0) {
2669 sp_nodepath_node_select(n, TRUE, TRUE);
2670 }
2671 return;
2672 }
2674 if (g_list_length (nodepath->selected) == 1) {
2675 if (grow < 0) {
2676 sp_nodepath_deselect (nodepath);
2677 return;
2678 }
2679 }
2681 Inkscape::NodePath::Node *farthest_selected = NULL;
2682 double farthest_dist = 0;
2684 Inkscape::NodePath::Node *closest_unselected = NULL;
2685 double closest_dist = NR_HUGE;
2687 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2688 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2689 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2690 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2691 if (node == n)
2692 continue;
2693 if (node->selected) {
2694 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2695 farthest_dist = NR::L2(node->pos - n->pos);
2696 farthest_selected = node;
2697 }
2698 } else {
2699 if (NR::L2(node->pos - n->pos) < closest_dist) {
2700 closest_dist = NR::L2(node->pos - n->pos);
2701 closest_unselected = node;
2702 }
2703 }
2704 }
2705 }
2707 if (grow > 0) {
2708 if (closest_unselected) {
2709 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2710 }
2711 } else {
2712 if (farthest_selected) {
2713 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2714 }
2715 }
2716 }
2719 /**
2720 \brief Saves all nodes' and handles' current positions in their origin members
2721 */
2722 void
2723 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2724 {
2725 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2726 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2727 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2728 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2729 n->origin = n->pos;
2730 n->p.origin = n->p.pos;
2731 n->n.origin = n->n.pos;
2732 }
2733 }
2734 }
2736 /**
2737 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2738 */
2739 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2740 {
2741 if (!nodepath->selected) {
2742 return NULL;
2743 }
2745 GList *r = NULL;
2746 guint i = 0;
2747 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2748 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2749 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2750 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2751 i++;
2752 if (node->selected) {
2753 r = g_list_append(r, GINT_TO_POINTER(i));
2754 }
2755 }
2756 }
2757 return r;
2758 }
2760 /**
2761 \brief Restores selection by selecting nodes whose positions are in the list
2762 */
2763 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2764 {
2765 sp_nodepath_deselect(nodepath);
2767 guint i = 0;
2768 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2769 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2770 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2771 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2772 i++;
2773 if (g_list_find(r, GINT_TO_POINTER(i))) {
2774 sp_nodepath_node_select(node, TRUE, TRUE);
2775 }
2776 }
2777 }
2779 }
2781 /**
2782 \brief Adjusts handle according to node type and line code.
2783 */
2784 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2785 {
2786 double len, otherlen, linelen;
2788 g_assert(node);
2790 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2791 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2793 /** \todo fixme: */
2794 if (me->other == NULL) return;
2795 if (other->other == NULL) return;
2797 /* I have line */
2799 NRPathcode mecode, ocode;
2800 if (which_adjust == 1) {
2801 mecode = (NRPathcode)me->other->code;
2802 ocode = (NRPathcode)node->code;
2803 } else {
2804 mecode = (NRPathcode)node->code;
2805 ocode = (NRPathcode)other->other->code;
2806 }
2808 if (mecode == NR_LINETO) return;
2810 /* I am curve */
2812 if (other->other == NULL) return;
2814 /* Other has line */
2816 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2818 NR::Point delta;
2819 if (ocode == NR_LINETO) {
2820 /* other is lineto, we are either smooth or symm */
2821 Inkscape::NodePath::Node *othernode = other->other;
2822 len = NR::L2(me->pos - node->pos);
2823 delta = node->pos - othernode->pos;
2824 linelen = NR::L2(delta);
2825 if (linelen < 1e-18)
2826 return;
2827 me->pos = node->pos + (len / linelen)*delta;
2828 return;
2829 }
2831 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2833 me->pos = 2 * node->pos - other->pos;
2834 return;
2835 }
2837 /* We are smooth */
2839 len = NR::L2(me->pos - node->pos);
2840 delta = other->pos - node->pos;
2841 otherlen = NR::L2(delta);
2842 if (otherlen < 1e-18) return;
2844 me->pos = node->pos - (len / otherlen) * delta;
2845 }
2847 /**
2848 \brief Adjusts both handles according to node type and line code
2849 */
2850 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2851 {
2852 g_assert(node);
2854 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2856 /* we are either smooth or symm */
2858 if (node->p.other == NULL) return;
2860 if (node->n.other == NULL) return;
2862 if (node->code == NR_LINETO) {
2863 if (node->n.other->code == NR_LINETO) return;
2864 sp_node_adjust_handle(node, 1);
2865 return;
2866 }
2868 if (node->n.other->code == NR_LINETO) {
2869 if (node->code == NR_LINETO) return;
2870 sp_node_adjust_handle(node, -1);
2871 return;
2872 }
2874 /* both are curves */
2875 NR::Point const delta( node->n.pos - node->p.pos );
2877 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2878 node->p.pos = node->pos - delta / 2;
2879 node->n.pos = node->pos + delta / 2;
2880 return;
2881 }
2883 /* We are smooth */
2884 double plen = NR::L2(node->p.pos - node->pos);
2885 if (plen < 1e-18) return;
2886 double nlen = NR::L2(node->n.pos - node->pos);
2887 if (nlen < 1e-18) return;
2888 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2889 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2890 }
2892 /**
2893 * Node event callback.
2894 */
2895 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2896 {
2897 gboolean ret = FALSE;
2898 switch (event->type) {
2899 case GDK_ENTER_NOTIFY:
2900 Inkscape::NodePath::Path::active_node = n;
2901 break;
2902 case GDK_LEAVE_NOTIFY:
2903 Inkscape::NodePath::Path::active_node = NULL;
2904 break;
2905 case GDK_SCROLL:
2906 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2907 switch (event->scroll.direction) {
2908 case GDK_SCROLL_UP:
2909 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2910 break;
2911 case GDK_SCROLL_DOWN:
2912 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2913 break;
2914 default:
2915 break;
2916 }
2917 ret = TRUE;
2918 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2919 switch (event->scroll.direction) {
2920 case GDK_SCROLL_UP:
2921 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2922 break;
2923 case GDK_SCROLL_DOWN:
2924 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2925 break;
2926 default:
2927 break;
2928 }
2929 ret = TRUE;
2930 }
2931 break;
2932 case GDK_KEY_PRESS:
2933 switch (get_group0_keyval (&event->key)) {
2934 case GDK_space:
2935 if (event->key.state & GDK_BUTTON1_MASK) {
2936 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2937 stamp_repr(nodepath);
2938 ret = TRUE;
2939 }
2940 break;
2941 case GDK_Page_Up:
2942 if (event->key.state & GDK_CONTROL_MASK) {
2943 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2944 } else {
2945 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2946 }
2947 break;
2948 case GDK_Page_Down:
2949 if (event->key.state & GDK_CONTROL_MASK) {
2950 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2951 } else {
2952 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2953 }
2954 break;
2955 default:
2956 break;
2957 }
2958 break;
2959 default:
2960 break;
2961 }
2963 return ret;
2964 }
2966 /**
2967 * Handle keypress on node; directly called.
2968 */
2969 gboolean node_key(GdkEvent *event)
2970 {
2971 Inkscape::NodePath::Path *np;
2973 // there is no way to verify nodes so set active_node to nil when deleting!!
2974 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
2976 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2977 gint ret = FALSE;
2978 switch (get_group0_keyval (&event->key)) {
2979 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2980 case GDK_BackSpace:
2981 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
2982 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
2983 sp_nodepath_update_repr(np, _("Delete node"));
2984 Inkscape::NodePath::Path::active_node = NULL;
2985 ret = TRUE;
2986 break;
2987 case GDK_c:
2988 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
2989 ret = TRUE;
2990 break;
2991 case GDK_s:
2992 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
2993 ret = TRUE;
2994 break;
2995 case GDK_y:
2996 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
2997 ret = TRUE;
2998 break;
2999 case GDK_b:
3000 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3001 ret = TRUE;
3002 break;
3003 }
3004 return ret;
3005 }
3006 return FALSE;
3007 }
3009 /**
3010 * Mouseclick on node callback.
3011 */
3012 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3013 {
3014 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3016 if (state & GDK_CONTROL_MASK) {
3017 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3019 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3020 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3021 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3022 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3023 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3024 } else {
3025 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3026 }
3027 sp_nodepath_update_repr(nodepath, _("Change node type"));
3028 sp_nodepath_update_statusbar(nodepath);
3030 } else { //ctrl+alt+click: delete node
3031 GList *node_to_delete = NULL;
3032 node_to_delete = g_list_append(node_to_delete, n);
3033 sp_node_delete_preserve(node_to_delete);
3034 }
3036 } else {
3037 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3038 }
3039 }
3041 /**
3042 * Mouse grabbed node callback.
3043 */
3044 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3045 {
3046 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3048 if (!n->selected) {
3049 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3050 }
3052 n->is_dragging = true;
3053 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3055 sp_nodepath_remember_origins (n->subpath->nodepath);
3056 }
3058 /**
3059 * Mouse ungrabbed node callback.
3060 */
3061 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3062 {
3063 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3065 n->dragging_out = NULL;
3066 n->is_dragging = false;
3067 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3069 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3070 }
3072 /**
3073 * The point on a line, given by its angle, closest to the given point.
3074 * \param p A point.
3075 * \param a Angle of the line; it is assumed to go through coordinate origin.
3076 * \param closest Pointer to the point struct where the result is stored.
3077 * \todo FIXME: use dot product perhaps?
3078 */
3079 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3080 {
3081 if (a == HUGE_VAL) { // vertical
3082 *closest = NR::Point(0, (*p)[NR::Y]);
3083 } else {
3084 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3085 (*closest)[NR::Y] = a * (*closest)[NR::X];
3086 }
3087 }
3089 /**
3090 * Distance from the point to a line given by its angle.
3091 * \param p A point.
3092 * \param a Angle of the line; it is assumed to go through coordinate origin.
3093 */
3094 static double point_line_distance(NR::Point *p, double a)
3095 {
3096 NR::Point c;
3097 point_line_closest(p, a, &c);
3098 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]));
3099 }
3101 /**
3102 * Callback for node "request" signal.
3103 * \todo fixme: This goes to "moved" event? (lauris)
3104 */
3105 static gboolean
3106 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3107 {
3108 double yn, xn, yp, xp;
3109 double an, ap, na, pa;
3110 double d_an, d_ap, d_na, d_pa;
3111 gboolean collinear = FALSE;
3112 NR::Point c;
3113 NR::Point pr;
3115 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3117 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3118 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3120 NR::Point mouse = (*p);
3122 if (!n->dragging_out) {
3123 // This is the first drag-out event; find out which handle to drag out
3124 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3125 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3127 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3128 return FALSE;
3130 Inkscape::NodePath::NodeSide *opposite;
3131 if (appr_p > appr_n) { // closer to p
3132 n->dragging_out = &n->p;
3133 opposite = &n->n;
3134 n->code = NR_CURVETO;
3135 } else if (appr_p < appr_n) { // closer to n
3136 n->dragging_out = &n->n;
3137 opposite = &n->p;
3138 n->n.other->code = NR_CURVETO;
3139 } else { // p and n nodes are the same
3140 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3141 n->dragging_out = &n->p;
3142 opposite = &n->n;
3143 n->code = NR_CURVETO;
3144 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3145 n->dragging_out = &n->n;
3146 opposite = &n->p;
3147 n->n.other->code = NR_CURVETO;
3148 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3149 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);
3150 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);
3151 if (appr_other_p > appr_other_n) { // closer to other's p handle
3152 n->dragging_out = &n->n;
3153 opposite = &n->p;
3154 n->n.other->code = NR_CURVETO;
3155 } else { // closer to other's n handle
3156 n->dragging_out = &n->p;
3157 opposite = &n->n;
3158 n->code = NR_CURVETO;
3159 }
3160 }
3161 }
3163 // if there's another handle, make sure the one we drag out starts parallel to it
3164 if (opposite->pos != n->pos) {
3165 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3166 }
3168 // knots might not be created yet!
3169 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3170 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3171 }
3173 // pass this on to the handle-moved callback
3174 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3175 sp_node_update_handles(n);
3176 return TRUE;
3177 }
3179 if (state & GDK_CONTROL_MASK) { // constrained motion
3181 // calculate relative distances of handles
3182 // n handle:
3183 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3184 xn = n->n.pos[NR::X] - n->pos[NR::X];
3185 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3186 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3187 if (n->n.other) { // if there is the next point
3188 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3189 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3190 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3191 }
3192 }
3193 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3194 if (yn < 0) { xn = -xn; yn = -yn; }
3196 // p handle:
3197 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3198 xp = n->p.pos[NR::X] - n->pos[NR::X];
3199 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3200 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3201 if (n->p.other) {
3202 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3203 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3204 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3205 }
3206 }
3207 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3208 if (yp < 0) { xp = -xp; yp = -yp; }
3210 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3211 // sliding on handles, only if at least one of the handles is non-vertical
3212 // (otherwise it's the same as ctrl+drag anyway)
3214 // calculate angles of the handles
3215 if (xn == 0) {
3216 if (yn == 0) { // no handle, consider it the continuation of the other one
3217 an = 0;
3218 collinear = TRUE;
3219 }
3220 else an = 0; // vertical; set the angle to horizontal
3221 } else an = yn/xn;
3223 if (xp == 0) {
3224 if (yp == 0) { // no handle, consider it the continuation of the other one
3225 ap = an;
3226 }
3227 else ap = 0; // vertical; set the angle to horizontal
3228 } else ap = yp/xp;
3230 if (collinear) an = ap;
3232 // angles of the perpendiculars; HUGE_VAL means vertical
3233 if (an == 0) na = HUGE_VAL; else na = -1/an;
3234 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3236 // mouse point relative to the node's original pos
3237 pr = (*p) - n->origin;
3239 // distances to the four lines (two handles and two perpendiculars)
3240 d_an = point_line_distance(&pr, an);
3241 d_na = point_line_distance(&pr, na);
3242 d_ap = point_line_distance(&pr, ap);
3243 d_pa = point_line_distance(&pr, pa);
3245 // find out which line is the closest, save its closest point in c
3246 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3247 point_line_closest(&pr, an, &c);
3248 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3249 point_line_closest(&pr, ap, &c);
3250 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3251 point_line_closest(&pr, na, &c);
3252 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3253 point_line_closest(&pr, pa, &c);
3254 }
3256 // move the node to the closest point
3257 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3258 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3259 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3261 } else { // constraining to hor/vert
3263 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3264 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3265 } else { // snap to vert
3266 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3267 }
3268 }
3269 } else { // move freely
3270 if (n->is_dragging) {
3271 if (state & GDK_MOD1_MASK) { // sculpt
3272 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3273 } else {
3274 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3275 (*p)[NR::X] - n->pos[NR::X],
3276 (*p)[NR::Y] - n->pos[NR::Y],
3277 (state & GDK_SHIFT_MASK) == 0);
3278 }
3279 }
3280 }
3282 n->subpath->nodepath->desktop->scroll_to_point(p);
3284 return TRUE;
3285 }
3287 /**
3288 * Node handle clicked callback.
3289 */
3290 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3291 {
3292 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3294 if (state & GDK_CONTROL_MASK) { // "delete" handle
3295 if (n->p.knot == knot) {
3296 n->p.pos = n->pos;
3297 } else if (n->n.knot == knot) {
3298 n->n.pos = n->pos;
3299 }
3300 sp_node_update_handles(n);
3301 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3302 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3303 sp_nodepath_update_statusbar(nodepath);
3305 } else { // just select or add to selection, depending in Shift
3306 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3307 }
3308 }
3310 /**
3311 * Node handle grabbed callback.
3312 */
3313 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3314 {
3315 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3317 if (!n->selected) {
3318 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3319 }
3321 // remember the origin point of the handle
3322 if (n->p.knot == knot) {
3323 n->p.origin_radial = n->p.pos - n->pos;
3324 } else if (n->n.knot == knot) {
3325 n->n.origin_radial = n->n.pos - n->pos;
3326 } else {
3327 g_assert_not_reached();
3328 }
3330 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3331 }
3333 /**
3334 * Node handle ungrabbed callback.
3335 */
3336 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3337 {
3338 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3340 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3341 if (n->p.knot == knot) {
3342 n->p.origin_radial.a = 0;
3343 sp_knot_set_position(knot, &n->p.pos, state);
3344 } else if (n->n.knot == knot) {
3345 n->n.origin_radial.a = 0;
3346 sp_knot_set_position(knot, &n->n.pos, state);
3347 } else {
3348 g_assert_not_reached();
3349 }
3351 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3352 }
3354 /**
3355 * Node handle "request" signal callback.
3356 */
3357 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3358 {
3359 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3361 Inkscape::NodePath::NodeSide *me, *opposite;
3362 gint which;
3363 if (n->p.knot == knot) {
3364 me = &n->p;
3365 opposite = &n->n;
3366 which = -1;
3367 } else if (n->n.knot == knot) {
3368 me = &n->n;
3369 opposite = &n->p;
3370 which = 1;
3371 } else {
3372 me = opposite = NULL;
3373 which = 0;
3374 g_assert_not_reached();
3375 }
3377 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3379 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3381 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3382 /* We are smooth node adjacent with line */
3383 NR::Point const delta = *p - n->pos;
3384 NR::Coord const len = NR::L2(delta);
3385 Inkscape::NodePath::Node *othernode = opposite->other;
3386 NR::Point const ndelta = n->pos - othernode->pos;
3387 NR::Coord const linelen = NR::L2(ndelta);
3388 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3389 NR::Coord const scal = dot(delta, ndelta) / linelen;
3390 (*p) = n->pos + (scal / linelen) * ndelta;
3391 }
3392 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3393 } else {
3394 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3395 }
3397 sp_node_adjust_handle(n, -which);
3399 return FALSE;
3400 }
3402 /**
3403 * Node handle moved callback.
3404 */
3405 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3406 {
3407 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3409 Inkscape::NodePath::NodeSide *me;
3410 Inkscape::NodePath::NodeSide *other;
3411 if (n->p.knot == knot) {
3412 me = &n->p;
3413 other = &n->n;
3414 } else if (n->n.knot == knot) {
3415 me = &n->n;
3416 other = &n->p;
3417 } else {
3418 me = NULL;
3419 other = NULL;
3420 g_assert_not_reached();
3421 }
3423 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3424 Radial rme(me->pos - n->pos);
3425 Radial rother(other->pos - n->pos);
3426 Radial rnew(*p - n->pos);
3428 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3429 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3430 /* 0 interpreted as "no snapping". */
3432 // The closest PI/snaps angle, starting from zero.
3433 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3434 if (me->origin_radial.a == HUGE_VAL) {
3435 // ortho doesn't exist: original handle was zero length.
3436 rnew.a = a_snapped;
3437 } else {
3438 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3439 * its opposite and perpendiculars). */
3440 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3442 // Snap to the closest.
3443 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3444 ? a_snapped
3445 : a_ortho );
3446 }
3447 }
3449 if (state & GDK_MOD1_MASK) {
3450 // lock handle length
3451 rnew.r = me->origin_radial.r;
3452 }
3454 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3455 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3456 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3457 rother.a += rnew.a - rme.a;
3458 other->pos = NR::Point(rother) + n->pos;
3459 if (other->knot) {
3460 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3461 sp_knot_moveto(other->knot, &other->pos);
3462 }
3463 }
3465 me->pos = NR::Point(rnew) + n->pos;
3466 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3468 // move knot, but without emitting the signal:
3469 // we cannot emit a "moved" signal because we're now processing it
3470 sp_knot_moveto(me->knot, &(me->pos));
3472 update_object(n->subpath->nodepath);
3474 /* status text */
3475 SPDesktop *desktop = n->subpath->nodepath->desktop;
3476 if (!desktop) return;
3477 SPEventContext *ec = desktop->event_context;
3478 if (!ec) return;
3479 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3480 if (!mc) return;
3482 double degrees = 180 / M_PI * rnew.a;
3483 if (degrees > 180) degrees -= 360;
3484 if (degrees < -180) degrees += 360;
3485 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3486 degrees = angle_to_compass (degrees);
3488 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3490 mc->setF(Inkscape::NORMAL_MESSAGE,
3491 _("<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);
3493 g_string_free(length, TRUE);
3494 }
3496 /**
3497 * Node handle event callback.
3498 */
3499 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3500 {
3501 gboolean ret = FALSE;
3502 switch (event->type) {
3503 case GDK_KEY_PRESS:
3504 switch (get_group0_keyval (&event->key)) {
3505 case GDK_space:
3506 if (event->key.state & GDK_BUTTON1_MASK) {
3507 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3508 stamp_repr(nodepath);
3509 ret = TRUE;
3510 }
3511 break;
3512 default:
3513 break;
3514 }
3515 break;
3516 case GDK_ENTER_NOTIFY:
3517 // we use an experimentally determined threshold that seems to work fine
3518 if (NR::L2(n->pos - knot->pos) < 0.75)
3519 Inkscape::NodePath::Path::active_node = n;
3520 break;
3521 case GDK_LEAVE_NOTIFY:
3522 // we use an experimentally determined threshold that seems to work fine
3523 if (NR::L2(n->pos - knot->pos) < 0.75)
3524 Inkscape::NodePath::Path::active_node = NULL;
3525 break;
3526 default:
3527 break;
3528 }
3530 return ret;
3531 }
3533 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3534 Radial &rme, Radial &rother, gboolean const both)
3535 {
3536 rme.a += angle;
3537 if ( both
3538 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3539 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3540 {
3541 rother.a += angle;
3542 }
3543 }
3545 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3546 Radial &rme, Radial &rother, gboolean const both)
3547 {
3548 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3550 gdouble r;
3551 if ( both
3552 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3553 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3554 {
3555 r = MAX(rme.r, rother.r);
3556 } else {
3557 r = rme.r;
3558 }
3560 gdouble const weird_angle = atan2(norm_angle, r);
3561 /* Bulia says norm_angle is just the visible distance that the
3562 * object's end must travel on the screen. Left as 'angle' for want of
3563 * a better name.*/
3565 rme.a += weird_angle;
3566 if ( both
3567 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3568 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3569 {
3570 rother.a += weird_angle;
3571 }
3572 }
3574 /**
3575 * Rotate one node.
3576 */
3577 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3578 {
3579 Inkscape::NodePath::NodeSide *me, *other;
3580 bool both = false;
3582 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3583 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3585 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3586 me = &(n->p);
3587 other = &(n->n);
3588 } else if (!n->p.other) {
3589 me = &(n->n);
3590 other = &(n->p);
3591 } else {
3592 if (which > 0) { // right handle
3593 if (xn > xp) {
3594 me = &(n->n);
3595 other = &(n->p);
3596 } else {
3597 me = &(n->p);
3598 other = &(n->n);
3599 }
3600 } else if (which < 0){ // left handle
3601 if (xn <= xp) {
3602 me = &(n->n);
3603 other = &(n->p);
3604 } else {
3605 me = &(n->p);
3606 other = &(n->n);
3607 }
3608 } else { // both handles
3609 me = &(n->n);
3610 other = &(n->p);
3611 both = true;
3612 }
3613 }
3615 Radial rme(me->pos - n->pos);
3616 Radial rother(other->pos - n->pos);
3618 if (screen) {
3619 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3620 } else {
3621 node_rotate_one_internal (*n, angle, rme, rother, both);
3622 }
3624 me->pos = n->pos + NR::Point(rme);
3626 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3627 other->pos = n->pos + NR::Point(rother);
3628 }
3630 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3631 // so here we just move all the knots without emitting move signals, for speed
3632 sp_node_update_handles(n, false);
3633 }
3635 /**
3636 * Rotate selected nodes.
3637 */
3638 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3639 {
3640 if (!nodepath || !nodepath->selected) return;
3642 if (g_list_length(nodepath->selected) == 1) {
3643 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3644 node_rotate_one (n, angle, which, screen);
3645 } else {
3646 // rotate as an object:
3648 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3649 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3650 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3651 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3652 box.expandTo (n->pos); // contain all selected nodes
3653 }
3655 gdouble rot;
3656 if (screen) {
3657 gdouble const zoom = nodepath->desktop->current_zoom();
3658 gdouble const zmove = angle / zoom;
3659 gdouble const r = NR::L2(box.max() - box.midpoint());
3660 rot = atan2(zmove, r);
3661 } else {
3662 rot = angle;
3663 }
3665 NR::Matrix t =
3666 NR::Matrix (NR::translate(-box.midpoint())) *
3667 NR::Matrix (NR::rotate(rot)) *
3668 NR::Matrix (NR::translate(box.midpoint()));
3670 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3671 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3672 n->pos *= t;
3673 n->n.pos *= t;
3674 n->p.pos *= t;
3675 sp_node_update_handles(n, false);
3676 }
3677 }
3679 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3680 }
3682 /**
3683 * Scale one node.
3684 */
3685 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3686 {
3687 bool both = false;
3688 Inkscape::NodePath::NodeSide *me, *other;
3690 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3691 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3693 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3694 me = &(n->p);
3695 other = &(n->n);
3696 n->code = NR_CURVETO;
3697 } else if (!n->p.other) {
3698 me = &(n->n);
3699 other = &(n->p);
3700 if (n->n.other)
3701 n->n.other->code = NR_CURVETO;
3702 } else {
3703 if (which > 0) { // right handle
3704 if (xn > xp) {
3705 me = &(n->n);
3706 other = &(n->p);
3707 if (n->n.other)
3708 n->n.other->code = NR_CURVETO;
3709 } else {
3710 me = &(n->p);
3711 other = &(n->n);
3712 n->code = NR_CURVETO;
3713 }
3714 } else if (which < 0){ // left handle
3715 if (xn <= xp) {
3716 me = &(n->n);
3717 other = &(n->p);
3718 if (n->n.other)
3719 n->n.other->code = NR_CURVETO;
3720 } else {
3721 me = &(n->p);
3722 other = &(n->n);
3723 n->code = NR_CURVETO;
3724 }
3725 } else { // both handles
3726 me = &(n->n);
3727 other = &(n->p);
3728 both = true;
3729 n->code = NR_CURVETO;
3730 if (n->n.other)
3731 n->n.other->code = NR_CURVETO;
3732 }
3733 }
3735 Radial rme(me->pos - n->pos);
3736 Radial rother(other->pos - n->pos);
3738 rme.r += grow;
3739 if (rme.r < 0) rme.r = 0;
3740 if (rme.a == HUGE_VAL) {
3741 if (me->other) { // if direction is unknown, initialize it towards the next node
3742 Radial rme_next(me->other->pos - n->pos);
3743 rme.a = rme_next.a;
3744 } else { // if there's no next, initialize to 0
3745 rme.a = 0;
3746 }
3747 }
3748 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3749 rother.r += grow;
3750 if (rother.r < 0) rother.r = 0;
3751 if (rother.a == HUGE_VAL) {
3752 rother.a = rme.a + M_PI;
3753 }
3754 }
3756 me->pos = n->pos + NR::Point(rme);
3758 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3759 other->pos = n->pos + NR::Point(rother);
3760 }
3762 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3763 // so here we just move all the knots without emitting move signals, for speed
3764 sp_node_update_handles(n, false);
3765 }
3767 /**
3768 * Scale selected nodes.
3769 */
3770 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3771 {
3772 if (!nodepath || !nodepath->selected) return;
3774 if (g_list_length(nodepath->selected) == 1) {
3775 // scale handles of the single selected node
3776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3777 node_scale_one (n, grow, which);
3778 } else {
3779 // scale nodes as an "object":
3781 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3782 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3783 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3784 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3785 box.expandTo (n->pos); // contain all selected nodes
3786 }
3788 double scale = (box.maxExtent() + grow)/box.maxExtent();
3790 NR::Matrix t =
3791 NR::Matrix (NR::translate(-box.midpoint())) *
3792 NR::Matrix (NR::scale(scale, scale)) *
3793 NR::Matrix (NR::translate(box.midpoint()));
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 :