e3681f59adcd94e7a4225407bf8edf8ecf5728aa
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 if (np->livarot_path) {
486 delete np->livarot_path;
487 np->livarot_path = NULL;
488 }
490 if (np->path && SP_IS_ITEM(np->path)) {
491 np->livarot_path = Path_for_item (np->path, true, true);
492 if (np->livarot_path)
493 np->livarot_path->ConvertWithBackData(0.01);
494 }
496 update_repr_internal(np);
497 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
498 annotation);
499 }
501 /**
502 * Update XML path node with data from path object, commit changes with undo.
503 */
504 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
505 {
506 update_repr_internal(np);
507 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
508 annotation);
510 if (np->livarot_path) {
511 delete np->livarot_path;
512 np->livarot_path = NULL;
513 }
515 if (np->path && SP_IS_ITEM(np->path)) {
516 np->livarot_path = Path_for_item (np->path, true, true);
517 if (np->livarot_path)
518 np->livarot_path->ConvertWithBackData(0.01);
519 }
520 }
522 /**
523 * Make duplicate of path, replace corresponding XML node in tree, commit.
524 */
525 static void stamp_repr(Inkscape::NodePath::Path *np)
526 {
527 g_assert(np);
529 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
530 Inkscape::XML::Node *new_repr = old_repr->duplicate();
532 // remember the position of the item
533 gint pos = old_repr->position();
534 // remember parent
535 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
537 SPCurve *curve = create_curve(np);
538 gchar *typestr = create_typestr(np);
540 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
542 new_repr->setAttribute("d", svgpath);
543 new_repr->setAttribute("sodipodi:nodetypes", typestr);
545 // add the new repr to the parent
546 parent->appendChild(new_repr);
547 // move to the saved position
548 new_repr->setPosition(pos > 0 ? pos : 0);
550 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
551 _("Stamp"));
553 Inkscape::GC::release(new_repr);
554 g_free(svgpath);
555 g_free(typestr);
556 sp_curve_unref(curve);
557 }
559 /**
560 * Create curve from path.
561 */
562 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
563 {
564 SPCurve *curve = sp_curve_new();
566 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
567 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
568 sp_curve_moveto(curve,
569 sp->first->pos * np->d2i);
570 Inkscape::NodePath::Node *n = sp->first->n.other;
571 while (n) {
572 NR::Point const end_pt = n->pos * np->d2i;
573 switch (n->code) {
574 case NR_LINETO:
575 sp_curve_lineto(curve, end_pt);
576 break;
577 case NR_CURVETO:
578 sp_curve_curveto(curve,
579 n->p.other->n.pos * np->d2i,
580 n->p.pos * np->d2i,
581 end_pt);
582 break;
583 default:
584 g_assert_not_reached();
585 break;
586 }
587 if (n != sp->last) {
588 n = n->n.other;
589 } else {
590 n = NULL;
591 }
592 }
593 if (sp->closed) {
594 sp_curve_closepath(curve);
595 }
596 }
598 return curve;
599 }
601 /**
602 * Convert path type string to sodipodi:nodetypes style.
603 */
604 static gchar *create_typestr(Inkscape::NodePath::Path *np)
605 {
606 gchar *typestr = g_new(gchar, 32);
607 gint len = 32;
608 gint pos = 0;
610 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
611 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
613 if (pos >= len) {
614 typestr = g_renew(gchar, typestr, len + 32);
615 len += 32;
616 }
618 typestr[pos++] = 'c';
620 Inkscape::NodePath::Node *n;
621 n = sp->first->n.other;
622 while (n) {
623 gchar code;
625 switch (n->type) {
626 case Inkscape::NodePath::NODE_CUSP:
627 code = 'c';
628 break;
629 case Inkscape::NodePath::NODE_SMOOTH:
630 code = 's';
631 break;
632 case Inkscape::NodePath::NODE_SYMM:
633 code = 'z';
634 break;
635 default:
636 g_assert_not_reached();
637 code = '\0';
638 break;
639 }
641 if (pos >= len) {
642 typestr = g_renew(gchar, typestr, len + 32);
643 len += 32;
644 }
646 typestr[pos++] = code;
648 if (n != sp->last) {
649 n = n->n.other;
650 } else {
651 n = NULL;
652 }
653 }
654 }
656 if (pos >= len) {
657 typestr = g_renew(gchar, typestr, len + 1);
658 len += 1;
659 }
661 typestr[pos++] = '\0';
663 return typestr;
664 }
666 /**
667 * Returns current path in context.
668 */
669 static Inkscape::NodePath::Path *sp_nodepath_current()
670 {
671 if (!SP_ACTIVE_DESKTOP) {
672 return NULL;
673 }
675 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
677 if (!SP_IS_NODE_CONTEXT(event_context)) {
678 return NULL;
679 }
681 return SP_NODE_CONTEXT(event_context)->nodepath;
682 }
686 /**
687 \brief Fills node and handle positions for three nodes, splitting line
688 marked by end at distance t.
689 */
690 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
691 {
692 g_assert(new_path != NULL);
693 g_assert(end != NULL);
695 g_assert(end->p.other == new_path);
696 Inkscape::NodePath::Node *start = new_path->p.other;
697 g_assert(start);
699 if (end->code == NR_LINETO) {
700 new_path->type =Inkscape::NodePath::NODE_CUSP;
701 new_path->code = NR_LINETO;
702 new_path->pos = (t * start->pos + (1 - t) * end->pos);
703 } else {
704 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
705 new_path->code = NR_CURVETO;
706 gdouble s = 1 - t;
707 for (int dim = 0; dim < 2; dim++) {
708 NR::Coord const f000 = start->pos[dim];
709 NR::Coord const f001 = start->n.pos[dim];
710 NR::Coord const f011 = end->p.pos[dim];
711 NR::Coord const f111 = end->pos[dim];
712 NR::Coord const f00t = s * f000 + t * f001;
713 NR::Coord const f01t = s * f001 + t * f011;
714 NR::Coord const f11t = s * f011 + t * f111;
715 NR::Coord const f0tt = s * f00t + t * f01t;
716 NR::Coord const f1tt = s * f01t + t * f11t;
717 NR::Coord const fttt = s * f0tt + t * f1tt;
718 start->n.pos[dim] = f00t;
719 new_path->p.pos[dim] = f0tt;
720 new_path->pos[dim] = fttt;
721 new_path->n.pos[dim] = f1tt;
722 end->p.pos[dim] = f11t;
723 }
724 }
725 }
727 /**
728 * Adds new node on direct line between two nodes, activates handles of all
729 * three nodes.
730 */
731 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
732 {
733 g_assert(end);
734 g_assert(end->subpath);
735 g_assert(g_list_find(end->subpath->nodes, end));
737 Inkscape::NodePath::Node *start = end->p.other;
738 g_assert( start->n.other == end );
739 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
740 end,
741 Inkscape::NodePath::NODE_SMOOTH,
742 (NRPathcode)end->code,
743 &start->pos, &start->pos, &start->n.pos);
744 sp_nodepath_line_midpoint(newnode, end, t);
746 sp_node_update_handles(start);
747 sp_node_update_handles(newnode);
748 sp_node_update_handles(end);
750 return newnode;
751 }
753 /**
754 \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
755 */
756 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
757 {
758 g_assert(node);
759 g_assert(node->subpath);
760 g_assert(g_list_find(node->subpath->nodes, node));
762 Inkscape::NodePath::SubPath *sp = node->subpath;
763 Inkscape::NodePath::Path *np = sp->nodepath;
765 if (sp->closed) {
766 sp_nodepath_subpath_open(sp, node);
767 return sp->first;
768 } else {
769 // no break for end nodes
770 if (node == sp->first) return NULL;
771 if (node == sp->last ) return NULL;
773 // create a new subpath
774 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
776 // duplicate the break node as start of the new subpath
777 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
779 while (node->n.other) { // copy the remaining nodes into the new subpath
780 Inkscape::NodePath::Node *n = node->n.other;
781 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);
782 if (n->selected) {
783 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
784 }
785 sp_nodepath_node_destroy(n); // remove the point on the original subpath
786 }
788 return newnode;
789 }
790 }
792 /**
793 * Duplicate node and connect to neighbours.
794 */
795 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
796 {
797 g_assert(node);
798 g_assert(node->subpath);
799 g_assert(g_list_find(node->subpath->nodes, node));
801 Inkscape::NodePath::SubPath *sp = node->subpath;
803 NRPathcode code = (NRPathcode) node->code;
804 if (code == NR_MOVETO) { // if node is the endnode,
805 node->code = NR_LINETO; // new one is inserted before it, so change that to line
806 }
808 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
810 if (!node->n.other || !node->p.other) // if node is an endnode, select it
811 return node;
812 else
813 return newnode; // otherwise select the newly created node
814 }
816 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
817 {
818 node->p.pos = (node->pos + (node->pos - node->n.pos));
819 }
821 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
822 {
823 node->n.pos = (node->pos + (node->pos - node->p.pos));
824 }
826 /**
827 * Change line type at node, with side effects on neighbours.
828 */
829 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
830 {
831 g_assert(end);
832 g_assert(end->subpath);
833 g_assert(end->p.other);
835 if (end->code == static_cast< guint > ( code ) )
836 return;
838 Inkscape::NodePath::Node *start = end->p.other;
840 end->code = code;
842 if (code == NR_LINETO) {
843 if (start->code == NR_LINETO) start->type =Inkscape::NodePath::NODE_CUSP;
844 if (end->n.other) {
845 if (end->n.other->code == NR_LINETO) end->type =Inkscape::NodePath::NODE_CUSP;
846 }
847 sp_node_adjust_handle(start, -1);
848 sp_node_adjust_handle(end, 1);
849 } else {
850 NR::Point delta = end->pos - start->pos;
851 start->n.pos = start->pos + delta / 3;
852 end->p.pos = end->pos - delta / 3;
853 sp_node_adjust_handle(start, 1);
854 sp_node_adjust_handle(end, -1);
855 }
857 sp_node_update_handles(start);
858 sp_node_update_handles(end);
859 }
861 /**
862 * Change node type, and its handles accordingly.
863 */
864 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
865 {
866 g_assert(node);
867 g_assert(node->subpath);
869 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
870 return node;
872 if ((node->p.other != NULL) && (node->n.other != NULL)) {
873 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
874 type =Inkscape::NodePath::NODE_CUSP;
875 }
876 }
878 node->type = type;
880 if (node->type == Inkscape::NodePath::NODE_CUSP) {
881 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
882 node->knot->setSize (node->selected? 11 : 9);
883 sp_knot_update_ctrl(node->knot);
884 } else {
885 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
886 node->knot->setSize (node->selected? 9 : 7);
887 sp_knot_update_ctrl(node->knot);
888 }
890 // if one of handles is mouseovered, preserve its position
891 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
892 sp_node_adjust_handle(node, 1);
893 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
894 sp_node_adjust_handle(node, -1);
895 } else {
896 sp_node_adjust_handles(node);
897 }
899 sp_node_update_handles(node);
901 sp_nodepath_update_statusbar(node->subpath->nodepath);
903 return node;
904 }
906 /**
907 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
908 * adjacent segments from lines to curves.
909 */
910 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
911 {
912 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
913 if ((node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos)) {
914 // convert adjacent segment BEFORE to curve
915 node->code = NR_CURVETO;
916 NR::Point delta;
917 if (node->n.other != NULL)
918 delta = node->n.other->pos - node->p.other->pos;
919 else
920 delta = node->pos - node->p.other->pos;
921 node->p.pos = node->pos - delta / 4;
922 sp_node_update_handles(node);
923 }
925 if ((node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos)) {
926 // convert adjacent segment AFTER to curve
927 node->n.other->code = NR_CURVETO;
928 NR::Point delta;
929 if (node->p.other != NULL)
930 delta = node->p.other->pos - node->n.other->pos;
931 else
932 delta = node->pos - node->n.other->pos;
933 node->n.pos = node->pos - delta / 4;
934 sp_node_update_handles(node);
935 }
936 }
938 sp_nodepath_set_node_type (node, type);
939 }
941 /**
942 * Move node to point, and adjust its and neighbouring handles.
943 */
944 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
945 {
946 NR::Point delta = p - node->pos;
947 node->pos = p;
949 node->p.pos += delta;
950 node->n.pos += delta;
952 if (node->p.other) {
953 if (node->code == NR_LINETO) {
954 sp_node_adjust_handle(node, 1);
955 sp_node_adjust_handle(node->p.other, -1);
956 }
957 }
958 if (node->n.other) {
959 if (node->n.other->code == NR_LINETO) {
960 sp_node_adjust_handle(node, -1);
961 sp_node_adjust_handle(node->n.other, 1);
962 }
963 }
965 // this function is only called from batch movers that will update display at the end
966 // themselves, so here we just move all the knots without emitting move signals, for speed
967 sp_node_update_handles(node, false);
968 }
970 /**
971 * Call sp_node_moveto() for node selection and handle possible snapping.
972 */
973 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
974 bool const snap = true)
975 {
976 NR::Coord best = NR_HUGE;
977 NR::Point delta(dx, dy);
978 NR::Point best_pt = delta;
980 if (snap) {
981 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
983 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
984 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
985 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
986 if (s.getDistance() < best) {
987 best = s.getDistance();
988 best_pt = s.getPoint() - n->pos;
989 }
990 }
991 }
993 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
994 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
995 sp_node_moveto(n, n->pos + best_pt);
996 }
998 // do not update repr here so that node dragging is acceptably fast
999 update_object(nodepath);
1000 }
1002 /**
1003 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1004 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1005 near x = 0.
1006 */
1007 double
1008 sculpt_profile (double x, double alpha, guint profile)
1009 {
1010 if (x >= 1)
1011 return 0;
1012 if (x <= 0)
1013 return 1;
1015 switch (profile) {
1016 case SCULPT_PROFILE_LINEAR:
1017 return 1 - x;
1018 case SCULPT_PROFILE_BELL:
1019 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1020 case SCULPT_PROFILE_ELLIPTIC:
1021 return sqrt(1 - x*x);
1022 }
1024 return 1;
1025 }
1027 double
1028 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1029 {
1030 // extremely primitive for now, don't have time to look for the real one
1031 double lower = NR::L2(b - a);
1032 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1033 return (lower + upper)/2;
1034 }
1036 void
1037 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1038 {
1039 n->pos = n->origin + delta;
1040 n->n.pos = n->n.origin + delta_n;
1041 n->p.pos = n->p.origin + delta_p;
1042 sp_node_adjust_handles(n);
1043 sp_node_update_handles(n, false);
1044 }
1046 /**
1047 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1048 * on how far they are from the dragged node n.
1049 */
1050 static void
1051 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1052 {
1053 g_assert (n);
1054 g_assert (nodepath);
1055 g_assert (n->subpath->nodepath == nodepath);
1057 double pressure = n->knot->pressure;
1058 if (pressure == 0)
1059 pressure = 0.5; // default
1060 pressure = CLAMP (pressure, 0.2, 0.8);
1062 // map pressure to alpha = 1/5 ... 5
1063 double alpha = 1 - 2 * fabs(pressure - 0.5);
1064 if (pressure > 0.5)
1065 alpha = 1/alpha;
1067 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1069 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1070 // Only one subpath has selected nodes:
1071 // use linear mode, where the distance from n to node being dragged is calculated along the path
1073 double n_sel_range = 0, p_sel_range = 0;
1074 guint n_nodes = 0, p_nodes = 0;
1075 guint n_sel_nodes = 0, p_sel_nodes = 0;
1077 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1078 {
1079 double n_range = 0, p_range = 0;
1080 bool n_going = true, p_going = true;
1081 Inkscape::NodePath::Node *n_node = n;
1082 Inkscape::NodePath::Node *p_node = n;
1083 do {
1084 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1085 if (n_node && n_going)
1086 n_node = n_node->n.other;
1087 if (n_node == NULL) {
1088 n_going = false;
1089 } else {
1090 n_nodes ++;
1091 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1092 if (n_node->selected) {
1093 n_sel_nodes ++;
1094 n_sel_range = n_range;
1095 }
1096 if (n_node == p_node) {
1097 n_going = false;
1098 p_going = false;
1099 }
1100 }
1101 if (p_node && p_going)
1102 p_node = p_node->p.other;
1103 if (p_node == NULL) {
1104 p_going = false;
1105 } else {
1106 p_nodes ++;
1107 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1108 if (p_node->selected) {
1109 p_sel_nodes ++;
1110 p_sel_range = p_range;
1111 }
1112 if (p_node == n_node) {
1113 n_going = false;
1114 p_going = false;
1115 }
1116 }
1117 } while (n_going || p_going);
1118 }
1120 // Second pass: actually move nodes in this subpath
1121 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1122 {
1123 double n_range = 0, p_range = 0;
1124 bool n_going = true, p_going = true;
1125 Inkscape::NodePath::Node *n_node = n;
1126 Inkscape::NodePath::Node *p_node = n;
1127 do {
1128 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1129 if (n_node && n_going)
1130 n_node = n_node->n.other;
1131 if (n_node == NULL) {
1132 n_going = false;
1133 } else {
1134 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1135 if (n_node->selected) {
1136 sp_nodepath_move_node_and_handles (n_node,
1137 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1138 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1139 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1140 }
1141 if (n_node == p_node) {
1142 n_going = false;
1143 p_going = false;
1144 }
1145 }
1146 if (p_node && p_going)
1147 p_node = p_node->p.other;
1148 if (p_node == NULL) {
1149 p_going = false;
1150 } else {
1151 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1152 if (p_node->selected) {
1153 sp_nodepath_move_node_and_handles (p_node,
1154 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1155 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1156 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1157 }
1158 if (p_node == n_node) {
1159 n_going = false;
1160 p_going = false;
1161 }
1162 }
1163 } while (n_going || p_going);
1164 }
1166 } else {
1167 // Multiple subpaths have selected nodes:
1168 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1169 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1170 // fix the pear-like shape when sculpting e.g. a ring
1172 // First pass: calculate range
1173 gdouble direct_range = 0;
1174 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1175 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1176 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1177 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1178 if (node->selected) {
1179 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1180 }
1181 }
1182 }
1184 // Second pass: actually move nodes
1185 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1186 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1187 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1188 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1189 if (node->selected) {
1190 if (direct_range > 1e-6) {
1191 sp_nodepath_move_node_and_handles (node,
1192 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1193 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1194 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1195 } else {
1196 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1197 }
1199 }
1200 }
1201 }
1202 }
1204 // do not update repr here so that node dragging is acceptably fast
1205 update_object(nodepath);
1206 }
1209 /**
1210 * Move node selection to point, adjust its and neighbouring handles,
1211 * handle possible snapping, and commit the change with possible undo.
1212 */
1213 void
1214 sp_node_selected_move(gdouble dx, gdouble dy)
1215 {
1216 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1217 if (!nodepath) return;
1219 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1221 if (dx == 0) {
1222 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1223 } else if (dy == 0) {
1224 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1225 } else {
1226 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1227 }
1228 }
1230 /**
1231 * Move node selection off screen and commit the change.
1232 */
1233 void
1234 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1235 {
1236 // borrowed from sp_selection_move_screen in selection-chemistry.c
1237 // we find out the current zoom factor and divide deltas by it
1238 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1240 gdouble zoom = desktop->current_zoom();
1241 gdouble zdx = dx / zoom;
1242 gdouble zdy = dy / zoom;
1244 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1245 if (!nodepath) return;
1247 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1249 if (dx == 0) {
1250 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1251 } else if (dy == 0) {
1252 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1253 } else {
1254 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1255 }
1256 }
1258 /** If they don't yet exist, creates knot and line for the given side of the node */
1259 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1260 {
1261 if (!side->knot) {
1262 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"));
1264 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1265 side->knot->setSize (7);
1266 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1267 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1268 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1269 sp_knot_update_ctrl(side->knot);
1271 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1272 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1273 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1274 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1275 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1276 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1277 }
1279 if (!side->line) {
1280 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1281 SP_TYPE_CTRLLINE, NULL);
1282 }
1283 }
1285 /**
1286 * Ensure the given handle of the node is visible/invisible, update its screen position
1287 */
1288 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1289 {
1290 g_assert(node != NULL);
1292 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1293 NRPathcode code = sp_node_path_code_from_side(node, side);
1295 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1297 if (show_handle) {
1298 if (!side->knot) { // No handle knot at all
1299 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1300 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1301 side->knot->pos = side->pos;
1302 if (side->knot->item)
1303 SP_CTRL(side->knot->item)->moveto(side->pos);
1304 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1305 sp_knot_show(side->knot);
1306 } else {
1307 if (side->knot->pos != side->pos) { // only if it's really moved
1308 if (fire_move_signals) {
1309 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1310 } else {
1311 sp_knot_moveto(side->knot, &side->pos);
1312 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1313 }
1314 }
1315 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1316 sp_knot_show(side->knot);
1317 }
1318 }
1319 sp_canvas_item_show(side->line);
1320 } else {
1321 if (side->knot) {
1322 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1323 sp_knot_hide(side->knot);
1324 }
1325 }
1326 if (side->line) {
1327 sp_canvas_item_hide(side->line);
1328 }
1329 }
1330 }
1332 /**
1333 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1334 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1335 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1336 * updated; otherwise, just move the knots silently (used in batch moves).
1337 */
1338 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1339 {
1340 g_assert(node != NULL);
1342 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1343 sp_knot_show(node->knot);
1344 }
1346 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1347 if (fire_move_signals)
1348 sp_knot_set_position(node->knot, &node->pos, 0);
1349 else
1350 sp_knot_moveto(node->knot, &node->pos);
1351 }
1353 gboolean show_handles = node->selected;
1354 if (node->p.other != NULL) {
1355 if (node->p.other->selected) show_handles = TRUE;
1356 }
1357 if (node->n.other != NULL) {
1358 if (node->n.other->selected) show_handles = TRUE;
1359 }
1361 if (node->subpath->nodepath->show_handles == false)
1362 show_handles = FALSE;
1364 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1365 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1366 }
1368 /**
1369 * Call sp_node_update_handles() for all nodes on subpath.
1370 */
1371 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1372 {
1373 g_assert(subpath != NULL);
1375 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1376 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1377 }
1378 }
1380 /**
1381 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1382 */
1383 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1384 {
1385 g_assert(nodepath != NULL);
1387 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1388 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1389 }
1390 }
1392 void
1393 sp_nodepath_show_handles(bool show)
1394 {
1395 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1396 if (nodepath == NULL) return;
1398 nodepath->show_handles = show;
1399 sp_nodepath_update_handles(nodepath);
1400 }
1402 /**
1403 * Adds all selected nodes in nodepath to list.
1404 */
1405 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1406 {
1407 StlConv<Node *>::list(l, selected);
1408 /// \todo this adds a copying, rework when the selection becomes a stl list
1409 }
1411 /**
1412 * Align selected nodes on the specified axis.
1413 */
1414 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1415 {
1416 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1417 return;
1418 }
1420 if ( !nodepath->selected->next ) { // only one node selected
1421 return;
1422 }
1423 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1424 NR::Point dest(pNode->pos);
1425 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1426 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1427 if (pNode) {
1428 dest[axis] = pNode->pos[axis];
1429 sp_node_moveto(pNode, dest);
1430 }
1431 }
1433 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1434 }
1436 /// Helper struct.
1437 struct NodeSort
1438 {
1439 Inkscape::NodePath::Node *_node;
1440 NR::Coord _coord;
1441 /// \todo use vectorof pointers instead of calling copy ctor
1442 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1443 _node(node), _coord(node->pos[axis])
1444 {}
1446 };
1448 static bool operator<(NodeSort const &a, NodeSort const &b)
1449 {
1450 return (a._coord < b._coord);
1451 }
1453 /**
1454 * Distribute selected nodes on the specified axis.
1455 */
1456 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1457 {
1458 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1459 return;
1460 }
1462 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1463 return;
1464 }
1466 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1467 std::vector<NodeSort> sorted;
1468 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1469 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1470 if (pNode) {
1471 NodeSort n(pNode, axis);
1472 sorted.push_back(n);
1473 //dest[axis] = pNode->pos[axis];
1474 //sp_node_moveto(pNode, dest);
1475 }
1476 }
1477 std::sort(sorted.begin(), sorted.end());
1478 unsigned int len = sorted.size();
1479 //overall bboxes span
1480 float dist = (sorted.back()._coord -
1481 sorted.front()._coord);
1482 //new distance between each bbox
1483 float step = (dist) / (len - 1);
1484 float pos = sorted.front()._coord;
1485 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1486 it < sorted.end();
1487 it ++ )
1488 {
1489 NR::Point dest((*it)._node->pos);
1490 dest[axis] = pos;
1491 sp_node_moveto((*it)._node, dest);
1492 pos += step;
1493 }
1495 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1496 }
1499 /**
1500 * Call sp_nodepath_line_add_node() for all selected segments.
1501 */
1502 void
1503 sp_node_selected_add_node(void)
1504 {
1505 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1506 if (!nodepath) {
1507 return;
1508 }
1510 GList *nl = NULL;
1512 int n_added = 0;
1514 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1515 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1516 g_assert(t->selected);
1517 if (t->p.other && t->p.other->selected) {
1518 nl = g_list_prepend(nl, t);
1519 }
1520 }
1522 while (nl) {
1523 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1524 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1525 sp_nodepath_node_select(n, TRUE, FALSE);
1526 n_added ++;
1527 nl = g_list_remove(nl, t);
1528 }
1530 /** \todo fixme: adjust ? */
1531 sp_nodepath_update_handles(nodepath);
1533 if (n_added > 1) {
1534 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1535 } else if (n_added > 0) {
1536 sp_nodepath_update_repr(nodepath, _("Add node"));
1537 }
1539 sp_nodepath_update_statusbar(nodepath);
1540 }
1542 /**
1543 * Select segment nearest to point
1544 */
1545 void
1546 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1547 {
1548 if (!nodepath) {
1549 return;
1550 }
1552 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1554 //find segment to segment
1555 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1557 //fixme: this can return NULL, so check before proceeding.
1558 g_return_if_fail(e != NULL);
1560 gboolean force = FALSE;
1561 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1562 force = TRUE;
1563 }
1564 sp_nodepath_node_select(e, (gboolean) toggle, force);
1565 if (e->p.other)
1566 sp_nodepath_node_select(e->p.other, TRUE, force);
1568 sp_nodepath_update_handles(nodepath);
1570 sp_nodepath_update_statusbar(nodepath);
1571 }
1573 /**
1574 * Add a node nearest to point
1575 */
1576 void
1577 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1578 {
1579 if (!nodepath) {
1580 return;
1581 }
1583 Path::cut_position position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1585 //find segment to split
1586 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1588 //don't know why but t seems to flip for lines
1589 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1590 position.t = 1.0 - position.t;
1591 }
1592 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1593 sp_nodepath_node_select(n, FALSE, TRUE);
1595 /* fixme: adjust ? */
1596 sp_nodepath_update_handles(nodepath);
1598 sp_nodepath_update_repr(nodepath, _("Add node"));
1600 sp_nodepath_update_statusbar(nodepath);
1601 }
1603 /*
1604 * Adjusts a segment so that t moves by a certain delta for dragging
1605 * converts lines to curves
1606 *
1607 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1608 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1609 */
1610 void
1611 sp_nodepath_curve_drag(Inkscape::NodePath::Node * e, double t, NR::Point delta)
1612 {
1613 //fixme: e and e->p can be NULL, so check for those before proceeding
1614 g_return_if_fail(e != NULL);
1615 g_return_if_fail(&e->p != NULL);
1617 /* feel good is an arbitrary parameter that distributes the delta between handles
1618 * if t of the drag point is less than 1/6 distance form the endpoint only
1619 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1620 */
1621 double feel_good;
1622 if (t <= 1.0 / 6.0)
1623 feel_good = 0;
1624 else if (t <= 0.5)
1625 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1626 else if (t <= 5.0 / 6.0)
1627 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1628 else
1629 feel_good = 1;
1631 //if we're dragging a line convert it to a curve
1632 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1633 sp_nodepath_set_line_type(e, NR_CURVETO);
1634 }
1636 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1637 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1638 e->p.other->n.pos += offsetcoord0;
1639 e->p.pos += offsetcoord1;
1641 // adjust handles of adjacent nodes where necessary
1642 sp_node_adjust_handle(e,1);
1643 sp_node_adjust_handle(e->p.other,-1);
1645 sp_nodepath_update_handles(e->subpath->nodepath);
1647 update_object(e->subpath->nodepath);
1649 sp_nodepath_update_statusbar(e->subpath->nodepath);
1650 }
1653 /**
1654 * Call sp_nodepath_break() for all selected segments.
1655 */
1656 void sp_node_selected_break()
1657 {
1658 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1659 if (!nodepath) return;
1661 GList *temp = NULL;
1662 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1663 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1664 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1665 if (nn == NULL) continue; // no break, no new node
1666 temp = g_list_prepend(temp, nn);
1667 }
1669 if (temp) {
1670 sp_nodepath_deselect(nodepath);
1671 }
1672 for (GList *l = temp; l != NULL; l = l->next) {
1673 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1674 }
1676 sp_nodepath_update_handles(nodepath);
1678 sp_nodepath_update_repr(nodepath, _("Break path"));
1679 }
1681 /**
1682 * Duplicate the selected node(s).
1683 */
1684 void sp_node_selected_duplicate()
1685 {
1686 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1687 if (!nodepath) {
1688 return;
1689 }
1691 GList *temp = NULL;
1692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1694 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1695 if (nn == NULL) continue; // could not duplicate
1696 temp = g_list_prepend(temp, nn);
1697 }
1699 if (temp) {
1700 sp_nodepath_deselect(nodepath);
1701 }
1702 for (GList *l = temp; l != NULL; l = l->next) {
1703 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1704 }
1706 sp_nodepath_update_handles(nodepath);
1708 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1709 }
1711 /**
1712 * Join two nodes by merging them into one.
1713 */
1714 void sp_node_selected_join()
1715 {
1716 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1717 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1719 if (g_list_length(nodepath->selected) != 2) {
1720 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1721 return;
1722 }
1724 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1725 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1727 g_assert(a != b);
1728 g_assert(a->p.other || a->n.other);
1729 g_assert(b->p.other || b->n.other);
1731 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1732 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1733 return;
1734 }
1736 /* a and b are endpoints */
1738 NR::Point c;
1739 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1740 c = a->pos;
1741 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1742 c = b->pos;
1743 } else {
1744 c = (a->pos + b->pos) / 2;
1745 }
1747 if (a->subpath == b->subpath) {
1748 Inkscape::NodePath::SubPath *sp = a->subpath;
1749 sp_nodepath_subpath_close(sp);
1750 sp_node_moveto (sp->first, c);
1752 sp_nodepath_update_handles(sp->nodepath);
1753 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1754 return;
1755 }
1757 /* a and b are separate subpaths */
1758 Inkscape::NodePath::SubPath *sa = a->subpath;
1759 Inkscape::NodePath::SubPath *sb = b->subpath;
1760 NR::Point p;
1761 Inkscape::NodePath::Node *n;
1762 NRPathcode code;
1763 if (a == sa->first) {
1764 p = sa->first->n.pos;
1765 code = (NRPathcode)sa->first->n.other->code;
1766 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1767 n = sa->last;
1768 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1769 n = n->p.other;
1770 while (n) {
1771 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1772 n = n->p.other;
1773 if (n == sa->first) n = NULL;
1774 }
1775 sp_nodepath_subpath_destroy(sa);
1776 sa = t;
1777 } else if (a == sa->last) {
1778 p = sa->last->p.pos;
1779 code = (NRPathcode)sa->last->code;
1780 sp_nodepath_node_destroy(sa->last);
1781 } else {
1782 code = NR_END;
1783 g_assert_not_reached();
1784 }
1786 if (b == sb->first) {
1787 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1788 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1789 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1790 }
1791 } else if (b == sb->last) {
1792 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1793 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1794 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1795 }
1796 } else {
1797 g_assert_not_reached();
1798 }
1799 /* and now destroy sb */
1801 sp_nodepath_subpath_destroy(sb);
1803 sp_nodepath_update_handles(sa->nodepath);
1805 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1807 sp_nodepath_update_statusbar(nodepath);
1808 }
1810 /**
1811 * Join two nodes by adding a segment between them.
1812 */
1813 void sp_node_selected_join_segment()
1814 {
1815 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1816 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1818 if (g_list_length(nodepath->selected) != 2) {
1819 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1820 return;
1821 }
1823 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1824 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1826 g_assert(a != b);
1827 g_assert(a->p.other || a->n.other);
1828 g_assert(b->p.other || b->n.other);
1830 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1831 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1832 return;
1833 }
1835 if (a->subpath == b->subpath) {
1836 Inkscape::NodePath::SubPath *sp = a->subpath;
1838 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1839 sp->closed = TRUE;
1841 sp->first->p.other = sp->last;
1842 sp->last->n.other = sp->first;
1844 sp_node_handle_mirror_p_to_n(sp->last);
1845 sp_node_handle_mirror_n_to_p(sp->first);
1847 sp->first->code = sp->last->code;
1848 sp->first = sp->last;
1850 sp_nodepath_update_handles(sp->nodepath);
1852 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1854 return;
1855 }
1857 /* a and b are separate subpaths */
1858 Inkscape::NodePath::SubPath *sa = a->subpath;
1859 Inkscape::NodePath::SubPath *sb = b->subpath;
1861 Inkscape::NodePath::Node *n;
1862 NR::Point p;
1863 NRPathcode code;
1864 if (a == sa->first) {
1865 code = (NRPathcode) sa->first->n.other->code;
1866 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1867 n = sa->last;
1868 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1869 for (n = n->p.other; n != NULL; n = n->p.other) {
1870 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1871 }
1872 sp_nodepath_subpath_destroy(sa);
1873 sa = t;
1874 } else if (a == sa->last) {
1875 code = (NRPathcode)sa->last->code;
1876 } else {
1877 code = NR_END;
1878 g_assert_not_reached();
1879 }
1881 if (b == sb->first) {
1882 n = sb->first;
1883 sp_node_handle_mirror_p_to_n(sa->last);
1884 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1885 sp_node_handle_mirror_n_to_p(sa->last);
1886 for (n = n->n.other; n != NULL; n = n->n.other) {
1887 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1888 }
1889 } else if (b == sb->last) {
1890 n = sb->last;
1891 sp_node_handle_mirror_p_to_n(sa->last);
1892 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1893 sp_node_handle_mirror_n_to_p(sa->last);
1894 for (n = n->p.other; n != NULL; n = n->p.other) {
1895 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1896 }
1897 } else {
1898 g_assert_not_reached();
1899 }
1900 /* and now destroy sb */
1902 sp_nodepath_subpath_destroy(sb);
1904 sp_nodepath_update_handles(sa->nodepath);
1906 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1907 }
1909 /**
1910 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1911 */
1912 void sp_node_delete_preserve(GList *nodes_to_delete)
1913 {
1914 GSList *nodepaths = NULL;
1916 while (nodes_to_delete) {
1917 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1918 Inkscape::NodePath::SubPath *sp = node->subpath;
1919 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1920 Inkscape::NodePath::Node *sample_cursor = NULL;
1921 Inkscape::NodePath::Node *sample_end = NULL;
1922 Inkscape::NodePath::Node *delete_cursor = node;
1923 bool just_delete = false;
1925 //find the start of this contiguous selection
1926 //move left to the first node that is not selected
1927 //or the start of the non-closed path
1928 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1929 delete_cursor = curr;
1930 }
1932 //just delete at the beginning of an open path
1933 if (!delete_cursor->p.other) {
1934 sample_cursor = delete_cursor;
1935 just_delete = true;
1936 } else {
1937 sample_cursor = delete_cursor->p.other;
1938 }
1940 //calculate points for each segment
1941 int rate = 5;
1942 float period = 1.0 / rate;
1943 std::vector<NR::Point> data;
1944 if (!just_delete) {
1945 data.push_back(sample_cursor->pos);
1946 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1947 //just delete at the end of an open path
1948 if (!sp->closed && curr == sp->last) {
1949 just_delete = true;
1950 break;
1951 }
1953 //sample points on the contiguous selected segment
1954 NR::Point *bez;
1955 bez = new NR::Point [4];
1956 bez[0] = curr->pos;
1957 bez[1] = curr->n.pos;
1958 bez[2] = curr->n.other->p.pos;
1959 bez[3] = curr->n.other->pos;
1960 for (int i=1; i<rate; i++) {
1961 gdouble t = i * period;
1962 NR::Point p = bezier_pt(3, bez, t);
1963 data.push_back(p);
1964 }
1965 data.push_back(curr->n.other->pos);
1967 sample_end = curr->n.other;
1968 //break if we've come full circle or hit the end of the selection
1969 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
1970 break;
1971 }
1972 }
1973 }
1975 if (!just_delete) {
1976 //calculate the best fitting single segment and adjust the endpoints
1977 NR::Point *adata;
1978 adata = new NR::Point [data.size()];
1979 copy(data.begin(), data.end(), adata);
1981 NR::Point *bez;
1982 bez = new NR::Point [4];
1983 //would decreasing error create a better fitting approximation?
1984 gdouble error = 1.0;
1985 gint ret;
1986 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
1988 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
1989 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
1990 //the resulting nodes behave as expected.
1991 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
1992 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
1994 //adjust endpoints
1995 sample_cursor->n.pos = bez[1];
1996 sample_end->p.pos = bez[2];
1997 }
1999 //destroy this contiguous selection
2000 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2001 Inkscape::NodePath::Node *temp = delete_cursor;
2002 if (delete_cursor->n.other == delete_cursor) {
2003 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2004 delete_cursor = NULL;
2005 } else {
2006 delete_cursor = delete_cursor->n.other;
2007 }
2008 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2009 sp_nodepath_node_destroy(temp);
2010 }
2012 sp_nodepath_update_handles(nodepath);
2014 if (!g_slist_find(nodepaths, nodepath))
2015 nodepaths = g_slist_prepend (nodepaths, nodepath);
2016 }
2018 for (GSList *i = nodepaths; i; i = i->next) {
2019 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2020 // different nodepaths will give us one undo event per nodepath
2021 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2023 // if the entire nodepath is removed, delete the selected object.
2024 if (nodepath->subpaths == NULL ||
2025 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2026 //at least 2
2027 sp_nodepath_get_node_count(nodepath) < 2) {
2028 SPDocument *document = sp_desktop_document (nodepath->desktop);
2029 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2030 //delete this nodepath's object, not the entire selection! (though at this time, this
2031 //does not matter)
2032 sp_selection_delete();
2033 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2034 _("Delete nodes"));
2035 } else {
2036 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2037 sp_nodepath_update_statusbar(nodepath);
2038 }
2039 }
2041 g_slist_free (nodepaths);
2042 }
2044 /**
2045 * Delete one or more selected nodes.
2046 */
2047 void sp_node_selected_delete()
2048 {
2049 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2050 if (!nodepath) return;
2051 if (!nodepath->selected) return;
2053 /** \todo fixme: do it the right way */
2054 while (nodepath->selected) {
2055 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2056 sp_nodepath_node_destroy(node);
2057 }
2060 //clean up the nodepath (such as for trivial subpaths)
2061 sp_nodepath_cleanup(nodepath);
2063 sp_nodepath_update_handles(nodepath);
2065 // if the entire nodepath is removed, delete the selected object.
2066 if (nodepath->subpaths == NULL ||
2067 sp_nodepath_get_node_count(nodepath) < 2) {
2068 SPDocument *document = sp_desktop_document (nodepath->desktop);
2069 sp_selection_delete();
2070 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2071 _("Delete nodes"));
2072 return;
2073 }
2075 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2077 sp_nodepath_update_statusbar(nodepath);
2078 }
2080 /**
2081 * Delete one or more segments between two selected nodes.
2082 * This is the code for 'split'.
2083 */
2084 void
2085 sp_node_selected_delete_segment(void)
2086 {
2087 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2088 Inkscape::NodePath::Node *curr, *next; //Iterators
2090 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2091 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2093 if (g_list_length(nodepath->selected) != 2) {
2094 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2095 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2096 return;
2097 }
2099 //Selected nodes, not inclusive
2100 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2101 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2103 if ( ( a==b) || //same node
2104 (a->subpath != b->subpath ) || //not the same path
2105 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2106 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2107 {
2108 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2109 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2110 return;
2111 }
2113 //###########################################
2114 //# BEGIN EDITS
2115 //###########################################
2116 //##################################
2117 //# CLOSED PATH
2118 //##################################
2119 if (a->subpath->closed) {
2122 gboolean reversed = FALSE;
2124 //Since we can go in a circle, we need to find the shorter distance.
2125 // a->b or b->a
2126 start = end = NULL;
2127 int distance = 0;
2128 int minDistance = 0;
2129 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2130 if (curr==b) {
2131 //printf("a to b:%d\n", distance);
2132 start = a;//go from a to b
2133 end = b;
2134 minDistance = distance;
2135 //printf("A to B :\n");
2136 break;
2137 }
2138 distance++;
2139 }
2141 //try again, the other direction
2142 distance = 0;
2143 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2144 if (curr==a) {
2145 //printf("b to a:%d\n", distance);
2146 if (distance < minDistance) {
2147 start = b; //we go from b to a
2148 end = a;
2149 reversed = TRUE;
2150 //printf("B to A\n");
2151 }
2152 break;
2153 }
2154 distance++;
2155 }
2158 //Copy everything from 'end' to 'start' to a new subpath
2159 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2160 for (curr=end ; curr ; curr=curr->n.other) {
2161 NRPathcode code = (NRPathcode) curr->code;
2162 if (curr == end)
2163 code = NR_MOVETO;
2164 sp_nodepath_node_new(t, NULL,
2165 (Inkscape::NodePath::NodeType)curr->type, code,
2166 &curr->p.pos, &curr->pos, &curr->n.pos);
2167 if (curr == start)
2168 break;
2169 }
2170 sp_nodepath_subpath_destroy(a->subpath);
2173 }
2177 //##################################
2178 //# OPEN PATH
2179 //##################################
2180 else {
2182 //We need to get the direction of the list between A and B
2183 //Can we walk from a to b?
2184 start = end = NULL;
2185 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2186 if (curr==b) {
2187 start = a; //did it! we go from a to b
2188 end = b;
2189 //printf("A to B\n");
2190 break;
2191 }
2192 }
2193 if (!start) {//didn't work? let's try the other direction
2194 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2195 if (curr==a) {
2196 start = b; //did it! we go from b to a
2197 end = a;
2198 //printf("B to A\n");
2199 break;
2200 }
2201 }
2202 }
2203 if (!start) {
2204 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2205 _("Cannot find path between nodes."));
2206 return;
2207 }
2211 //Copy everything after 'end' to a new subpath
2212 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2213 for (curr=end ; curr ; curr=curr->n.other) {
2214 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, (NRPathcode)curr->code,
2215 &curr->p.pos, &curr->pos, &curr->n.pos);
2216 }
2218 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2219 for (curr = start->n.other ; curr ; curr=next) {
2220 next = curr->n.other;
2221 sp_nodepath_node_destroy(curr);
2222 }
2224 }
2225 //###########################################
2226 //# END EDITS
2227 //###########################################
2229 //clean up the nodepath (such as for trivial subpaths)
2230 sp_nodepath_cleanup(nodepath);
2232 sp_nodepath_update_handles(nodepath);
2234 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2236 sp_nodepath_update_statusbar(nodepath);
2237 }
2239 /**
2240 * Call sp_nodepath_set_line() for all selected segments.
2241 */
2242 void
2243 sp_node_selected_set_line_type(NRPathcode code)
2244 {
2245 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2246 if (nodepath == NULL) return;
2248 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2249 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2250 g_assert(n->selected);
2251 if (n->p.other && n->p.other->selected) {
2252 sp_nodepath_set_line_type(n, code);
2253 }
2254 }
2256 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2257 }
2259 /**
2260 * Call sp_nodepath_convert_node_type() for all selected nodes.
2261 */
2262 void
2263 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2264 {
2265 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2266 if (nodepath == NULL) return;
2268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2269 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2270 }
2272 sp_nodepath_update_repr(nodepath, _("Change node type"));
2273 }
2275 /**
2276 * Change select status of node, update its own and neighbour handles.
2277 */
2278 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2279 {
2280 node->selected = selected;
2282 if (selected) {
2283 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2284 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2285 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2286 sp_knot_update_ctrl(node->knot);
2287 } else {
2288 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2289 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2290 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2291 sp_knot_update_ctrl(node->knot);
2292 }
2294 sp_node_update_handles(node);
2295 if (node->n.other) sp_node_update_handles(node->n.other);
2296 if (node->p.other) sp_node_update_handles(node->p.other);
2297 }
2299 /**
2300 \brief Select a node
2301 \param node The node to select
2302 \param incremental If true, add to selection, otherwise deselect others
2303 \param override If true, always select this node, otherwise toggle selected status
2304 */
2305 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2306 {
2307 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2309 if (incremental) {
2310 if (override) {
2311 if (!g_list_find(nodepath->selected, node)) {
2312 nodepath->selected = g_list_prepend(nodepath->selected, node);
2313 }
2314 sp_node_set_selected(node, TRUE);
2315 } else { // toggle
2316 if (node->selected) {
2317 g_assert(g_list_find(nodepath->selected, node));
2318 nodepath->selected = g_list_remove(nodepath->selected, node);
2319 } else {
2320 g_assert(!g_list_find(nodepath->selected, node));
2321 nodepath->selected = g_list_prepend(nodepath->selected, node);
2322 }
2323 sp_node_set_selected(node, !node->selected);
2324 }
2325 } else {
2326 sp_nodepath_deselect(nodepath);
2327 nodepath->selected = g_list_prepend(nodepath->selected, node);
2328 sp_node_set_selected(node, TRUE);
2329 }
2331 sp_nodepath_update_statusbar(nodepath);
2332 }
2335 /**
2336 \brief Deselect all nodes in the nodepath
2337 */
2338 void
2339 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2340 {
2341 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2343 while (nodepath->selected) {
2344 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2345 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2346 }
2347 sp_nodepath_update_statusbar(nodepath);
2348 }
2350 /**
2351 \brief Select or invert selection of all nodes in the nodepath
2352 */
2353 void
2354 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2355 {
2356 if (!nodepath) return;
2358 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2359 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2360 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2361 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2362 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2363 }
2364 }
2365 }
2367 /**
2368 * If nothing selected, does the same as sp_nodepath_select_all();
2369 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2370 * (i.e., similar to "select all in layer", with the "selected" subpaths
2371 * being treated as "layers" in the path).
2372 */
2373 void
2374 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2375 {
2376 if (!nodepath) return;
2378 if (g_list_length (nodepath->selected) == 0) {
2379 sp_nodepath_select_all (nodepath, invert);
2380 return;
2381 }
2383 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2384 GSList *subpaths = NULL;
2386 for (GList *l = copy; l != NULL; l = l->next) {
2387 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2388 Inkscape::NodePath::SubPath *subpath = n->subpath;
2389 if (!g_slist_find (subpaths, subpath))
2390 subpaths = g_slist_prepend (subpaths, subpath);
2391 }
2393 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2394 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2395 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2396 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2397 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2398 }
2399 }
2401 g_slist_free (subpaths);
2402 g_list_free (copy);
2403 }
2405 /**
2406 * \brief Select the node after the last selected; if none is selected,
2407 * select the first within path.
2408 */
2409 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2410 {
2411 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2413 Inkscape::NodePath::Node *last = NULL;
2414 if (nodepath->selected) {
2415 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2416 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2417 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2418 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2419 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2420 if (node->selected) {
2421 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2422 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2423 if (spl->next) { // there's a next subpath
2424 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2425 last = subpath_next->first;
2426 } else if (spl->prev) { // there's a previous subpath
2427 last = NULL; // to be set later to the first node of first subpath
2428 } else {
2429 last = node->n.other;
2430 }
2431 } else {
2432 last = node->n.other;
2433 }
2434 } else {
2435 if (node->n.other) {
2436 last = node->n.other;
2437 } else {
2438 if (spl->next) { // there's a next subpath
2439 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2440 last = subpath_next->first;
2441 } else if (spl->prev) { // there's a previous subpath
2442 last = NULL; // to be set later to the first node of first subpath
2443 } else {
2444 last = (Inkscape::NodePath::Node *) subpath->first;
2445 }
2446 }
2447 }
2448 }
2449 }
2450 }
2451 sp_nodepath_deselect(nodepath);
2452 }
2454 if (last) { // there's at least one more node after selected
2455 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2456 } else { // no more nodes, select the first one in first subpath
2457 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2458 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2459 }
2460 }
2462 /**
2463 * \brief Select the node before the first selected; if none is selected,
2464 * select the last within path
2465 */
2466 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2467 {
2468 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2470 Inkscape::NodePath::Node *last = NULL;
2471 if (nodepath->selected) {
2472 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2473 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2474 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2475 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2476 if (node->selected) {
2477 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2478 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2479 if (spl->prev) { // there's a prev subpath
2480 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2481 last = subpath_prev->last;
2482 } else if (spl->next) { // there's a next subpath
2483 last = NULL; // to be set later to the last node of last subpath
2484 } else {
2485 last = node->p.other;
2486 }
2487 } else {
2488 last = node->p.other;
2489 }
2490 } else {
2491 if (node->p.other) {
2492 last = node->p.other;
2493 } else {
2494 if (spl->prev) { // there's a prev subpath
2495 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2496 last = subpath_prev->last;
2497 } else if (spl->next) { // there's a next subpath
2498 last = NULL; // to be set later to the last node of last subpath
2499 } else {
2500 last = (Inkscape::NodePath::Node *) subpath->last;
2501 }
2502 }
2503 }
2504 }
2505 }
2506 }
2507 sp_nodepath_deselect(nodepath);
2508 }
2510 if (last) { // there's at least one more node before selected
2511 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2512 } else { // no more nodes, select the last one in last subpath
2513 GList *spl = g_list_last(nodepath->subpaths);
2514 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2515 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2516 }
2517 }
2519 /**
2520 * \brief Select all nodes that are within the rectangle.
2521 */
2522 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2523 {
2524 if (!incremental) {
2525 sp_nodepath_deselect(nodepath);
2526 }
2528 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2529 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2530 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2531 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2533 if (b.contains(node->pos)) {
2534 sp_nodepath_node_select(node, TRUE, TRUE);
2535 }
2536 }
2537 }
2538 }
2541 void
2542 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2543 {
2544 g_assert (n);
2545 g_assert (nodepath);
2546 g_assert (n->subpath->nodepath == nodepath);
2548 if (g_list_length (nodepath->selected) == 0) {
2549 if (grow > 0) {
2550 sp_nodepath_node_select(n, TRUE, TRUE);
2551 }
2552 return;
2553 }
2555 if (g_list_length (nodepath->selected) == 1) {
2556 if (grow < 0) {
2557 sp_nodepath_deselect (nodepath);
2558 return;
2559 }
2560 }
2562 double n_sel_range = 0, p_sel_range = 0;
2563 Inkscape::NodePath::Node *farthest_n_node = n;
2564 Inkscape::NodePath::Node *farthest_p_node = n;
2566 // Calculate ranges
2567 {
2568 double n_range = 0, p_range = 0;
2569 bool n_going = true, p_going = true;
2570 Inkscape::NodePath::Node *n_node = n;
2571 Inkscape::NodePath::Node *p_node = n;
2572 do {
2573 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2574 if (n_node && n_going)
2575 n_node = n_node->n.other;
2576 if (n_node == NULL) {
2577 n_going = false;
2578 } else {
2579 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2580 if (n_node->selected) {
2581 n_sel_range = n_range;
2582 farthest_n_node = n_node;
2583 }
2584 if (n_node == p_node) {
2585 n_going = false;
2586 p_going = false;
2587 }
2588 }
2589 if (p_node && p_going)
2590 p_node = p_node->p.other;
2591 if (p_node == NULL) {
2592 p_going = false;
2593 } else {
2594 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2595 if (p_node->selected) {
2596 p_sel_range = p_range;
2597 farthest_p_node = p_node;
2598 }
2599 if (p_node == n_node) {
2600 n_going = false;
2601 p_going = false;
2602 }
2603 }
2604 } while (n_going || p_going);
2605 }
2607 if (grow > 0) {
2608 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2609 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2610 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2611 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2612 }
2613 } else {
2614 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2615 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2616 } else if (farthest_p_node && farthest_p_node->selected) {
2617 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2618 }
2619 }
2620 }
2622 void
2623 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2624 {
2625 g_assert (n);
2626 g_assert (nodepath);
2627 g_assert (n->subpath->nodepath == nodepath);
2629 if (g_list_length (nodepath->selected) == 0) {
2630 if (grow > 0) {
2631 sp_nodepath_node_select(n, TRUE, TRUE);
2632 }
2633 return;
2634 }
2636 if (g_list_length (nodepath->selected) == 1) {
2637 if (grow < 0) {
2638 sp_nodepath_deselect (nodepath);
2639 return;
2640 }
2641 }
2643 Inkscape::NodePath::Node *farthest_selected = NULL;
2644 double farthest_dist = 0;
2646 Inkscape::NodePath::Node *closest_unselected = NULL;
2647 double closest_dist = NR_HUGE;
2649 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2650 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2651 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2652 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2653 if (node == n)
2654 continue;
2655 if (node->selected) {
2656 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2657 farthest_dist = NR::L2(node->pos - n->pos);
2658 farthest_selected = node;
2659 }
2660 } else {
2661 if (NR::L2(node->pos - n->pos) < closest_dist) {
2662 closest_dist = NR::L2(node->pos - n->pos);
2663 closest_unselected = node;
2664 }
2665 }
2666 }
2667 }
2669 if (grow > 0) {
2670 if (closest_unselected) {
2671 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2672 }
2673 } else {
2674 if (farthest_selected) {
2675 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2676 }
2677 }
2678 }
2681 /**
2682 \brief Saves all nodes' and handles' current positions in their origin members
2683 */
2684 void
2685 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2686 {
2687 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2688 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2689 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2690 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2691 n->origin = n->pos;
2692 n->p.origin = n->p.pos;
2693 n->n.origin = n->n.pos;
2694 }
2695 }
2696 }
2698 /**
2699 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2700 */
2701 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2702 {
2703 if (!nodepath->selected) {
2704 return NULL;
2705 }
2707 GList *r = NULL;
2708 guint i = 0;
2709 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2710 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2711 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2712 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2713 i++;
2714 if (node->selected) {
2715 r = g_list_append(r, GINT_TO_POINTER(i));
2716 }
2717 }
2718 }
2719 return r;
2720 }
2722 /**
2723 \brief Restores selection by selecting nodes whose positions are in the list
2724 */
2725 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2726 {
2727 sp_nodepath_deselect(nodepath);
2729 guint i = 0;
2730 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2731 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2732 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2733 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2734 i++;
2735 if (g_list_find(r, GINT_TO_POINTER(i))) {
2736 sp_nodepath_node_select(node, TRUE, TRUE);
2737 }
2738 }
2739 }
2741 }
2743 /**
2744 \brief Adjusts handle according to node type and line code.
2745 */
2746 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2747 {
2748 double len, otherlen, linelen;
2750 g_assert(node);
2752 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2753 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2755 /** \todo fixme: */
2756 if (me->other == NULL) return;
2757 if (other->other == NULL) return;
2759 /* I have line */
2761 NRPathcode mecode, ocode;
2762 if (which_adjust == 1) {
2763 mecode = (NRPathcode)me->other->code;
2764 ocode = (NRPathcode)node->code;
2765 } else {
2766 mecode = (NRPathcode)node->code;
2767 ocode = (NRPathcode)other->other->code;
2768 }
2770 if (mecode == NR_LINETO) return;
2772 /* I am curve */
2774 if (other->other == NULL) return;
2776 /* Other has line */
2778 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2780 NR::Point delta;
2781 if (ocode == NR_LINETO) {
2782 /* other is lineto, we are either smooth or symm */
2783 Inkscape::NodePath::Node *othernode = other->other;
2784 len = NR::L2(me->pos - node->pos);
2785 delta = node->pos - othernode->pos;
2786 linelen = NR::L2(delta);
2787 if (linelen < 1e-18)
2788 return;
2789 me->pos = node->pos + (len / linelen)*delta;
2790 return;
2791 }
2793 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2795 me->pos = 2 * node->pos - other->pos;
2796 return;
2797 }
2799 /* We are smooth */
2801 len = NR::L2(me->pos - node->pos);
2802 delta = other->pos - node->pos;
2803 otherlen = NR::L2(delta);
2804 if (otherlen < 1e-18) return;
2806 me->pos = node->pos - (len / otherlen) * delta;
2807 }
2809 /**
2810 \brief Adjusts both handles according to node type and line code
2811 */
2812 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2813 {
2814 g_assert(node);
2816 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2818 /* we are either smooth or symm */
2820 if (node->p.other == NULL) return;
2822 if (node->n.other == NULL) return;
2824 if (node->code == NR_LINETO) {
2825 if (node->n.other->code == NR_LINETO) return;
2826 sp_node_adjust_handle(node, 1);
2827 return;
2828 }
2830 if (node->n.other->code == NR_LINETO) {
2831 if (node->code == NR_LINETO) return;
2832 sp_node_adjust_handle(node, -1);
2833 return;
2834 }
2836 /* both are curves */
2837 NR::Point const delta( node->n.pos - node->p.pos );
2839 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2840 node->p.pos = node->pos - delta / 2;
2841 node->n.pos = node->pos + delta / 2;
2842 return;
2843 }
2845 /* We are smooth */
2846 double plen = NR::L2(node->p.pos - node->pos);
2847 if (plen < 1e-18) return;
2848 double nlen = NR::L2(node->n.pos - node->pos);
2849 if (nlen < 1e-18) return;
2850 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2851 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2852 }
2854 /**
2855 * Node event callback.
2856 */
2857 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2858 {
2859 gboolean ret = FALSE;
2860 switch (event->type) {
2861 case GDK_ENTER_NOTIFY:
2862 active_node = n;
2863 break;
2864 case GDK_LEAVE_NOTIFY:
2865 active_node = NULL;
2866 break;
2867 case GDK_KEY_PRESS:
2868 switch (get_group0_keyval (&event->key)) {
2869 case GDK_space:
2870 if (event->key.state & GDK_BUTTON1_MASK) {
2871 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2872 stamp_repr(nodepath);
2873 ret = TRUE;
2874 }
2875 break;
2876 case GDK_Page_Up:
2877 if (event->key.state & GDK_CONTROL_MASK) {
2878 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2879 } else {
2880 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2881 }
2882 break;
2883 case GDK_Page_Down:
2884 if (event->key.state & GDK_CONTROL_MASK) {
2885 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2886 } else {
2887 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2888 }
2889 break;
2890 default:
2891 break;
2892 }
2893 break;
2894 default:
2895 break;
2896 }
2898 return ret;
2899 }
2901 /**
2902 * Handle keypress on node; directly called.
2903 */
2904 gboolean node_key(GdkEvent *event)
2905 {
2906 Inkscape::NodePath::Path *np;
2908 // there is no way to verify nodes so set active_node to nil when deleting!!
2909 if (active_node == NULL) return FALSE;
2911 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2912 gint ret = FALSE;
2913 switch (get_group0_keyval (&event->key)) {
2914 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2915 case GDK_BackSpace:
2916 np = active_node->subpath->nodepath;
2917 sp_nodepath_node_destroy(active_node);
2918 sp_nodepath_update_repr(np, _("Delete node"));
2919 active_node = NULL;
2920 ret = TRUE;
2921 break;
2922 case GDK_c:
2923 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2924 ret = TRUE;
2925 break;
2926 case GDK_s:
2927 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2928 ret = TRUE;
2929 break;
2930 case GDK_y:
2931 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2932 ret = TRUE;
2933 break;
2934 case GDK_b:
2935 sp_nodepath_node_break(active_node);
2936 ret = TRUE;
2937 break;
2938 }
2939 return ret;
2940 }
2941 return FALSE;
2942 }
2944 /**
2945 * Mouseclick on node callback.
2946 */
2947 static void node_clicked(SPKnot *knot, guint state, gpointer data)
2948 {
2949 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2951 if (state & GDK_CONTROL_MASK) {
2952 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2954 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
2955 if (n->type == Inkscape::NodePath::NODE_CUSP) {
2956 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
2957 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
2958 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
2959 } else {
2960 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
2961 }
2962 sp_nodepath_update_repr(nodepath, _("Change node type"));
2963 sp_nodepath_update_statusbar(nodepath);
2965 } else { //ctrl+alt+click: delete node
2966 GList *node_to_delete = NULL;
2967 node_to_delete = g_list_append(node_to_delete, n);
2968 sp_node_delete_preserve(node_to_delete);
2969 }
2971 } else {
2972 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2973 }
2974 }
2976 /**
2977 * Mouse grabbed node callback.
2978 */
2979 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
2980 {
2981 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2983 if (!n->selected) {
2984 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
2985 }
2987 sp_nodepath_remember_origins (n->subpath->nodepath);
2988 }
2990 /**
2991 * Mouse ungrabbed node callback.
2992 */
2993 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
2994 {
2995 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
2997 n->dragging_out = NULL;
2999 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3000 }
3002 /**
3003 * The point on a line, given by its angle, closest to the given point.
3004 * \param p A point.
3005 * \param a Angle of the line; it is assumed to go through coordinate origin.
3006 * \param closest Pointer to the point struct where the result is stored.
3007 * \todo FIXME: use dot product perhaps?
3008 */
3009 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3010 {
3011 if (a == HUGE_VAL) { // vertical
3012 *closest = NR::Point(0, (*p)[NR::Y]);
3013 } else {
3014 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3015 (*closest)[NR::Y] = a * (*closest)[NR::X];
3016 }
3017 }
3019 /**
3020 * Distance from the point to a line given by its angle.
3021 * \param p A point.
3022 * \param a Angle of the line; it is assumed to go through coordinate origin.
3023 */
3024 static double point_line_distance(NR::Point *p, double a)
3025 {
3026 NR::Point c;
3027 point_line_closest(p, a, &c);
3028 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]));
3029 }
3031 /**
3032 * Callback for node "request" signal.
3033 * \todo fixme: This goes to "moved" event? (lauris)
3034 */
3035 static gboolean
3036 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3037 {
3038 double yn, xn, yp, xp;
3039 double an, ap, na, pa;
3040 double d_an, d_ap, d_na, d_pa;
3041 gboolean collinear = FALSE;
3042 NR::Point c;
3043 NR::Point pr;
3045 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3047 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3048 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3050 NR::Point mouse = (*p);
3052 if (!n->dragging_out) {
3053 // This is the first drag-out event; find out which handle to drag out
3054 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3055 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3057 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3058 return FALSE;
3060 Inkscape::NodePath::NodeSide *opposite;
3061 if (appr_p > appr_n) { // closer to p
3062 n->dragging_out = &n->p;
3063 opposite = &n->n;
3064 n->code = NR_CURVETO;
3065 } else if (appr_p < appr_n) { // closer to n
3066 n->dragging_out = &n->n;
3067 opposite = &n->p;
3068 n->n.other->code = NR_CURVETO;
3069 } else { // p and n nodes are the same
3070 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3071 n->dragging_out = &n->p;
3072 opposite = &n->n;
3073 n->code = NR_CURVETO;
3074 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3075 n->dragging_out = &n->n;
3076 opposite = &n->p;
3077 n->n.other->code = NR_CURVETO;
3078 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3079 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);
3080 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);
3081 if (appr_other_p > appr_other_n) { // closer to other's p handle
3082 n->dragging_out = &n->n;
3083 opposite = &n->p;
3084 n->n.other->code = NR_CURVETO;
3085 } else { // closer to other's n handle
3086 n->dragging_out = &n->p;
3087 opposite = &n->n;
3088 n->code = NR_CURVETO;
3089 }
3090 }
3091 }
3093 // if there's another handle, make sure the one we drag out starts parallel to it
3094 if (opposite->pos != n->pos) {
3095 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3096 }
3098 // knots might not be created yet!
3099 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3100 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3101 }
3103 // pass this on to the handle-moved callback
3104 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3105 sp_node_update_handles(n);
3106 return TRUE;
3107 }
3109 if (state & GDK_CONTROL_MASK) { // constrained motion
3111 // calculate relative distances of handles
3112 // n handle:
3113 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3114 xn = n->n.pos[NR::X] - n->pos[NR::X];
3115 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3116 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3117 if (n->n.other) { // if there is the next point
3118 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3119 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3120 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3121 }
3122 }
3123 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3124 if (yn < 0) { xn = -xn; yn = -yn; }
3126 // p handle:
3127 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3128 xp = n->p.pos[NR::X] - n->pos[NR::X];
3129 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3130 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3131 if (n->p.other) {
3132 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3133 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3134 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3135 }
3136 }
3137 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3138 if (yp < 0) { xp = -xp; yp = -yp; }
3140 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3141 // sliding on handles, only if at least one of the handles is non-vertical
3142 // (otherwise it's the same as ctrl+drag anyway)
3144 // calculate angles of the handles
3145 if (xn == 0) {
3146 if (yn == 0) { // no handle, consider it the continuation of the other one
3147 an = 0;
3148 collinear = TRUE;
3149 }
3150 else an = 0; // vertical; set the angle to horizontal
3151 } else an = yn/xn;
3153 if (xp == 0) {
3154 if (yp == 0) { // no handle, consider it the continuation of the other one
3155 ap = an;
3156 }
3157 else ap = 0; // vertical; set the angle to horizontal
3158 } else ap = yp/xp;
3160 if (collinear) an = ap;
3162 // angles of the perpendiculars; HUGE_VAL means vertical
3163 if (an == 0) na = HUGE_VAL; else na = -1/an;
3164 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3166 // mouse point relative to the node's original pos
3167 pr = (*p) - n->origin;
3169 // distances to the four lines (two handles and two perpendiculars)
3170 d_an = point_line_distance(&pr, an);
3171 d_na = point_line_distance(&pr, na);
3172 d_ap = point_line_distance(&pr, ap);
3173 d_pa = point_line_distance(&pr, pa);
3175 // find out which line is the closest, save its closest point in c
3176 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3177 point_line_closest(&pr, an, &c);
3178 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3179 point_line_closest(&pr, ap, &c);
3180 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3181 point_line_closest(&pr, na, &c);
3182 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3183 point_line_closest(&pr, pa, &c);
3184 }
3186 // move the node to the closest point
3187 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3188 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3189 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3191 } else { // constraining to hor/vert
3193 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3194 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3195 } else { // snap to vert
3196 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3197 }
3198 }
3199 } else { // move freely
3200 if (state & GDK_MOD1_MASK) { // sculpt
3201 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3202 } else {
3203 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3204 (*p)[NR::X] - n->pos[NR::X],
3205 (*p)[NR::Y] - n->pos[NR::Y],
3206 (state & GDK_SHIFT_MASK) == 0);
3207 }
3208 }
3210 n->subpath->nodepath->desktop->scroll_to_point(p);
3212 return TRUE;
3213 }
3215 /**
3216 * Node handle clicked callback.
3217 */
3218 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3219 {
3220 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3222 if (state & GDK_CONTROL_MASK) { // "delete" handle
3223 if (n->p.knot == knot) {
3224 n->p.pos = n->pos;
3225 } else if (n->n.knot == knot) {
3226 n->n.pos = n->pos;
3227 }
3228 sp_node_update_handles(n);
3229 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3230 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3231 sp_nodepath_update_statusbar(nodepath);
3233 } else { // just select or add to selection, depending in Shift
3234 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3235 }
3236 }
3238 /**
3239 * Node handle grabbed callback.
3240 */
3241 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3242 {
3243 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3245 if (!n->selected) {
3246 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3247 }
3249 // remember the origin point of the handle
3250 if (n->p.knot == knot) {
3251 n->p.origin_radial = n->p.pos - n->pos;
3252 } else if (n->n.knot == knot) {
3253 n->n.origin_radial = n->n.pos - n->pos;
3254 } else {
3255 g_assert_not_reached();
3256 }
3258 }
3260 /**
3261 * Node handle ungrabbed callback.
3262 */
3263 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3264 {
3265 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3267 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3268 if (n->p.knot == knot) {
3269 n->p.origin_radial.a = 0;
3270 sp_knot_set_position(knot, &n->p.pos, state);
3271 } else if (n->n.knot == knot) {
3272 n->n.origin_radial.a = 0;
3273 sp_knot_set_position(knot, &n->n.pos, state);
3274 } else {
3275 g_assert_not_reached();
3276 }
3278 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3279 }
3281 /**
3282 * Node handle "request" signal callback.
3283 */
3284 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3285 {
3286 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3288 Inkscape::NodePath::NodeSide *me, *opposite;
3289 gint which;
3290 if (n->p.knot == knot) {
3291 me = &n->p;
3292 opposite = &n->n;
3293 which = -1;
3294 } else if (n->n.knot == knot) {
3295 me = &n->n;
3296 opposite = &n->p;
3297 which = 1;
3298 } else {
3299 me = opposite = NULL;
3300 which = 0;
3301 g_assert_not_reached();
3302 }
3304 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3306 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3308 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3309 /* We are smooth node adjacent with line */
3310 NR::Point const delta = *p - n->pos;
3311 NR::Coord const len = NR::L2(delta);
3312 Inkscape::NodePath::Node *othernode = opposite->other;
3313 NR::Point const ndelta = n->pos - othernode->pos;
3314 NR::Coord const linelen = NR::L2(ndelta);
3315 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3316 NR::Coord const scal = dot(delta, ndelta) / linelen;
3317 (*p) = n->pos + (scal / linelen) * ndelta;
3318 }
3319 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3320 } else {
3321 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3322 }
3324 sp_node_adjust_handle(n, -which);
3326 return FALSE;
3327 }
3329 /**
3330 * Node handle moved callback.
3331 */
3332 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3333 {
3334 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3336 Inkscape::NodePath::NodeSide *me;
3337 Inkscape::NodePath::NodeSide *other;
3338 if (n->p.knot == knot) {
3339 me = &n->p;
3340 other = &n->n;
3341 } else if (n->n.knot == knot) {
3342 me = &n->n;
3343 other = &n->p;
3344 } else {
3345 me = NULL;
3346 other = NULL;
3347 g_assert_not_reached();
3348 }
3350 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3351 Radial rme(me->pos - n->pos);
3352 Radial rother(other->pos - n->pos);
3353 Radial rnew(*p - n->pos);
3355 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3356 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3357 /* 0 interpreted as "no snapping". */
3359 // The closest PI/snaps angle, starting from zero.
3360 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3361 if (me->origin_radial.a == HUGE_VAL) {
3362 // ortho doesn't exist: original handle was zero length.
3363 rnew.a = a_snapped;
3364 } else {
3365 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3366 * its opposite and perpendiculars). */
3367 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3369 // Snap to the closest.
3370 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3371 ? a_snapped
3372 : a_ortho );
3373 }
3374 }
3376 if (state & GDK_MOD1_MASK) {
3377 // lock handle length
3378 rnew.r = me->origin_radial.r;
3379 }
3381 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3382 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3383 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3384 rother.a += rnew.a - rme.a;
3385 other->pos = NR::Point(rother) + n->pos;
3386 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3387 sp_knot_set_position(other->knot, &other->pos, 0);
3388 }
3390 me->pos = NR::Point(rnew) + n->pos;
3391 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3393 // this is what sp_knot_set_position does, but without emitting the signal:
3394 // we cannot emit a "moved" signal because we're now processing it
3395 if (me->knot->item) SP_CTRL(me->knot->item)->moveto(me->pos);
3397 knot->desktop->set_coordinate_status(me->pos);
3399 update_object(n->subpath->nodepath);
3401 /* status text */
3402 SPDesktop *desktop = n->subpath->nodepath->desktop;
3403 if (!desktop) return;
3404 SPEventContext *ec = desktop->event_context;
3405 if (!ec) return;
3406 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3407 if (!mc) return;
3409 double degrees = 180 / M_PI * rnew.a;
3410 if (degrees > 180) degrees -= 360;
3411 if (degrees < -180) degrees += 360;
3412 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3413 degrees = angle_to_compass (degrees);
3415 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3417 mc->setF(Inkscape::NORMAL_MESSAGE,
3418 _("<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);
3420 g_string_free(length, TRUE);
3421 }
3423 /**
3424 * Node handle event callback.
3425 */
3426 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3427 {
3428 gboolean ret = FALSE;
3429 switch (event->type) {
3430 case GDK_KEY_PRESS:
3431 switch (get_group0_keyval (&event->key)) {
3432 case GDK_space:
3433 if (event->key.state & GDK_BUTTON1_MASK) {
3434 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3435 stamp_repr(nodepath);
3436 ret = TRUE;
3437 }
3438 break;
3439 default:
3440 break;
3441 }
3442 break;
3443 default:
3444 break;
3445 }
3447 return ret;
3448 }
3450 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3451 Radial &rme, Radial &rother, gboolean const both)
3452 {
3453 rme.a += angle;
3454 if ( both
3455 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3456 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3457 {
3458 rother.a += angle;
3459 }
3460 }
3462 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3463 Radial &rme, Radial &rother, gboolean const both)
3464 {
3465 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3467 gdouble r;
3468 if ( both
3469 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3470 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3471 {
3472 r = MAX(rme.r, rother.r);
3473 } else {
3474 r = rme.r;
3475 }
3477 gdouble const weird_angle = atan2(norm_angle, r);
3478 /* Bulia says norm_angle is just the visible distance that the
3479 * object's end must travel on the screen. Left as 'angle' for want of
3480 * a better name.*/
3482 rme.a += weird_angle;
3483 if ( both
3484 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3485 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3486 {
3487 rother.a += weird_angle;
3488 }
3489 }
3491 /**
3492 * Rotate one node.
3493 */
3494 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3495 {
3496 Inkscape::NodePath::NodeSide *me, *other;
3497 bool both = false;
3499 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3500 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3502 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3503 me = &(n->p);
3504 other = &(n->n);
3505 } else if (!n->p.other) {
3506 me = &(n->n);
3507 other = &(n->p);
3508 } else {
3509 if (which > 0) { // right handle
3510 if (xn > xp) {
3511 me = &(n->n);
3512 other = &(n->p);
3513 } else {
3514 me = &(n->p);
3515 other = &(n->n);
3516 }
3517 } else if (which < 0){ // left handle
3518 if (xn <= xp) {
3519 me = &(n->n);
3520 other = &(n->p);
3521 } else {
3522 me = &(n->p);
3523 other = &(n->n);
3524 }
3525 } else { // both handles
3526 me = &(n->n);
3527 other = &(n->p);
3528 both = true;
3529 }
3530 }
3532 Radial rme(me->pos - n->pos);
3533 Radial rother(other->pos - n->pos);
3535 if (screen) {
3536 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3537 } else {
3538 node_rotate_one_internal (*n, angle, rme, rother, both);
3539 }
3541 me->pos = n->pos + NR::Point(rme);
3543 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3544 other->pos = n->pos + NR::Point(rother);
3545 }
3547 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3548 // so here we just move all the knots without emitting move signals, for speed
3549 sp_node_update_handles(n, false);
3550 }
3552 /**
3553 * Rotate selected nodes.
3554 */
3555 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3556 {
3557 if (!nodepath || !nodepath->selected) return;
3559 if (g_list_length(nodepath->selected) == 1) {
3560 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3561 node_rotate_one (n, angle, which, screen);
3562 } else {
3563 // rotate as an object:
3565 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3566 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3567 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3568 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3569 box.expandTo (n->pos); // contain all selected nodes
3570 }
3572 gdouble rot;
3573 if (screen) {
3574 gdouble const zoom = nodepath->desktop->current_zoom();
3575 gdouble const zmove = angle / zoom;
3576 gdouble const r = NR::L2(box.max() - box.midpoint());
3577 rot = atan2(zmove, r);
3578 } else {
3579 rot = angle;
3580 }
3582 NR::Matrix t =
3583 NR::Matrix (NR::translate(-box.midpoint())) *
3584 NR::Matrix (NR::rotate(rot)) *
3585 NR::Matrix (NR::translate(box.midpoint()));
3587 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3588 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3589 n->pos *= t;
3590 n->n.pos *= t;
3591 n->p.pos *= t;
3592 sp_node_update_handles(n, false);
3593 }
3594 }
3596 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3597 }
3599 /**
3600 * Scale one node.
3601 */
3602 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3603 {
3604 bool both = false;
3605 Inkscape::NodePath::NodeSide *me, *other;
3607 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3608 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3610 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3611 me = &(n->p);
3612 other = &(n->n);
3613 n->code = NR_CURVETO;
3614 } else if (!n->p.other) {
3615 me = &(n->n);
3616 other = &(n->p);
3617 if (n->n.other)
3618 n->n.other->code = NR_CURVETO;
3619 } else {
3620 if (which > 0) { // right handle
3621 if (xn > xp) {
3622 me = &(n->n);
3623 other = &(n->p);
3624 if (n->n.other)
3625 n->n.other->code = NR_CURVETO;
3626 } else {
3627 me = &(n->p);
3628 other = &(n->n);
3629 n->code = NR_CURVETO;
3630 }
3631 } else if (which < 0){ // left handle
3632 if (xn <= xp) {
3633 me = &(n->n);
3634 other = &(n->p);
3635 if (n->n.other)
3636 n->n.other->code = NR_CURVETO;
3637 } else {
3638 me = &(n->p);
3639 other = &(n->n);
3640 n->code = NR_CURVETO;
3641 }
3642 } else { // both handles
3643 me = &(n->n);
3644 other = &(n->p);
3645 both = true;
3646 n->code = NR_CURVETO;
3647 if (n->n.other)
3648 n->n.other->code = NR_CURVETO;
3649 }
3650 }
3652 Radial rme(me->pos - n->pos);
3653 Radial rother(other->pos - n->pos);
3655 rme.r += grow;
3656 if (rme.r < 0) rme.r = 0;
3657 if (rme.a == HUGE_VAL) {
3658 if (me->other) { // if direction is unknown, initialize it towards the next node
3659 Radial rme_next(me->other->pos - n->pos);
3660 rme.a = rme_next.a;
3661 } else { // if there's no next, initialize to 0
3662 rme.a = 0;
3663 }
3664 }
3665 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3666 rother.r += grow;
3667 if (rother.r < 0) rother.r = 0;
3668 if (rother.a == HUGE_VAL) {
3669 rother.a = rme.a + M_PI;
3670 }
3671 }
3673 me->pos = n->pos + NR::Point(rme);
3675 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3676 other->pos = n->pos + NR::Point(rother);
3677 }
3679 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3680 // so here we just move all the knots without emitting move signals, for speed
3681 sp_node_update_handles(n, false);
3682 }
3684 /**
3685 * Scale selected nodes.
3686 */
3687 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3688 {
3689 if (!nodepath || !nodepath->selected) return;
3691 if (g_list_length(nodepath->selected) == 1) {
3692 // scale handles of the single selected node
3693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3694 node_scale_one (n, grow, which);
3695 } else {
3696 // scale nodes as an "object":
3698 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3699 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3700 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3701 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3702 box.expandTo (n->pos); // contain all selected nodes
3703 }
3705 double scale = (box.maxExtent() + grow)/box.maxExtent();
3707 NR::Matrix t =
3708 NR::Matrix (NR::translate(-box.midpoint())) *
3709 NR::Matrix (NR::scale(scale, scale)) *
3710 NR::Matrix (NR::translate(box.midpoint()));
3712 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3713 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3714 n->pos *= t;
3715 n->n.pos *= t;
3716 n->p.pos *= t;
3717 sp_node_update_handles(n, false);
3718 }
3719 }
3721 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3722 }
3724 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3725 {
3726 if (!nodepath) return;
3727 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3728 }
3730 /**
3731 * Flip selected nodes horizontally/vertically.
3732 */
3733 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3734 {
3735 if (!nodepath || !nodepath->selected) return;
3737 if (g_list_length(nodepath->selected) == 1) {
3738 // flip handles of the single selected node
3739 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3740 double temp = n->p.pos[axis];
3741 n->p.pos[axis] = n->n.pos[axis];
3742 n->n.pos[axis] = temp;
3743 sp_node_update_handles(n, false);
3744 } else {
3745 // scale nodes as an "object":
3747 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3748 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3749 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3750 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3751 box.expandTo (n->pos); // contain all selected nodes
3752 }
3754 NR::Matrix t =
3755 NR::Matrix (NR::translate(-box.midpoint())) *
3756 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3757 NR::Matrix (NR::translate(box.midpoint()));
3759 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3760 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3761 n->pos *= t;
3762 n->n.pos *= t;
3763 n->p.pos *= t;
3764 sp_node_update_handles(n, false);
3765 }
3766 }
3768 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3769 }
3771 //-----------------------------------------------
3772 /**
3773 * Return new subpath under given nodepath.
3774 */
3775 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3776 {
3777 g_assert(nodepath);
3778 g_assert(nodepath->desktop);
3780 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3782 s->nodepath = nodepath;
3783 s->closed = FALSE;
3784 s->nodes = NULL;
3785 s->first = NULL;
3786 s->last = NULL;
3788 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3789 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3790 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3792 return s;
3793 }
3795 /**
3796 * Destroy nodes in subpath, then subpath itself.
3797 */
3798 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3799 {
3800 g_assert(subpath);
3801 g_assert(subpath->nodepath);
3802 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3804 while (subpath->nodes) {
3805 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3806 }
3808 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3810 g_free(subpath);
3811 }
3813 /**
3814 * Link head to tail in subpath.
3815 */
3816 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3817 {
3818 g_assert(!sp->closed);
3819 g_assert(sp->last != sp->first);
3820 g_assert(sp->first->code == NR_MOVETO);
3822 sp->closed = TRUE;
3824 //Link the head to the tail
3825 sp->first->p.other = sp->last;
3826 sp->last->n.other = sp->first;
3827 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3828 sp->first = sp->last;
3830 //Remove the extra end node
3831 sp_nodepath_node_destroy(sp->last->n.other);
3832 }
3834 /**
3835 * Open closed (loopy) subpath at node.
3836 */
3837 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3838 {
3839 g_assert(sp->closed);
3840 g_assert(n->subpath == sp);
3841 g_assert(sp->first == sp->last);
3843 /* We create new startpoint, current node will become last one */
3845 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3846 &n->pos, &n->pos, &n->n.pos);
3849 sp->closed = FALSE;
3851 //Unlink to make a head and tail
3852 sp->first = new_path;
3853 sp->last = n;
3854 n->n.other = NULL;
3855 new_path->p.other = NULL;
3856 }
3858 /**
3859 * Returns area in triangle given by points; may be negative.
3860 */
3861 inline double
3862 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3863 {
3864 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]);
3865 }
3867 /**
3868 * Return new node in subpath with given properties.
3869 * \param pos Position of node.
3870 * \param ppos Handle position in previous direction
3871 * \param npos Handle position in previous direction
3872 */
3873 Inkscape::NodePath::Node *
3874 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)
3875 {
3876 g_assert(sp);
3877 g_assert(sp->nodepath);
3878 g_assert(sp->nodepath->desktop);
3880 if (nodechunk == NULL)
3881 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3883 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3885 n->subpath = sp;
3887 if (type != Inkscape::NodePath::NODE_NONE) {
3888 // use the type from sodipodi:nodetypes
3889 n->type = type;
3890 } else {
3891 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3892 // points are (almost) collinear
3893 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3894 // endnode, or a node with a retracted handle
3895 n->type = Inkscape::NodePath::NODE_CUSP;
3896 } else {
3897 n->type = Inkscape::NodePath::NODE_SMOOTH;
3898 }
3899 } else {
3900 n->type = Inkscape::NodePath::NODE_CUSP;
3901 }
3902 }
3904 n->code = code;
3905 n->selected = FALSE;
3906 n->pos = *pos;
3907 n->p.pos = *ppos;
3908 n->n.pos = *npos;
3910 n->dragging_out = NULL;
3912 Inkscape::NodePath::Node *prev;
3913 if (next) {
3914 //g_assert(g_list_find(sp->nodes, next));
3915 prev = next->p.other;
3916 } else {
3917 prev = sp->last;
3918 }
3920 if (prev)
3921 prev->n.other = n;
3922 else
3923 sp->first = n;
3925 if (next)
3926 next->p.other = n;
3927 else
3928 sp->last = n;
3930 n->p.other = prev;
3931 n->n.other = next;
3933 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"));
3934 sp_knot_set_position(n->knot, pos, 0);
3936 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
3937 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
3938 n->knot->setAnchor (GTK_ANCHOR_CENTER);
3939 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
3940 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
3941 sp_knot_update_ctrl(n->knot);
3943 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
3944 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
3945 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
3946 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
3947 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
3948 sp_knot_show(n->knot);
3950 // We only create handle knots and lines on demand
3951 n->p.knot = NULL;
3952 n->p.line = NULL;
3953 n->n.knot = NULL;
3954 n->n.line = NULL;
3956 sp->nodes = g_list_prepend(sp->nodes, n);
3958 return n;
3959 }
3961 /**
3962 * Destroy node and its knots, link neighbors in subpath.
3963 */
3964 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
3965 {
3966 g_assert(node);
3967 g_assert(node->subpath);
3968 g_assert(SP_IS_KNOT(node->knot));
3970 Inkscape::NodePath::SubPath *sp = node->subpath;
3972 if (node->selected) { // first, deselect
3973 g_assert(g_list_find(node->subpath->nodepath->selected, node));
3974 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
3975 }
3977 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
3979 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
3980 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
3981 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
3982 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
3983 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
3984 g_object_unref(G_OBJECT(node->knot));
3986 if (node->p.knot) {
3987 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
3988 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
3989 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
3990 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
3991 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
3992 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
3993 g_object_unref(G_OBJECT(node->p.knot));
3994 }
3996 if (node->n.knot) {
3997 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
3998 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
3999 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4000 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4001 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4002 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4003 g_object_unref(G_OBJECT(node->n.knot));
4004 }
4006 if (node->p.line)
4007 gtk_object_destroy(GTK_OBJECT(node->p.line));
4008 if (node->n.line)
4009 gtk_object_destroy(GTK_OBJECT(node->n.line));
4011 if (sp->nodes) { // there are others nodes on the subpath
4012 if (sp->closed) {
4013 if (sp->first == node) {
4014 g_assert(sp->last == node);
4015 sp->first = node->n.other;
4016 sp->last = sp->first;
4017 }
4018 node->p.other->n.other = node->n.other;
4019 node->n.other->p.other = node->p.other;
4020 } else {
4021 if (sp->first == node) {
4022 sp->first = node->n.other;
4023 sp->first->code = NR_MOVETO;
4024 }
4025 if (sp->last == node) sp->last = node->p.other;
4026 if (node->p.other) node->p.other->n.other = node->n.other;
4027 if (node->n.other) node->n.other->p.other = node->p.other;
4028 }
4029 } else { // this was the last node on subpath
4030 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4031 }
4033 g_mem_chunk_free(nodechunk, node);
4034 }
4036 /**
4037 * Returns one of the node's two sides.
4038 * \param which Indicates which side.
4039 * \return Pointer to previous node side if which==-1, next if which==1.
4040 */
4041 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4042 {
4043 g_assert(node);
4045 switch (which) {
4046 case -1:
4047 return &node->p;
4048 case 1:
4049 return &node->n;
4050 default:
4051 break;
4052 }
4054 g_assert_not_reached();
4056 return NULL;
4057 }
4059 /**
4060 * Return the other side of the node, given one of its sides.
4061 */
4062 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4063 {
4064 g_assert(node);
4066 if (me == &node->p) return &node->n;
4067 if (me == &node->n) return &node->p;
4069 g_assert_not_reached();
4071 return NULL;
4072 }
4074 /**
4075 * Return NRPathcode on the given side of the node.
4076 */
4077 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4078 {
4079 g_assert(node);
4081 if (me == &node->p) {
4082 if (node->p.other) return (NRPathcode)node->code;
4083 return NR_MOVETO;
4084 }
4086 if (me == &node->n) {
4087 if (node->n.other) return (NRPathcode)node->n.other->code;
4088 return NR_MOVETO;
4089 }
4091 g_assert_not_reached();
4093 return NR_END;
4094 }
4096 /**
4097 * Call sp_nodepath_line_add_node() at t on the segment denoted by piece
4098 */
4099 Inkscape::NodePath::Node *
4100 sp_nodepath_get_node_by_index(int index)
4101 {
4102 Inkscape::NodePath::Node *e = NULL;
4104 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4105 if (!nodepath) {
4106 return e;
4107 }
4109 //find segment
4110 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4112 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4113 int n = g_list_length(sp->nodes);
4114 if (sp->closed) {
4115 n++;
4116 }
4118 //if the piece belongs to this subpath grab it
4119 //otherwise move onto the next subpath
4120 if (index < n) {
4121 e = sp->first;
4122 for (int i = 0; i < index; ++i) {
4123 e = e->n.other;
4124 }
4125 break;
4126 } else {
4127 if (sp->closed) {
4128 index -= (n+1);
4129 } else {
4130 index -= n;
4131 }
4132 }
4133 }
4135 return e;
4136 }
4138 /**
4139 * Returns plain text meaning of node type.
4140 */
4141 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4142 {
4143 unsigned retracted = 0;
4144 bool endnode = false;
4146 for (int which = -1; which <= 1; which += 2) {
4147 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4148 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4149 retracted ++;
4150 if (!side->other)
4151 endnode = true;
4152 }
4154 if (retracted == 0) {
4155 if (endnode) {
4156 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4157 return _("end node");
4158 } else {
4159 switch (node->type) {
4160 case Inkscape::NodePath::NODE_CUSP:
4161 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4162 return _("cusp");
4163 case Inkscape::NodePath::NODE_SMOOTH:
4164 // TRANSLATORS: "smooth" is an adjective here
4165 return _("smooth");
4166 case Inkscape::NodePath::NODE_SYMM:
4167 return _("symmetric");
4168 }
4169 }
4170 } else if (retracted == 1) {
4171 if (endnode) {
4172 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4173 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4174 } else {
4175 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4176 }
4177 } else {
4178 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4179 }
4181 return NULL;
4182 }
4184 /**
4185 * Handles content of statusbar as long as node tool is active.
4186 */
4187 void
4188 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)
4189 {
4190 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");
4191 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4193 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4194 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4195 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4196 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4198 SPDesktop *desktop = NULL;
4199 if (nodepath) {
4200 desktop = nodepath->desktop;
4201 } else {
4202 desktop = SP_ACTIVE_DESKTOP;
4203 }
4205 SPEventContext *ec = desktop->event_context;
4206 if (!ec) return;
4207 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4208 if (!mc) return;
4210 if (selected_nodes == 0) {
4211 Inkscape::Selection *sel = desktop->selection;
4212 if (!sel || sel->isEmpty()) {
4213 mc->setF(Inkscape::NORMAL_MESSAGE,
4214 _("Select a single object to edit its nodes or handles."));
4215 } else {
4216 if (nodepath) {
4217 mc->setF(Inkscape::NORMAL_MESSAGE,
4218 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.",
4219 "<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.",
4220 total_nodes),
4221 total_nodes);
4222 } else {
4223 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4224 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4225 } else {
4226 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4227 }
4228 }
4229 }
4230 } else if (nodepath && selected_nodes == 1) {
4231 mc->setF(Inkscape::NORMAL_MESSAGE,
4232 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4233 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4234 total_nodes),
4235 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4236 } else {
4237 if (selected_subpaths > 1) {
4238 mc->setF(Inkscape::NORMAL_MESSAGE,
4239 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4240 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4241 total_nodes),
4242 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4243 } else {
4244 mc->setF(Inkscape::NORMAL_MESSAGE,
4245 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4246 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4247 total_nodes),
4248 selected_nodes, total_nodes, when_selected);
4249 }
4250 }
4251 }
4254 /*
4255 Local Variables:
4256 mode:c++
4257 c-file-style:"stroustrup"
4258 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4259 indent-tabs-mode:nil
4260 fill-column:99
4261 End:
4262 */
4263 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :