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