257071a42ae2b455caeb75e766512e2a91d57893
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 "selection-chemistry.h"
35 #include "selection.h"
36 #include "xml/repr.h"
37 #include "prefs-utils.h"
38 #include "sp-metrics.h"
39 #include "sp-path.h"
40 #include "libnr/nr-matrix-ops.h"
41 #include "splivarot.h"
42 #include "svg/svg.h"
43 #include "verbs.h"
44 #include "display/bezier-utils.h"
45 #include <vector>
46 #include <algorithm>
48 class NR::Matrix;
50 /// \todo
51 /// evil evil evil. FIXME: conflict of two different Path classes!
52 /// There is a conflict in the namespace between two classes named Path.
53 /// #include "sp-flowtext.h"
54 /// #include "sp-flowregion.h"
56 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
57 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
58 GType sp_flowregion_get_type (void);
59 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
60 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
61 GType sp_flowtext_get_type (void);
62 // end evil workaround
64 #include "helper/stlport.h"
67 /// \todo fixme: Implement these via preferences */
69 #define NODE_FILL 0xbfbfbf00
70 #define NODE_STROKE 0x000000ff
71 #define NODE_FILL_HI 0xff000000
72 #define NODE_STROKE_HI 0x000000ff
73 #define NODE_FILL_SEL 0x0000ffff
74 #define NODE_STROKE_SEL 0x000000ff
75 #define NODE_FILL_SEL_HI 0xff000000
76 #define NODE_STROKE_SEL_HI 0x000000ff
77 #define KNOT_FILL 0xffffffff
78 #define KNOT_STROKE 0x000000ff
79 #define KNOT_FILL_HI 0xff000000
80 #define KNOT_STROKE_HI 0x000000ff
82 static GMemChunk *nodechunk = NULL;
84 /* Creation from object */
86 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
87 static gchar *parse_nodetypes(gchar const *types, gint length);
89 /* Object updating */
91 static void stamp_repr(Inkscape::NodePath::Path *np);
92 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
93 static gchar *create_typestr(Inkscape::NodePath::Path *np);
95 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
97 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
99 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
101 /* Adjust handle placement, if the node or the other handle is moved */
102 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
103 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
105 /* Node event callbacks */
106 static void node_clicked(SPKnot *knot, guint state, gpointer data);
107 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
108 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
109 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
111 /* Handle event callbacks */
112 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
113 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
114 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
115 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
116 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
117 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
119 /* Constructors and destructors */
121 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
122 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
123 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
124 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
125 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
126 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
127 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
129 /* Helpers */
131 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
132 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
133 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
135 // active_node indicates mouseover node
136 static Inkscape::NodePath::Node *active_node = NULL;
138 /**
139 * \brief Creates new nodepath from item
140 */
141 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
142 {
143 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
145 /** \todo
146 * FIXME: remove this. We don't want to edit paths inside flowtext.
147 * Instead we will build our flowtext with cloned paths, so that the
148 * real paths are outside the flowtext and thus editable as usual.
149 */
150 if (SP_IS_FLOWTEXT(item)) {
151 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
152 if SP_IS_FLOWREGION(child) {
153 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
154 if (grandchild && SP_IS_PATH(grandchild)) {
155 item = SP_ITEM(grandchild);
156 break;
157 }
158 }
159 }
160 }
162 if (!SP_IS_PATH(item))
163 return NULL;
164 SPPath *path = SP_PATH(item);
165 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
166 if (curve == NULL)
167 return NULL;
169 NArtBpath *bpath = sp_curve_first_bpath(curve);
170 gint length = curve->end;
171 if (length == 0)
172 return NULL; // prevent crash for one-node paths
174 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
175 gchar *typestr = parse_nodetypes(nodetypes, length);
177 //Create new nodepath
178 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
179 if (!np)
180 return NULL;
182 // Set defaults
183 np->desktop = desktop;
184 np->path = path;
185 np->subpaths = NULL;
186 np->selected = NULL;
187 np->nodeContext = NULL; //Let the context that makes this set it
188 np->livarot_path = NULL;
189 np->local_change = 0;
190 np->show_handles = show_handles;
192 // we need to update item's transform from the repr here,
193 // because they may be out of sync when we respond
194 // to a change in repr by regenerating nodepath --bb
195 sp_object_read_attr(SP_OBJECT(item), "transform");
197 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
198 np->d2i = np->i2d.inverse();
199 np->repr = repr;
201 // create the subpath(s) from the bpath
202 NArtBpath *b = bpath;
203 while (b->code != NR_END) {
204 b = subpath_from_bpath(np, b, typestr + (b - bpath));
205 }
207 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
208 np->subpaths = g_list_reverse(np->subpaths);
210 g_free(typestr);
211 sp_curve_unref(curve);
213 // create the livarot representation from the same item
214 np->livarot_path = Path_for_item(item, true, true);
215 if (np->livarot_path)
216 np->livarot_path->ConvertWithBackData(0.01);
218 return np;
219 }
221 /**
222 * Destroys nodepath's subpaths, then itself, also tell context about it.
223 */
224 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
226 if (!np) //soft fail, like delete
227 return;
229 while (np->subpaths) {
230 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
231 }
233 //Inform the context that made me, if any, that I am gone.
234 if (np->nodeContext)
235 np->nodeContext->nodepath = NULL;
237 g_assert(!np->selected);
239 if (np->livarot_path) {
240 delete np->livarot_path;
241 np->livarot_path = NULL;
242 }
244 np->desktop = NULL;
246 g_free(np);
247 }
250 /**
251 * Return the node count of a given NodeSubPath.
252 */
253 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
254 {
255 if (!subpath)
256 return 0;
257 gint nodeCount = g_list_length(subpath->nodes);
258 return nodeCount;
259 }
261 /**
262 * Return the node count of a given NodePath.
263 */
264 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
265 {
266 if (!np)
267 return 0;
268 gint nodeCount = 0;
269 for (GList *item = np->subpaths ; item ; item=item->next) {
270 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
271 nodeCount += g_list_length(subpath->nodes);
272 }
273 return nodeCount;
274 }
276 /**
277 * Return the subpath count of a given NodePath.
278 */
279 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
280 {
281 if (!np)
282 return 0;
283 return g_list_length (np->subpaths);
284 }
286 /**
287 * Return the selected node count of a given NodePath.
288 */
289 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
290 {
291 if (!np)
292 return 0;
293 return g_list_length (np->selected);
294 }
296 /**
297 * Return the number of subpaths where nodes are selected in a given NodePath.
298 */
299 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
300 {
301 if (!np)
302 return 0;
303 if (!np->selected)
304 return 0;
305 if (!np->selected->next)
306 return 1;
307 gint count = 0;
308 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
309 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
310 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
311 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
312 if (node->selected) {
313 count ++;
314 break;
315 }
316 }
317 }
318 return count;
319 }
321 /**
322 * Clean up a nodepath after editing.
323 *
324 * Currently we are deleting trivial subpaths.
325 */
326 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
327 {
328 GList *badSubPaths = NULL;
330 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
331 for (GList *l = nodepath->subpaths; l ; l=l->next) {
332 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
333 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
334 badSubPaths = g_list_append(badSubPaths, sp);
335 }
337 //Delete them. This second step is because sp_nodepath_subpath_destroy()
338 //also removes the subpath from nodepath->subpaths
339 for (GList *l = badSubPaths; l ; l=l->next) {
340 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
341 sp_nodepath_subpath_destroy(sp);
342 }
344 g_list_free(badSubPaths);
345 }
347 /**
348 * Create new nodepath from b, make it subpath of np.
349 * \param t The node type.
350 * \todo Fixme: t should be a proper type, rather than gchar
351 */
352 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
353 {
354 NR::Point ppos, pos, npos;
356 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
358 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
359 bool const closed = (b->code == NR_MOVETO);
361 pos = NR::Point(b->x3, b->y3) * np->i2d;
362 if (b[1].code == NR_CURVETO) {
363 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
364 } else {
365 npos = pos;
366 }
367 Inkscape::NodePath::Node *n;
368 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
369 g_assert(sp->first == n);
370 g_assert(sp->last == n);
372 b++;
373 t++;
374 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
375 pos = NR::Point(b->x3, b->y3) * np->i2d;
376 if (b->code == NR_CURVETO) {
377 ppos = NR::Point(b->x2, b->y2) * np->i2d;
378 } else {
379 ppos = pos;
380 }
381 if (b[1].code == NR_CURVETO) {
382 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
383 } else {
384 npos = pos;
385 }
386 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
387 b++;
388 t++;
389 }
391 if (closed) sp_nodepath_subpath_close(sp);
393 return b;
394 }
396 /**
397 * Convert from sodipodi:nodetypes to new style type string.
398 */
399 static gchar *parse_nodetypes(gchar const *types, gint length)
400 {
401 g_assert(length > 0);
403 gchar *typestr = g_new(gchar, length + 1);
405 gint pos = 0;
407 if (types) {
408 for (gint i = 0; types[i] && ( i < length ); i++) {
409 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
410 if (types[i] != '\0') {
411 switch (types[i]) {
412 case 's':
413 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
414 break;
415 case 'z':
416 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
417 break;
418 case 'c':
419 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
420 break;
421 default:
422 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
423 break;
424 }
425 }
426 }
427 }
429 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
431 return typestr;
432 }
434 /**
435 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
436 * updated but repr is not (for speed). Used during curve and node drag.
437 */
438 static void update_object(Inkscape::NodePath::Path *np)
439 {
440 g_assert(np);
442 SPCurve *curve = create_curve(np);
444 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
446 sp_curve_unref(curve);
447 }
449 /**
450 * Update XML path node with data from path object.
451 */
452 static void update_repr_internal(Inkscape::NodePath::Path *np)
453 {
454 g_assert(np);
456 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
458 SPCurve *curve = create_curve(np);
459 gchar *typestr = create_typestr(np);
460 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
462 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
463 np->local_change++;
464 repr->setAttribute("d", svgpath);
465 }
467 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
468 np->local_change++;
469 repr->setAttribute("sodipodi:nodetypes", typestr);
470 }
472 g_free(svgpath);
473 g_free(typestr);
474 sp_curve_unref(curve);
475 }
477 /**
478 * Update XML path node with data from path object, commit changes forever.
479 */
480 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
481 {
482 //fixme: np can be NULL, so check before proceeding
483 g_return_if_fail(np != NULL);
485 update_repr_internal(np);
486 //sp_canvas_end_forced_full_redraws(np->desktop->canvas);
488 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
489 annotation);
491 if (np->livarot_path) {
492 delete np->livarot_path;
493 np->livarot_path = NULL;
494 }
496 if (np->path && SP_IS_ITEM(np->path)) {
497 np->livarot_path = Path_for_item (np->path, true, true);
498 if (np->livarot_path)
499 np->livarot_path->ConvertWithBackData(0.01);
500 }
502 }
504 /**
505 * Update XML path node with data from path object, commit changes with undo.
506 */
507 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
508 {
509 update_repr_internal(np);
510 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
511 annotation);
513 if (np->livarot_path) {
514 delete np->livarot_path;
515 np->livarot_path = NULL;
516 }
518 if (np->path && SP_IS_ITEM(np->path)) {
519 np->livarot_path = Path_for_item (np->path, true, true);
520 if (np->livarot_path)
521 np->livarot_path->ConvertWithBackData(0.01);
522 }
523 }
525 /**
526 * Make duplicate of path, replace corresponding XML node in tree, commit.
527 */
528 static void stamp_repr(Inkscape::NodePath::Path *np)
529 {
530 g_assert(np);
532 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
533 Inkscape::XML::Node *new_repr = old_repr->duplicate();
535 // remember the position of the item
536 gint pos = old_repr->position();
537 // remember parent
538 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
540 SPCurve *curve = create_curve(np);
541 gchar *typestr = create_typestr(np);
543 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
545 new_repr->setAttribute("d", svgpath);
546 new_repr->setAttribute("sodipodi:nodetypes", typestr);
548 // add the new repr to the parent
549 parent->appendChild(new_repr);
550 // move to the saved position
551 new_repr->setPosition(pos > 0 ? pos : 0);
553 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
554 _("Stamp"));
556 Inkscape::GC::release(new_repr);
557 g_free(svgpath);
558 g_free(typestr);
559 sp_curve_unref(curve);
560 }
562 /**
563 * Create curve from path.
564 */
565 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
566 {
567 SPCurve *curve = sp_curve_new();
569 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
570 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
571 sp_curve_moveto(curve,
572 sp->first->pos * np->d2i);
573 Inkscape::NodePath::Node *n = sp->first->n.other;
574 while (n) {
575 NR::Point const end_pt = n->pos * np->d2i;
576 switch (n->code) {
577 case NR_LINETO:
578 sp_curve_lineto(curve, end_pt);
579 break;
580 case NR_CURVETO:
581 sp_curve_curveto(curve,
582 n->p.other->n.pos * np->d2i,
583 n->p.pos * np->d2i,
584 end_pt);
585 break;
586 default:
587 g_assert_not_reached();
588 break;
589 }
590 if (n != sp->last) {
591 n = n->n.other;
592 } else {
593 n = NULL;
594 }
595 }
596 if (sp->closed) {
597 sp_curve_closepath(curve);
598 }
599 }
601 return curve;
602 }
604 /**
605 * Convert path type string to sodipodi:nodetypes style.
606 */
607 static gchar *create_typestr(Inkscape::NodePath::Path *np)
608 {
609 gchar *typestr = g_new(gchar, 32);
610 gint len = 32;
611 gint pos = 0;
613 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
614 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
616 if (pos >= len) {
617 typestr = g_renew(gchar, typestr, len + 32);
618 len += 32;
619 }
621 typestr[pos++] = 'c';
623 Inkscape::NodePath::Node *n;
624 n = sp->first->n.other;
625 while (n) {
626 gchar code;
628 switch (n->type) {
629 case Inkscape::NodePath::NODE_CUSP:
630 code = 'c';
631 break;
632 case Inkscape::NodePath::NODE_SMOOTH:
633 code = 's';
634 break;
635 case Inkscape::NodePath::NODE_SYMM:
636 code = 'z';
637 break;
638 default:
639 g_assert_not_reached();
640 code = '\0';
641 break;
642 }
644 if (pos >= len) {
645 typestr = g_renew(gchar, typestr, len + 32);
646 len += 32;
647 }
649 typestr[pos++] = code;
651 if (n != sp->last) {
652 n = n->n.other;
653 } else {
654 n = NULL;
655 }
656 }
657 }
659 if (pos >= len) {
660 typestr = g_renew(gchar, typestr, len + 1);
661 len += 1;
662 }
664 typestr[pos++] = '\0';
666 return typestr;
667 }
669 /**
670 * Returns current path in context.
671 */
672 static Inkscape::NodePath::Path *sp_nodepath_current()
673 {
674 if (!SP_ACTIVE_DESKTOP) {
675 return NULL;
676 }
678 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
680 if (!SP_IS_NODE_CONTEXT(event_context)) {
681 return NULL;
682 }
684 return SP_NODE_CONTEXT(event_context)->nodepath;
685 }
689 /**
690 \brief Fills node and handle positions for three nodes, splitting line
691 marked by end at distance t.
692 */
693 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
694 {
695 g_assert(new_path != NULL);
696 g_assert(end != NULL);
698 g_assert(end->p.other == new_path);
699 Inkscape::NodePath::Node *start = new_path->p.other;
700 g_assert(start);
702 if (end->code == NR_LINETO) {
703 new_path->type =Inkscape::NodePath::NODE_CUSP;
704 new_path->code = NR_LINETO;
705 new_path->pos = (t * start->pos + (1 - t) * end->pos);
706 } else {
707 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
708 new_path->code = NR_CURVETO;
709 gdouble s = 1 - t;
710 for (int dim = 0; dim < 2; dim++) {
711 NR::Coord const f000 = start->pos[dim];
712 NR::Coord const f001 = start->n.pos[dim];
713 NR::Coord const f011 = end->p.pos[dim];
714 NR::Coord const f111 = end->pos[dim];
715 NR::Coord const f00t = s * f000 + t * f001;
716 NR::Coord const f01t = s * f001 + t * f011;
717 NR::Coord const f11t = s * f011 + t * f111;
718 NR::Coord const f0tt = s * f00t + t * f01t;
719 NR::Coord const f1tt = s * f01t + t * f11t;
720 NR::Coord const fttt = s * f0tt + t * f1tt;
721 start->n.pos[dim] = f00t;
722 new_path->p.pos[dim] = f0tt;
723 new_path->pos[dim] = fttt;
724 new_path->n.pos[dim] = f1tt;
725 end->p.pos[dim] = f11t;
726 }
727 }
728 }
730 /**
731 * Adds new node on direct line between two nodes, activates handles of all
732 * three nodes.
733 */
734 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
735 {
736 g_assert(end);
737 g_assert(end->subpath);
738 g_assert(g_list_find(end->subpath->nodes, end));
740 Inkscape::NodePath::Node *start = end->p.other;
741 g_assert( start->n.other == end );
742 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
743 end,
744 Inkscape::NodePath::NODE_SMOOTH,
745 (NRPathcode)end->code,
746 &start->pos, &start->pos, &start->n.pos);
747 sp_nodepath_line_midpoint(newnode, end, t);
749 sp_node_update_handles(start);
750 sp_node_update_handles(newnode);
751 sp_node_update_handles(end);
753 return newnode;
754 }
756 /**
757 \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
758 */
759 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
760 {
761 g_assert(node);
762 g_assert(node->subpath);
763 g_assert(g_list_find(node->subpath->nodes, node));
765 Inkscape::NodePath::SubPath *sp = node->subpath;
766 Inkscape::NodePath::Path *np = sp->nodepath;
768 if (sp->closed) {
769 sp_nodepath_subpath_open(sp, node);
770 return sp->first;
771 } else {
772 // no break for end nodes
773 if (node == sp->first) return NULL;
774 if (node == sp->last ) return NULL;
776 // create a new subpath
777 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
779 // duplicate the break node as start of the new subpath
780 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
782 while (node->n.other) { // copy the remaining nodes into the new subpath
783 Inkscape::NodePath::Node *n = node->n.other;
784 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);
785 if (n->selected) {
786 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
787 }
788 sp_nodepath_node_destroy(n); // remove the point on the original subpath
789 }
791 return newnode;
792 }
793 }
795 /**
796 * Duplicate node and connect to neighbours.
797 */
798 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
799 {
800 g_assert(node);
801 g_assert(node->subpath);
802 g_assert(g_list_find(node->subpath->nodes, node));
804 Inkscape::NodePath::SubPath *sp = node->subpath;
806 NRPathcode code = (NRPathcode) node->code;
807 if (code == NR_MOVETO) { // if node is the endnode,
808 node->code = NR_LINETO; // new one is inserted before it, so change that to line
809 }
811 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
813 if (!node->n.other || !node->p.other) // if node is an endnode, select it
814 return node;
815 else
816 return newnode; // otherwise select the newly created node
817 }
819 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
820 {
821 node->p.pos = (node->pos + (node->pos - node->n.pos));
822 }
824 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
825 {
826 node->n.pos = (node->pos + (node->pos - node->p.pos));
827 }
829 /**
830 * Change line type at node, with side effects on neighbours.
831 */
832 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
833 {
834 g_assert(end);
835 g_assert(end->subpath);
836 g_assert(end->p.other);
838 if (end->code == static_cast< guint > ( code ) )
839 return;
841 Inkscape::NodePath::Node *start = end->p.other;
843 end->code = code;
845 if (code == NR_LINETO) {
846 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
847 if (end->n.other) {
848 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
849 }
850 sp_node_adjust_handle(start, -1);
851 sp_node_adjust_handle(end, 1);
852 } else {
853 NR::Point delta = end->pos - start->pos;
854 start->n.pos = start->pos + delta / 3;
855 end->p.pos = end->pos - delta / 3;
856 sp_node_adjust_handle(start, 1);
857 sp_node_adjust_handle(end, -1);
858 }
860 sp_node_update_handles(start);
861 sp_node_update_handles(end);
862 }
864 /**
865 * Change node type, and its handles accordingly.
866 */
867 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
868 {
869 g_assert(node);
870 g_assert(node->subpath);
872 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
873 return node;
875 if ((node->p.other != NULL) && (node->n.other != NULL)) {
876 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
877 type =Inkscape::NodePath::NODE_CUSP;
878 }
879 }
881 node->type = type;
883 if (node->type == Inkscape::NodePath::NODE_CUSP) {
884 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
885 node->knot->setSize (node->selected? 11 : 9);
886 sp_knot_update_ctrl(node->knot);
887 } else {
888 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
889 node->knot->setSize (node->selected? 9 : 7);
890 sp_knot_update_ctrl(node->knot);
891 }
893 // if one of handles is mouseovered, preserve its position
894 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
895 sp_node_adjust_handle(node, 1);
896 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
897 sp_node_adjust_handle(node, -1);
898 } else {
899 sp_node_adjust_handles(node);
900 }
902 sp_node_update_handles(node);
904 sp_nodepath_update_statusbar(node->subpath->nodepath);
906 return node;
907 }
909 /**
910 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
911 * adjacent segments from lines to curves.
912 */
913 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
914 {
915 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
916 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
917 // convert adjacent segment BEFORE to curve
918 node->code = NR_CURVETO;
919 NR::Point delta;
920 if (node->n.other != NULL)
921 delta = node->n.other->pos - node->p.other->pos;
922 else
923 delta = node->pos - node->p.other->pos;
924 node->p.pos = node->pos - delta / 4;
925 sp_node_update_handles(node);
926 }
928 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
929 // convert adjacent segment AFTER to curve
930 node->n.other->code = NR_CURVETO;
931 NR::Point delta;
932 if (node->p.other != NULL)
933 delta = node->p.other->pos - node->n.other->pos;
934 else
935 delta = node->pos - node->n.other->pos;
936 node->n.pos = node->pos - delta / 4;
937 sp_node_update_handles(node);
938 }
939 }
941 sp_nodepath_set_node_type (node, type);
942 }
944 /**
945 * Move node to point, and adjust its and neighbouring handles.
946 */
947 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
948 {
949 NR::Point delta = p - node->pos;
950 node->pos = p;
952 node->p.pos += delta;
953 node->n.pos += delta;
955 if (node->p.other) {
956 if (node->code == NR_LINETO) {
957 sp_node_adjust_handle(node, 1);
958 sp_node_adjust_handle(node->p.other, -1);
959 }
960 }
961 if (node->n.other) {
962 if (node->n.other->code == NR_LINETO) {
963 sp_node_adjust_handle(node, -1);
964 sp_node_adjust_handle(node->n.other, 1);
965 }
966 }
968 // this function is only called from batch movers that will update display at the end
969 // themselves, so here we just move all the knots without emitting move signals, for speed
970 sp_node_update_handles(node, false);
971 }
973 /**
974 * Call sp_node_moveto() for node selection and handle possible snapping.
975 */
976 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
977 bool const snap = true)
978 {
979 NR::Coord best = NR_HUGE;
980 NR::Point delta(dx, dy);
981 NR::Point best_pt = delta;
983 if (snap) {
984 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
986 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
987 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
988 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
989 if (s.getDistance() < best) {
990 best = s.getDistance();
991 best_pt = s.getPoint() - n->pos;
992 }
993 }
994 }
996 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
997 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
998 sp_node_moveto(n, n->pos + best_pt);
999 }
1001 // do not update repr here so that node dragging is acceptably fast
1002 update_object(nodepath);
1003 }
1005 /**
1006 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1007 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1008 near x = 0.
1009 */
1010 double
1011 sculpt_profile (double x, double alpha, guint profile)
1012 {
1013 if (x >= 1)
1014 return 0;
1015 if (x <= 0)
1016 return 1;
1018 switch (profile) {
1019 case SCULPT_PROFILE_LINEAR:
1020 return 1 - x;
1021 case SCULPT_PROFILE_BELL:
1022 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1023 case SCULPT_PROFILE_ELLIPTIC:
1024 return sqrt(1 - x*x);
1025 }
1027 return 1;
1028 }
1030 double
1031 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1032 {
1033 // extremely primitive for now, don't have time to look for the real one
1034 double lower = NR::L2(b - a);
1035 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1036 return (lower + upper)/2;
1037 }
1039 void
1040 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1041 {
1042 n->pos = n->origin + delta;
1043 n->n.pos = n->n.origin + delta_n;
1044 n->p.pos = n->p.origin + delta_p;
1045 sp_node_adjust_handles(n);
1046 sp_node_update_handles(n, false);
1047 }
1049 /**
1050 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1051 * on how far they are from the dragged node n.
1052 */
1053 static void
1054 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1055 {
1056 g_assert (n);
1057 g_assert (nodepath);
1058 g_assert (n->subpath->nodepath == nodepath);
1060 double pressure = n->knot->pressure;
1061 if (pressure == 0)
1062 pressure = 0.5; // default
1063 pressure = CLAMP (pressure, 0.2, 0.8);
1065 // map pressure to alpha = 1/5 ... 5
1066 double alpha = 1 - 2 * fabs(pressure - 0.5);
1067 if (pressure > 0.5)
1068 alpha = 1/alpha;
1070 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1072 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1073 // Only one subpath has selected nodes:
1074 // use linear mode, where the distance from n to node being dragged is calculated along the path
1076 double n_sel_range = 0, p_sel_range = 0;
1077 guint n_nodes = 0, p_nodes = 0;
1078 guint n_sel_nodes = 0, p_sel_nodes = 0;
1080 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1081 {
1082 double n_range = 0, p_range = 0;
1083 bool n_going = true, p_going = true;
1084 Inkscape::NodePath::Node *n_node = n;
1085 Inkscape::NodePath::Node *p_node = n;
1086 do {
1087 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1088 if (n_node && n_going)
1089 n_node = n_node->n.other;
1090 if (n_node == NULL) {
1091 n_going = false;
1092 } else {
1093 n_nodes ++;
1094 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1095 if (n_node->selected) {
1096 n_sel_nodes ++;
1097 n_sel_range = n_range;
1098 }
1099 if (n_node == p_node) {
1100 n_going = false;
1101 p_going = false;
1102 }
1103 }
1104 if (p_node && p_going)
1105 p_node = p_node->p.other;
1106 if (p_node == NULL) {
1107 p_going = false;
1108 } else {
1109 p_nodes ++;
1110 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1111 if (p_node->selected) {
1112 p_sel_nodes ++;
1113 p_sel_range = p_range;
1114 }
1115 if (p_node == n_node) {
1116 n_going = false;
1117 p_going = false;
1118 }
1119 }
1120 } while (n_going || p_going);
1121 }
1123 // Second pass: actually move nodes in this subpath
1124 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1125 {
1126 double n_range = 0, p_range = 0;
1127 bool n_going = true, p_going = true;
1128 Inkscape::NodePath::Node *n_node = n;
1129 Inkscape::NodePath::Node *p_node = n;
1130 do {
1131 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1132 if (n_node && n_going)
1133 n_node = n_node->n.other;
1134 if (n_node == NULL) {
1135 n_going = false;
1136 } else {
1137 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1138 if (n_node->selected) {
1139 sp_nodepath_move_node_and_handles (n_node,
1140 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1141 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1142 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1143 }
1144 if (n_node == p_node) {
1145 n_going = false;
1146 p_going = false;
1147 }
1148 }
1149 if (p_node && p_going)
1150 p_node = p_node->p.other;
1151 if (p_node == NULL) {
1152 p_going = false;
1153 } else {
1154 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1155 if (p_node->selected) {
1156 sp_nodepath_move_node_and_handles (p_node,
1157 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1158 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1159 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1160 }
1161 if (p_node == n_node) {
1162 n_going = false;
1163 p_going = false;
1164 }
1165 }
1166 } while (n_going || p_going);
1167 }
1169 } else {
1170 // Multiple subpaths have selected nodes:
1171 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1172 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1173 // fix the pear-like shape when sculpting e.g. a ring
1175 // First pass: calculate range
1176 gdouble direct_range = 0;
1177 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1178 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1179 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1180 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1181 if (node->selected) {
1182 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1183 }
1184 }
1185 }
1187 // Second pass: actually move nodes
1188 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1189 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1190 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1191 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1192 if (node->selected) {
1193 if (direct_range > 1e-6) {
1194 sp_nodepath_move_node_and_handles (node,
1195 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1196 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1197 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1198 } else {
1199 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1200 }
1202 }
1203 }
1204 }
1205 }
1207 // do not update repr here so that node dragging is acceptably fast
1208 update_object(nodepath);
1209 }
1212 /**
1213 * Move node selection to point, adjust its and neighbouring handles,
1214 * handle possible snapping, and commit the change with possible undo.
1215 */
1216 void
1217 sp_node_selected_move(gdouble dx, gdouble dy)
1218 {
1219 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1220 if (!nodepath) return;
1222 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1224 if (dx == 0) {
1225 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1226 } else if (dy == 0) {
1227 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1228 } else {
1229 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1230 }
1231 }
1233 /**
1234 * Move node selection off screen and commit the change.
1235 */
1236 void
1237 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1238 {
1239 // borrowed from sp_selection_move_screen in selection-chemistry.c
1240 // we find out the current zoom factor and divide deltas by it
1241 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1243 gdouble zoom = desktop->current_zoom();
1244 gdouble zdx = dx / zoom;
1245 gdouble zdy = dy / zoom;
1247 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1248 if (!nodepath) return;
1250 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1252 if (dx == 0) {
1253 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1254 } else if (dy == 0) {
1255 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1256 } else {
1257 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1258 }
1259 }
1261 /** If they don't yet exist, creates knot and line for the given side of the node */
1262 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1263 {
1264 if (!side->knot) {
1265 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"));
1267 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1268 side->knot->setSize (7);
1269 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1270 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1271 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1272 sp_knot_update_ctrl(side->knot);
1274 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1275 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1276 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1277 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1278 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1279 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1280 }
1282 if (!side->line) {
1283 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1284 SP_TYPE_CTRLLINE, NULL);
1285 }
1286 }
1288 /**
1289 * Ensure the given handle of the node is visible/invisible, update its screen position
1290 */
1291 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1292 {
1293 g_assert(node != NULL);
1295 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1296 NRPathcode code = sp_node_path_code_from_side(node, side);
1298 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1300 if (show_handle) {
1301 if (!side->knot) { // No handle knot at all
1302 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1303 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1304 side->knot->pos = side->pos;
1305 if (side->knot->item)
1306 SP_CTRL(side->knot->item)->moveto(side->pos);
1307 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1308 sp_knot_show(side->knot);
1309 } else {
1310 if (side->knot->pos != side->pos) { // only if it's really moved
1311 if (fire_move_signals) {
1312 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1313 } else {
1314 sp_knot_moveto(side->knot, &side->pos);
1315 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1316 }
1317 }
1318 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1319 sp_knot_show(side->knot);
1320 }
1321 }
1322 sp_canvas_item_show(side->line);
1323 } else {
1324 if (side->knot) {
1325 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1326 sp_knot_hide(side->knot);
1327 }
1328 }
1329 if (side->line) {
1330 sp_canvas_item_hide(side->line);
1331 }
1332 }
1333 }
1335 /**
1336 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1337 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1338 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1339 * updated; otherwise, just move the knots silently (used in batch moves).
1340 */
1341 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1342 {
1343 g_assert(node != NULL);
1345 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1346 sp_knot_show(node->knot);
1347 }
1349 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1350 if (fire_move_signals)
1351 sp_knot_set_position(node->knot, &node->pos, 0);
1352 else
1353 sp_knot_moveto(node->knot, &node->pos);
1354 }
1356 gboolean show_handles = node->selected;
1357 if (node->p.other != NULL) {
1358 if (node->p.other->selected) show_handles = TRUE;
1359 }
1360 if (node->n.other != NULL) {
1361 if (node->n.other->selected) show_handles = TRUE;
1362 }
1364 if (node->subpath->nodepath->show_handles == false)
1365 show_handles = FALSE;
1367 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1368 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1369 }
1371 /**
1372 * Call sp_node_update_handles() for all nodes on subpath.
1373 */
1374 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1375 {
1376 g_assert(subpath != NULL);
1378 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1379 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1380 }
1381 }
1383 /**
1384 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1385 */
1386 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1387 {
1388 g_assert(nodepath != NULL);
1390 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1391 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1392 }
1393 }
1395 void
1396 sp_nodepath_show_handles(bool show)
1397 {
1398 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1399 if (nodepath == NULL) return;
1401 nodepath->show_handles = show;
1402 sp_nodepath_update_handles(nodepath);
1403 }
1405 /**
1406 * Adds all selected nodes in nodepath to list.
1407 */
1408 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1409 {
1410 StlConv<Node *>::list(l, selected);
1411 /// \todo this adds a copying, rework when the selection becomes a stl list
1412 }
1414 /**
1415 * Align selected nodes on the specified axis.
1416 */
1417 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1418 {
1419 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1420 return;
1421 }
1423 if ( !nodepath->selected->next ) { // only one node selected
1424 return;
1425 }
1426 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1427 NR::Point dest(pNode->pos);
1428 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1429 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1430 if (pNode) {
1431 dest[axis] = pNode->pos[axis];
1432 sp_node_moveto(pNode, dest);
1433 }
1434 }
1436 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1437 }
1439 /// Helper struct.
1440 struct NodeSort
1441 {
1442 Inkscape::NodePath::Node *_node;
1443 NR::Coord _coord;
1444 /// \todo use vectorof pointers instead of calling copy ctor
1445 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1446 _node(node), _coord(node->pos[axis])
1447 {}
1449 };
1451 static bool operator<(NodeSort const &a, NodeSort const &b)
1452 {
1453 return (a._coord < b._coord);
1454 }
1456 /**
1457 * Distribute selected nodes on the specified axis.
1458 */
1459 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1460 {
1461 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1462 return;
1463 }
1465 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1466 return;
1467 }
1469 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1470 std::vector<NodeSort> sorted;
1471 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1472 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1473 if (pNode) {
1474 NodeSort n(pNode, axis);
1475 sorted.push_back(n);
1476 //dest[axis] = pNode->pos[axis];
1477 //sp_node_moveto(pNode, dest);
1478 }
1479 }
1480 std::sort(sorted.begin(), sorted.end());
1481 unsigned int len = sorted.size();
1482 //overall bboxes span
1483 float dist = (sorted.back()._coord -
1484 sorted.front()._coord);
1485 //new distance between each bbox
1486 float step = (dist) / (len - 1);
1487 float pos = sorted.front()._coord;
1488 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1489 it < sorted.end();
1490 it ++ )
1491 {
1492 NR::Point dest((*it)._node->pos);
1493 dest[axis] = pos;
1494 sp_node_moveto((*it)._node, dest);
1495 pos += step;
1496 }
1498 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1499 }
1502 /**
1503 * Call sp_nodepath_line_add_node() for all selected segments.
1504 */
1505 void
1506 sp_node_selected_add_node(void)
1507 {
1508 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1509 if (!nodepath) {
1510 return;
1511 }
1513 GList *nl = NULL;
1515 int n_added = 0;
1517 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1518 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1519 g_assert(t->selected);
1520 if (t->p.other && t->p.other->selected) {
1521 nl = g_list_prepend(nl, t);
1522 }
1523 }
1525 while (nl) {
1526 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1527 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1528 sp_nodepath_node_select(n, TRUE, FALSE);
1529 n_added ++;
1530 nl = g_list_remove(nl, t);
1531 }
1533 /** \todo fixme: adjust ? */
1534 sp_nodepath_update_handles(nodepath);
1536 if (n_added > 1) {
1537 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1538 } else if (n_added > 0) {
1539 sp_nodepath_update_repr(nodepath, _("Add node"));
1540 }
1542 sp_nodepath_update_statusbar(nodepath);
1543 }
1545 /**
1546 * Select segment nearest to point
1547 */
1548 void
1549 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1550 {
1551 if (!nodepath) {
1552 return;
1553 }
1555 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1557 //find segment to segment
1558 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1560 //fixme: this can return NULL, so check before proceeding.
1561 g_return_if_fail(e != NULL);
1563 gboolean force = FALSE;
1564 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1565 force = TRUE;
1566 }
1567 sp_nodepath_node_select(e, (gboolean) toggle, force);
1568 if (e->p.other)
1569 sp_nodepath_node_select(e->p.other, TRUE, force);
1571 sp_nodepath_update_handles(nodepath);
1573 sp_nodepath_update_statusbar(nodepath);
1574 }
1576 /**
1577 * Add a node nearest to point
1578 */
1579 void
1580 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1581 {
1582 if (!nodepath) {
1583 return;
1584 }
1586 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1588 //find segment to split
1589 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1591 //don't know why but t seems to flip for lines
1592 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1593 position.t = 1.0 - position.t;
1594 }
1595 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1596 sp_nodepath_node_select(n, FALSE, TRUE);
1598 /* fixme: adjust ? */
1599 sp_nodepath_update_handles(nodepath);
1601 sp_nodepath_update_repr(nodepath, _("Add node"));
1603 sp_nodepath_update_statusbar(nodepath);
1604 }
1606 /*
1607 * Adjusts a segment so that t moves by a certain delta for dragging
1608 * converts lines to curves
1609 *
1610 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1611 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1612 */
1613 void
1614 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1615 {
1616 //fixme: e and e->p can be NULL, so check for those before proceeding
1617 g_return_if_fail(e != NULL);
1618 g_return_if_fail(&e->p != NULL);
1620 /* feel good is an arbitrary parameter that distributes the delta between handles
1621 * if t of the drag point is less than 1/6 distance form the endpoint only
1622 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1623 */
1624 double feel_good;
1625 if (t <= 1.0 / 6.0)
1626 feel_good = 0;
1627 else if (t <= 0.5)
1628 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1629 else if (t <= 5.0 / 6.0)
1630 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1631 else
1632 feel_good = 1;
1634 //if we're dragging a line convert it to a curve
1635 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1636 sp_nodepath_set_line_type(e, NR_CURVETO);
1637 }
1639 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1640 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1641 e->p.other->n.pos += offsetcoord0;
1642 e->p.pos += offsetcoord1;
1644 // adjust handles of adjacent nodes where necessary
1645 sp_node_adjust_handle(e,1);
1646 sp_node_adjust_handle(e->p.other,-1);
1648 sp_nodepath_update_handles(e->subpath->nodepath);
1650 update_object(e->subpath->nodepath);
1652 sp_nodepath_update_statusbar(e->subpath->nodepath);
1653 }
1656 /**
1657 * Call sp_nodepath_break() for all selected segments.
1658 */
1659 void sp_node_selected_break()
1660 {
1661 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1662 if (!nodepath) return;
1664 GList *temp = NULL;
1665 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1666 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1667 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1668 if (nn == NULL) continue; // no break, no new node
1669 temp = g_list_prepend(temp, nn);
1670 }
1672 if (temp) {
1673 sp_nodepath_deselect(nodepath);
1674 }
1675 for (GList *l = temp; l != NULL; l = l->next) {
1676 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1677 }
1679 sp_nodepath_update_handles(nodepath);
1681 sp_nodepath_update_repr(nodepath, _("Break path"));
1682 }
1684 /**
1685 * Duplicate the selected node(s).
1686 */
1687 void sp_node_selected_duplicate()
1688 {
1689 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1690 if (!nodepath) {
1691 return;
1692 }
1694 GList *temp = NULL;
1695 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1696 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1697 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1698 if (nn == NULL) continue; // could not duplicate
1699 temp = g_list_prepend(temp, nn);
1700 }
1702 if (temp) {
1703 sp_nodepath_deselect(nodepath);
1704 }
1705 for (GList *l = temp; l != NULL; l = l->next) {
1706 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1707 }
1709 sp_nodepath_update_handles(nodepath);
1711 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1712 }
1714 /**
1715 * Join two nodes by merging them into one.
1716 */
1717 void sp_node_selected_join()
1718 {
1719 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1720 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1722 if (g_list_length(nodepath->selected) != 2) {
1723 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1724 return;
1725 }
1727 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1728 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1730 g_assert(a != b);
1731 g_assert(a->p.other || a->n.other);
1732 g_assert(b->p.other || b->n.other);
1734 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1735 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1736 return;
1737 }
1739 /* a and b are endpoints */
1741 NR::Point c;
1742 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1743 c = a->pos;
1744 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1745 c = b->pos;
1746 } else {
1747 c = (a->pos + b->pos) / 2;
1748 }
1750 if (a->subpath == b->subpath) {
1751 Inkscape::NodePath::SubPath *sp = a->subpath;
1752 sp_nodepath_subpath_close(sp);
1753 sp_node_moveto (sp->first, c);
1755 sp_nodepath_update_handles(sp->nodepath);
1756 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1757 return;
1758 }
1760 /* a and b are separate subpaths */
1761 Inkscape::NodePath::SubPath *sa = a->subpath;
1762 Inkscape::NodePath::SubPath *sb = b->subpath;
1763 NR::Point p;
1764 Inkscape::NodePath::Node *n;
1765 NRPathcode code;
1766 if (a == sa->first) {
1767 p = sa->first->n.pos;
1768 code = (NRPathcode)sa->first->n.other->code;
1769 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1770 n = sa->last;
1771 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1772 n = n->p.other;
1773 while (n) {
1774 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1775 n = n->p.other;
1776 if (n == sa->first) n = NULL;
1777 }
1778 sp_nodepath_subpath_destroy(sa);
1779 sa = t;
1780 } else if (a == sa->last) {
1781 p = sa->last->p.pos;
1782 code = (NRPathcode)sa->last->code;
1783 sp_nodepath_node_destroy(sa->last);
1784 } else {
1785 code = NR_END;
1786 g_assert_not_reached();
1787 }
1789 if (b == sb->first) {
1790 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1791 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1792 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1793 }
1794 } else if (b == sb->last) {
1795 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1796 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1797 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1798 }
1799 } else {
1800 g_assert_not_reached();
1801 }
1802 /* and now destroy sb */
1804 sp_nodepath_subpath_destroy(sb);
1806 sp_nodepath_update_handles(sa->nodepath);
1808 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1810 sp_nodepath_update_statusbar(nodepath);
1811 }
1813 /**
1814 * Join two nodes by adding a segment between them.
1815 */
1816 void sp_node_selected_join_segment()
1817 {
1818 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1819 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1821 if (g_list_length(nodepath->selected) != 2) {
1822 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1823 return;
1824 }
1826 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1827 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1829 g_assert(a != b);
1830 g_assert(a->p.other || a->n.other);
1831 g_assert(b->p.other || b->n.other);
1833 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1834 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1835 return;
1836 }
1838 if (a->subpath == b->subpath) {
1839 Inkscape::NodePath::SubPath *sp = a->subpath;
1841 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1842 sp->closed = TRUE;
1844 sp->first->p.other = sp->last;
1845 sp->last->n.other = sp->first;
1847 sp_node_handle_mirror_p_to_n(sp->last);
1848 sp_node_handle_mirror_n_to_p(sp->first);
1850 sp->first->code = sp->last->code;
1851 sp->first = sp->last;
1853 sp_nodepath_update_handles(sp->nodepath);
1855 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1857 return;
1858 }
1860 /* a and b are separate subpaths */
1861 Inkscape::NodePath::SubPath *sa = a->subpath;
1862 Inkscape::NodePath::SubPath *sb = b->subpath;
1864 Inkscape::NodePath::Node *n;
1865 NR::Point p;
1866 NRPathcode code;
1867 if (a == sa->first) {
1868 code = (NRPathcode) sa->first->n.other->code;
1869 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1870 n = sa->last;
1871 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1872 for (n = n->p.other; n != NULL; n = n->p.other) {
1873 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1874 }
1875 sp_nodepath_subpath_destroy(sa);
1876 sa = t;
1877 } else if (a == sa->last) {
1878 code = (NRPathcode)sa->last->code;
1879 } else {
1880 code = NR_END;
1881 g_assert_not_reached();
1882 }
1884 if (b == sb->first) {
1885 n = sb->first;
1886 sp_node_handle_mirror_p_to_n(sa->last);
1887 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1888 sp_node_handle_mirror_n_to_p(sa->last);
1889 for (n = n->n.other; n != NULL; n = n->n.other) {
1890 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1891 }
1892 } else if (b == sb->last) {
1893 n = sb->last;
1894 sp_node_handle_mirror_p_to_n(sa->last);
1895 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1896 sp_node_handle_mirror_n_to_p(sa->last);
1897 for (n = n->p.other; n != NULL; n = n->p.other) {
1898 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1899 }
1900 } else {
1901 g_assert_not_reached();
1902 }
1903 /* and now destroy sb */
1905 sp_nodepath_subpath_destroy(sb);
1907 sp_nodepath_update_handles(sa->nodepath);
1909 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1910 }
1912 /**
1913 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1914 */
1915 void sp_node_delete_preserve(GList *nodes_to_delete)
1916 {
1917 GSList *nodepaths = NULL;
1919 while (nodes_to_delete) {
1920 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1921 Inkscape::NodePath::SubPath *sp = node->subpath;
1922 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1923 Inkscape::NodePath::Node *sample_cursor = NULL;
1924 Inkscape::NodePath::Node *sample_end = NULL;
1925 Inkscape::NodePath::Node *delete_cursor = node;
1926 bool just_delete = false;
1928 //find the start of this contiguous selection
1929 //move left to the first node that is not selected
1930 //or the start of the non-closed path
1931 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1932 delete_cursor = curr;
1933 }
1935 //just delete at the beginning of an open path
1936 if (!delete_cursor->p.other) {
1937 sample_cursor = delete_cursor;
1938 just_delete = true;
1939 } else {
1940 sample_cursor = delete_cursor->p.other;
1941 }
1943 //calculate points for each segment
1944 int rate = 5;
1945 float period = 1.0 / rate;
1946 std::vector<NR::Point> data;
1947 if (!just_delete) {
1948 data.push_back(sample_cursor->pos);
1949 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1950 //just delete at the end of an open path
1951 if (!sp->closed && curr == sp->last) {
1952 just_delete = true;
1953 break;
1954 }
1956 //sample points on the contiguous selected segment
1957 NR::Point *bez;
1958 bez = new NR::Point [4];
1959 bez[0] = curr->pos;
1960 bez[1] = curr->n.pos;
1961 bez[2] = curr->n.other->p.pos;
1962 bez[3] = curr->n.other->pos;
1963 for (int i=1; i<rate; i++) {
1964 gdouble t = i * period;
1965 NR::Point p = bezier_pt(3, bez, t);
1966 data.push_back(p);
1967 }
1968 data.push_back(curr->n.other->pos);
1970 sample_end = curr->n.other;
1971 //break if we've come full circle or hit the end of the selection
1972 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1973 break;
1974 }
1975 }
1976 }
1978 if (!just_delete) {
1979 //calculate the best fitting single segment and adjust the endpoints
1980 NR::Point *adata;
1981 adata = new NR::Point [data.size()];
1982 copy(data.begin(), data.end(), adata);
1984 NR::Point *bez;
1985 bez = new NR::Point [4];
1986 //would decreasing error create a better fitting approximation?
1987 gdouble error = 1.0;
1988 gint ret;
1989 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1991 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1992 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1993 //the resulting nodes behave as expected.
1994 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1995 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1997 //adjust endpoints
1998 sample_cursor->n.pos = bez[1];
1999 sample_end->p.pos = bez[2];
2000 }
2002 //destroy this contiguous selection
2003 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2004 Inkscape::NodePath::Node *temp = delete_cursor;
2005 if (delete_cursor->n.other == delete_cursor) {
2006 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2007 delete_cursor = NULL;
2008 } else {
2009 delete_cursor = delete_cursor->n.other;
2010 }
2011 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2012 sp_nodepath_node_destroy(temp);
2013 }
2015 sp_nodepath_update_handles(nodepath);
2017 if (!g_slist_find(nodepaths, nodepath))
2018 nodepaths = g_slist_prepend (nodepaths, nodepath);
2019 }
2021 for (GSList *i = nodepaths; i; i = i->next) {
2022 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2023 // different nodepaths will give us one undo event per nodepath
2024 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2026 // if the entire nodepath is removed, delete the selected object.
2027 if (nodepath->subpaths == NULL ||
2028 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2029 //at least 2
2030 sp_nodepath_get_node_count(nodepath) < 2) {
2031 SPDocument *document = sp_desktop_document (nodepath->desktop);
2032 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2033 //delete this nodepath's object, not the entire selection! (though at this time, this
2034 //does not matter)
2035 sp_selection_delete();
2036 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2037 _("Delete nodes"));
2038 } else {
2039 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2040 sp_nodepath_update_statusbar(nodepath);
2041 }
2042 }
2044 g_slist_free (nodepaths);
2045 }
2047 /**
2048 * Delete one or more selected nodes.
2049 */
2050 void sp_node_selected_delete()
2051 {
2052 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2053 if (!nodepath) return;
2054 if (!nodepath->selected) return;
2056 /** \todo fixme: do it the right way */
2057 while (nodepath->selected) {
2058 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2059 sp_nodepath_node_destroy(node);
2060 }
2063 //clean up the nodepath (such as for trivial subpaths)
2064 sp_nodepath_cleanup(nodepath);
2066 sp_nodepath_update_handles(nodepath);
2068 // if the entire nodepath is removed, delete the selected object.
2069 if (nodepath->subpaths == NULL ||
2070 sp_nodepath_get_node_count(nodepath) < 2) {
2071 SPDocument *document = sp_desktop_document (nodepath->desktop);
2072 sp_selection_delete();
2073 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2074 _("Delete nodes"));
2075 return;
2076 }
2078 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2080 sp_nodepath_update_statusbar(nodepath);
2081 }
2083 /**
2084 * Delete one or more segments between two selected nodes.
2085 * This is the code for 'split'.
2086 */
2087 void
2088 sp_node_selected_delete_segment(void)
2089 {
2090 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2091 Inkscape::NodePath::Node *curr, *next; //Iterators
2093 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2094 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2096 if (g_list_length(nodepath->selected) != 2) {
2097 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2098 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2099 return;
2100 }
2102 //Selected nodes, not inclusive
2103 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2104 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2106 if ( ( a==b) || //same node
2107 (a->subpath != b->subpath ) || //not the same path
2108 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2109 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2110 {
2111 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2112 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2113 return;
2114 }
2116 //###########################################
2117 //# BEGIN EDITS
2118 //###########################################
2119 //##################################
2120 //# CLOSED PATH
2121 //##################################
2122 if (a->subpath->closed) {
2125 gboolean reversed = FALSE;
2127 //Since we can go in a circle, we need to find the shorter distance.
2128 // a->b or b->a
2129 start = end = NULL;
2130 int distance = 0;
2131 int minDistance = 0;
2132 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2133 if (curr==b) {
2134 //printf("a to b:%d\n", distance);
2135 start = a;//go from a to b
2136 end = b;
2137 minDistance = distance;
2138 //printf("A to B :\n");
2139 break;
2140 }
2141 distance++;
2142 }
2144 //try again, the other direction
2145 distance = 0;
2146 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2147 if (curr==a) {
2148 //printf("b to a:%d\n", distance);
2149 if (distance < minDistance) {
2150 start = b; //we go from b to a
2151 end = a;
2152 reversed = TRUE;
2153 //printf("B to A\n");
2154 }
2155 break;
2156 }
2157 distance++;
2158 }
2161 //Copy everything from 'end' to 'start' to a new subpath
2162 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2163 for (curr=end ; curr ; curr=curr->n.other) {
2164 NRPathcode code = (NRPathcode) curr->code;
2165 if (curr == end)
2166 code = NR_MOVETO;
2167 sp_nodepath_node_new(t, NULL,
2168 (Inkscape::NodePath::NodeType)curr->type, code,
2169 &curr->p.pos, &curr->pos, &curr->n.pos);
2170 if (curr == start)
2171 break;
2172 }
2173 sp_nodepath_subpath_destroy(a->subpath);
2176 }
2180 //##################################
2181 //# OPEN PATH
2182 //##################################
2183 else {
2185 //We need to get the direction of the list between A and B
2186 //Can we walk from a to b?
2187 start = end = NULL;
2188 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2189 if (curr==b) {
2190 start = a; //did it! we go from a to b
2191 end = b;
2192 //printf("A to B\n");
2193 break;
2194 }
2195 }
2196 if (!start) {//didn't work? let's try the other direction
2197 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2198 if (curr==a) {
2199 start = b; //did it! we go from b to a
2200 end = a;
2201 //printf("B to A\n");
2202 break;
2203 }
2204 }
2205 }
2206 if (!start) {
2207 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2208 _("Cannot find path between nodes."));
2209 return;
2210 }
2214 //Copy everything after 'end' to a new subpath
2215 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2216 for (curr=end ; curr ; curr=curr->n.other) {
2217 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2218 &curr->p.pos, &curr->pos, &curr->n.pos);
2219 }
2221 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2222 for (curr = start->n.other ; curr ; curr=next) {
2223 next = curr->n.other;
2224 sp_nodepath_node_destroy(curr);
2225 }
2227 }
2228 //###########################################
2229 //# END EDITS
2230 //###########################################
2232 //clean up the nodepath (such as for trivial subpaths)
2233 sp_nodepath_cleanup(nodepath);
2235 sp_nodepath_update_handles(nodepath);
2237 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2239 sp_nodepath_update_statusbar(nodepath);
2240 }
2242 /**
2243 * Call sp_nodepath_set_line() for all selected segments.
2244 */
2245 void
2246 sp_node_selected_set_line_type(NRPathcode code)
2247 {
2248 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2249 if (nodepath == NULL) return;
2251 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2252 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2253 g_assert(n->selected);
2254 if (n->p.other && n->p.other->selected) {
2255 sp_nodepath_set_line_type(n, code);
2256 }
2257 }
2259 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2260 }
2262 /**
2263 * Call sp_nodepath_convert_node_type() for all selected nodes.
2264 */
2265 void
2266 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2267 {
2268 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2269 if (nodepath == NULL) return;
2271 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2272 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2273 }
2275 sp_nodepath_update_repr(nodepath, _("Change node type"));
2276 }
2278 /**
2279 * Change select status of node, update its own and neighbour handles.
2280 */
2281 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2282 {
2283 node->selected = selected;
2285 if (selected) {
2286 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2287 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2288 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2289 sp_knot_update_ctrl(node->knot);
2290 } else {
2291 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2292 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2293 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2294 sp_knot_update_ctrl(node->knot);
2295 }
2297 sp_node_update_handles(node);
2298 if (node->n.other) sp_node_update_handles(node->n.other);
2299 if (node->p.other) sp_node_update_handles(node->p.other);
2300 }
2302 /**
2303 \brief Select a node
2304 \param node The node to select
2305 \param incremental If true, add to selection, otherwise deselect others
2306 \param override If true, always select this node, otherwise toggle selected status
2307 */
2308 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2309 {
2310 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2312 if (incremental) {
2313 if (override) {
2314 if (!g_list_find(nodepath->selected, node)) {
2315 nodepath->selected = g_list_prepend(nodepath->selected, node);
2316 }
2317 sp_node_set_selected(node, TRUE);
2318 } else { // toggle
2319 if (node->selected) {
2320 g_assert(g_list_find(nodepath->selected, node));
2321 nodepath->selected = g_list_remove(nodepath->selected, node);
2322 } else {
2323 g_assert(!g_list_find(nodepath->selected, node));
2324 nodepath->selected = g_list_prepend(nodepath->selected, node);
2325 }
2326 sp_node_set_selected(node, !node->selected);
2327 }
2328 } else {
2329 sp_nodepath_deselect(nodepath);
2330 nodepath->selected = g_list_prepend(nodepath->selected, node);
2331 sp_node_set_selected(node, TRUE);
2332 }
2334 sp_nodepath_update_statusbar(nodepath);
2335 }
2338 /**
2339 \brief Deselect all nodes in the nodepath
2340 */
2341 void
2342 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2343 {
2344 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2346 while (nodepath->selected) {
2347 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2348 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2349 }
2350 sp_nodepath_update_statusbar(nodepath);
2351 }
2353 /**
2354 \brief Select or invert selection of all nodes in the nodepath
2355 */
2356 void
2357 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2358 {
2359 if (!nodepath) return;
2361 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2362 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2363 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2364 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2365 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2366 }
2367 }
2368 }
2370 /**
2371 * If nothing selected, does the same as sp_nodepath_select_all();
2372 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2373 * (i.e., similar to "select all in layer", with the "selected" subpaths
2374 * being treated as "layers" in the path).
2375 */
2376 void
2377 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2378 {
2379 if (!nodepath) return;
2381 if (g_list_length (nodepath->selected) == 0) {
2382 sp_nodepath_select_all (nodepath, invert);
2383 return;
2384 }
2386 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2387 GSList *subpaths = NULL;
2389 for (GList *l = copy; l != NULL; l = l->next) {
2390 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2391 Inkscape::NodePath::SubPath *subpath = n->subpath;
2392 if (!g_slist_find (subpaths, subpath))
2393 subpaths = g_slist_prepend (subpaths, subpath);
2394 }
2396 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2397 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->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? !g_list_find(copy, node) : TRUE);
2401 }
2402 }
2404 g_slist_free (subpaths);
2405 g_list_free (copy);
2406 }
2408 /**
2409 * \brief Select the node after the last selected; if none is selected,
2410 * select the first within path.
2411 */
2412 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2413 {
2414 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2416 Inkscape::NodePath::Node *last = NULL;
2417 if (nodepath->selected) {
2418 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2419 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2420 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2421 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2422 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2423 if (node->selected) {
2424 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2425 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2426 if (spl->next) { // there's a next subpath
2427 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2428 last = subpath_next->first;
2429 } else if (spl->prev) { // there's a previous subpath
2430 last = NULL; // to be set later to the first node of first subpath
2431 } else {
2432 last = node->n.other;
2433 }
2434 } else {
2435 last = node->n.other;
2436 }
2437 } else {
2438 if (node->n.other) {
2439 last = node->n.other;
2440 } else {
2441 if (spl->next) { // there's a next subpath
2442 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2443 last = subpath_next->first;
2444 } else if (spl->prev) { // there's a previous subpath
2445 last = NULL; // to be set later to the first node of first subpath
2446 } else {
2447 last = (Inkscape::NodePath::Node *) subpath->first;
2448 }
2449 }
2450 }
2451 }
2452 }
2453 }
2454 sp_nodepath_deselect(nodepath);
2455 }
2457 if (last) { // there's at least one more node after selected
2458 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2459 } else { // no more nodes, select the first one in first subpath
2460 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2461 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2462 }
2463 }
2465 /**
2466 * \brief Select the node before the first selected; if none is selected,
2467 * select the last within path
2468 */
2469 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2470 {
2471 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2473 Inkscape::NodePath::Node *last = NULL;
2474 if (nodepath->selected) {
2475 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2476 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2477 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2478 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2479 if (node->selected) {
2480 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2481 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2482 if (spl->prev) { // there's a prev subpath
2483 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2484 last = subpath_prev->last;
2485 } else if (spl->next) { // there's a next subpath
2486 last = NULL; // to be set later to the last node of last subpath
2487 } else {
2488 last = node->p.other;
2489 }
2490 } else {
2491 last = node->p.other;
2492 }
2493 } else {
2494 if (node->p.other) {
2495 last = node->p.other;
2496 } else {
2497 if (spl->prev) { // there's a prev subpath
2498 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2499 last = subpath_prev->last;
2500 } else if (spl->next) { // there's a next subpath
2501 last = NULL; // to be set later to the last node of last subpath
2502 } else {
2503 last = (Inkscape::NodePath::Node *) subpath->last;
2504 }
2505 }
2506 }
2507 }
2508 }
2509 }
2510 sp_nodepath_deselect(nodepath);
2511 }
2513 if (last) { // there's at least one more node before selected
2514 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2515 } else { // no more nodes, select the last one in last subpath
2516 GList *spl = g_list_last(nodepath->subpaths);
2517 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2518 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2519 }
2520 }
2522 /**
2523 * \brief Select all nodes that are within the rectangle.
2524 */
2525 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2526 {
2527 if (!incremental) {
2528 sp_nodepath_deselect(nodepath);
2529 }
2531 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2532 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2533 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2534 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2536 if (b.contains(node->pos)) {
2537 sp_nodepath_node_select(node, TRUE, TRUE);
2538 }
2539 }
2540 }
2541 }
2544 void
2545 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2546 {
2547 g_assert (n);
2548 g_assert (nodepath);
2549 g_assert (n->subpath->nodepath == nodepath);
2551 if (g_list_length (nodepath->selected) == 0) {
2552 if (grow > 0) {
2553 sp_nodepath_node_select(n, TRUE, TRUE);
2554 }
2555 return;
2556 }
2558 if (g_list_length (nodepath->selected) == 1) {
2559 if (grow < 0) {
2560 sp_nodepath_deselect (nodepath);
2561 return;
2562 }
2563 }
2565 double n_sel_range = 0, p_sel_range = 0;
2566 Inkscape::NodePath::Node *farthest_n_node = n;
2567 Inkscape::NodePath::Node *farthest_p_node = n;
2569 // Calculate ranges
2570 {
2571 double n_range = 0, p_range = 0;
2572 bool n_going = true, p_going = true;
2573 Inkscape::NodePath::Node *n_node = n;
2574 Inkscape::NodePath::Node *p_node = n;
2575 do {
2576 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2577 if (n_node && n_going)
2578 n_node = n_node->n.other;
2579 if (n_node == NULL) {
2580 n_going = false;
2581 } else {
2582 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2583 if (n_node->selected) {
2584 n_sel_range = n_range;
2585 farthest_n_node = n_node;
2586 }
2587 if (n_node == p_node) {
2588 n_going = false;
2589 p_going = false;
2590 }
2591 }
2592 if (p_node && p_going)
2593 p_node = p_node->p.other;
2594 if (p_node == NULL) {
2595 p_going = false;
2596 } else {
2597 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2598 if (p_node->selected) {
2599 p_sel_range = p_range;
2600 farthest_p_node = p_node;
2601 }
2602 if (p_node == n_node) {
2603 n_going = false;
2604 p_going = false;
2605 }
2606 }
2607 } while (n_going || p_going);
2608 }
2610 if (grow > 0) {
2611 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2612 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2613 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2614 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2615 }
2616 } else {
2617 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2618 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2619 } else if (farthest_p_node && farthest_p_node->selected) {
2620 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2621 }
2622 }
2623 }
2625 void
2626 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2627 {
2628 g_assert (n);
2629 g_assert (nodepath);
2630 g_assert (n->subpath->nodepath == nodepath);
2632 if (g_list_length (nodepath->selected) == 0) {
2633 if (grow > 0) {
2634 sp_nodepath_node_select(n, TRUE, TRUE);
2635 }
2636 return;
2637 }
2639 if (g_list_length (nodepath->selected) == 1) {
2640 if (grow < 0) {
2641 sp_nodepath_deselect (nodepath);
2642 return;
2643 }
2644 }
2646 Inkscape::NodePath::Node *farthest_selected = NULL;
2647 double farthest_dist = 0;
2649 Inkscape::NodePath::Node *closest_unselected = NULL;
2650 double closest_dist = NR_HUGE;
2652 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2653 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2654 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2655 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2656 if (node == n)
2657 continue;
2658 if (node->selected) {
2659 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2660 farthest_dist = NR::L2(node->pos - n->pos);
2661 farthest_selected = node;
2662 }
2663 } else {
2664 if (NR::L2(node->pos - n->pos) < closest_dist) {
2665 closest_dist = NR::L2(node->pos - n->pos);
2666 closest_unselected = node;
2667 }
2668 }
2669 }
2670 }
2672 if (grow > 0) {
2673 if (closest_unselected) {
2674 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2675 }
2676 } else {
2677 if (farthest_selected) {
2678 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2679 }
2680 }
2681 }
2684 /**
2685 \brief Saves all nodes' and handles' current positions in their origin members
2686 */
2687 void
2688 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2689 {
2690 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2691 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2692 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2694 n->origin = n->pos;
2695 n->p.origin = n->p.pos;
2696 n->n.origin = n->n.pos;
2697 }
2698 }
2699 }
2701 /**
2702 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2703 */
2704 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2705 {
2706 if (!nodepath->selected) {
2707 return NULL;
2708 }
2710 GList *r = NULL;
2711 guint i = 0;
2712 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2713 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2714 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2715 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2716 i++;
2717 if (node->selected) {
2718 r = g_list_append(r, GINT_TO_POINTER(i));
2719 }
2720 }
2721 }
2722 return r;
2723 }
2725 /**
2726 \brief Restores selection by selecting nodes whose positions are in the list
2727 */
2728 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2729 {
2730 sp_nodepath_deselect(nodepath);
2732 guint i = 0;
2733 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2734 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2735 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2736 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2737 i++;
2738 if (g_list_find(r, GINT_TO_POINTER(i))) {
2739 sp_nodepath_node_select(node, TRUE, TRUE);
2740 }
2741 }
2742 }
2744 }
2746 /**
2747 \brief Adjusts handle according to node type and line code.
2748 */
2749 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2750 {
2751 double len, otherlen, linelen;
2753 g_assert(node);
2755 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2756 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2758 /** \todo fixme: */
2759 if (me->other == NULL) return;
2760 if (other->other == NULL) return;
2762 /* I have line */
2764 NRPathcode mecode, ocode;
2765 if (which_adjust == 1) {
2766 mecode = (NRPathcode)me->other->code;
2767 ocode = (NRPathcode)node->code;
2768 } else {
2769 mecode = (NRPathcode)node->code;
2770 ocode = (NRPathcode)other->other->code;
2771 }
2773 if (mecode == NR_LINETO) return;
2775 /* I am curve */
2777 if (other->other == NULL) return;
2779 /* Other has line */
2781 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2783 NR::Point delta;
2784 if (ocode == NR_LINETO) {
2785 /* other is lineto, we are either smooth or symm */
2786 Inkscape::NodePath::Node *othernode = other->other;
2787 len = NR::L2(me->pos - node->pos);
2788 delta = node->pos - othernode->pos;
2789 linelen = NR::L2(delta);
2790 if (linelen < 1e-18)
2791 return;
2792 me->pos = node->pos + (len / linelen)*delta;
2793 return;
2794 }
2796 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2798 me->pos = 2 * node->pos - other->pos;
2799 return;
2800 }
2802 /* We are smooth */
2804 len = NR::L2(me->pos - node->pos);
2805 delta = other->pos - node->pos;
2806 otherlen = NR::L2(delta);
2807 if (otherlen < 1e-18) return;
2809 me->pos = node->pos - (len / otherlen) * delta;
2810 }
2812 /**
2813 \brief Adjusts both handles according to node type and line code
2814 */
2815 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2816 {
2817 g_assert(node);
2819 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2821 /* we are either smooth or symm */
2823 if (node->p.other == NULL) return;
2825 if (node->n.other == NULL) return;
2827 if (node->code == NR_LINETO) {
2828 if (node->n.other->code == NR_LINETO) return;
2829 sp_node_adjust_handle(node, 1);
2830 return;
2831 }
2833 if (node->n.other->code == NR_LINETO) {
2834 if (node->code == NR_LINETO) return;
2835 sp_node_adjust_handle(node, -1);
2836 return;
2837 }
2839 /* both are curves */
2840 NR::Point const delta( node->n.pos - node->p.pos );
2842 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2843 node->p.pos = node->pos - delta / 2;
2844 node->n.pos = node->pos + delta / 2;
2845 return;
2846 }
2848 /* We are smooth */
2849 double plen = NR::L2(node->p.pos - node->pos);
2850 if (plen < 1e-18) return;
2851 double nlen = NR::L2(node->n.pos - node->pos);
2852 if (nlen < 1e-18) return;
2853 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2854 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2855 }
2857 /**
2858 * Node event callback.
2859 */
2860 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2861 {
2862 gboolean ret = FALSE;
2863 switch (event->type) {
2864 case GDK_ENTER_NOTIFY:
2865 active_node = n;
2866 break;
2867 case GDK_LEAVE_NOTIFY:
2868 active_node = NULL;
2869 break;
2870 case GDK_KEY_PRESS:
2871 switch (get_group0_keyval (&event->key)) {
2872 case GDK_space:
2873 if (event->key.state & GDK_BUTTON1_MASK) {
2874 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2875 stamp_repr(nodepath);
2876 ret = TRUE;
2877 }
2878 break;
2879 case GDK_Page_Up:
2880 if (event->key.state & GDK_CONTROL_MASK) {
2881 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2882 } else {
2883 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2884 }
2885 break;
2886 case GDK_Page_Down:
2887 if (event->key.state & GDK_CONTROL_MASK) {
2888 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2889 } else {
2890 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2891 }
2892 break;
2893 default:
2894 break;
2895 }
2896 break;
2897 default:
2898 break;
2899 }
2901 return ret;
2902 }
2904 /**
2905 * Handle keypress on node; directly called.
2906 */
2907 gboolean node_key(GdkEvent *event)
2908 {
2909 Inkscape::NodePath::Path *np;
2911 // there is no way to verify nodes so set active_node to nil when deleting!!
2912 if (active_node == NULL) return FALSE;
2914 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2915 gint ret = FALSE;
2916 switch (get_group0_keyval (&event->key)) {
2917 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2918 case GDK_BackSpace:
2919 np = active_node->subpath->nodepath;
2920 sp_nodepath_node_destroy(active_node);
2921 sp_nodepath_update_repr(np, _("Delete node"));
2922 active_node = NULL;
2923 ret = TRUE;
2924 break;
2925 case GDK_c:
2926 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2927 ret = TRUE;
2928 break;
2929 case GDK_s:
2930 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2931 ret = TRUE;
2932 break;
2933 case GDK_y:
2934 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2935 ret = TRUE;
2936 break;
2937 case GDK_b:
2938 sp_nodepath_node_break(active_node);
2939 ret = TRUE;
2940 break;
2941 }
2942 return ret;
2943 }
2944 return FALSE;
2945 }
2947 /**
2948 * Mouseclick on node callback.
2949 */
2950 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2951 {
2952 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2954 if (state & GDK_CONTROL_MASK) {
2955 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2957 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2958 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2959 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2960 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2961 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2962 } else {
2963 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2964 }
2965 sp_nodepath_update_repr(nodepath, _("Change node type"));
2966 sp_nodepath_update_statusbar(nodepath);
2968 } else { //ctrl+alt+click: delete node
2969 GList *node_to_delete = NULL;
2970 node_to_delete = g_list_append(node_to_delete, n);
2971 sp_node_delete_preserve(node_to_delete);
2972 }
2974 } else {
2975 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2976 }
2977 }
2979 /**
2980 * Mouse grabbed node callback.
2981 */
2982 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2983 {
2984 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2986 if (!n->selected) {
2987 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2988 }
2990 n->is_dragging = true;
2991 //sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
2993 sp_nodepath_remember_origins (n->subpath->nodepath);
2994 }
2996 /**
2997 * Mouse ungrabbed node callback.
2998 */
2999 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3000 {
3001 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3003 n->dragging_out = NULL;
3004 n->is_dragging = false;
3005 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3007 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3008 }
3010 /**
3011 * The point on a line, given by its angle, closest to the given point.
3012 * \param p A point.
3013 * \param a Angle of the line; it is assumed to go through coordinate origin.
3014 * \param closest Pointer to the point struct where the result is stored.
3015 * \todo FIXME: use dot product perhaps?
3016 */
3017 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3018 {
3019 if (a == HUGE_VAL) { // vertical
3020 *closest = NR::Point(0, (*p)[NR::Y]);
3021 } else {
3022 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3023 (*closest)[NR::Y] = a * (*closest)[NR::X];
3024 }
3025 }
3027 /**
3028 * Distance from the point to a line given by its angle.
3029 * \param p A point.
3030 * \param a Angle of the line; it is assumed to go through coordinate origin.
3031 */
3032 static double point_line_distance(NR::Point *p, double a)
3033 {
3034 NR::Point c;
3035 point_line_closest(p, a, &c);
3036 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]));
3037 }
3039 /**
3040 * Callback for node "request" signal.
3041 * \todo fixme: This goes to "moved" event? (lauris)
3042 */
3043 static gboolean
3044 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3045 {
3046 double yn, xn, yp, xp;
3047 double an, ap, na, pa;
3048 double d_an, d_ap, d_na, d_pa;
3049 gboolean collinear = FALSE;
3050 NR::Point c;
3051 NR::Point pr;
3053 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3055 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3056 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3058 NR::Point mouse = (*p);
3060 if (!n->dragging_out) {
3061 // This is the first drag-out event; find out which handle to drag out
3062 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3063 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3065 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3066 return FALSE;
3068 Inkscape::NodePath::NodeSide *opposite;
3069 if (appr_p > appr_n) { // closer to p
3070 n->dragging_out = &n->p;
3071 opposite = &n->n;
3072 n->code = NR_CURVETO;
3073 } else if (appr_p < appr_n) { // closer to n
3074 n->dragging_out = &n->n;
3075 opposite = &n->p;
3076 n->n.other->code = NR_CURVETO;
3077 } else { // p and n nodes are the same
3078 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3079 n->dragging_out = &n->p;
3080 opposite = &n->n;
3081 n->code = NR_CURVETO;
3082 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3083 n->dragging_out = &n->n;
3084 opposite = &n->p;
3085 n->n.other->code = NR_CURVETO;
3086 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3087 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);
3088 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);
3089 if (appr_other_p > appr_other_n) { // closer to other's p handle
3090 n->dragging_out = &n->n;
3091 opposite = &n->p;
3092 n->n.other->code = NR_CURVETO;
3093 } else { // closer to other's n handle
3094 n->dragging_out = &n->p;
3095 opposite = &n->n;
3096 n->code = NR_CURVETO;
3097 }
3098 }
3099 }
3101 // if there's another handle, make sure the one we drag out starts parallel to it
3102 if (opposite->pos != n->pos) {
3103 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3104 }
3106 // knots might not be created yet!
3107 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3108 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3109 }
3111 // pass this on to the handle-moved callback
3112 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3113 sp_node_update_handles(n);
3114 return TRUE;
3115 }
3117 if (state & GDK_CONTROL_MASK) { // constrained motion
3119 // calculate relative distances of handles
3120 // n handle:
3121 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3122 xn = n->n.pos[NR::X] - n->pos[NR::X];
3123 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3124 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3125 if (n->n.other) { // if there is the next point
3126 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3127 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3128 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3129 }
3130 }
3131 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3132 if (yn < 0) { xn = -xn; yn = -yn; }
3134 // p handle:
3135 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3136 xp = n->p.pos[NR::X] - n->pos[NR::X];
3137 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3138 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3139 if (n->p.other) {
3140 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3141 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3142 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3143 }
3144 }
3145 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3146 if (yp < 0) { xp = -xp; yp = -yp; }
3148 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3149 // sliding on handles, only if at least one of the handles is non-vertical
3150 // (otherwise it's the same as ctrl+drag anyway)
3152 // calculate angles of the handles
3153 if (xn == 0) {
3154 if (yn == 0) { // no handle, consider it the continuation of the other one
3155 an = 0;
3156 collinear = TRUE;
3157 }
3158 else an = 0; // vertical; set the angle to horizontal
3159 } else an = yn/xn;
3161 if (xp == 0) {
3162 if (yp == 0) { // no handle, consider it the continuation of the other one
3163 ap = an;
3164 }
3165 else ap = 0; // vertical; set the angle to horizontal
3166 } else ap = yp/xp;
3168 if (collinear) an = ap;
3170 // angles of the perpendiculars; HUGE_VAL means vertical
3171 if (an == 0) na = HUGE_VAL; else na = -1/an;
3172 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3174 // mouse point relative to the node's original pos
3175 pr = (*p) - n->origin;
3177 // distances to the four lines (two handles and two perpendiculars)
3178 d_an = point_line_distance(&pr, an);
3179 d_na = point_line_distance(&pr, na);
3180 d_ap = point_line_distance(&pr, ap);
3181 d_pa = point_line_distance(&pr, pa);
3183 // find out which line is the closest, save its closest point in c
3184 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3185 point_line_closest(&pr, an, &c);
3186 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3187 point_line_closest(&pr, ap, &c);
3188 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3189 point_line_closest(&pr, na, &c);
3190 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3191 point_line_closest(&pr, pa, &c);
3192 }
3194 // move the node to the closest point
3195 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3196 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3197 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3199 } else { // constraining to hor/vert
3201 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3202 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3203 } else { // snap to vert
3204 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3205 }
3206 }
3207 } else { // move freely
3208 if (n->is_dragging) {
3209 if (state & GDK_MOD1_MASK) { // sculpt
3210 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3211 } else {
3212 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3213 (*p)[NR::X] - n->pos[NR::X],
3214 (*p)[NR::Y] - n->pos[NR::Y],
3215 (state & GDK_SHIFT_MASK) == 0);
3216 }
3217 }
3218 }
3220 n->subpath->nodepath->desktop->scroll_to_point(p);
3222 return TRUE;
3223 }
3225 /**
3226 * Node handle clicked callback.
3227 */
3228 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3229 {
3230 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3232 if (state & GDK_CONTROL_MASK) { // "delete" handle
3233 if (n->p.knot == knot) {
3234 n->p.pos = n->pos;
3235 } else if (n->n.knot == knot) {
3236 n->n.pos = n->pos;
3237 }
3238 sp_node_update_handles(n);
3239 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3240 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3241 sp_nodepath_update_statusbar(nodepath);
3243 } else { // just select or add to selection, depending in Shift
3244 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3245 }
3246 }
3248 /**
3249 * Node handle grabbed callback.
3250 */
3251 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3252 {
3253 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3255 if (!n->selected) {
3256 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3257 }
3259 // remember the origin point of the handle
3260 if (n->p.knot == knot) {
3261 n->p.origin_radial = n->p.pos - n->pos;
3262 } else if (n->n.knot == knot) {
3263 n->n.origin_radial = n->n.pos - n->pos;
3264 } else {
3265 g_assert_not_reached();
3266 }
3268 //sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3269 }
3271 /**
3272 * Node handle ungrabbed callback.
3273 */
3274 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3275 {
3276 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3278 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3279 if (n->p.knot == knot) {
3280 n->p.origin_radial.a = 0;
3281 sp_knot_set_position(knot, &n->p.pos, state);
3282 } else if (n->n.knot == knot) {
3283 n->n.origin_radial.a = 0;
3284 sp_knot_set_position(knot, &n->n.pos, state);
3285 } else {
3286 g_assert_not_reached();
3287 }
3289 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3290 //sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3291 }
3293 /**
3294 * Node handle "request" signal callback.
3295 */
3296 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3297 {
3298 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3300 Inkscape::NodePath::NodeSide *me, *opposite;
3301 gint which;
3302 if (n->p.knot == knot) {
3303 me = &n->p;
3304 opposite = &n->n;
3305 which = -1;
3306 } else if (n->n.knot == knot) {
3307 me = &n->n;
3308 opposite = &n->p;
3309 which = 1;
3310 } else {
3311 me = opposite = NULL;
3312 which = 0;
3313 g_assert_not_reached();
3314 }
3316 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3318 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3320 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3321 /* We are smooth node adjacent with line */
3322 NR::Point const delta = *p - n->pos;
3323 NR::Coord const len = NR::L2(delta);
3324 Inkscape::NodePath::Node *othernode = opposite->other;
3325 NR::Point const ndelta = n->pos - othernode->pos;
3326 NR::Coord const linelen = NR::L2(ndelta);
3327 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3328 NR::Coord const scal = dot(delta, ndelta) / linelen;
3329 (*p) = n->pos + (scal / linelen) * ndelta;
3330 }
3331 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3332 } else {
3333 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3334 }
3336 sp_node_adjust_handle(n, -which);
3338 return FALSE;
3339 }
3341 /**
3342 * Node handle moved callback.
3343 */
3344 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3345 {
3346 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3348 Inkscape::NodePath::NodeSide *me;
3349 Inkscape::NodePath::NodeSide *other;
3350 if (n->p.knot == knot) {
3351 me = &n->p;
3352 other = &n->n;
3353 } else if (n->n.knot == knot) {
3354 me = &n->n;
3355 other = &n->p;
3356 } else {
3357 me = NULL;
3358 other = NULL;
3359 g_assert_not_reached();
3360 }
3362 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3363 Radial rme(me->pos - n->pos);
3364 Radial rother(other->pos - n->pos);
3365 Radial rnew(*p - n->pos);
3367 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3368 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3369 /* 0 interpreted as "no snapping". */
3371 // The closest PI/snaps angle, starting from zero.
3372 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3373 if (me->origin_radial.a == HUGE_VAL) {
3374 // ortho doesn't exist: original handle was zero length.
3375 rnew.a = a_snapped;
3376 } else {
3377 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3378 * its opposite and perpendiculars). */
3379 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3381 // Snap to the closest.
3382 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3383 ? a_snapped
3384 : a_ortho );
3385 }
3386 }
3388 if (state & GDK_MOD1_MASK) {
3389 // lock handle length
3390 rnew.r = me->origin_radial.r;
3391 }
3393 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3394 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3395 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3396 rother.a += rnew.a - rme.a;
3397 other->pos = NR::Point(rother) + n->pos;
3398 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3399 sp_knot_set_position(other->knot, &other->pos, 0);
3400 }
3402 me->pos = NR::Point(rnew) + n->pos;
3403 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3405 // this is what sp_knot_set_position does, but without emitting the signal:
3406 // we cannot emit a "moved" signal because we're now processing it
3407 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3409 knot->desktop->set_coordinate_status(me->pos);
3411 update_object(n->subpath->nodepath);
3413 /* status text */
3414 SPDesktop *desktop = n->subpath->nodepath->desktop;
3415 if (!desktop) return;
3416 SPEventContext *ec = desktop->event_context;
3417 if (!ec) return;
3418 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3419 if (!mc) return;
3421 double degrees = 180 / M_PI * rnew.a;
3422 if (degrees > 180) degrees -= 360;
3423 if (degrees < -180) degrees += 360;
3424 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3425 degrees = angle_to_compass (degrees);
3427 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3429 mc->setF(Inkscape::NORMAL_MESSAGE,
3430 _("<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);
3432 g_string_free(length, TRUE);
3433 }
3435 /**
3436 * Node handle event callback.
3437 */
3438 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3439 {
3440 gboolean ret = FALSE;
3441 switch (event->type) {
3442 case GDK_KEY_PRESS:
3443 switch (get_group0_keyval (&event->key)) {
3444 case GDK_space:
3445 if (event->key.state & GDK_BUTTON1_MASK) {
3446 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3447 stamp_repr(nodepath);
3448 ret = TRUE;
3449 }
3450 break;
3451 default:
3452 break;
3453 }
3454 break;
3455 default:
3456 break;
3457 }
3459 return ret;
3460 }
3462 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3463 Radial &rme, Radial &rother, gboolean const both)
3464 {
3465 rme.a += angle;
3466 if ( both
3467 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3468 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3469 {
3470 rother.a += angle;
3471 }
3472 }
3474 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3475 Radial &rme, Radial &rother, gboolean const both)
3476 {
3477 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3479 gdouble r;
3480 if ( both
3481 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3482 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3483 {
3484 r = MAX(rme.r, rother.r);
3485 } else {
3486 r = rme.r;
3487 }
3489 gdouble const weird_angle = atan2(norm_angle, r);
3490 /* Bulia says norm_angle is just the visible distance that the
3491 * object's end must travel on the screen. Left as 'angle' for want of
3492 * a better name.*/
3494 rme.a += weird_angle;
3495 if ( both
3496 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3497 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3498 {
3499 rother.a += weird_angle;
3500 }
3501 }
3503 /**
3504 * Rotate one node.
3505 */
3506 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3507 {
3508 Inkscape::NodePath::NodeSide *me, *other;
3509 bool both = false;
3511 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3512 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3514 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3515 me = &(n->p);
3516 other = &(n->n);
3517 } else if (!n->p.other) {
3518 me = &(n->n);
3519 other = &(n->p);
3520 } else {
3521 if (which > 0) { // right handle
3522 if (xn > xp) {
3523 me = &(n->n);
3524 other = &(n->p);
3525 } else {
3526 me = &(n->p);
3527 other = &(n->n);
3528 }
3529 } else if (which < 0){ // left handle
3530 if (xn <= xp) {
3531 me = &(n->n);
3532 other = &(n->p);
3533 } else {
3534 me = &(n->p);
3535 other = &(n->n);
3536 }
3537 } else { // both handles
3538 me = &(n->n);
3539 other = &(n->p);
3540 both = true;
3541 }
3542 }
3544 Radial rme(me->pos - n->pos);
3545 Radial rother(other->pos - n->pos);
3547 if (screen) {
3548 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3549 } else {
3550 node_rotate_one_internal (*n, angle, rme, rother, both);
3551 }
3553 me->pos = n->pos + NR::Point(rme);
3555 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3556 other->pos = n->pos + NR::Point(rother);
3557 }
3559 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3560 // so here we just move all the knots without emitting move signals, for speed
3561 sp_node_update_handles(n, false);
3562 }
3564 /**
3565 * Rotate selected nodes.
3566 */
3567 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3568 {
3569 if (!nodepath || !nodepath->selected) return;
3571 if (g_list_length(nodepath->selected) == 1) {
3572 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3573 node_rotate_one (n, angle, which, screen);
3574 } else {
3575 // rotate as an object:
3577 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3578 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3579 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3580 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3581 box.expandTo (n->pos); // contain all selected nodes
3582 }
3584 gdouble rot;
3585 if (screen) {
3586 gdouble const zoom = nodepath->desktop->current_zoom();
3587 gdouble const zmove = angle / zoom;
3588 gdouble const r = NR::L2(box.max() - box.midpoint());
3589 rot = atan2(zmove, r);
3590 } else {
3591 rot = angle;
3592 }
3594 NR::Matrix t =
3595 NR::Matrix (NR::translate(-box.midpoint())) *
3596 NR::Matrix (NR::rotate(rot)) *
3597 NR::Matrix (NR::translate(box.midpoint()));
3599 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3600 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3601 n->pos *= t;
3602 n->n.pos *= t;
3603 n->p.pos *= t;
3604 sp_node_update_handles(n, false);
3605 }
3606 }
3608 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3609 }
3611 /**
3612 * Scale one node.
3613 */
3614 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3615 {
3616 bool both = false;
3617 Inkscape::NodePath::NodeSide *me, *other;
3619 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3620 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3622 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3623 me = &(n->p);
3624 other = &(n->n);
3625 n->code = NR_CURVETO;
3626 } else if (!n->p.other) {
3627 me = &(n->n);
3628 other = &(n->p);
3629 if (n->n.other)
3630 n->n.other->code = NR_CURVETO;
3631 } else {
3632 if (which > 0) { // right handle
3633 if (xn > xp) {
3634 me = &(n->n);
3635 other = &(n->p);
3636 if (n->n.other)
3637 n->n.other->code = NR_CURVETO;
3638 } else {
3639 me = &(n->p);
3640 other = &(n->n);
3641 n->code = NR_CURVETO;
3642 }
3643 } else if (which < 0){ // left handle
3644 if (xn <= xp) {
3645 me = &(n->n);
3646 other = &(n->p);
3647 if (n->n.other)
3648 n->n.other->code = NR_CURVETO;
3649 } else {
3650 me = &(n->p);
3651 other = &(n->n);
3652 n->code = NR_CURVETO;
3653 }
3654 } else { // both handles
3655 me = &(n->n);
3656 other = &(n->p);
3657 both = true;
3658 n->code = NR_CURVETO;
3659 if (n->n.other)
3660 n->n.other->code = NR_CURVETO;
3661 }
3662 }
3664 Radial rme(me->pos - n->pos);
3665 Radial rother(other->pos - n->pos);
3667 rme.r += grow;
3668 if (rme.r < 0) rme.r = 0;
3669 if (rme.a == HUGE_VAL) {
3670 if (me->other) { // if direction is unknown, initialize it towards the next node
3671 Radial rme_next(me->other->pos - n->pos);
3672 rme.a = rme_next.a;
3673 } else { // if there's no next, initialize to 0
3674 rme.a = 0;
3675 }
3676 }
3677 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3678 rother.r += grow;
3679 if (rother.r < 0) rother.r = 0;
3680 if (rother.a == HUGE_VAL) {
3681 rother.a = rme.a + M_PI;
3682 }
3683 }
3685 me->pos = n->pos + NR::Point(rme);
3687 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3688 other->pos = n->pos + NR::Point(rother);
3689 }
3691 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3692 // so here we just move all the knots without emitting move signals, for speed
3693 sp_node_update_handles(n, false);
3694 }
3696 /**
3697 * Scale selected nodes.
3698 */
3699 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3700 {
3701 if (!nodepath || !nodepath->selected) return;
3703 if (g_list_length(nodepath->selected) == 1) {
3704 // scale handles of the single selected node
3705 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3706 node_scale_one (n, grow, which);
3707 } else {
3708 // scale nodes as an "object":
3710 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3711 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3712 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3713 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3714 box.expandTo (n->pos); // contain all selected nodes
3715 }
3717 double scale = (box.maxExtent() + grow)/box.maxExtent();
3719 NR::Matrix t =
3720 NR::Matrix (NR::translate(-box.midpoint())) *
3721 NR::Matrix (NR::scale(scale, scale)) *
3722 NR::Matrix (NR::translate(box.midpoint()));
3724 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3725 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3726 n->pos *= t;
3727 n->n.pos *= t;
3728 n->p.pos *= t;
3729 sp_node_update_handles(n, false);
3730 }
3731 }
3733 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3734 }
3736 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3737 {
3738 if (!nodepath) return;
3739 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3740 }
3742 /**
3743 * Flip selected nodes horizontally/vertically.
3744 */
3745 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3746 {
3747 if (!nodepath || !nodepath->selected) return;
3749 if (g_list_length(nodepath->selected) == 1) {
3750 // flip handles of the single selected node
3751 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3752 double temp = n->p.pos[axis];
3753 n->p.pos[axis] = n->n.pos[axis];
3754 n->n.pos[axis] = temp;
3755 sp_node_update_handles(n, false);
3756 } else {
3757 // scale nodes as an "object":
3759 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3760 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3761 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3762 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3763 box.expandTo (n->pos); // contain all selected nodes
3764 }
3766 NR::Matrix t =
3767 NR::Matrix (NR::translate(-box.midpoint())) *
3768 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3769 NR::Matrix (NR::translate(box.midpoint()));
3771 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3772 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3773 n->pos *= t;
3774 n->n.pos *= t;
3775 n->p.pos *= t;
3776 sp_node_update_handles(n, false);
3777 }
3778 }
3780 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3781 }
3783 //-----------------------------------------------
3784 /**
3785 * Return new subpath under given nodepath.
3786 */
3787 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3788 {
3789 g_assert(nodepath);
3790 g_assert(nodepath->desktop);
3792 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3794 s->nodepath = nodepath;
3795 s->closed = FALSE;
3796 s->nodes = NULL;
3797 s->first = NULL;
3798 s->last = NULL;
3800 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3801 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3802 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3804 return s;
3805 }
3807 /**
3808 * Destroy nodes in subpath, then subpath itself.
3809 */
3810 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3811 {
3812 g_assert(subpath);
3813 g_assert(subpath->nodepath);
3814 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3816 while (subpath->nodes) {
3817 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3818 }
3820 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3822 g_free(subpath);
3823 }
3825 /**
3826 * Link head to tail in subpath.
3827 */
3828 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3829 {
3830 g_assert(!sp->closed);
3831 g_assert(sp->last != sp->first);
3832 g_assert(sp->first->code == NR_MOVETO);
3834 sp->closed = TRUE;
3836 //Link the head to the tail
3837 sp->first->p.other = sp->last;
3838 sp->last->n.other = sp->first;
3839 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3840 sp->first = sp->last;
3842 //Remove the extra end node
3843 sp_nodepath_node_destroy(sp->last->n.other);
3844 }
3846 /**
3847 * Open closed (loopy) subpath at node.
3848 */
3849 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3850 {
3851 g_assert(sp->closed);
3852 g_assert(n->subpath == sp);
3853 g_assert(sp->first == sp->last);
3855 /* We create new startpoint, current node will become last one */
3857 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3858 &n->pos, &n->pos, &n->n.pos);
3861 sp->closed = FALSE;
3863 //Unlink to make a head and tail
3864 sp->first = new_path;
3865 sp->last = n;
3866 n->n.other = NULL;
3867 new_path->p.other = NULL;
3868 }
3870 /**
3871 * Returns area in triangle given by points; may be negative.
3872 */
3873 inline double
3874 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3875 {
3876 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]);
3877 }
3879 /**
3880 * Return new node in subpath with given properties.
3881 * \param pos Position of node.
3882 * \param ppos Handle position in previous direction
3883 * \param npos Handle position in previous direction
3884 */
3885 Inkscape::NodePath::Node *
3886 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)
3887 {
3888 g_assert(sp);
3889 g_assert(sp->nodepath);
3890 g_assert(sp->nodepath->desktop);
3892 if (nodechunk == NULL)
3893 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3895 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3897 n->subpath = sp;
3899 if (type != Inkscape::NodePath::NODE_NONE) {
3900 // use the type from sodipodi:nodetypes
3901 n->type = type;
3902 } else {
3903 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3904 // points are (almost) collinear
3905 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3906 // endnode, or a node with a retracted handle
3907 n->type = Inkscape::NodePath::NODE_CUSP;
3908 } else {
3909 n->type = Inkscape::NodePath::NODE_SMOOTH;
3910 }
3911 } else {
3912 n->type = Inkscape::NodePath::NODE_CUSP;
3913 }
3914 }
3916 n->code = code;
3917 n->selected = FALSE;
3918 n->pos = *pos;
3919 n->p.pos = *ppos;
3920 n->n.pos = *npos;
3922 n->dragging_out = NULL;
3924 Inkscape::NodePath::Node *prev;
3925 if (next) {
3926 //g_assert(g_list_find(sp->nodes, next));
3927 prev = next->p.other;
3928 } else {
3929 prev = sp->last;
3930 }
3932 if (prev)
3933 prev->n.other = n;
3934 else
3935 sp->first = n;
3937 if (next)
3938 next->p.other = n;
3939 else
3940 sp->last = n;
3942 n->p.other = prev;
3943 n->n.other = next;
3945 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"));
3946 sp_knot_set_position(n->knot, pos, 0);
3948 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3949 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3950 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3951 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3952 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3953 sp_knot_update_ctrl(n->knot);
3955 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3956 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3957 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3958 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3959 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3960 sp_knot_show(n->knot);
3962 // We only create handle knots and lines on demand
3963 n->p.knot = NULL;
3964 n->p.line = NULL;
3965 n->n.knot = NULL;
3966 n->n.line = NULL;
3968 sp->nodes = g_list_prepend(sp->nodes, n);
3970 return n;
3971 }
3973 /**
3974 * Destroy node and its knots, link neighbors in subpath.
3975 */
3976 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3977 {
3978 g_assert(node);
3979 g_assert(node->subpath);
3980 g_assert(SP_IS_KNOT(node->knot));
3982 Inkscape::NodePath::SubPath *sp = node->subpath;
3984 if (node->selected) { // first, deselect
3985 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3986 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3987 }
3989 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3991 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
3992 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
3993 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
3994 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
3995 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
3996 g_object_unref(G_OBJECT(node->knot));
3998 if (node->p.knot) {
3999 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4000 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4001 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4002 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4003 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4004 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4005 g_object_unref(G_OBJECT(node->p.knot));
4006 }
4008 if (node->n.knot) {
4009 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4010 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4011 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4012 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4013 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4014 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4015 g_object_unref(G_OBJECT(node->n.knot));
4016 }
4018 if (node->p.line)
4019 gtk_object_destroy(GTK_OBJECT(node->p.line));
4020 if (node->n.line)
4021 gtk_object_destroy(GTK_OBJECT(node->n.line));
4023 if (sp->nodes) { // there are others nodes on the subpath
4024 if (sp->closed) {
4025 if (sp->first == node) {
4026 g_assert(sp->last == node);
4027 sp->first = node->n.other;
4028 sp->last = sp->first;
4029 }
4030 node->p.other->n.other = node->n.other;
4031 node->n.other->p.other = node->p.other;
4032 } else {
4033 if (sp->first == node) {
4034 sp->first = node->n.other;
4035 sp->first->code = NR_MOVETO;
4036 }
4037 if (sp->last == node) sp->last = node->p.other;
4038 if (node->p.other) node->p.other->n.other = node->n.other;
4039 if (node->n.other) node->n.other->p.other = node->p.other;
4040 }
4041 } else { // this was the last node on subpath
4042 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4043 }
4045 g_mem_chunk_free(nodechunk, node);
4046 }
4048 /**
4049 * Returns one of the node's two sides.
4050 * \param which Indicates which side.
4051 * \return Pointer to previous node side if which==-1, next if which==1.
4052 */
4053 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4054 {
4055 g_assert(node);
4057 switch (which) {
4058 case -1:
4059 return &node->p;
4060 case 1:
4061 return &node->n;
4062 default:
4063 break;
4064 }
4066 g_assert_not_reached();
4068 return NULL;
4069 }
4071 /**
4072 * Return the other side of the node, given one of its sides.
4073 */
4074 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4075 {
4076 g_assert(node);
4078 if (me == &node->p) return &node->n;
4079 if (me == &node->n) return &node->p;
4081 g_assert_not_reached();
4083 return NULL;
4084 }
4086 /**
4087 * Return NRPathcode on the given side of the node.
4088 */
4089 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4090 {
4091 g_assert(node);
4093 if (me == &node->p) {
4094 if (node->p.other) return (NRPathcode)node->code;
4095 return NR_MOVETO;
4096 }
4098 if (me == &node->n) {
4099 if (node->n.other) return (NRPathcode)node->n.other->code;
4100 return NR_MOVETO;
4101 }
4103 g_assert_not_reached();
4105 return NR_END;
4106 }
4108 /**
4109 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4110 */
4111 Inkscape::NodePath::Node *
4112 sp_nodepath_get_node_by_index(int index)
4113 {
4114 Inkscape::NodePath::Node *e = NULL;
4116 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4117 if (!nodepath) {
4118 return e;
4119 }
4121 //find segment
4122 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4124 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4125 int n = g_list_length(sp->nodes);
4126 if (sp->closed) {
4127 n++;
4128 }
4130 //if the piece belongs to this subpath grab it
4131 //otherwise move onto the next subpath
4132 if (index < n) {
4133 e = sp->first;
4134 for (int i = 0; i < index; ++i) {
4135 e = e->n.other;
4136 }
4137 break;
4138 } else {
4139 if (sp->closed) {
4140 index -= (n+1);
4141 } else {
4142 index -= n;
4143 }
4144 }
4145 }
4147 return e;
4148 }
4150 /**
4151 * Returns plain text meaning of node type.
4152 */
4153 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4154 {
4155 unsigned retracted = 0;
4156 bool endnode = false;
4158 for (int which = -1; which <= 1; which += 2) {
4159 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4160 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4161 retracted ++;
4162 if (!side->other)
4163 endnode = true;
4164 }
4166 if (retracted == 0) {
4167 if (endnode) {
4168 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4169 return _("end node");
4170 } else {
4171 switch (node->type) {
4172 case Inkscape::NodePath::NODE_CUSP:
4173 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4174 return _("cusp");
4175 case Inkscape::NodePath::NODE_SMOOTH:
4176 // TRANSLATORS: "smooth" is an adjective here
4177 return _("smooth");
4178 case Inkscape::NodePath::NODE_SYMM:
4179 return _("symmetric");
4180 }
4181 }
4182 } else if (retracted == 1) {
4183 if (endnode) {
4184 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4185 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4186 } else {
4187 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4188 }
4189 } else {
4190 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4191 }
4193 return NULL;
4194 }
4196 /**
4197 * Handles content of statusbar as long as node tool is active.
4198 */
4199 void
4200 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4201 {
4202 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");
4203 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4205 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4206 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4207 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4208 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4210 SPDesktop *desktop = NULL;
4211 if (nodepath) {
4212 desktop = nodepath->desktop;
4213 } else {
4214 desktop = SP_ACTIVE_DESKTOP;
4215 }
4217 SPEventContext *ec = desktop->event_context;
4218 if (!ec) return;
4219 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4220 if (!mc) return;
4222 if (selected_nodes == 0) {
4223 Inkscape::Selection *sel = desktop->selection;
4224 if (!sel || sel->isEmpty()) {
4225 mc->setF(Inkscape::NORMAL_MESSAGE,
4226 _("Select a single object to edit its nodes or handles."));
4227 } else {
4228 if (nodepath) {
4229 mc->setF(Inkscape::NORMAL_MESSAGE,
4230 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.",
4231 "<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.",
4232 total_nodes),
4233 total_nodes);
4234 } else {
4235 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4236 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4237 } else {
4238 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4239 }
4240 }
4241 }
4242 } else if (nodepath && selected_nodes == 1) {
4243 mc->setF(Inkscape::NORMAL_MESSAGE,
4244 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4245 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4246 total_nodes),
4247 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4248 } else {
4249 if (selected_subpaths > 1) {
4250 mc->setF(Inkscape::NORMAL_MESSAGE,
4251 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4252 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4253 total_nodes),
4254 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4255 } else {
4256 mc->setF(Inkscape::NORMAL_MESSAGE,
4257 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4258 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4259 total_nodes),
4260 selected_nodes, total_nodes, when_selected);
4261 }
4262 }
4263 }
4266 /*
4267 Local Variables:
4268 mode:c++
4269 c-file-style:"stroustrup"
4270 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4271 indent-tabs-mode:nil
4272 fill-column:99
4273 End:
4274 */
4275 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :