067cf5435428d6a2b2452d900532bd464ffaa652
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_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
487 annotation);
489 if (np->livarot_path) {
490 delete np->livarot_path;
491 np->livarot_path = NULL;
492 }
494 if (np->path && SP_IS_ITEM(np->path)) {
495 np->livarot_path = Path_for_item (np->path, true, true);
496 if (np->livarot_path)
497 np->livarot_path->ConvertWithBackData(0.01);
498 }
500 }
502 /**
503 * Update XML path node with data from path object, commit changes with undo.
504 */
505 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
506 {
507 update_repr_internal(np);
508 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
509 annotation);
511 if (np->livarot_path) {
512 delete np->livarot_path;
513 np->livarot_path = NULL;
514 }
516 if (np->path && SP_IS_ITEM(np->path)) {
517 np->livarot_path = Path_for_item (np->path, true, true);
518 if (np->livarot_path)
519 np->livarot_path->ConvertWithBackData(0.01);
520 }
521 }
523 /**
524 * Make duplicate of path, replace corresponding XML node in tree, commit.
525 */
526 static void stamp_repr(Inkscape::NodePath::Path *np)
527 {
528 g_assert(np);
530 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
531 Inkscape::XML::Node *new_repr = old_repr->duplicate();
533 // remember the position of the item
534 gint pos = old_repr->position();
535 // remember parent
536 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
538 SPCurve *curve = create_curve(np);
539 gchar *typestr = create_typestr(np);
541 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
543 new_repr->setAttribute("d", svgpath);
544 new_repr->setAttribute("sodipodi:nodetypes", typestr);
546 // add the new repr to the parent
547 parent->appendChild(new_repr);
548 // move to the saved position
549 new_repr->setPosition(pos > 0 ? pos : 0);
551 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
552 _("Stamp"));
554 Inkscape::GC::release(new_repr);
555 g_free(svgpath);
556 g_free(typestr);
557 sp_curve_unref(curve);
558 }
560 /**
561 * Create curve from path.
562 */
563 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
564 {
565 SPCurve *curve = sp_curve_new();
567 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
568 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
569 sp_curve_moveto(curve,
570 sp->first->pos * np->d2i);
571 Inkscape::NodePath::Node *n = sp->first->n.other;
572 while (n) {
573 NR::Point const end_pt = n->pos * np->d2i;
574 switch (n->code) {
575 case NR_LINETO:
576 sp_curve_lineto(curve, end_pt);
577 break;
578 case NR_CURVETO:
579 sp_curve_curveto(curve,
580 n->p.other->n.pos * np->d2i,
581 n->p.pos * np->d2i,
582 end_pt);
583 break;
584 default:
585 g_assert_not_reached();
586 break;
587 }
588 if (n != sp->last) {
589 n = n->n.other;
590 } else {
591 n = NULL;
592 }
593 }
594 if (sp->closed) {
595 sp_curve_closepath(curve);
596 }
597 }
599 return curve;
600 }
602 /**
603 * Convert path type string to sodipodi:nodetypes style.
604 */
605 static gchar *create_typestr(Inkscape::NodePath::Path *np)
606 {
607 gchar *typestr = g_new(gchar, 32);
608 gint len = 32;
609 gint pos = 0;
611 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
612 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
614 if (pos >= len) {
615 typestr = g_renew(gchar, typestr, len + 32);
616 len += 32;
617 }
619 typestr[pos++] = 'c';
621 Inkscape::NodePath::Node *n;
622 n = sp->first->n.other;
623 while (n) {
624 gchar code;
626 switch (n->type) {
627 case Inkscape::NodePath::NODE_CUSP:
628 code = 'c';
629 break;
630 case Inkscape::NodePath::NODE_SMOOTH:
631 code = 's';
632 break;
633 case Inkscape::NodePath::NODE_SYMM:
634 code = 'z';
635 break;
636 default:
637 g_assert_not_reached();
638 code = '\0';
639 break;
640 }
642 if (pos >= len) {
643 typestr = g_renew(gchar, typestr, len + 32);
644 len += 32;
645 }
647 typestr[pos++] = code;
649 if (n != sp->last) {
650 n = n->n.other;
651 } else {
652 n = NULL;
653 }
654 }
655 }
657 if (pos >= len) {
658 typestr = g_renew(gchar, typestr, len + 1);
659 len += 1;
660 }
662 typestr[pos++] = '\0';
664 return typestr;
665 }
667 /**
668 * Returns current path in context.
669 */
670 static Inkscape::NodePath::Path *sp_nodepath_current()
671 {
672 if (!SP_ACTIVE_DESKTOP) {
673 return NULL;
674 }
676 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
678 if (!SP_IS_NODE_CONTEXT(event_context)) {
679 return NULL;
680 }
682 return SP_NODE_CONTEXT(event_context)->nodepath;
683 }
687 /**
688 \brief Fills node and handle positions for three nodes, splitting line
689 marked by end at distance t.
690 */
691 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
692 {
693 g_assert(new_path != NULL);
694 g_assert(end != NULL);
696 g_assert(end->p.other == new_path);
697 Inkscape::NodePath::Node *start = new_path->p.other;
698 g_assert(start);
700 if (end->code == NR_LINETO) {
701 new_path->type =Inkscape::NodePath::NODE_CUSP;
702 new_path->code = NR_LINETO;
703 new_path->pos = (t * start->pos + (1 - t) * end->pos);
704 } else {
705 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
706 new_path->code = NR_CURVETO;
707 gdouble s = 1 - t;
708 for (int dim = 0; dim < 2; dim++) {
709 NR::Coord const f000 = start->pos[dim];
710 NR::Coord const f001 = start->n.pos[dim];
711 NR::Coord const f011 = end->p.pos[dim];
712 NR::Coord const f111 = end->pos[dim];
713 NR::Coord const f00t = s * f000 + t * f001;
714 NR::Coord const f01t = s * f001 + t * f011;
715 NR::Coord const f11t = s * f011 + t * f111;
716 NR::Coord const f0tt = s * f00t + t * f01t;
717 NR::Coord const f1tt = s * f01t + t * f11t;
718 NR::Coord const fttt = s * f0tt + t * f1tt;
719 start->n.pos[dim] = f00t;
720 new_path->p.pos[dim] = f0tt;
721 new_path->pos[dim] = fttt;
722 new_path->n.pos[dim] = f1tt;
723 end->p.pos[dim] = f11t;
724 }
725 }
726 }
728 /**
729 * Adds new node on direct line between two nodes, activates handles of all
730 * three nodes.
731 */
732 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
733 {
734 g_assert(end);
735 g_assert(end->subpath);
736 g_assert(g_list_find(end->subpath->nodes, end));
738 Inkscape::NodePath::Node *start = end->p.other;
739 g_assert( start->n.other == end );
740 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
741 end,
742 Inkscape::NodePath::NODE_SMOOTH,
743 (NRPathcode)end->code,
744 &start->pos, &start->pos, &start->n.pos);
745 sp_nodepath_line_midpoint(newnode, end, t);
747 sp_node_update_handles(start);
748 sp_node_update_handles(newnode);
749 sp_node_update_handles(end);
751 return newnode;
752 }
754 /**
755 \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
756 */
757 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
758 {
759 g_assert(node);
760 g_assert(node->subpath);
761 g_assert(g_list_find(node->subpath->nodes, node));
763 Inkscape::NodePath::SubPath *sp = node->subpath;
764 Inkscape::NodePath::Path *np = sp->nodepath;
766 if (sp->closed) {
767 sp_nodepath_subpath_open(sp, node);
768 return sp->first;
769 } else {
770 // no break for end nodes
771 if (node == sp->first) return NULL;
772 if (node == sp->last ) return NULL;
774 // create a new subpath
775 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
777 // duplicate the break node as start of the new subpath
778 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
780 while (node->n.other) { // copy the remaining nodes into the new subpath
781 Inkscape::NodePath::Node *n = node->n.other;
782 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);
783 if (n->selected) {
784 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
785 }
786 sp_nodepath_node_destroy(n); // remove the point on the original subpath
787 }
789 return newnode;
790 }
791 }
793 /**
794 * Duplicate node and connect to neighbours.
795 */
796 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
797 {
798 g_assert(node);
799 g_assert(node->subpath);
800 g_assert(g_list_find(node->subpath->nodes, node));
802 Inkscape::NodePath::SubPath *sp = node->subpath;
804 NRPathcode code = (NRPathcode) node->code;
805 if (code == NR_MOVETO) { // if node is the endnode,
806 node->code = NR_LINETO; // new one is inserted before it, so change that to line
807 }
809 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
811 if (!node->n.other || !node->p.other) // if node is an endnode, select it
812 return node;
813 else
814 return newnode; // otherwise select the newly created node
815 }
817 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
818 {
819 node->p.pos = (node->pos + (node->pos - node->n.pos));
820 }
822 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
823 {
824 node->n.pos = (node->pos + (node->pos - node->p.pos));
825 }
827 /**
828 * Change line type at node, with side effects on neighbours.
829 */
830 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
831 {
832 g_assert(end);
833 g_assert(end->subpath);
834 g_assert(end->p.other);
836 if (end->code == static_cast< guint > ( code ) )
837 return;
839 Inkscape::NodePath::Node *start = end->p.other;
841 end->code = code;
843 if (code == NR_LINETO) {
844 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
845 if (end->n.other) {
846 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
847 }
848 sp_node_adjust_handle(start, -1);
849 sp_node_adjust_handle(end, 1);
850 } else {
851 NR::Point delta = end->pos - start->pos;
852 start->n.pos = start->pos + delta / 3;
853 end->p.pos = end->pos - delta / 3;
854 sp_node_adjust_handle(start, 1);
855 sp_node_adjust_handle(end, -1);
856 }
858 sp_node_update_handles(start);
859 sp_node_update_handles(end);
860 }
862 /**
863 * Change node type, and its handles accordingly.
864 */
865 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
866 {
867 g_assert(node);
868 g_assert(node->subpath);
870 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
871 return node;
873 if ((node->p.other != NULL) && (node->n.other != NULL)) {
874 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
875 type =Inkscape::NodePath::NODE_CUSP;
876 }
877 }
879 node->type = type;
881 if (node->type == Inkscape::NodePath::NODE_CUSP) {
882 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
883 node->knot->setSize (node->selected? 11 : 9);
884 sp_knot_update_ctrl(node->knot);
885 } else {
886 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
887 node->knot->setSize (node->selected? 9 : 7);
888 sp_knot_update_ctrl(node->knot);
889 }
891 // if one of handles is mouseovered, preserve its position
892 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
893 sp_node_adjust_handle(node, 1);
894 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
895 sp_node_adjust_handle(node, -1);
896 } else {
897 sp_node_adjust_handles(node);
898 }
900 sp_node_update_handles(node);
902 sp_nodepath_update_statusbar(node->subpath->nodepath);
904 return node;
905 }
907 /**
908 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
909 * adjacent segments from lines to curves.
910 */
911 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
912 {
913 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
914 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
915 // convert adjacent segment BEFORE to curve
916 node->code = NR_CURVETO;
917 NR::Point delta;
918 if (node->n.other != NULL)
919 delta = node->n.other->pos - node->p.other->pos;
920 else
921 delta = node->pos - node->p.other->pos;
922 node->p.pos = node->pos - delta / 4;
923 sp_node_update_handles(node);
924 }
926 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
927 // convert adjacent segment AFTER to curve
928 node->n.other->code = NR_CURVETO;
929 NR::Point delta;
930 if (node->p.other != NULL)
931 delta = node->p.other->pos - node->n.other->pos;
932 else
933 delta = node->pos - node->n.other->pos;
934 node->n.pos = node->pos - delta / 4;
935 sp_node_update_handles(node);
936 }
937 }
939 sp_nodepath_set_node_type (node, type);
940 }
942 /**
943 * Move node to point, and adjust its and neighbouring handles.
944 */
945 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
946 {
947 NR::Point delta = p - node->pos;
948 node->pos = p;
950 node->p.pos += delta;
951 node->n.pos += delta;
953 if (node->p.other) {
954 if (node->code == NR_LINETO) {
955 sp_node_adjust_handle(node, 1);
956 sp_node_adjust_handle(node->p.other, -1);
957 }
958 }
959 if (node->n.other) {
960 if (node->n.other->code == NR_LINETO) {
961 sp_node_adjust_handle(node, -1);
962 sp_node_adjust_handle(node->n.other, 1);
963 }
964 }
966 // this function is only called from batch movers that will update display at the end
967 // themselves, so here we just move all the knots without emitting move signals, for speed
968 sp_node_update_handles(node, false);
969 }
971 /**
972 * Call sp_node_moveto() for node selection and handle possible snapping.
973 */
974 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
975 bool const snap = true)
976 {
977 NR::Coord best = NR_HUGE;
978 NR::Point delta(dx, dy);
979 NR::Point best_pt = delta;
981 if (snap) {
982 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
984 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
985 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
986 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
987 if (s.getDistance() < best) {
988 best = s.getDistance();
989 best_pt = s.getPoint() - n->pos;
990 }
991 }
992 }
994 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
995 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
996 sp_node_moveto(n, n->pos + best_pt);
997 }
999 // do not update repr here so that node dragging is acceptably fast
1000 update_object(nodepath);
1001 }
1003 /**
1004 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1005 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1006 near x = 0.
1007 */
1008 double
1009 sculpt_profile (double x, double alpha, guint profile)
1010 {
1011 if (x >= 1)
1012 return 0;
1013 if (x <= 0)
1014 return 1;
1016 switch (profile) {
1017 case SCULPT_PROFILE_LINEAR:
1018 return 1 - x;
1019 case SCULPT_PROFILE_BELL:
1020 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1021 case SCULPT_PROFILE_ELLIPTIC:
1022 return sqrt(1 - x*x);
1023 }
1025 return 1;
1026 }
1028 double
1029 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1030 {
1031 // extremely primitive for now, don't have time to look for the real one
1032 double lower = NR::L2(b - a);
1033 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1034 return (lower + upper)/2;
1035 }
1037 void
1038 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1039 {
1040 n->pos = n->origin + delta;
1041 n->n.pos = n->n.origin + delta_n;
1042 n->p.pos = n->p.origin + delta_p;
1043 sp_node_adjust_handles(n);
1044 sp_node_update_handles(n, false);
1045 }
1047 /**
1048 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1049 * on how far they are from the dragged node n.
1050 */
1051 static void
1052 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1053 {
1054 g_assert (n);
1055 g_assert (nodepath);
1056 g_assert (n->subpath->nodepath == nodepath);
1058 double pressure = n->knot->pressure;
1059 if (pressure == 0)
1060 pressure = 0.5; // default
1061 pressure = CLAMP (pressure, 0.2, 0.8);
1063 // map pressure to alpha = 1/5 ... 5
1064 double alpha = 1 - 2 * fabs(pressure - 0.5);
1065 if (pressure > 0.5)
1066 alpha = 1/alpha;
1068 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1070 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1071 // Only one subpath has selected nodes:
1072 // use linear mode, where the distance from n to node being dragged is calculated along the path
1074 double n_sel_range = 0, p_sel_range = 0;
1075 guint n_nodes = 0, p_nodes = 0;
1076 guint n_sel_nodes = 0, p_sel_nodes = 0;
1078 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1079 {
1080 double n_range = 0, p_range = 0;
1081 bool n_going = true, p_going = true;
1082 Inkscape::NodePath::Node *n_node = n;
1083 Inkscape::NodePath::Node *p_node = n;
1084 do {
1085 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1086 if (n_node && n_going)
1087 n_node = n_node->n.other;
1088 if (n_node == NULL) {
1089 n_going = false;
1090 } else {
1091 n_nodes ++;
1092 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1093 if (n_node->selected) {
1094 n_sel_nodes ++;
1095 n_sel_range = n_range;
1096 }
1097 if (n_node == p_node) {
1098 n_going = false;
1099 p_going = false;
1100 }
1101 }
1102 if (p_node && p_going)
1103 p_node = p_node->p.other;
1104 if (p_node == NULL) {
1105 p_going = false;
1106 } else {
1107 p_nodes ++;
1108 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1109 if (p_node->selected) {
1110 p_sel_nodes ++;
1111 p_sel_range = p_range;
1112 }
1113 if (p_node == n_node) {
1114 n_going = false;
1115 p_going = false;
1116 }
1117 }
1118 } while (n_going || p_going);
1119 }
1121 // Second pass: actually move nodes in this subpath
1122 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1123 {
1124 double n_range = 0, p_range = 0;
1125 bool n_going = true, p_going = true;
1126 Inkscape::NodePath::Node *n_node = n;
1127 Inkscape::NodePath::Node *p_node = n;
1128 do {
1129 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1130 if (n_node && n_going)
1131 n_node = n_node->n.other;
1132 if (n_node == NULL) {
1133 n_going = false;
1134 } else {
1135 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1136 if (n_node->selected) {
1137 sp_nodepath_move_node_and_handles (n_node,
1138 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1139 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1140 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1141 }
1142 if (n_node == p_node) {
1143 n_going = false;
1144 p_going = false;
1145 }
1146 }
1147 if (p_node && p_going)
1148 p_node = p_node->p.other;
1149 if (p_node == NULL) {
1150 p_going = false;
1151 } else {
1152 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1153 if (p_node->selected) {
1154 sp_nodepath_move_node_and_handles (p_node,
1155 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1156 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1157 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1158 }
1159 if (p_node == n_node) {
1160 n_going = false;
1161 p_going = false;
1162 }
1163 }
1164 } while (n_going || p_going);
1165 }
1167 } else {
1168 // Multiple subpaths have selected nodes:
1169 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1170 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1171 // fix the pear-like shape when sculpting e.g. a ring
1173 // First pass: calculate range
1174 gdouble direct_range = 0;
1175 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1176 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1177 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1178 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1179 if (node->selected) {
1180 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1181 }
1182 }
1183 }
1185 // Second pass: actually move nodes
1186 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1187 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1188 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1189 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1190 if (node->selected) {
1191 if (direct_range > 1e-6) {
1192 sp_nodepath_move_node_and_handles (node,
1193 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1194 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1195 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1196 } else {
1197 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1198 }
1200 }
1201 }
1202 }
1203 }
1205 // do not update repr here so that node dragging is acceptably fast
1206 update_object(nodepath);
1207 }
1210 /**
1211 * Move node selection to point, adjust its and neighbouring handles,
1212 * handle possible snapping, and commit the change with possible undo.
1213 */
1214 void
1215 sp_node_selected_move(gdouble dx, gdouble dy)
1216 {
1217 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1218 if (!nodepath) return;
1220 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1222 if (dx == 0) {
1223 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1224 } else if (dy == 0) {
1225 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1226 } else {
1227 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1228 }
1229 }
1231 /**
1232 * Move node selection off screen and commit the change.
1233 */
1234 void
1235 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1236 {
1237 // borrowed from sp_selection_move_screen in selection-chemistry.c
1238 // we find out the current zoom factor and divide deltas by it
1239 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1241 gdouble zoom = desktop->current_zoom();
1242 gdouble zdx = dx / zoom;
1243 gdouble zdy = dy / zoom;
1245 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1246 if (!nodepath) return;
1248 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1250 if (dx == 0) {
1251 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1252 } else if (dy == 0) {
1253 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1254 } else {
1255 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1256 }
1257 }
1259 /** If they don't yet exist, creates knot and line for the given side of the node */
1260 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1261 {
1262 if (!side->knot) {
1263 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"));
1265 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1266 side->knot->setSize (7);
1267 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1268 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1269 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1270 sp_knot_update_ctrl(side->knot);
1272 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1273 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1274 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1275 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1276 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1277 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1278 }
1280 if (!side->line) {
1281 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1282 SP_TYPE_CTRLLINE, NULL);
1283 }
1284 }
1286 /**
1287 * Ensure the given handle of the node is visible/invisible, update its screen position
1288 */
1289 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1290 {
1291 g_assert(node != NULL);
1293 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1294 NRPathcode code = sp_node_path_code_from_side(node, side);
1296 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1298 if (show_handle) {
1299 if (!side->knot) { // No handle knot at all
1300 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1301 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1302 side->knot->pos = side->pos;
1303 if (side->knot->item)
1304 SP_CTRL(side->knot->item)->moveto(side->pos);
1305 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1306 sp_knot_show(side->knot);
1307 } else {
1308 if (side->knot->pos != side->pos) { // only if it's really moved
1309 if (fire_move_signals) {
1310 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1311 } else {
1312 sp_knot_moveto(side->knot, &side->pos);
1313 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1314 }
1315 }
1316 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1317 sp_knot_show(side->knot);
1318 }
1319 }
1320 sp_canvas_item_show(side->line);
1321 } else {
1322 if (side->knot) {
1323 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1324 sp_knot_hide(side->knot);
1325 }
1326 }
1327 if (side->line) {
1328 sp_canvas_item_hide(side->line);
1329 }
1330 }
1331 }
1333 /**
1334 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1335 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1336 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1337 * updated; otherwise, just move the knots silently (used in batch moves).
1338 */
1339 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1340 {
1341 g_assert(node != NULL);
1343 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1344 sp_knot_show(node->knot);
1345 }
1347 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1348 if (fire_move_signals)
1349 sp_knot_set_position(node->knot, &node->pos, 0);
1350 else
1351 sp_knot_moveto(node->knot, &node->pos);
1352 }
1354 gboolean show_handles = node->selected;
1355 if (node->p.other != NULL) {
1356 if (node->p.other->selected) show_handles = TRUE;
1357 }
1358 if (node->n.other != NULL) {
1359 if (node->n.other->selected) show_handles = TRUE;
1360 }
1362 if (node->subpath->nodepath->show_handles == false)
1363 show_handles = FALSE;
1365 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1366 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1367 }
1369 /**
1370 * Call sp_node_update_handles() for all nodes on subpath.
1371 */
1372 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1373 {
1374 g_assert(subpath != NULL);
1376 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1377 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1378 }
1379 }
1381 /**
1382 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1383 */
1384 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1385 {
1386 g_assert(nodepath != NULL);
1388 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1389 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1390 }
1391 }
1393 void
1394 sp_nodepath_show_handles(bool show)
1395 {
1396 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1397 if (nodepath == NULL) return;
1399 nodepath->show_handles = show;
1400 sp_nodepath_update_handles(nodepath);
1401 }
1403 /**
1404 * Adds all selected nodes in nodepath to list.
1405 */
1406 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1407 {
1408 StlConv<Node *>::list(l, selected);
1409 /// \todo this adds a copying, rework when the selection becomes a stl list
1410 }
1412 /**
1413 * Align selected nodes on the specified axis.
1414 */
1415 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1416 {
1417 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1418 return;
1419 }
1421 if ( !nodepath->selected->next ) { // only one node selected
1422 return;
1423 }
1424 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1425 NR::Point dest(pNode->pos);
1426 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1427 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1428 if (pNode) {
1429 dest[axis] = pNode->pos[axis];
1430 sp_node_moveto(pNode, dest);
1431 }
1432 }
1434 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1435 }
1437 /// Helper struct.
1438 struct NodeSort
1439 {
1440 Inkscape::NodePath::Node *_node;
1441 NR::Coord _coord;
1442 /// \todo use vectorof pointers instead of calling copy ctor
1443 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1444 _node(node), _coord(node->pos[axis])
1445 {}
1447 };
1449 static bool operator<(NodeSort const &a, NodeSort const &b)
1450 {
1451 return (a._coord < b._coord);
1452 }
1454 /**
1455 * Distribute selected nodes on the specified axis.
1456 */
1457 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1458 {
1459 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1460 return;
1461 }
1463 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1464 return;
1465 }
1467 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1468 std::vector<NodeSort> sorted;
1469 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1470 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1471 if (pNode) {
1472 NodeSort n(pNode, axis);
1473 sorted.push_back(n);
1474 //dest[axis] = pNode->pos[axis];
1475 //sp_node_moveto(pNode, dest);
1476 }
1477 }
1478 std::sort(sorted.begin(), sorted.end());
1479 unsigned int len = sorted.size();
1480 //overall bboxes span
1481 float dist = (sorted.back()._coord -
1482 sorted.front()._coord);
1483 //new distance between each bbox
1484 float step = (dist) / (len - 1);
1485 float pos = sorted.front()._coord;
1486 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1487 it < sorted.end();
1488 it ++ )
1489 {
1490 NR::Point dest((*it)._node->pos);
1491 dest[axis] = pos;
1492 sp_node_moveto((*it)._node, dest);
1493 pos += step;
1494 }
1496 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1497 }
1500 /**
1501 * Call sp_nodepath_line_add_node() for all selected segments.
1502 */
1503 void
1504 sp_node_selected_add_node(void)
1505 {
1506 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1507 if (!nodepath) {
1508 return;
1509 }
1511 GList *nl = NULL;
1513 int n_added = 0;
1515 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1516 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1517 g_assert(t->selected);
1518 if (t->p.other && t->p.other->selected) {
1519 nl = g_list_prepend(nl, t);
1520 }
1521 }
1523 while (nl) {
1524 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1525 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1526 sp_nodepath_node_select(n, TRUE, FALSE);
1527 n_added ++;
1528 nl = g_list_remove(nl, t);
1529 }
1531 /** \todo fixme: adjust ? */
1532 sp_nodepath_update_handles(nodepath);
1534 if (n_added > 1) {
1535 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1536 } else if (n_added > 0) {
1537 sp_nodepath_update_repr(nodepath, _("Add node"));
1538 }
1540 sp_nodepath_update_statusbar(nodepath);
1541 }
1543 /**
1544 * Select segment nearest to point
1545 */
1546 void
1547 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1548 {
1549 if (!nodepath) {
1550 return;
1551 }
1553 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1555 //find segment to segment
1556 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1558 //fixme: this can return NULL, so check before proceeding.
1559 g_return_if_fail(e != NULL);
1561 gboolean force = FALSE;
1562 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1563 force = TRUE;
1564 }
1565 sp_nodepath_node_select(e, (gboolean) toggle, force);
1566 if (e->p.other)
1567 sp_nodepath_node_select(e->p.other, TRUE, force);
1569 sp_nodepath_update_handles(nodepath);
1571 sp_nodepath_update_statusbar(nodepath);
1572 }
1574 /**
1575 * Add a node nearest to point
1576 */
1577 void
1578 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1579 {
1580 if (!nodepath) {
1581 return;
1582 }
1584 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1586 //find segment to split
1587 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1589 //don't know why but t seems to flip for lines
1590 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1591 position.t = 1.0 - position.t;
1592 }
1593 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1594 sp_nodepath_node_select(n, FALSE, TRUE);
1596 /* fixme: adjust ? */
1597 sp_nodepath_update_handles(nodepath);
1599 sp_nodepath_update_repr(nodepath, _("Add node"));
1601 sp_nodepath_update_statusbar(nodepath);
1602 }
1604 /*
1605 * Adjusts a segment so that t moves by a certain delta for dragging
1606 * converts lines to curves
1607 *
1608 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1609 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1610 */
1611 void
1612 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1613 {
1614 //fixme: e and e->p can be NULL, so check for those before proceeding
1615 g_return_if_fail(e != NULL);
1616 g_return_if_fail(&e->p != NULL);
1618 /* feel good is an arbitrary parameter that distributes the delta between handles
1619 * if t of the drag point is less than 1/6 distance form the endpoint only
1620 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1621 */
1622 double feel_good;
1623 if (t <= 1.0 / 6.0)
1624 feel_good = 0;
1625 else if (t <= 0.5)
1626 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1627 else if (t <= 5.0 / 6.0)
1628 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1629 else
1630 feel_good = 1;
1632 //if we're dragging a line convert it to a curve
1633 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1634 sp_nodepath_set_line_type(e, NR_CURVETO);
1635 }
1637 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1638 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1639 e->p.other->n.pos += offsetcoord0;
1640 e->p.pos += offsetcoord1;
1642 // adjust handles of adjacent nodes where necessary
1643 sp_node_adjust_handle(e,1);
1644 sp_node_adjust_handle(e->p.other,-1);
1646 sp_nodepath_update_handles(e->subpath->nodepath);
1648 update_object(e->subpath->nodepath);
1650 sp_nodepath_update_statusbar(e->subpath->nodepath);
1651 }
1654 /**
1655 * Call sp_nodepath_break() for all selected segments.
1656 */
1657 void sp_node_selected_break()
1658 {
1659 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1660 if (!nodepath) return;
1662 GList *temp = NULL;
1663 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1664 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1665 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1666 if (nn == NULL) continue; // no break, no new node
1667 temp = g_list_prepend(temp, nn);
1668 }
1670 if (temp) {
1671 sp_nodepath_deselect(nodepath);
1672 }
1673 for (GList *l = temp; l != NULL; l = l->next) {
1674 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1675 }
1677 sp_nodepath_update_handles(nodepath);
1679 sp_nodepath_update_repr(nodepath, _("Break path"));
1680 }
1682 /**
1683 * Duplicate the selected node(s).
1684 */
1685 void sp_node_selected_duplicate()
1686 {
1687 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1688 if (!nodepath) {
1689 return;
1690 }
1692 GList *temp = NULL;
1693 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1694 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1695 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1696 if (nn == NULL) continue; // could not duplicate
1697 temp = g_list_prepend(temp, nn);
1698 }
1700 if (temp) {
1701 sp_nodepath_deselect(nodepath);
1702 }
1703 for (GList *l = temp; l != NULL; l = l->next) {
1704 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1705 }
1707 sp_nodepath_update_handles(nodepath);
1709 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1710 }
1712 /**
1713 * Join two nodes by merging them into one.
1714 */
1715 void sp_node_selected_join()
1716 {
1717 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1718 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1720 if (g_list_length(nodepath->selected) != 2) {
1721 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1722 return;
1723 }
1725 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1726 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1728 g_assert(a != b);
1729 g_assert(a->p.other || a->n.other);
1730 g_assert(b->p.other || b->n.other);
1732 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1733 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1734 return;
1735 }
1737 /* a and b are endpoints */
1739 NR::Point c;
1740 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1741 c = a->pos;
1742 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1743 c = b->pos;
1744 } else {
1745 c = (a->pos + b->pos) / 2;
1746 }
1748 if (a->subpath == b->subpath) {
1749 Inkscape::NodePath::SubPath *sp = a->subpath;
1750 sp_nodepath_subpath_close(sp);
1751 sp_node_moveto (sp->first, c);
1753 sp_nodepath_update_handles(sp->nodepath);
1754 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1755 return;
1756 }
1758 /* a and b are separate subpaths */
1759 Inkscape::NodePath::SubPath *sa = a->subpath;
1760 Inkscape::NodePath::SubPath *sb = b->subpath;
1761 NR::Point p;
1762 Inkscape::NodePath::Node *n;
1763 NRPathcode code;
1764 if (a == sa->first) {
1765 p = sa->first->n.pos;
1766 code = (NRPathcode)sa->first->n.other->code;
1767 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1768 n = sa->last;
1769 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1770 n = n->p.other;
1771 while (n) {
1772 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1773 n = n->p.other;
1774 if (n == sa->first) n = NULL;
1775 }
1776 sp_nodepath_subpath_destroy(sa);
1777 sa = t;
1778 } else if (a == sa->last) {
1779 p = sa->last->p.pos;
1780 code = (NRPathcode)sa->last->code;
1781 sp_nodepath_node_destroy(sa->last);
1782 } else {
1783 code = NR_END;
1784 g_assert_not_reached();
1785 }
1787 if (b == sb->first) {
1788 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1789 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1790 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1791 }
1792 } else if (b == sb->last) {
1793 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1794 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1795 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1796 }
1797 } else {
1798 g_assert_not_reached();
1799 }
1800 /* and now destroy sb */
1802 sp_nodepath_subpath_destroy(sb);
1804 sp_nodepath_update_handles(sa->nodepath);
1806 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1808 sp_nodepath_update_statusbar(nodepath);
1809 }
1811 /**
1812 * Join two nodes by adding a segment between them.
1813 */
1814 void sp_node_selected_join_segment()
1815 {
1816 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1817 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1819 if (g_list_length(nodepath->selected) != 2) {
1820 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1821 return;
1822 }
1824 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1825 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1827 g_assert(a != b);
1828 g_assert(a->p.other || a->n.other);
1829 g_assert(b->p.other || b->n.other);
1831 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1832 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1833 return;
1834 }
1836 if (a->subpath == b->subpath) {
1837 Inkscape::NodePath::SubPath *sp = a->subpath;
1839 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1840 sp->closed = TRUE;
1842 sp->first->p.other = sp->last;
1843 sp->last->n.other = sp->first;
1845 sp_node_handle_mirror_p_to_n(sp->last);
1846 sp_node_handle_mirror_n_to_p(sp->first);
1848 sp->first->code = sp->last->code;
1849 sp->first = sp->last;
1851 sp_nodepath_update_handles(sp->nodepath);
1853 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1855 return;
1856 }
1858 /* a and b are separate subpaths */
1859 Inkscape::NodePath::SubPath *sa = a->subpath;
1860 Inkscape::NodePath::SubPath *sb = b->subpath;
1862 Inkscape::NodePath::Node *n;
1863 NR::Point p;
1864 NRPathcode code;
1865 if (a == sa->first) {
1866 code = (NRPathcode) sa->first->n.other->code;
1867 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1868 n = sa->last;
1869 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1870 for (n = n->p.other; n != NULL; n = n->p.other) {
1871 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1872 }
1873 sp_nodepath_subpath_destroy(sa);
1874 sa = t;
1875 } else if (a == sa->last) {
1876 code = (NRPathcode)sa->last->code;
1877 } else {
1878 code = NR_END;
1879 g_assert_not_reached();
1880 }
1882 if (b == sb->first) {
1883 n = sb->first;
1884 sp_node_handle_mirror_p_to_n(sa->last);
1885 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1886 sp_node_handle_mirror_n_to_p(sa->last);
1887 for (n = n->n.other; n != NULL; n = n->n.other) {
1888 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1889 }
1890 } else if (b == sb->last) {
1891 n = sb->last;
1892 sp_node_handle_mirror_p_to_n(sa->last);
1893 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1894 sp_node_handle_mirror_n_to_p(sa->last);
1895 for (n = n->p.other; n != NULL; n = n->p.other) {
1896 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1897 }
1898 } else {
1899 g_assert_not_reached();
1900 }
1901 /* and now destroy sb */
1903 sp_nodepath_subpath_destroy(sb);
1905 sp_nodepath_update_handles(sa->nodepath);
1907 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1908 }
1910 /**
1911 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1912 */
1913 void sp_node_delete_preserve(GList *nodes_to_delete)
1914 {
1915 GSList *nodepaths = NULL;
1917 while (nodes_to_delete) {
1918 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1919 Inkscape::NodePath::SubPath *sp = node->subpath;
1920 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1921 Inkscape::NodePath::Node *sample_cursor = NULL;
1922 Inkscape::NodePath::Node *sample_end = NULL;
1923 Inkscape::NodePath::Node *delete_cursor = node;
1924 bool just_delete = false;
1926 //find the start of this contiguous selection
1927 //move left to the first node that is not selected
1928 //or the start of the non-closed path
1929 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1930 delete_cursor = curr;
1931 }
1933 //just delete at the beginning of an open path
1934 if (!delete_cursor->p.other) {
1935 sample_cursor = delete_cursor;
1936 just_delete = true;
1937 } else {
1938 sample_cursor = delete_cursor->p.other;
1939 }
1941 //calculate points for each segment
1942 int rate = 5;
1943 float period = 1.0 / rate;
1944 std::vector<NR::Point> data;
1945 if (!just_delete) {
1946 data.push_back(sample_cursor->pos);
1947 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1948 //just delete at the end of an open path
1949 if (!sp->closed && curr == sp->last) {
1950 just_delete = true;
1951 break;
1952 }
1954 //sample points on the contiguous selected segment
1955 NR::Point *bez;
1956 bez = new NR::Point [4];
1957 bez[0] = curr->pos;
1958 bez[1] = curr->n.pos;
1959 bez[2] = curr->n.other->p.pos;
1960 bez[3] = curr->n.other->pos;
1961 for (int i=1; i<rate; i++) {
1962 gdouble t = i * period;
1963 NR::Point p = bezier_pt(3, bez, t);
1964 data.push_back(p);
1965 }
1966 data.push_back(curr->n.other->pos);
1968 sample_end = curr->n.other;
1969 //break if we've come full circle or hit the end of the selection
1970 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1971 break;
1972 }
1973 }
1974 }
1976 if (!just_delete) {
1977 //calculate the best fitting single segment and adjust the endpoints
1978 NR::Point *adata;
1979 adata = new NR::Point [data.size()];
1980 copy(data.begin(), data.end(), adata);
1982 NR::Point *bez;
1983 bez = new NR::Point [4];
1984 //would decreasing error create a better fitting approximation?
1985 gdouble error = 1.0;
1986 gint ret;
1987 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1989 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1990 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1991 //the resulting nodes behave as expected.
1992 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1993 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1995 //adjust endpoints
1996 sample_cursor->n.pos = bez[1];
1997 sample_end->p.pos = bez[2];
1998 }
2000 //destroy this contiguous selection
2001 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2002 Inkscape::NodePath::Node *temp = delete_cursor;
2003 if (delete_cursor->n.other == delete_cursor) {
2004 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2005 delete_cursor = NULL;
2006 } else {
2007 delete_cursor = delete_cursor->n.other;
2008 }
2009 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2010 sp_nodepath_node_destroy(temp);
2011 }
2013 sp_nodepath_update_handles(nodepath);
2015 if (!g_slist_find(nodepaths, nodepath))
2016 nodepaths = g_slist_prepend (nodepaths, nodepath);
2017 }
2019 for (GSList *i = nodepaths; i; i = i->next) {
2020 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2021 // different nodepaths will give us one undo event per nodepath
2022 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2024 // if the entire nodepath is removed, delete the selected object.
2025 if (nodepath->subpaths == NULL ||
2026 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2027 //at least 2
2028 sp_nodepath_get_node_count(nodepath) < 2) {
2029 SPDocument *document = sp_desktop_document (nodepath->desktop);
2030 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2031 //delete this nodepath's object, not the entire selection! (though at this time, this
2032 //does not matter)
2033 sp_selection_delete();
2034 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2035 _("Delete nodes"));
2036 } else {
2037 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2038 sp_nodepath_update_statusbar(nodepath);
2039 }
2040 }
2042 g_slist_free (nodepaths);
2043 }
2045 /**
2046 * Delete one or more selected nodes.
2047 */
2048 void sp_node_selected_delete()
2049 {
2050 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2051 if (!nodepath) return;
2052 if (!nodepath->selected) return;
2054 /** \todo fixme: do it the right way */
2055 while (nodepath->selected) {
2056 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2057 sp_nodepath_node_destroy(node);
2058 }
2061 //clean up the nodepath (such as for trivial subpaths)
2062 sp_nodepath_cleanup(nodepath);
2064 sp_nodepath_update_handles(nodepath);
2066 // if the entire nodepath is removed, delete the selected object.
2067 if (nodepath->subpaths == NULL ||
2068 sp_nodepath_get_node_count(nodepath) < 2) {
2069 SPDocument *document = sp_desktop_document (nodepath->desktop);
2070 sp_selection_delete();
2071 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2072 _("Delete nodes"));
2073 return;
2074 }
2076 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2078 sp_nodepath_update_statusbar(nodepath);
2079 }
2081 /**
2082 * Delete one or more segments between two selected nodes.
2083 * This is the code for 'split'.
2084 */
2085 void
2086 sp_node_selected_delete_segment(void)
2087 {
2088 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2089 Inkscape::NodePath::Node *curr, *next; //Iterators
2091 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2092 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2094 if (g_list_length(nodepath->selected) != 2) {
2095 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2096 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2097 return;
2098 }
2100 //Selected nodes, not inclusive
2101 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2102 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2104 if ( ( a==b) || //same node
2105 (a->subpath != b->subpath ) || //not the same path
2106 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2107 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2108 {
2109 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2110 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2111 return;
2112 }
2114 //###########################################
2115 //# BEGIN EDITS
2116 //###########################################
2117 //##################################
2118 //# CLOSED PATH
2119 //##################################
2120 if (a->subpath->closed) {
2123 gboolean reversed = FALSE;
2125 //Since we can go in a circle, we need to find the shorter distance.
2126 // a->b or b->a
2127 start = end = NULL;
2128 int distance = 0;
2129 int minDistance = 0;
2130 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2131 if (curr==b) {
2132 //printf("a to b:%d\n", distance);
2133 start = a;//go from a to b
2134 end = b;
2135 minDistance = distance;
2136 //printf("A to B :\n");
2137 break;
2138 }
2139 distance++;
2140 }
2142 //try again, the other direction
2143 distance = 0;
2144 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2145 if (curr==a) {
2146 //printf("b to a:%d\n", distance);
2147 if (distance < minDistance) {
2148 start = b; //we go from b to a
2149 end = a;
2150 reversed = TRUE;
2151 //printf("B to A\n");
2152 }
2153 break;
2154 }
2155 distance++;
2156 }
2159 //Copy everything from 'end' to 'start' to a new subpath
2160 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2161 for (curr=end ; curr ; curr=curr->n.other) {
2162 NRPathcode code = (NRPathcode) curr->code;
2163 if (curr == end)
2164 code = NR_MOVETO;
2165 sp_nodepath_node_new(t, NULL,
2166 (Inkscape::NodePath::NodeType)curr->type, code,
2167 &curr->p.pos, &curr->pos, &curr->n.pos);
2168 if (curr == start)
2169 break;
2170 }
2171 sp_nodepath_subpath_destroy(a->subpath);
2174 }
2178 //##################################
2179 //# OPEN PATH
2180 //##################################
2181 else {
2183 //We need to get the direction of the list between A and B
2184 //Can we walk from a to b?
2185 start = end = NULL;
2186 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2187 if (curr==b) {
2188 start = a; //did it! we go from a to b
2189 end = b;
2190 //printf("A to B\n");
2191 break;
2192 }
2193 }
2194 if (!start) {//didn't work? let's try the other direction
2195 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2196 if (curr==a) {
2197 start = b; //did it! we go from b to a
2198 end = a;
2199 //printf("B to A\n");
2200 break;
2201 }
2202 }
2203 }
2204 if (!start) {
2205 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2206 _("Cannot find path between nodes."));
2207 return;
2208 }
2212 //Copy everything after 'end' to a new subpath
2213 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2214 for (curr=end ; curr ; curr=curr->n.other) {
2215 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2216 &curr->p.pos, &curr->pos, &curr->n.pos);
2217 }
2219 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2220 for (curr = start->n.other ; curr ; curr=next) {
2221 next = curr->n.other;
2222 sp_nodepath_node_destroy(curr);
2223 }
2225 }
2226 //###########################################
2227 //# END EDITS
2228 //###########################################
2230 //clean up the nodepath (such as for trivial subpaths)
2231 sp_nodepath_cleanup(nodepath);
2233 sp_nodepath_update_handles(nodepath);
2235 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2237 sp_nodepath_update_statusbar(nodepath);
2238 }
2240 /**
2241 * Call sp_nodepath_set_line() for all selected segments.
2242 */
2243 void
2244 sp_node_selected_set_line_type(NRPathcode code)
2245 {
2246 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2247 if (nodepath == NULL) return;
2249 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2250 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2251 g_assert(n->selected);
2252 if (n->p.other && n->p.other->selected) {
2253 sp_nodepath_set_line_type(n, code);
2254 }
2255 }
2257 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2258 }
2260 /**
2261 * Call sp_nodepath_convert_node_type() for all selected nodes.
2262 */
2263 void
2264 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2265 {
2266 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2267 if (nodepath == NULL) return;
2269 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2270 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2271 }
2273 sp_nodepath_update_repr(nodepath, _("Change node type"));
2274 }
2276 /**
2277 * Change select status of node, update its own and neighbour handles.
2278 */
2279 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2280 {
2281 node->selected = selected;
2283 if (selected) {
2284 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2285 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2286 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2287 sp_knot_update_ctrl(node->knot);
2288 } else {
2289 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2290 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2291 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2292 sp_knot_update_ctrl(node->knot);
2293 }
2295 sp_node_update_handles(node);
2296 if (node->n.other) sp_node_update_handles(node->n.other);
2297 if (node->p.other) sp_node_update_handles(node->p.other);
2298 }
2300 /**
2301 \brief Select a node
2302 \param node The node to select
2303 \param incremental If true, add to selection, otherwise deselect others
2304 \param override If true, always select this node, otherwise toggle selected status
2305 */
2306 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2307 {
2308 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2310 if (incremental) {
2311 if (override) {
2312 if (!g_list_find(nodepath->selected, node)) {
2313 nodepath->selected = g_list_prepend(nodepath->selected, node);
2314 }
2315 sp_node_set_selected(node, TRUE);
2316 } else { // toggle
2317 if (node->selected) {
2318 g_assert(g_list_find(nodepath->selected, node));
2319 nodepath->selected = g_list_remove(nodepath->selected, node);
2320 } else {
2321 g_assert(!g_list_find(nodepath->selected, node));
2322 nodepath->selected = g_list_prepend(nodepath->selected, node);
2323 }
2324 sp_node_set_selected(node, !node->selected);
2325 }
2326 } else {
2327 sp_nodepath_deselect(nodepath);
2328 nodepath->selected = g_list_prepend(nodepath->selected, node);
2329 sp_node_set_selected(node, TRUE);
2330 }
2332 sp_nodepath_update_statusbar(nodepath);
2333 }
2336 /**
2337 \brief Deselect all nodes in the nodepath
2338 */
2339 void
2340 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2341 {
2342 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2344 while (nodepath->selected) {
2345 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2346 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2347 }
2348 sp_nodepath_update_statusbar(nodepath);
2349 }
2351 /**
2352 \brief Select or invert selection of all nodes in the nodepath
2353 */
2354 void
2355 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2356 {
2357 if (!nodepath) return;
2359 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2360 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2361 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2362 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2363 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2364 }
2365 }
2366 }
2368 /**
2369 * If nothing selected, does the same as sp_nodepath_select_all();
2370 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2371 * (i.e., similar to "select all in layer", with the "selected" subpaths
2372 * being treated as "layers" in the path).
2373 */
2374 void
2375 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2376 {
2377 if (!nodepath) return;
2379 if (g_list_length (nodepath->selected) == 0) {
2380 sp_nodepath_select_all (nodepath, invert);
2381 return;
2382 }
2384 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2385 GSList *subpaths = NULL;
2387 for (GList *l = copy; l != NULL; l = l->next) {
2388 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2389 Inkscape::NodePath::SubPath *subpath = n->subpath;
2390 if (!g_slist_find (subpaths, subpath))
2391 subpaths = g_slist_prepend (subpaths, subpath);
2392 }
2394 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2395 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2396 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2397 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2398 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2399 }
2400 }
2402 g_slist_free (subpaths);
2403 g_list_free (copy);
2404 }
2406 /**
2407 * \brief Select the node after the last selected; if none is selected,
2408 * select the first within path.
2409 */
2410 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2411 {
2412 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2414 Inkscape::NodePath::Node *last = NULL;
2415 if (nodepath->selected) {
2416 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2417 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2418 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2419 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2420 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2421 if (node->selected) {
2422 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2423 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2424 if (spl->next) { // there's a next subpath
2425 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2426 last = subpath_next->first;
2427 } else if (spl->prev) { // there's a previous subpath
2428 last = NULL; // to be set later to the first node of first subpath
2429 } else {
2430 last = node->n.other;
2431 }
2432 } else {
2433 last = node->n.other;
2434 }
2435 } else {
2436 if (node->n.other) {
2437 last = node->n.other;
2438 } else {
2439 if (spl->next) { // there's a next subpath
2440 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2441 last = subpath_next->first;
2442 } else if (spl->prev) { // there's a previous subpath
2443 last = NULL; // to be set later to the first node of first subpath
2444 } else {
2445 last = (Inkscape::NodePath::Node *) subpath->first;
2446 }
2447 }
2448 }
2449 }
2450 }
2451 }
2452 sp_nodepath_deselect(nodepath);
2453 }
2455 if (last) { // there's at least one more node after selected
2456 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2457 } else { // no more nodes, select the first one in first subpath
2458 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2459 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2460 }
2461 }
2463 /**
2464 * \brief Select the node before the first selected; if none is selected,
2465 * select the last within path
2466 */
2467 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2468 {
2469 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2471 Inkscape::NodePath::Node *last = NULL;
2472 if (nodepath->selected) {
2473 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2474 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2475 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2476 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2477 if (node->selected) {
2478 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2479 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2480 if (spl->prev) { // there's a prev subpath
2481 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2482 last = subpath_prev->last;
2483 } else if (spl->next) { // there's a next subpath
2484 last = NULL; // to be set later to the last node of last subpath
2485 } else {
2486 last = node->p.other;
2487 }
2488 } else {
2489 last = node->p.other;
2490 }
2491 } else {
2492 if (node->p.other) {
2493 last = node->p.other;
2494 } else {
2495 if (spl->prev) { // there's a prev subpath
2496 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2497 last = subpath_prev->last;
2498 } else if (spl->next) { // there's a next subpath
2499 last = NULL; // to be set later to the last node of last subpath
2500 } else {
2501 last = (Inkscape::NodePath::Node *) subpath->last;
2502 }
2503 }
2504 }
2505 }
2506 }
2507 }
2508 sp_nodepath_deselect(nodepath);
2509 }
2511 if (last) { // there's at least one more node before selected
2512 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2513 } else { // no more nodes, select the last one in last subpath
2514 GList *spl = g_list_last(nodepath->subpaths);
2515 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2516 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2517 }
2518 }
2520 /**
2521 * \brief Select all nodes that are within the rectangle.
2522 */
2523 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2524 {
2525 if (!incremental) {
2526 sp_nodepath_deselect(nodepath);
2527 }
2529 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2530 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2531 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2532 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2534 if (b.contains(node->pos)) {
2535 sp_nodepath_node_select(node, TRUE, TRUE);
2536 }
2537 }
2538 }
2539 }
2542 void
2543 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2544 {
2545 g_assert (n);
2546 g_assert (nodepath);
2547 g_assert (n->subpath->nodepath == nodepath);
2549 if (g_list_length (nodepath->selected) == 0) {
2550 if (grow > 0) {
2551 sp_nodepath_node_select(n, TRUE, TRUE);
2552 }
2553 return;
2554 }
2556 if (g_list_length (nodepath->selected) == 1) {
2557 if (grow < 0) {
2558 sp_nodepath_deselect (nodepath);
2559 return;
2560 }
2561 }
2563 double n_sel_range = 0, p_sel_range = 0;
2564 Inkscape::NodePath::Node *farthest_n_node = n;
2565 Inkscape::NodePath::Node *farthest_p_node = n;
2567 // Calculate ranges
2568 {
2569 double n_range = 0, p_range = 0;
2570 bool n_going = true, p_going = true;
2571 Inkscape::NodePath::Node *n_node = n;
2572 Inkscape::NodePath::Node *p_node = n;
2573 do {
2574 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2575 if (n_node && n_going)
2576 n_node = n_node->n.other;
2577 if (n_node == NULL) {
2578 n_going = false;
2579 } else {
2580 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2581 if (n_node->selected) {
2582 n_sel_range = n_range;
2583 farthest_n_node = n_node;
2584 }
2585 if (n_node == p_node) {
2586 n_going = false;
2587 p_going = false;
2588 }
2589 }
2590 if (p_node && p_going)
2591 p_node = p_node->p.other;
2592 if (p_node == NULL) {
2593 p_going = false;
2594 } else {
2595 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2596 if (p_node->selected) {
2597 p_sel_range = p_range;
2598 farthest_p_node = p_node;
2599 }
2600 if (p_node == n_node) {
2601 n_going = false;
2602 p_going = false;
2603 }
2604 }
2605 } while (n_going || p_going);
2606 }
2608 if (grow > 0) {
2609 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2610 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2611 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2612 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2613 }
2614 } else {
2615 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2616 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2617 } else if (farthest_p_node && farthest_p_node->selected) {
2618 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2619 }
2620 }
2621 }
2623 void
2624 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2625 {
2626 g_assert (n);
2627 g_assert (nodepath);
2628 g_assert (n->subpath->nodepath == nodepath);
2630 if (g_list_length (nodepath->selected) == 0) {
2631 if (grow > 0) {
2632 sp_nodepath_node_select(n, TRUE, TRUE);
2633 }
2634 return;
2635 }
2637 if (g_list_length (nodepath->selected) == 1) {
2638 if (grow < 0) {
2639 sp_nodepath_deselect (nodepath);
2640 return;
2641 }
2642 }
2644 Inkscape::NodePath::Node *farthest_selected = NULL;
2645 double farthest_dist = 0;
2647 Inkscape::NodePath::Node *closest_unselected = NULL;
2648 double closest_dist = NR_HUGE;
2650 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2651 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2652 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2653 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2654 if (node == n)
2655 continue;
2656 if (node->selected) {
2657 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2658 farthest_dist = NR::L2(node->pos - n->pos);
2659 farthest_selected = node;
2660 }
2661 } else {
2662 if (NR::L2(node->pos - n->pos) < closest_dist) {
2663 closest_dist = NR::L2(node->pos - n->pos);
2664 closest_unselected = node;
2665 }
2666 }
2667 }
2668 }
2670 if (grow > 0) {
2671 if (closest_unselected) {
2672 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2673 }
2674 } else {
2675 if (farthest_selected) {
2676 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2677 }
2678 }
2679 }
2682 /**
2683 \brief Saves all nodes' and handles' current positions in their origin members
2684 */
2685 void
2686 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2687 {
2688 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2689 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2690 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2691 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2692 n->origin = n->pos;
2693 n->p.origin = n->p.pos;
2694 n->n.origin = n->n.pos;
2695 }
2696 }
2697 }
2699 /**
2700 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2701 */
2702 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2703 {
2704 if (!nodepath->selected) {
2705 return NULL;
2706 }
2708 GList *r = NULL;
2709 guint i = 0;
2710 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2711 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2712 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2713 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2714 i++;
2715 if (node->selected) {
2716 r = g_list_append(r, GINT_TO_POINTER(i));
2717 }
2718 }
2719 }
2720 return r;
2721 }
2723 /**
2724 \brief Restores selection by selecting nodes whose positions are in the list
2725 */
2726 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2727 {
2728 sp_nodepath_deselect(nodepath);
2730 guint i = 0;
2731 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2732 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2733 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2734 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2735 i++;
2736 if (g_list_find(r, GINT_TO_POINTER(i))) {
2737 sp_nodepath_node_select(node, TRUE, TRUE);
2738 }
2739 }
2740 }
2742 }
2744 /**
2745 \brief Adjusts handle according to node type and line code.
2746 */
2747 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2748 {
2749 double len, otherlen, linelen;
2751 g_assert(node);
2753 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2754 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2756 /** \todo fixme: */
2757 if (me->other == NULL) return;
2758 if (other->other == NULL) return;
2760 /* I have line */
2762 NRPathcode mecode, ocode;
2763 if (which_adjust == 1) {
2764 mecode = (NRPathcode)me->other->code;
2765 ocode = (NRPathcode)node->code;
2766 } else {
2767 mecode = (NRPathcode)node->code;
2768 ocode = (NRPathcode)other->other->code;
2769 }
2771 if (mecode == NR_LINETO) return;
2773 /* I am curve */
2775 if (other->other == NULL) return;
2777 /* Other has line */
2779 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2781 NR::Point delta;
2782 if (ocode == NR_LINETO) {
2783 /* other is lineto, we are either smooth or symm */
2784 Inkscape::NodePath::Node *othernode = other->other;
2785 len = NR::L2(me->pos - node->pos);
2786 delta = node->pos - othernode->pos;
2787 linelen = NR::L2(delta);
2788 if (linelen < 1e-18)
2789 return;
2790 me->pos = node->pos + (len / linelen)*delta;
2791 return;
2792 }
2794 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2796 me->pos = 2 * node->pos - other->pos;
2797 return;
2798 }
2800 /* We are smooth */
2802 len = NR::L2(me->pos - node->pos);
2803 delta = other->pos - node->pos;
2804 otherlen = NR::L2(delta);
2805 if (otherlen < 1e-18) return;
2807 me->pos = node->pos - (len / otherlen) * delta;
2808 }
2810 /**
2811 \brief Adjusts both handles according to node type and line code
2812 */
2813 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2814 {
2815 g_assert(node);
2817 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2819 /* we are either smooth or symm */
2821 if (node->p.other == NULL) return;
2823 if (node->n.other == NULL) return;
2825 if (node->code == NR_LINETO) {
2826 if (node->n.other->code == NR_LINETO) return;
2827 sp_node_adjust_handle(node, 1);
2828 return;
2829 }
2831 if (node->n.other->code == NR_LINETO) {
2832 if (node->code == NR_LINETO) return;
2833 sp_node_adjust_handle(node, -1);
2834 return;
2835 }
2837 /* both are curves */
2838 NR::Point const delta( node->n.pos - node->p.pos );
2840 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2841 node->p.pos = node->pos - delta / 2;
2842 node->n.pos = node->pos + delta / 2;
2843 return;
2844 }
2846 /* We are smooth */
2847 double plen = NR::L2(node->p.pos - node->pos);
2848 if (plen < 1e-18) return;
2849 double nlen = NR::L2(node->n.pos - node->pos);
2850 if (nlen < 1e-18) return;
2851 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2852 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2853 }
2855 /**
2856 * Node event callback.
2857 */
2858 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2859 {
2860 gboolean ret = FALSE;
2861 switch (event->type) {
2862 case GDK_ENTER_NOTIFY:
2863 active_node = n;
2864 break;
2865 case GDK_LEAVE_NOTIFY:
2866 active_node = NULL;
2867 break;
2868 case GDK_KEY_PRESS:
2869 switch (get_group0_keyval (&event->key)) {
2870 case GDK_space:
2871 if (event->key.state & GDK_BUTTON1_MASK) {
2872 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2873 stamp_repr(nodepath);
2874 ret = TRUE;
2875 }
2876 break;
2877 case GDK_Page_Up:
2878 if (event->key.state & GDK_CONTROL_MASK) {
2879 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2880 } else {
2881 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2882 }
2883 break;
2884 case GDK_Page_Down:
2885 if (event->key.state & GDK_CONTROL_MASK) {
2886 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2887 } else {
2888 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2889 }
2890 break;
2891 default:
2892 break;
2893 }
2894 break;
2895 default:
2896 break;
2897 }
2899 return ret;
2900 }
2902 /**
2903 * Handle keypress on node; directly called.
2904 */
2905 gboolean node_key(GdkEvent *event)
2906 {
2907 Inkscape::NodePath::Path *np;
2909 // there is no way to verify nodes so set active_node to nil when deleting!!
2910 if (active_node == NULL) return FALSE;
2912 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2913 gint ret = FALSE;
2914 switch (get_group0_keyval (&event->key)) {
2915 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2916 case GDK_BackSpace:
2917 np = active_node->subpath->nodepath;
2918 sp_nodepath_node_destroy(active_node);
2919 sp_nodepath_update_repr(np, _("Delete node"));
2920 active_node = NULL;
2921 ret = TRUE;
2922 break;
2923 case GDK_c:
2924 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2925 ret = TRUE;
2926 break;
2927 case GDK_s:
2928 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2929 ret = TRUE;
2930 break;
2931 case GDK_y:
2932 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2933 ret = TRUE;
2934 break;
2935 case GDK_b:
2936 sp_nodepath_node_break(active_node);
2937 ret = TRUE;
2938 break;
2939 }
2940 return ret;
2941 }
2942 return FALSE;
2943 }
2945 /**
2946 * Mouseclick on node callback.
2947 */
2948 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2949 {
2950 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2952 if (state & GDK_CONTROL_MASK) {
2953 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2955 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2956 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2957 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2958 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2959 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2960 } else {
2961 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2962 }
2963 sp_nodepath_update_repr(nodepath, _("Change node type"));
2964 sp_nodepath_update_statusbar(nodepath);
2966 } else { //ctrl+alt+click: delete node
2967 GList *node_to_delete = NULL;
2968 node_to_delete = g_list_append(node_to_delete, n);
2969 sp_node_delete_preserve(node_to_delete);
2970 }
2972 } else {
2973 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2974 }
2975 }
2977 /**
2978 * Mouse grabbed node callback.
2979 */
2980 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2981 {
2982 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2984 if (!n->selected) {
2985 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2986 }
2988 n->is_dragging = true;
2990 sp_nodepath_remember_origins (n->subpath->nodepath);
2991 }
2993 /**
2994 * Mouse ungrabbed node callback.
2995 */
2996 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2997 {
2998 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3000 n->dragging_out = NULL;
3001 n->is_dragging = false;
3003 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3004 }
3006 /**
3007 * The point on a line, given by its angle, closest to the given point.
3008 * \param p A point.
3009 * \param a Angle of the line; it is assumed to go through coordinate origin.
3010 * \param closest Pointer to the point struct where the result is stored.
3011 * \todo FIXME: use dot product perhaps?
3012 */
3013 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3014 {
3015 if (a == HUGE_VAL) { // vertical
3016 *closest = NR::Point(0, (*p)[NR::Y]);
3017 } else {
3018 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3019 (*closest)[NR::Y] = a * (*closest)[NR::X];
3020 }
3021 }
3023 /**
3024 * Distance from the point to a line given by its angle.
3025 * \param p A point.
3026 * \param a Angle of the line; it is assumed to go through coordinate origin.
3027 */
3028 static double point_line_distance(NR::Point *p, double a)
3029 {
3030 NR::Point c;
3031 point_line_closest(p, a, &c);
3032 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]));
3033 }
3035 /**
3036 * Callback for node "request" signal.
3037 * \todo fixme: This goes to "moved" event? (lauris)
3038 */
3039 static gboolean
3040 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3041 {
3042 double yn, xn, yp, xp;
3043 double an, ap, na, pa;
3044 double d_an, d_ap, d_na, d_pa;
3045 gboolean collinear = FALSE;
3046 NR::Point c;
3047 NR::Point pr;
3049 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3051 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3052 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3054 NR::Point mouse = (*p);
3056 if (!n->dragging_out) {
3057 // This is the first drag-out event; find out which handle to drag out
3058 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3059 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3061 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3062 return FALSE;
3064 Inkscape::NodePath::NodeSide *opposite;
3065 if (appr_p > appr_n) { // closer to p
3066 n->dragging_out = &n->p;
3067 opposite = &n->n;
3068 n->code = NR_CURVETO;
3069 } else if (appr_p < appr_n) { // closer to n
3070 n->dragging_out = &n->n;
3071 opposite = &n->p;
3072 n->n.other->code = NR_CURVETO;
3073 } else { // p and n nodes are the same
3074 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3075 n->dragging_out = &n->p;
3076 opposite = &n->n;
3077 n->code = NR_CURVETO;
3078 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3079 n->dragging_out = &n->n;
3080 opposite = &n->p;
3081 n->n.other->code = NR_CURVETO;
3082 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3083 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);
3084 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);
3085 if (appr_other_p > appr_other_n) { // closer to other's p handle
3086 n->dragging_out = &n->n;
3087 opposite = &n->p;
3088 n->n.other->code = NR_CURVETO;
3089 } else { // closer to other's n handle
3090 n->dragging_out = &n->p;
3091 opposite = &n->n;
3092 n->code = NR_CURVETO;
3093 }
3094 }
3095 }
3097 // if there's another handle, make sure the one we drag out starts parallel to it
3098 if (opposite->pos != n->pos) {
3099 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3100 }
3102 // knots might not be created yet!
3103 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3104 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3105 }
3107 // pass this on to the handle-moved callback
3108 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3109 sp_node_update_handles(n);
3110 return TRUE;
3111 }
3113 if (state & GDK_CONTROL_MASK) { // constrained motion
3115 // calculate relative distances of handles
3116 // n handle:
3117 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3118 xn = n->n.pos[NR::X] - n->pos[NR::X];
3119 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3120 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3121 if (n->n.other) { // if there is the next point
3122 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3123 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3124 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3125 }
3126 }
3127 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3128 if (yn < 0) { xn = -xn; yn = -yn; }
3130 // p handle:
3131 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3132 xp = n->p.pos[NR::X] - n->pos[NR::X];
3133 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3134 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3135 if (n->p.other) {
3136 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3137 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3138 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3139 }
3140 }
3141 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3142 if (yp < 0) { xp = -xp; yp = -yp; }
3144 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3145 // sliding on handles, only if at least one of the handles is non-vertical
3146 // (otherwise it's the same as ctrl+drag anyway)
3148 // calculate angles of the handles
3149 if (xn == 0) {
3150 if (yn == 0) { // no handle, consider it the continuation of the other one
3151 an = 0;
3152 collinear = TRUE;
3153 }
3154 else an = 0; // vertical; set the angle to horizontal
3155 } else an = yn/xn;
3157 if (xp == 0) {
3158 if (yp == 0) { // no handle, consider it the continuation of the other one
3159 ap = an;
3160 }
3161 else ap = 0; // vertical; set the angle to horizontal
3162 } else ap = yp/xp;
3164 if (collinear) an = ap;
3166 // angles of the perpendiculars; HUGE_VAL means vertical
3167 if (an == 0) na = HUGE_VAL; else na = -1/an;
3168 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3170 // mouse point relative to the node's original pos
3171 pr = (*p) - n->origin;
3173 // distances to the four lines (two handles and two perpendiculars)
3174 d_an = point_line_distance(&pr, an);
3175 d_na = point_line_distance(&pr, na);
3176 d_ap = point_line_distance(&pr, ap);
3177 d_pa = point_line_distance(&pr, pa);
3179 // find out which line is the closest, save its closest point in c
3180 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3181 point_line_closest(&pr, an, &c);
3182 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3183 point_line_closest(&pr, ap, &c);
3184 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3185 point_line_closest(&pr, na, &c);
3186 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3187 point_line_closest(&pr, pa, &c);
3188 }
3190 // move the node to the closest point
3191 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3192 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3193 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3195 } else { // constraining to hor/vert
3197 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3198 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3199 } else { // snap to vert
3200 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3201 }
3202 }
3203 } else { // move freely
3204 if (n->is_dragging) {
3205 if (state & GDK_MOD1_MASK) { // sculpt
3206 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3207 } else {
3208 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3209 (*p)[NR::X] - n->pos[NR::X],
3210 (*p)[NR::Y] - n->pos[NR::Y],
3211 (state & GDK_SHIFT_MASK) == 0);
3212 }
3213 }
3214 }
3216 n->subpath->nodepath->desktop->scroll_to_point(p);
3218 return TRUE;
3219 }
3221 /**
3222 * Node handle clicked callback.
3223 */
3224 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3225 {
3226 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3228 if (state & GDK_CONTROL_MASK) { // "delete" handle
3229 if (n->p.knot == knot) {
3230 n->p.pos = n->pos;
3231 } else if (n->n.knot == knot) {
3232 n->n.pos = n->pos;
3233 }
3234 sp_node_update_handles(n);
3235 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3236 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3237 sp_nodepath_update_statusbar(nodepath);
3239 } else { // just select or add to selection, depending in Shift
3240 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3241 }
3242 }
3244 /**
3245 * Node handle grabbed callback.
3246 */
3247 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3248 {
3249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3251 if (!n->selected) {
3252 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3253 }
3255 // remember the origin point of the handle
3256 if (n->p.knot == knot) {
3257 n->p.origin_radial = n->p.pos - n->pos;
3258 } else if (n->n.knot == knot) {
3259 n->n.origin_radial = n->n.pos - n->pos;
3260 } else {
3261 g_assert_not_reached();
3262 }
3264 }
3266 /**
3267 * Node handle ungrabbed callback.
3268 */
3269 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3270 {
3271 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3273 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3274 if (n->p.knot == knot) {
3275 n->p.origin_radial.a = 0;
3276 sp_knot_set_position(knot, &n->p.pos, state);
3277 } else if (n->n.knot == knot) {
3278 n->n.origin_radial.a = 0;
3279 sp_knot_set_position(knot, &n->n.pos, state);
3280 } else {
3281 g_assert_not_reached();
3282 }
3284 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3285 }
3287 /**
3288 * Node handle "request" signal callback.
3289 */
3290 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3291 {
3292 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3294 Inkscape::NodePath::NodeSide *me, *opposite;
3295 gint which;
3296 if (n->p.knot == knot) {
3297 me = &n->p;
3298 opposite = &n->n;
3299 which = -1;
3300 } else if (n->n.knot == knot) {
3301 me = &n->n;
3302 opposite = &n->p;
3303 which = 1;
3304 } else {
3305 me = opposite = NULL;
3306 which = 0;
3307 g_assert_not_reached();
3308 }
3310 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3312 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3314 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3315 /* We are smooth node adjacent with line */
3316 NR::Point const delta = *p - n->pos;
3317 NR::Coord const len = NR::L2(delta);
3318 Inkscape::NodePath::Node *othernode = opposite->other;
3319 NR::Point const ndelta = n->pos - othernode->pos;
3320 NR::Coord const linelen = NR::L2(ndelta);
3321 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3322 NR::Coord const scal = dot(delta, ndelta) / linelen;
3323 (*p) = n->pos + (scal / linelen) * ndelta;
3324 }
3325 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3326 } else {
3327 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3328 }
3330 sp_node_adjust_handle(n, -which);
3332 return FALSE;
3333 }
3335 /**
3336 * Node handle moved callback.
3337 */
3338 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3339 {
3340 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3342 Inkscape::NodePath::NodeSide *me;
3343 Inkscape::NodePath::NodeSide *other;
3344 if (n->p.knot == knot) {
3345 me = &n->p;
3346 other = &n->n;
3347 } else if (n->n.knot == knot) {
3348 me = &n->n;
3349 other = &n->p;
3350 } else {
3351 me = NULL;
3352 other = NULL;
3353 g_assert_not_reached();
3354 }
3356 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3357 Radial rme(me->pos - n->pos);
3358 Radial rother(other->pos - n->pos);
3359 Radial rnew(*p - n->pos);
3361 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3362 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3363 /* 0 interpreted as "no snapping". */
3365 // The closest PI/snaps angle, starting from zero.
3366 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3367 if (me->origin_radial.a == HUGE_VAL) {
3368 // ortho doesn't exist: original handle was zero length.
3369 rnew.a = a_snapped;
3370 } else {
3371 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3372 * its opposite and perpendiculars). */
3373 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3375 // Snap to the closest.
3376 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3377 ? a_snapped
3378 : a_ortho );
3379 }
3380 }
3382 if (state & GDK_MOD1_MASK) {
3383 // lock handle length
3384 rnew.r = me->origin_radial.r;
3385 }
3387 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3388 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3389 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3390 rother.a += rnew.a - rme.a;
3391 other->pos = NR::Point(rother) + n->pos;
3392 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3393 sp_knot_set_position(other->knot, &other->pos, 0);
3394 }
3396 me->pos = NR::Point(rnew) + n->pos;
3397 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3399 // this is what sp_knot_set_position does, but without emitting the signal:
3400 // we cannot emit a "moved" signal because we're now processing it
3401 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3403 knot->desktop->set_coordinate_status(me->pos);
3405 update_object(n->subpath->nodepath);
3407 /* status text */
3408 SPDesktop *desktop = n->subpath->nodepath->desktop;
3409 if (!desktop) return;
3410 SPEventContext *ec = desktop->event_context;
3411 if (!ec) return;
3412 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3413 if (!mc) return;
3415 double degrees = 180 / M_PI * rnew.a;
3416 if (degrees > 180) degrees -= 360;
3417 if (degrees < -180) degrees += 360;
3418 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3419 degrees = angle_to_compass (degrees);
3421 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3423 mc->setF(Inkscape::NORMAL_MESSAGE,
3424 _("<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);
3426 g_string_free(length, TRUE);
3427 }
3429 /**
3430 * Node handle event callback.
3431 */
3432 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3433 {
3434 gboolean ret = FALSE;
3435 switch (event->type) {
3436 case GDK_KEY_PRESS:
3437 switch (get_group0_keyval (&event->key)) {
3438 case GDK_space:
3439 if (event->key.state & GDK_BUTTON1_MASK) {
3440 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3441 stamp_repr(nodepath);
3442 ret = TRUE;
3443 }
3444 break;
3445 default:
3446 break;
3447 }
3448 break;
3449 default:
3450 break;
3451 }
3453 return ret;
3454 }
3456 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3457 Radial &rme, Radial &rother, gboolean const both)
3458 {
3459 rme.a += angle;
3460 if ( both
3461 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3462 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3463 {
3464 rother.a += angle;
3465 }
3466 }
3468 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3469 Radial &rme, Radial &rother, gboolean const both)
3470 {
3471 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3473 gdouble r;
3474 if ( both
3475 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3476 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3477 {
3478 r = MAX(rme.r, rother.r);
3479 } else {
3480 r = rme.r;
3481 }
3483 gdouble const weird_angle = atan2(norm_angle, r);
3484 /* Bulia says norm_angle is just the visible distance that the
3485 * object's end must travel on the screen. Left as 'angle' for want of
3486 * a better name.*/
3488 rme.a += weird_angle;
3489 if ( both
3490 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3491 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3492 {
3493 rother.a += weird_angle;
3494 }
3495 }
3497 /**
3498 * Rotate one node.
3499 */
3500 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3501 {
3502 Inkscape::NodePath::NodeSide *me, *other;
3503 bool both = false;
3505 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3506 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3508 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3509 me = &(n->p);
3510 other = &(n->n);
3511 } else if (!n->p.other) {
3512 me = &(n->n);
3513 other = &(n->p);
3514 } else {
3515 if (which > 0) { // right handle
3516 if (xn > xp) {
3517 me = &(n->n);
3518 other = &(n->p);
3519 } else {
3520 me = &(n->p);
3521 other = &(n->n);
3522 }
3523 } else if (which < 0){ // left handle
3524 if (xn <= xp) {
3525 me = &(n->n);
3526 other = &(n->p);
3527 } else {
3528 me = &(n->p);
3529 other = &(n->n);
3530 }
3531 } else { // both handles
3532 me = &(n->n);
3533 other = &(n->p);
3534 both = true;
3535 }
3536 }
3538 Radial rme(me->pos - n->pos);
3539 Radial rother(other->pos - n->pos);
3541 if (screen) {
3542 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3543 } else {
3544 node_rotate_one_internal (*n, angle, rme, rother, both);
3545 }
3547 me->pos = n->pos + NR::Point(rme);
3549 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3550 other->pos = n->pos + NR::Point(rother);
3551 }
3553 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3554 // so here we just move all the knots without emitting move signals, for speed
3555 sp_node_update_handles(n, false);
3556 }
3558 /**
3559 * Rotate selected nodes.
3560 */
3561 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3562 {
3563 if (!nodepath || !nodepath->selected) return;
3565 if (g_list_length(nodepath->selected) == 1) {
3566 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3567 node_rotate_one (n, angle, which, screen);
3568 } else {
3569 // rotate as an object:
3571 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3572 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3573 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3574 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3575 box.expandTo (n->pos); // contain all selected nodes
3576 }
3578 gdouble rot;
3579 if (screen) {
3580 gdouble const zoom = nodepath->desktop->current_zoom();
3581 gdouble const zmove = angle / zoom;
3582 gdouble const r = NR::L2(box.max() - box.midpoint());
3583 rot = atan2(zmove, r);
3584 } else {
3585 rot = angle;
3586 }
3588 NR::Matrix t =
3589 NR::Matrix (NR::translate(-box.midpoint())) *
3590 NR::Matrix (NR::rotate(rot)) *
3591 NR::Matrix (NR::translate(box.midpoint()));
3593 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3594 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3595 n->pos *= t;
3596 n->n.pos *= t;
3597 n->p.pos *= t;
3598 sp_node_update_handles(n, false);
3599 }
3600 }
3602 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3603 }
3605 /**
3606 * Scale one node.
3607 */
3608 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3609 {
3610 bool both = false;
3611 Inkscape::NodePath::NodeSide *me, *other;
3613 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3614 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3616 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3617 me = &(n->p);
3618 other = &(n->n);
3619 n->code = NR_CURVETO;
3620 } else if (!n->p.other) {
3621 me = &(n->n);
3622 other = &(n->p);
3623 if (n->n.other)
3624 n->n.other->code = NR_CURVETO;
3625 } else {
3626 if (which > 0) { // right handle
3627 if (xn > xp) {
3628 me = &(n->n);
3629 other = &(n->p);
3630 if (n->n.other)
3631 n->n.other->code = NR_CURVETO;
3632 } else {
3633 me = &(n->p);
3634 other = &(n->n);
3635 n->code = NR_CURVETO;
3636 }
3637 } else if (which < 0){ // left handle
3638 if (xn <= xp) {
3639 me = &(n->n);
3640 other = &(n->p);
3641 if (n->n.other)
3642 n->n.other->code = NR_CURVETO;
3643 } else {
3644 me = &(n->p);
3645 other = &(n->n);
3646 n->code = NR_CURVETO;
3647 }
3648 } else { // both handles
3649 me = &(n->n);
3650 other = &(n->p);
3651 both = true;
3652 n->code = NR_CURVETO;
3653 if (n->n.other)
3654 n->n.other->code = NR_CURVETO;
3655 }
3656 }
3658 Radial rme(me->pos - n->pos);
3659 Radial rother(other->pos - n->pos);
3661 rme.r += grow;
3662 if (rme.r < 0) rme.r = 0;
3663 if (rme.a == HUGE_VAL) {
3664 if (me->other) { // if direction is unknown, initialize it towards the next node
3665 Radial rme_next(me->other->pos - n->pos);
3666 rme.a = rme_next.a;
3667 } else { // if there's no next, initialize to 0
3668 rme.a = 0;
3669 }
3670 }
3671 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3672 rother.r += grow;
3673 if (rother.r < 0) rother.r = 0;
3674 if (rother.a == HUGE_VAL) {
3675 rother.a = rme.a + M_PI;
3676 }
3677 }
3679 me->pos = n->pos + NR::Point(rme);
3681 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3682 other->pos = n->pos + NR::Point(rother);
3683 }
3685 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3686 // so here we just move all the knots without emitting move signals, for speed
3687 sp_node_update_handles(n, false);
3688 }
3690 /**
3691 * Scale selected nodes.
3692 */
3693 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3694 {
3695 if (!nodepath || !nodepath->selected) return;
3697 if (g_list_length(nodepath->selected) == 1) {
3698 // scale handles of the single selected node
3699 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3700 node_scale_one (n, grow, which);
3701 } else {
3702 // scale nodes as an "object":
3704 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3705 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3706 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3707 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3708 box.expandTo (n->pos); // contain all selected nodes
3709 }
3711 double scale = (box.maxExtent() + grow)/box.maxExtent();
3713 NR::Matrix t =
3714 NR::Matrix (NR::translate(-box.midpoint())) *
3715 NR::Matrix (NR::scale(scale, scale)) *
3716 NR::Matrix (NR::translate(box.midpoint()));
3718 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3719 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3720 n->pos *= t;
3721 n->n.pos *= t;
3722 n->p.pos *= t;
3723 sp_node_update_handles(n, false);
3724 }
3725 }
3727 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3728 }
3730 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3731 {
3732 if (!nodepath) return;
3733 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3734 }
3736 /**
3737 * Flip selected nodes horizontally/vertically.
3738 */
3739 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3740 {
3741 if (!nodepath || !nodepath->selected) return;
3743 if (g_list_length(nodepath->selected) == 1) {
3744 // flip handles of the single selected node
3745 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3746 double temp = n->p.pos[axis];
3747 n->p.pos[axis] = n->n.pos[axis];
3748 n->n.pos[axis] = temp;
3749 sp_node_update_handles(n, false);
3750 } else {
3751 // scale nodes as an "object":
3753 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3754 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3755 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3756 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3757 box.expandTo (n->pos); // contain all selected nodes
3758 }
3760 NR::Matrix t =
3761 NR::Matrix (NR::translate(-box.midpoint())) *
3762 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3763 NR::Matrix (NR::translate(box.midpoint()));
3765 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3766 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3767 n->pos *= t;
3768 n->n.pos *= t;
3769 n->p.pos *= t;
3770 sp_node_update_handles(n, false);
3771 }
3772 }
3774 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3775 }
3777 //-----------------------------------------------
3778 /**
3779 * Return new subpath under given nodepath.
3780 */
3781 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3782 {
3783 g_assert(nodepath);
3784 g_assert(nodepath->desktop);
3786 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3788 s->nodepath = nodepath;
3789 s->closed = FALSE;
3790 s->nodes = NULL;
3791 s->first = NULL;
3792 s->last = NULL;
3794 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3795 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3796 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3798 return s;
3799 }
3801 /**
3802 * Destroy nodes in subpath, then subpath itself.
3803 */
3804 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3805 {
3806 g_assert(subpath);
3807 g_assert(subpath->nodepath);
3808 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3810 while (subpath->nodes) {
3811 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3812 }
3814 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3816 g_free(subpath);
3817 }
3819 /**
3820 * Link head to tail in subpath.
3821 */
3822 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3823 {
3824 g_assert(!sp->closed);
3825 g_assert(sp->last != sp->first);
3826 g_assert(sp->first->code == NR_MOVETO);
3828 sp->closed = TRUE;
3830 //Link the head to the tail
3831 sp->first->p.other = sp->last;
3832 sp->last->n.other = sp->first;
3833 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3834 sp->first = sp->last;
3836 //Remove the extra end node
3837 sp_nodepath_node_destroy(sp->last->n.other);
3838 }
3840 /**
3841 * Open closed (loopy) subpath at node.
3842 */
3843 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3844 {
3845 g_assert(sp->closed);
3846 g_assert(n->subpath == sp);
3847 g_assert(sp->first == sp->last);
3849 /* We create new startpoint, current node will become last one */
3851 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3852 &n->pos, &n->pos, &n->n.pos);
3855 sp->closed = FALSE;
3857 //Unlink to make a head and tail
3858 sp->first = new_path;
3859 sp->last = n;
3860 n->n.other = NULL;
3861 new_path->p.other = NULL;
3862 }
3864 /**
3865 * Returns area in triangle given by points; may be negative.
3866 */
3867 inline double
3868 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3869 {
3870 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]);
3871 }
3873 /**
3874 * Return new node in subpath with given properties.
3875 * \param pos Position of node.
3876 * \param ppos Handle position in previous direction
3877 * \param npos Handle position in previous direction
3878 */
3879 Inkscape::NodePath::Node *
3880 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)
3881 {
3882 g_assert(sp);
3883 g_assert(sp->nodepath);
3884 g_assert(sp->nodepath->desktop);
3886 if (nodechunk == NULL)
3887 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3889 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3891 n->subpath = sp;
3893 if (type != Inkscape::NodePath::NODE_NONE) {
3894 // use the type from sodipodi:nodetypes
3895 n->type = type;
3896 } else {
3897 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3898 // points are (almost) collinear
3899 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3900 // endnode, or a node with a retracted handle
3901 n->type = Inkscape::NodePath::NODE_CUSP;
3902 } else {
3903 n->type = Inkscape::NodePath::NODE_SMOOTH;
3904 }
3905 } else {
3906 n->type = Inkscape::NodePath::NODE_CUSP;
3907 }
3908 }
3910 n->code = code;
3911 n->selected = FALSE;
3912 n->pos = *pos;
3913 n->p.pos = *ppos;
3914 n->n.pos = *npos;
3916 n->dragging_out = NULL;
3918 Inkscape::NodePath::Node *prev;
3919 if (next) {
3920 //g_assert(g_list_find(sp->nodes, next));
3921 prev = next->p.other;
3922 } else {
3923 prev = sp->last;
3924 }
3926 if (prev)
3927 prev->n.other = n;
3928 else
3929 sp->first = n;
3931 if (next)
3932 next->p.other = n;
3933 else
3934 sp->last = n;
3936 n->p.other = prev;
3937 n->n.other = next;
3939 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"));
3940 sp_knot_set_position(n->knot, pos, 0);
3942 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3943 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3944 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3945 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3946 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3947 sp_knot_update_ctrl(n->knot);
3949 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3950 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3951 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3952 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3953 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3954 sp_knot_show(n->knot);
3956 // We only create handle knots and lines on demand
3957 n->p.knot = NULL;
3958 n->p.line = NULL;
3959 n->n.knot = NULL;
3960 n->n.line = NULL;
3962 sp->nodes = g_list_prepend(sp->nodes, n);
3964 return n;
3965 }
3967 /**
3968 * Destroy node and its knots, link neighbors in subpath.
3969 */
3970 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3971 {
3972 g_assert(node);
3973 g_assert(node->subpath);
3974 g_assert(SP_IS_KNOT(node->knot));
3976 Inkscape::NodePath::SubPath *sp = node->subpath;
3978 if (node->selected) { // first, deselect
3979 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3980 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3981 }
3983 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3985 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
3986 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
3987 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
3988 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
3989 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
3990 g_object_unref(G_OBJECT(node->knot));
3992 if (node->p.knot) {
3993 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
3994 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
3995 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
3996 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
3997 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
3998 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
3999 g_object_unref(G_OBJECT(node->p.knot));
4000 }
4002 if (node->n.knot) {
4003 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4004 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4005 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4006 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4007 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4008 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4009 g_object_unref(G_OBJECT(node->n.knot));
4010 }
4012 if (node->p.line)
4013 gtk_object_destroy(GTK_OBJECT(node->p.line));
4014 if (node->n.line)
4015 gtk_object_destroy(GTK_OBJECT(node->n.line));
4017 if (sp->nodes) { // there are others nodes on the subpath
4018 if (sp->closed) {
4019 if (sp->first == node) {
4020 g_assert(sp->last == node);
4021 sp->first = node->n.other;
4022 sp->last = sp->first;
4023 }
4024 node->p.other->n.other = node->n.other;
4025 node->n.other->p.other = node->p.other;
4026 } else {
4027 if (sp->first == node) {
4028 sp->first = node->n.other;
4029 sp->first->code = NR_MOVETO;
4030 }
4031 if (sp->last == node) sp->last = node->p.other;
4032 if (node->p.other) node->p.other->n.other = node->n.other;
4033 if (node->n.other) node->n.other->p.other = node->p.other;
4034 }
4035 } else { // this was the last node on subpath
4036 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4037 }
4039 g_mem_chunk_free(nodechunk, node);
4040 }
4042 /**
4043 * Returns one of the node's two sides.
4044 * \param which Indicates which side.
4045 * \return Pointer to previous node side if which==-1, next if which==1.
4046 */
4047 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4048 {
4049 g_assert(node);
4051 switch (which) {
4052 case -1:
4053 return &node->p;
4054 case 1:
4055 return &node->n;
4056 default:
4057 break;
4058 }
4060 g_assert_not_reached();
4062 return NULL;
4063 }
4065 /**
4066 * Return the other side of the node, given one of its sides.
4067 */
4068 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4069 {
4070 g_assert(node);
4072 if (me == &node->p) return &node->n;
4073 if (me == &node->n) return &node->p;
4075 g_assert_not_reached();
4077 return NULL;
4078 }
4080 /**
4081 * Return NRPathcode on the given side of the node.
4082 */
4083 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4084 {
4085 g_assert(node);
4087 if (me == &node->p) {
4088 if (node->p.other) return (NRPathcode)node->code;
4089 return NR_MOVETO;
4090 }
4092 if (me == &node->n) {
4093 if (node->n.other) return (NRPathcode)node->n.other->code;
4094 return NR_MOVETO;
4095 }
4097 g_assert_not_reached();
4099 return NR_END;
4100 }
4102 /**
4103 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4104 */
4105 Inkscape::NodePath::Node *
4106 sp_nodepath_get_node_by_index(int index)
4107 {
4108 Inkscape::NodePath::Node *e = NULL;
4110 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4111 if (!nodepath) {
4112 return e;
4113 }
4115 //find segment
4116 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4118 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4119 int n = g_list_length(sp->nodes);
4120 if (sp->closed) {
4121 n++;
4122 }
4124 //if the piece belongs to this subpath grab it
4125 //otherwise move onto the next subpath
4126 if (index < n) {
4127 e = sp->first;
4128 for (int i = 0; i < index; ++i) {
4129 e = e->n.other;
4130 }
4131 break;
4132 } else {
4133 if (sp->closed) {
4134 index -= (n+1);
4135 } else {
4136 index -= n;
4137 }
4138 }
4139 }
4141 return e;
4142 }
4144 /**
4145 * Returns plain text meaning of node type.
4146 */
4147 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4148 {
4149 unsigned retracted = 0;
4150 bool endnode = false;
4152 for (int which = -1; which <= 1; which += 2) {
4153 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4154 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4155 retracted ++;
4156 if (!side->other)
4157 endnode = true;
4158 }
4160 if (retracted == 0) {
4161 if (endnode) {
4162 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4163 return _("end node");
4164 } else {
4165 switch (node->type) {
4166 case Inkscape::NodePath::NODE_CUSP:
4167 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4168 return _("cusp");
4169 case Inkscape::NodePath::NODE_SMOOTH:
4170 // TRANSLATORS: "smooth" is an adjective here
4171 return _("smooth");
4172 case Inkscape::NodePath::NODE_SYMM:
4173 return _("symmetric");
4174 }
4175 }
4176 } else if (retracted == 1) {
4177 if (endnode) {
4178 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4179 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4180 } else {
4181 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4182 }
4183 } else {
4184 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4185 }
4187 return NULL;
4188 }
4190 /**
4191 * Handles content of statusbar as long as node tool is active.
4192 */
4193 void
4194 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4195 {
4196 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");
4197 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4199 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4200 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4201 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4202 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4204 SPDesktop *desktop = NULL;
4205 if (nodepath) {
4206 desktop = nodepath->desktop;
4207 } else {
4208 desktop = SP_ACTIVE_DESKTOP;
4209 }
4211 SPEventContext *ec = desktop->event_context;
4212 if (!ec) return;
4213 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4214 if (!mc) return;
4216 if (selected_nodes == 0) {
4217 Inkscape::Selection *sel = desktop->selection;
4218 if (!sel || sel->isEmpty()) {
4219 mc->setF(Inkscape::NORMAL_MESSAGE,
4220 _("Select a single object to edit its nodes or handles."));
4221 } else {
4222 if (nodepath) {
4223 mc->setF(Inkscape::NORMAL_MESSAGE,
4224 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.",
4225 "<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.",
4226 total_nodes),
4227 total_nodes);
4228 } else {
4229 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4230 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4231 } else {
4232 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4233 }
4234 }
4235 }
4236 } else if (nodepath && selected_nodes == 1) {
4237 mc->setF(Inkscape::NORMAL_MESSAGE,
4238 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4239 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4240 total_nodes),
4241 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4242 } else {
4243 if (selected_subpaths > 1) {
4244 mc->setF(Inkscape::NORMAL_MESSAGE,
4245 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4246 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4247 total_nodes),
4248 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4249 } else {
4250 mc->setF(Inkscape::NORMAL_MESSAGE,
4251 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4252 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4253 total_nodes),
4254 selected_nodes, total_nodes, when_selected);
4255 }
4256 }
4257 }
4260 /*
4261 Local Variables:
4262 mode:c++
4263 c-file-style:"stroustrup"
4264 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4265 indent-tabs-mode:nil
4266 fill-column:99
4267 End:
4268 */
4269 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :