1b67944d8c64b00980ed5e391749a759e6399740
1 #define __SP_NODEPATH_C__
3 /** \file
4 * Path handler in node edit mode
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL
11 */
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
17 #include <gdk/gdkkeysyms.h>
18 #include "display/curve.h"
19 #include "display/sp-ctrlline.h"
20 #include "display/sodipodi-ctrl.h"
21 #include <glibmm/i18n.h>
22 #include "libnr/n-art-bpath.h"
23 #include "helper/units.h"
24 #include "knot.h"
25 #include "inkscape.h"
26 #include "document.h"
27 #include "sp-namedview.h"
28 #include "desktop.h"
29 #include "desktop-handles.h"
30 #include "snap.h"
31 #include "message-stack.h"
32 #include "message-context.h"
33 #include "node-context.h"
34 #include "shape-editor.h"
35 #include "selection-chemistry.h"
36 #include "selection.h"
37 #include "xml/repr.h"
38 #include "prefs-utils.h"
39 #include "sp-metrics.h"
40 #include "sp-path.h"
41 #include "libnr/nr-matrix-ops.h"
42 #include "splivarot.h"
43 #include "svg/svg.h"
44 #include "verbs.h"
45 #include "display/bezier-utils.h"
46 #include <vector>
47 #include <algorithm>
49 class NR::Matrix;
51 /// \todo
52 /// evil evil evil. FIXME: conflict of two different Path classes!
53 /// There is a conflict in the namespace between two classes named Path.
54 /// #include "sp-flowtext.h"
55 /// #include "sp-flowregion.h"
57 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
58 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
59 GType sp_flowregion_get_type (void);
60 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
61 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
62 GType sp_flowtext_get_type (void);
63 // end evil workaround
65 #include "helper/stlport.h"
68 /// \todo fixme: Implement these via preferences */
70 #define NODE_FILL 0xbfbfbf00
71 #define NODE_STROKE 0x000000ff
72 #define NODE_FILL_HI 0xff000000
73 #define NODE_STROKE_HI 0x000000ff
74 #define NODE_FILL_SEL 0x0000ffff
75 #define NODE_STROKE_SEL 0x000000ff
76 #define NODE_FILL_SEL_HI 0xff000000
77 #define NODE_STROKE_SEL_HI 0x000000ff
78 #define KNOT_FILL 0xffffffff
79 #define KNOT_STROKE 0x000000ff
80 #define KNOT_FILL_HI 0xff000000
81 #define KNOT_STROKE_HI 0x000000ff
83 static GMemChunk *nodechunk = NULL;
85 /* Creation from object */
87 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t);
88 static gchar *parse_nodetypes(gchar const *types, gint length);
90 /* Object updating */
92 static void stamp_repr(Inkscape::NodePath::Path *np);
93 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
94 static gchar *create_typestr(Inkscape::NodePath::Path *np);
96 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
98 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
100 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
102 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
104 /* Adjust handle placement, if the node or the other handle is moved */
105 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
106 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
108 /* Node event callbacks */
109 static void node_clicked(SPKnot *knot, guint state, gpointer data);
110 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
111 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
112 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
114 /* Handle event callbacks */
115 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
116 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
117 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
118 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
119 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
120 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
122 /* Constructors and destructors */
124 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
125 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
126 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
127 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
128 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
129 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
130 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
132 /* Helpers */
134 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
135 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
136 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
138 // active_node indicates mouseover node
139 static Inkscape::NodePath::Node *active_node = NULL;
141 /**
142 * \brief Creates new nodepath from item
143 */
144 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
145 {
146 Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
148 /** \todo
149 * FIXME: remove this. We don't want to edit paths inside flowtext.
150 * Instead we will build our flowtext with cloned paths, so that the
151 * real paths are outside the flowtext and thus editable as usual.
152 */
153 if (SP_IS_FLOWTEXT(item)) {
154 for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
155 if SP_IS_FLOWREGION(child) {
156 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
157 if (grandchild && SP_IS_PATH(grandchild)) {
158 item = SP_ITEM(grandchild);
159 break;
160 }
161 }
162 }
163 }
165 if (!SP_IS_PATH(item))
166 return NULL;
167 SPPath *path = SP_PATH(item);
168 SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
169 if (curve == NULL)
170 return NULL;
172 NArtBpath *bpath = sp_curve_first_bpath(curve);
173 gint length = curve->end;
174 if (length == 0)
175 return NULL; // prevent crash for one-node paths
177 gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
178 gchar *typestr = parse_nodetypes(nodetypes, length);
180 //Create new nodepath
181 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
182 if (!np)
183 return NULL;
185 // Set defaults
186 np->desktop = desktop;
187 np->path = path;
188 np->subpaths = NULL;
189 np->selected = NULL;
190 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
191 np->livarot_path = NULL;
192 np->local_change = 0;
193 np->show_handles = show_handles;
195 // we need to update item's transform from the repr here,
196 // because they may be out of sync when we respond
197 // to a change in repr by regenerating nodepath --bb
198 sp_object_read_attr(SP_OBJECT(item), "transform");
200 np->i2d = sp_item_i2d_affine(SP_ITEM(path));
201 np->d2i = np->i2d.inverse();
202 np->repr = repr;
204 // create the subpath(s) from the bpath
205 NArtBpath *b = bpath;
206 while (b->code != NR_END) {
207 b = subpath_from_bpath(np, b, typestr + (b - bpath));
208 }
210 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
211 np->subpaths = g_list_reverse(np->subpaths);
213 g_free(typestr);
214 sp_curve_unref(curve);
216 // create the livarot representation from the same item
217 sp_nodepath_ensure_livarot_path(np);
219 return np;
220 }
222 /**
223 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
224 */
225 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
227 if (!np) //soft fail, like delete
228 return;
230 while (np->subpaths) {
231 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
232 }
234 //Inform the ShapeEditor that made me, if any, that I am gone.
235 if (np->shape_editor)
236 np->shape_editor->nodepath_destroyed();
238 g_assert(!np->selected);
240 if (np->livarot_path) {
241 delete np->livarot_path;
242 np->livarot_path = NULL;
243 }
245 np->desktop = NULL;
247 g_free(np);
248 }
251 void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
252 {
253 if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
254 np->livarot_path = Path_for_item (np->path, true, true);
255 if (np->livarot_path)
256 np->livarot_path->ConvertWithBackData(0.01);
257 }
258 }
261 /**
262 * Return the node count of a given NodeSubPath.
263 */
264 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
265 {
266 if (!subpath)
267 return 0;
268 gint nodeCount = g_list_length(subpath->nodes);
269 return nodeCount;
270 }
272 /**
273 * Return the node count of a given NodePath.
274 */
275 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
276 {
277 if (!np)
278 return 0;
279 gint nodeCount = 0;
280 for (GList *item = np->subpaths ; item ; item=item->next) {
281 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
282 nodeCount += g_list_length(subpath->nodes);
283 }
284 return nodeCount;
285 }
287 /**
288 * Return the subpath count of a given NodePath.
289 */
290 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
291 {
292 if (!np)
293 return 0;
294 return g_list_length (np->subpaths);
295 }
297 /**
298 * Return the selected node count of a given NodePath.
299 */
300 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
301 {
302 if (!np)
303 return 0;
304 return g_list_length (np->selected);
305 }
307 /**
308 * Return the number of subpaths where nodes are selected in a given NodePath.
309 */
310 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
311 {
312 if (!np)
313 return 0;
314 if (!np->selected)
315 return 0;
316 if (!np->selected->next)
317 return 1;
318 gint count = 0;
319 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
320 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
321 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
322 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
323 if (node->selected) {
324 count ++;
325 break;
326 }
327 }
328 }
329 return count;
330 }
332 /**
333 * Clean up a nodepath after editing.
334 *
335 * Currently we are deleting trivial subpaths.
336 */
337 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
338 {
339 GList *badSubPaths = NULL;
341 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
342 for (GList *l = nodepath->subpaths; l ; l=l->next) {
343 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
344 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
345 badSubPaths = g_list_append(badSubPaths, sp);
346 }
348 //Delete them. This second step is because sp_nodepath_subpath_destroy()
349 //also removes the subpath from nodepath->subpaths
350 for (GList *l = badSubPaths; l ; l=l->next) {
351 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
352 sp_nodepath_subpath_destroy(sp);
353 }
355 g_list_free(badSubPaths);
356 }
358 /**
359 * Create new nodepath from b, make it subpath of np.
360 * \param t The node type.
361 * \todo Fixme: t should be a proper type, rather than gchar
362 */
363 static NArtBpath *subpath_from_bpath(Inkscape::NodePath::Path *np, NArtBpath *b, gchar const *t)
364 {
365 NR::Point ppos, pos, npos;
367 g_assert((b->code == NR_MOVETO) || (b->code == NR_MOVETO_OPEN));
369 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
370 bool const closed = (b->code == NR_MOVETO);
372 pos = NR::Point(b->x3, b->y3) * np->i2d;
373 if (b[1].code == NR_CURVETO) {
374 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
375 } else {
376 npos = pos;
377 }
378 Inkscape::NodePath::Node *n;
379 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType) *t, NR_MOVETO, &pos, &pos, &npos);
380 g_assert(sp->first == n);
381 g_assert(sp->last == n);
383 b++;
384 t++;
385 while ((b->code == NR_CURVETO) || (b->code == NR_LINETO)) {
386 pos = NR::Point(b->x3, b->y3) * np->i2d;
387 if (b->code == NR_CURVETO) {
388 ppos = NR::Point(b->x2, b->y2) * np->i2d;
389 } else {
390 ppos = pos;
391 }
392 if (b[1].code == NR_CURVETO) {
393 npos = NR::Point(b[1].x1, b[1].y1) * np->i2d;
394 } else {
395 npos = pos;
396 }
397 n = sp_nodepath_node_new(sp, NULL, (Inkscape::NodePath::NodeType)*t, b->code, &ppos, &pos, &npos);
398 b++;
399 t++;
400 }
402 if (closed) sp_nodepath_subpath_close(sp);
404 return b;
405 }
407 /**
408 * Convert from sodipodi:nodetypes to new style type string.
409 */
410 static gchar *parse_nodetypes(gchar const *types, gint length)
411 {
412 g_assert(length > 0);
414 gchar *typestr = g_new(gchar, length + 1);
416 gint pos = 0;
418 if (types) {
419 for (gint i = 0; types[i] && ( i < length ); i++) {
420 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
421 if (types[i] != '\0') {
422 switch (types[i]) {
423 case 's':
424 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
425 break;
426 case 'z':
427 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
428 break;
429 case 'c':
430 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
431 break;
432 default:
433 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
434 break;
435 }
436 }
437 }
438 }
440 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
442 return typestr;
443 }
445 /**
446 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
447 * updated but repr is not (for speed). Used during curve and node drag.
448 */
449 static void update_object(Inkscape::NodePath::Path *np)
450 {
451 g_assert(np);
453 SPCurve *curve = create_curve(np);
455 sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
457 sp_curve_unref(curve);
458 }
460 /**
461 * Update XML path node with data from path object.
462 */
463 static void update_repr_internal(Inkscape::NodePath::Path *np)
464 {
465 g_assert(np);
467 Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
469 SPCurve *curve = create_curve(np);
470 gchar *typestr = create_typestr(np);
471 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
473 if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
474 np->local_change++;
475 repr->setAttribute("d", svgpath);
476 }
478 if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
479 np->local_change++;
480 repr->setAttribute("sodipodi:nodetypes", typestr);
481 }
483 g_free(svgpath);
484 g_free(typestr);
485 sp_curve_unref(curve);
486 }
488 /**
489 * Update XML path node with data from path object, commit changes forever.
490 */
491 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
492 {
493 //fixme: np can be NULL, so check before proceeding
494 g_return_if_fail(np != NULL);
496 if (np->livarot_path) {
497 delete np->livarot_path;
498 np->livarot_path = NULL;
499 }
501 update_repr_internal(np);
502 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
504 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
505 annotation);
506 }
508 /**
509 * Update XML path node with data from path object, commit changes with undo.
510 */
511 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
512 {
513 if (np->livarot_path) {
514 delete np->livarot_path;
515 np->livarot_path = NULL;
516 }
518 update_repr_internal(np);
519 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
520 annotation);
521 }
523 /**
524 * Make duplicate of path, replace corresponding XML node in tree, commit.
525 */
526 static void stamp_repr(Inkscape::NodePath::Path *np)
527 {
528 g_assert(np);
530 Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
531 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
533 // remember the position of the item
534 gint pos = old_repr->position();
535 // remember parent
536 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
538 SPCurve *curve = create_curve(np);
539 gchar *typestr = create_typestr(np);
541 gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
543 new_repr->setAttribute("d", svgpath);
544 new_repr->setAttribute("sodipodi:nodetypes", typestr);
546 // add the new repr to the parent
547 parent->appendChild(new_repr);
548 // move to the saved position
549 new_repr->setPosition(pos > 0 ? pos : 0);
551 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
552 _("Stamp"));
554 Inkscape::GC::release(new_repr);
555 g_free(svgpath);
556 g_free(typestr);
557 sp_curve_unref(curve);
558 }
560 /**
561 * Create curve from path.
562 */
563 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
564 {
565 SPCurve *curve = sp_curve_new();
567 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
568 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
569 sp_curve_moveto(curve,
570 sp->first->pos * np->d2i);
571 Inkscape::NodePath::Node *n = sp->first->n.other;
572 while (n) {
573 NR::Point const end_pt = n->pos * np->d2i;
574 switch (n->code) {
575 case NR_LINETO:
576 sp_curve_lineto(curve, end_pt);
577 break;
578 case NR_CURVETO:
579 sp_curve_curveto(curve,
580 n->p.other->n.pos * np->d2i,
581 n->p.pos * np->d2i,
582 end_pt);
583 break;
584 default:
585 g_assert_not_reached();
586 break;
587 }
588 if (n != sp->last) {
589 n = n->n.other;
590 } else {
591 n = NULL;
592 }
593 }
594 if (sp->closed) {
595 sp_curve_closepath(curve);
596 }
597 }
599 return curve;
600 }
602 /**
603 * Convert path type string to sodipodi:nodetypes style.
604 */
605 static gchar *create_typestr(Inkscape::NodePath::Path *np)
606 {
607 gchar *typestr = g_new(gchar, 32);
608 gint len = 32;
609 gint pos = 0;
611 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
612 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
614 if (pos >= len) {
615 typestr = g_renew(gchar, typestr, len + 32);
616 len += 32;
617 }
619 typestr[pos++] = 'c';
621 Inkscape::NodePath::Node *n;
622 n = sp->first->n.other;
623 while (n) {
624 gchar code;
626 switch (n->type) {
627 case Inkscape::NodePath::NODE_CUSP:
628 code = 'c';
629 break;
630 case Inkscape::NodePath::NODE_SMOOTH:
631 code = 's';
632 break;
633 case Inkscape::NodePath::NODE_SYMM:
634 code = 'z';
635 break;
636 default:
637 g_assert_not_reached();
638 code = '\0';
639 break;
640 }
642 if (pos >= len) {
643 typestr = g_renew(gchar, typestr, len + 32);
644 len += 32;
645 }
647 typestr[pos++] = code;
649 if (n != sp->last) {
650 n = n->n.other;
651 } else {
652 n = NULL;
653 }
654 }
655 }
657 if (pos >= len) {
658 typestr = g_renew(gchar, typestr, len + 1);
659 len += 1;
660 }
662 typestr[pos++] = '\0';
664 return typestr;
665 }
667 /**
668 * Returns current path in context. // later eliminate this function at all!
669 */
670 static Inkscape::NodePath::Path *sp_nodepath_current()
671 {
672 if (!SP_ACTIVE_DESKTOP) {
673 return NULL;
674 }
676 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
678 if (!SP_IS_NODE_CONTEXT(event_context)) {
679 return NULL;
680 }
682 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
683 }
687 /**
688 \brief Fills node and handle positions for three nodes, splitting line
689 marked by end at distance t.
690 */
691 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
692 {
693 g_assert(new_path != NULL);
694 g_assert(end != NULL);
696 g_assert(end->p.other == new_path);
697 Inkscape::NodePath::Node *start = new_path->p.other;
698 g_assert(start);
700 if (end->code == NR_LINETO) {
701 new_path->type =Inkscape::NodePath::NODE_CUSP;
702 new_path->code = NR_LINETO;
703 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
704 } else {
705 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
706 new_path->code = NR_CURVETO;
707 gdouble s = 1 - t;
708 for (int dim = 0; dim < 2; dim++) {
709 NR::Coord const f000 = start->pos[dim];
710 NR::Coord const f001 = start->n.pos[dim];
711 NR::Coord const f011 = end->p.pos[dim];
712 NR::Coord const f111 = end->pos[dim];
713 NR::Coord const f00t = s * f000 + t * f001;
714 NR::Coord const f01t = s * f001 + t * f011;
715 NR::Coord const f11t = s * f011 + t * f111;
716 NR::Coord const f0tt = s * f00t + t * f01t;
717 NR::Coord const f1tt = s * f01t + t * f11t;
718 NR::Coord const fttt = s * f0tt + t * f1tt;
719 start->n.pos[dim] = f00t;
720 new_path->p.pos[dim] = f0tt;
721 new_path->pos[dim] = fttt;
722 new_path->n.pos[dim] = f1tt;
723 end->p.pos[dim] = f11t;
724 }
725 }
726 }
728 /**
729 * Adds new node on direct line between two nodes, activates handles of all
730 * three nodes.
731 */
732 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
733 {
734 g_assert(end);
735 g_assert(end->subpath);
736 g_assert(g_list_find(end->subpath->nodes, end));
738 Inkscape::NodePath::Node *start = end->p.other;
739 g_assert( start->n.other == end );
740 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
741 end,
742 (NRPathcode)end->code == NR_LINETO?
743 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
744 (NRPathcode)end->code,
745 &start->pos, &start->pos, &start->n.pos);
746 sp_nodepath_line_midpoint(newnode, end, t);
748 sp_node_update_handles(start);
749 sp_node_update_handles(newnode);
750 sp_node_update_handles(end);
752 return newnode;
753 }
755 /**
756 \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
757 */
758 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
759 {
760 g_assert(node);
761 g_assert(node->subpath);
762 g_assert(g_list_find(node->subpath->nodes, node));
764 Inkscape::NodePath::SubPath *sp = node->subpath;
765 Inkscape::NodePath::Path *np = sp->nodepath;
767 if (sp->closed) {
768 sp_nodepath_subpath_open(sp, node);
769 return sp->first;
770 } else {
771 // no break for end nodes
772 if (node == sp->first) return NULL;
773 if (node == sp->last ) return NULL;
775 // create a new subpath
776 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
778 // duplicate the break node as start of the new subpath
779 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
781 while (node->n.other) { // copy the remaining nodes into the new subpath
782 Inkscape::NodePath::Node *n = node->n.other;
783 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);
784 if (n->selected) {
785 sp_nodepath_node_select(nn, TRUE, TRUE); //preserve selection
786 }
787 sp_nodepath_node_destroy(n); // remove the point on the original subpath
788 }
790 return newnode;
791 }
792 }
794 /**
795 * Duplicate node and connect to neighbours.
796 */
797 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
798 {
799 g_assert(node);
800 g_assert(node->subpath);
801 g_assert(g_list_find(node->subpath->nodes, node));
803 Inkscape::NodePath::SubPath *sp = node->subpath;
805 NRPathcode code = (NRPathcode) node->code;
806 if (code == NR_MOVETO) { // if node is the endnode,
807 node->code = NR_LINETO; // new one is inserted before it, so change that to line
808 }
810 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
812 if (!node->n.other || !node->p.other) // if node is an endnode, select it
813 return node;
814 else
815 return newnode; // otherwise select the newly created node
816 }
818 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
819 {
820 node->p.pos = (node->pos + (node->pos - node->n.pos));
821 }
823 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
824 {
825 node->n.pos = (node->pos + (node->pos - node->p.pos));
826 }
828 /**
829 * Change line type at node, with side effects on neighbours.
830 */
831 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
832 {
833 g_assert(end);
834 g_assert(end->subpath);
835 g_assert(end->p.other);
837 if (end->code == static_cast< guint > ( code ) )
838 return;
840 Inkscape::NodePath::Node *start = end->p.other;
842 end->code = code;
844 if (code == NR_LINETO) {
845 if (start->code == NR_LINETO) {
846 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
847 }
848 if (end->n.other) {
849 if (end->n.other->code == NR_LINETO) {
850 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
851 }
852 }
853 } else {
854 NR::Point delta = end->pos - start->pos;
855 start->n.pos = start->pos + delta / 3;
856 end->p.pos = end->pos - delta / 3;
857 sp_node_adjust_handle(start, 1);
858 sp_node_adjust_handle(end, -1);
859 }
861 sp_node_update_handles(start);
862 sp_node_update_handles(end);
863 }
865 /**
866 * Change node type, and its handles accordingly.
867 */
868 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
869 {
870 g_assert(node);
871 g_assert(node->subpath);
873 if (type == static_cast<Inkscape::NodePath::NodeType>(static_cast< guint >(node->type) ) )
874 return node;
876 if ((node->p.other != NULL) && (node->n.other != NULL)) {
877 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
878 type =Inkscape::NodePath::NODE_CUSP;
879 }
880 }
882 node->type = type;
884 if (node->type == Inkscape::NodePath::NODE_CUSP) {
885 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
886 node->knot->setSize (node->selected? 11 : 9);
887 sp_knot_update_ctrl(node->knot);
888 } else {
889 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
890 node->knot->setSize (node->selected? 9 : 7);
891 sp_knot_update_ctrl(node->knot);
892 }
894 // if one of handles is mouseovered, preserve its position
895 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
896 sp_node_adjust_handle(node, 1);
897 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
898 sp_node_adjust_handle(node, -1);
899 } else {
900 sp_node_adjust_handles(node);
901 }
903 sp_node_update_handles(node);
905 sp_nodepath_update_statusbar(node->subpath->nodepath);
907 return node;
908 }
910 /**
911 * Same as sp_nodepath_set_node_type(), but also converts, if necessary,
912 * adjacent segments from lines to curves.
913 */
914 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
915 {
916 bool p_line = (node->p.other != NULL) && (node->code == NR_LINETO || node->pos == node->p.pos);
917 bool n_line = (node->n.other != NULL) && (node->n.other->code == NR_LINETO || node->pos == node->n.pos);
919 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
920 if (p_line && n_line) {
921 // only if both adjacent segments are lines,
922 // convert both to curves:
924 // BEFORE:
925 {
926 node->code = NR_CURVETO;
927 NR::Point delta = node->n.other->pos - node->p.other->pos;
928 node->p.pos = node->pos - delta / 4;
929 }
931 // AFTER:
932 {
933 node->n.other->code = NR_CURVETO;
934 NR::Point delta = node->p.other->pos - node->n.other->pos;
935 node->n.pos = node->pos - delta / 4;
936 }
938 sp_node_update_handles(node);
939 }
940 }
942 sp_nodepath_set_node_type (node, type);
943 }
945 /**
946 * Move node to point, and adjust its and neighbouring handles.
947 */
948 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
949 {
950 NR::Point delta = p - node->pos;
951 node->pos = p;
953 node->p.pos += delta;
954 node->n.pos += delta;
956 Inkscape::NodePath::Node *node_p = NULL;
957 Inkscape::NodePath::Node *node_n = NULL;
959 if (node->p.other) {
960 if (node->code == NR_LINETO) {
961 sp_node_adjust_handle(node, 1);
962 sp_node_adjust_handle(node->p.other, -1);
963 node_p = node->p.other;
964 }
965 }
966 if (node->n.other) {
967 if (node->n.other->code == NR_LINETO) {
968 sp_node_adjust_handle(node, -1);
969 sp_node_adjust_handle(node->n.other, 1);
970 node_n = node->n.other;
971 }
972 }
974 // this function is only called from batch movers that will update display at the end
975 // themselves, so here we just move all the knots without emitting move signals, for speed
976 sp_node_update_handles(node, false);
977 if (node_n) {
978 sp_node_update_handles(node_n, false);
979 }
980 if (node_p) {
981 sp_node_update_handles(node_p, false);
982 }
983 }
985 /**
986 * Call sp_node_moveto() for node selection and handle possible snapping.
987 */
988 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
989 bool const snap = true)
990 {
991 NR::Coord best = NR_HUGE;
992 NR::Point delta(dx, dy);
993 NR::Point best_pt = delta;
995 if (snap) {
996 SnapManager const &m = nodepath->desktop->namedview->snap_manager;
998 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
999 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1000 Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAP_POINT, n->pos + delta, NULL);
1001 if (s.getDistance() < best) {
1002 best = s.getDistance();
1003 best_pt = s.getPoint() - n->pos;
1004 }
1005 }
1006 }
1008 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1009 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1010 sp_node_moveto(n, n->pos + best_pt);
1011 }
1013 // do not update repr here so that node dragging is acceptably fast
1014 update_object(nodepath);
1015 }
1017 /**
1018 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1019 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1020 near x = 0.
1021 */
1022 double
1023 sculpt_profile (double x, double alpha, guint profile)
1024 {
1025 if (x >= 1)
1026 return 0;
1027 if (x <= 0)
1028 return 1;
1030 switch (profile) {
1031 case SCULPT_PROFILE_LINEAR:
1032 return 1 - x;
1033 case SCULPT_PROFILE_BELL:
1034 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1035 case SCULPT_PROFILE_ELLIPTIC:
1036 return sqrt(1 - x*x);
1037 }
1039 return 1;
1040 }
1042 double
1043 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1044 {
1045 // extremely primitive for now, don't have time to look for the real one
1046 double lower = NR::L2(b - a);
1047 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1048 return (lower + upper)/2;
1049 }
1051 void
1052 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1053 {
1054 n->pos = n->origin + delta;
1055 n->n.pos = n->n.origin + delta_n;
1056 n->p.pos = n->p.origin + delta_p;
1057 sp_node_adjust_handles(n);
1058 sp_node_update_handles(n, false);
1059 }
1061 /**
1062 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1063 * on how far they are from the dragged node n.
1064 */
1065 static void
1066 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1067 {
1068 g_assert (n);
1069 g_assert (nodepath);
1070 g_assert (n->subpath->nodepath == nodepath);
1072 double pressure = n->knot->pressure;
1073 if (pressure == 0)
1074 pressure = 0.5; // default
1075 pressure = CLAMP (pressure, 0.2, 0.8);
1077 // map pressure to alpha = 1/5 ... 5
1078 double alpha = 1 - 2 * fabs(pressure - 0.5);
1079 if (pressure > 0.5)
1080 alpha = 1/alpha;
1082 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1084 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1085 // Only one subpath has selected nodes:
1086 // use linear mode, where the distance from n to node being dragged is calculated along the path
1088 double n_sel_range = 0, p_sel_range = 0;
1089 guint n_nodes = 0, p_nodes = 0;
1090 guint n_sel_nodes = 0, p_sel_nodes = 0;
1092 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1093 {
1094 double n_range = 0, p_range = 0;
1095 bool n_going = true, p_going = true;
1096 Inkscape::NodePath::Node *n_node = n;
1097 Inkscape::NodePath::Node *p_node = n;
1098 do {
1099 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1100 if (n_node && n_going)
1101 n_node = n_node->n.other;
1102 if (n_node == NULL) {
1103 n_going = false;
1104 } else {
1105 n_nodes ++;
1106 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1107 if (n_node->selected) {
1108 n_sel_nodes ++;
1109 n_sel_range = n_range;
1110 }
1111 if (n_node == p_node) {
1112 n_going = false;
1113 p_going = false;
1114 }
1115 }
1116 if (p_node && p_going)
1117 p_node = p_node->p.other;
1118 if (p_node == NULL) {
1119 p_going = false;
1120 } else {
1121 p_nodes ++;
1122 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1123 if (p_node->selected) {
1124 p_sel_nodes ++;
1125 p_sel_range = p_range;
1126 }
1127 if (p_node == n_node) {
1128 n_going = false;
1129 p_going = false;
1130 }
1131 }
1132 } while (n_going || p_going);
1133 }
1135 // Second pass: actually move nodes in this subpath
1136 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1137 {
1138 double n_range = 0, p_range = 0;
1139 bool n_going = true, p_going = true;
1140 Inkscape::NodePath::Node *n_node = n;
1141 Inkscape::NodePath::Node *p_node = n;
1142 do {
1143 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1144 if (n_node && n_going)
1145 n_node = n_node->n.other;
1146 if (n_node == NULL) {
1147 n_going = false;
1148 } else {
1149 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1150 if (n_node->selected) {
1151 sp_nodepath_move_node_and_handles (n_node,
1152 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1153 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1154 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1155 }
1156 if (n_node == p_node) {
1157 n_going = false;
1158 p_going = false;
1159 }
1160 }
1161 if (p_node && p_going)
1162 p_node = p_node->p.other;
1163 if (p_node == NULL) {
1164 p_going = false;
1165 } else {
1166 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1167 if (p_node->selected) {
1168 sp_nodepath_move_node_and_handles (p_node,
1169 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1170 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1171 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1172 }
1173 if (p_node == n_node) {
1174 n_going = false;
1175 p_going = false;
1176 }
1177 }
1178 } while (n_going || p_going);
1179 }
1181 } else {
1182 // Multiple subpaths have selected nodes:
1183 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1184 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1185 // fix the pear-like shape when sculpting e.g. a ring
1187 // First pass: calculate range
1188 gdouble direct_range = 0;
1189 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1190 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1191 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1192 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1193 if (node->selected) {
1194 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1195 }
1196 }
1197 }
1199 // Second pass: actually move nodes
1200 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1201 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1202 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1203 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1204 if (node->selected) {
1205 if (direct_range > 1e-6) {
1206 sp_nodepath_move_node_and_handles (node,
1207 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1208 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1209 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1210 } else {
1211 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1212 }
1214 }
1215 }
1216 }
1217 }
1219 // do not update repr here so that node dragging is acceptably fast
1220 update_object(nodepath);
1221 }
1224 /**
1225 * Move node selection to point, adjust its and neighbouring handles,
1226 * handle possible snapping, and commit the change with possible undo.
1227 */
1228 void
1229 sp_node_selected_move(gdouble dx, gdouble dy)
1230 {
1231 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1232 if (!nodepath) return;
1234 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1236 if (dx == 0) {
1237 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1238 } else if (dy == 0) {
1239 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1240 } else {
1241 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1242 }
1243 }
1245 /**
1246 * Move node selection off screen and commit the change.
1247 */
1248 void
1249 sp_node_selected_move_screen(gdouble dx, gdouble dy)
1250 {
1251 // borrowed from sp_selection_move_screen in selection-chemistry.c
1252 // we find out the current zoom factor and divide deltas by it
1253 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1255 gdouble zoom = desktop->current_zoom();
1256 gdouble zdx = dx / zoom;
1257 gdouble zdy = dy / zoom;
1259 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1260 if (!nodepath) return;
1262 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1264 if (dx == 0) {
1265 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1266 } else if (dy == 0) {
1267 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1268 } else {
1269 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1270 }
1271 }
1273 /** If they don't yet exist, creates knot and line for the given side of the node */
1274 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1275 {
1276 if (!side->knot) {
1277 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"));
1279 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1280 side->knot->setSize (7);
1281 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1282 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1283 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1284 sp_knot_update_ctrl(side->knot);
1286 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1287 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1288 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1289 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1290 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1291 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1292 }
1294 if (!side->line) {
1295 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1296 SP_TYPE_CTRLLINE, NULL);
1297 }
1298 }
1300 /**
1301 * Ensure the given handle of the node is visible/invisible, update its screen position
1302 */
1303 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1304 {
1305 g_assert(node != NULL);
1307 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1308 NRPathcode code = sp_node_path_code_from_side(node, side);
1310 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1312 if (show_handle) {
1313 if (!side->knot) { // No handle knot at all
1314 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1315 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1316 side->knot->pos = side->pos;
1317 if (side->knot->item)
1318 SP_CTRL(side->knot->item)->moveto(side->pos);
1319 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1320 sp_knot_show(side->knot);
1321 } else {
1322 if (side->knot->pos != side->pos) { // only if it's really moved
1323 if (fire_move_signals) {
1324 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1325 } else {
1326 sp_knot_moveto(side->knot, &side->pos);
1327 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1328 }
1329 }
1330 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1331 sp_knot_show(side->knot);
1332 }
1333 }
1334 sp_canvas_item_show(side->line);
1335 } else {
1336 if (side->knot) {
1337 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1338 sp_knot_hide(side->knot);
1339 }
1340 }
1341 if (side->line) {
1342 sp_canvas_item_hide(side->line);
1343 }
1344 }
1345 }
1347 /**
1348 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1349 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1350 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1351 * updated; otherwise, just move the knots silently (used in batch moves).
1352 */
1353 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1354 {
1355 g_assert(node != NULL);
1357 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1358 sp_knot_show(node->knot);
1359 }
1361 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1362 if (fire_move_signals)
1363 sp_knot_set_position(node->knot, &node->pos, 0);
1364 else
1365 sp_knot_moveto(node->knot, &node->pos);
1366 }
1368 gboolean show_handles = node->selected;
1369 if (node->p.other != NULL) {
1370 if (node->p.other->selected) show_handles = TRUE;
1371 }
1372 if (node->n.other != NULL) {
1373 if (node->n.other->selected) show_handles = TRUE;
1374 }
1376 if (node->subpath->nodepath->show_handles == false)
1377 show_handles = FALSE;
1379 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1380 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1381 }
1383 /**
1384 * Call sp_node_update_handles() for all nodes on subpath.
1385 */
1386 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1387 {
1388 g_assert(subpath != NULL);
1390 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1391 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1392 }
1393 }
1395 /**
1396 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1397 */
1398 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1399 {
1400 g_assert(nodepath != NULL);
1402 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1403 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1404 }
1405 }
1407 void
1408 sp_nodepath_show_handles(bool show)
1409 {
1410 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1411 if (nodepath == NULL) return;
1413 nodepath->show_handles = show;
1414 sp_nodepath_update_handles(nodepath);
1415 }
1417 /**
1418 * Adds all selected nodes in nodepath to list.
1419 */
1420 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1421 {
1422 StlConv<Node *>::list(l, selected);
1423 /// \todo this adds a copying, rework when the selection becomes a stl list
1424 }
1426 /**
1427 * Align selected nodes on the specified axis.
1428 */
1429 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1430 {
1431 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1432 return;
1433 }
1435 if ( !nodepath->selected->next ) { // only one node selected
1436 return;
1437 }
1438 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1439 NR::Point dest(pNode->pos);
1440 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1441 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1442 if (pNode) {
1443 dest[axis] = pNode->pos[axis];
1444 sp_node_moveto(pNode, dest);
1445 }
1446 }
1448 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1449 }
1451 /// Helper struct.
1452 struct NodeSort
1453 {
1454 Inkscape::NodePath::Node *_node;
1455 NR::Coord _coord;
1456 /// \todo use vectorof pointers instead of calling copy ctor
1457 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1458 _node(node), _coord(node->pos[axis])
1459 {}
1461 };
1463 static bool operator<(NodeSort const &a, NodeSort const &b)
1464 {
1465 return (a._coord < b._coord);
1466 }
1468 /**
1469 * Distribute selected nodes on the specified axis.
1470 */
1471 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1472 {
1473 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1474 return;
1475 }
1477 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1478 return;
1479 }
1481 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1482 std::vector<NodeSort> sorted;
1483 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1484 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1485 if (pNode) {
1486 NodeSort n(pNode, axis);
1487 sorted.push_back(n);
1488 //dest[axis] = pNode->pos[axis];
1489 //sp_node_moveto(pNode, dest);
1490 }
1491 }
1492 std::sort(sorted.begin(), sorted.end());
1493 unsigned int len = sorted.size();
1494 //overall bboxes span
1495 float dist = (sorted.back()._coord -
1496 sorted.front()._coord);
1497 //new distance between each bbox
1498 float step = (dist) / (len - 1);
1499 float pos = sorted.front()._coord;
1500 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1501 it < sorted.end();
1502 it ++ )
1503 {
1504 NR::Point dest((*it)._node->pos);
1505 dest[axis] = pos;
1506 sp_node_moveto((*it)._node, dest);
1507 pos += step;
1508 }
1510 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1511 }
1514 /**
1515 * Call sp_nodepath_line_add_node() for all selected segments.
1516 */
1517 void
1518 sp_node_selected_add_node(void)
1519 {
1520 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1521 if (!nodepath) {
1522 return;
1523 }
1525 GList *nl = NULL;
1527 int n_added = 0;
1529 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1530 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1531 g_assert(t->selected);
1532 if (t->p.other && t->p.other->selected) {
1533 nl = g_list_prepend(nl, t);
1534 }
1535 }
1537 while (nl) {
1538 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1539 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1540 sp_nodepath_node_select(n, TRUE, FALSE);
1541 n_added ++;
1542 nl = g_list_remove(nl, t);
1543 }
1545 /** \todo fixme: adjust ? */
1546 sp_nodepath_update_handles(nodepath);
1548 if (n_added > 1) {
1549 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1550 } else if (n_added > 0) {
1551 sp_nodepath_update_repr(nodepath, _("Add node"));
1552 }
1554 sp_nodepath_update_statusbar(nodepath);
1555 }
1557 /**
1558 * Select segment nearest to point
1559 */
1560 void
1561 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1562 {
1563 if (!nodepath) {
1564 return;
1565 }
1567 sp_nodepath_ensure_livarot_path(nodepath);
1568 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1569 if (!maybe_position) {
1570 return;
1571 }
1572 Path::cut_position position = *maybe_position;
1574 //find segment to segment
1575 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1577 //fixme: this can return NULL, so check before proceeding.
1578 g_return_if_fail(e != NULL);
1580 gboolean force = FALSE;
1581 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1582 force = TRUE;
1583 }
1584 sp_nodepath_node_select(e, (gboolean) toggle, force);
1585 if (e->p.other)
1586 sp_nodepath_node_select(e->p.other, TRUE, force);
1588 sp_nodepath_update_handles(nodepath);
1590 sp_nodepath_update_statusbar(nodepath);
1591 }
1593 /**
1594 * Add a node nearest to point
1595 */
1596 void
1597 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1598 {
1599 if (!nodepath) {
1600 return;
1601 }
1603 sp_nodepath_ensure_livarot_path(nodepath);
1604 NR::Maybe<Path::cut_position> maybe_position = get_nearest_position_on_Path(nodepath->livarot_path, p);
1605 if (!maybe_position) {
1606 return;
1607 }
1608 Path::cut_position position = *maybe_position;
1610 //find segment to split
1611 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(position.piece);
1613 //don't know why but t seems to flip for lines
1614 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
1615 position.t = 1.0 - position.t;
1616 }
1617 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, position.t);
1618 sp_nodepath_node_select(n, FALSE, TRUE);
1620 /* fixme: adjust ? */
1621 sp_nodepath_update_handles(nodepath);
1623 sp_nodepath_update_repr(nodepath, _("Add node"));
1625 sp_nodepath_update_statusbar(nodepath);
1626 }
1628 /*
1629 * Adjusts a segment so that t moves by a certain delta for dragging
1630 * converts lines to curves
1631 *
1632 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
1633 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
1634 */
1635 void
1636 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
1637 {
1638 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
1640 //fixme: e and e->p can be NULL, so check for those before proceeding
1641 g_return_if_fail(e != NULL);
1642 g_return_if_fail(&e->p != NULL);
1644 /* feel good is an arbitrary parameter that distributes the delta between handles
1645 * if t of the drag point is less than 1/6 distance form the endpoint only
1646 * the corresponding hadle is adjusted. This matches the behavior in GIMP
1647 */
1648 double feel_good;
1649 if (t <= 1.0 / 6.0)
1650 feel_good = 0;
1651 else if (t <= 0.5)
1652 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
1653 else if (t <= 5.0 / 6.0)
1654 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
1655 else
1656 feel_good = 1;
1658 //if we're dragging a line convert it to a curve
1659 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
1660 sp_nodepath_set_line_type(e, NR_CURVETO);
1661 }
1663 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
1664 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
1665 e->p.other->n.pos += offsetcoord0;
1666 e->p.pos += offsetcoord1;
1668 // adjust handles of adjacent nodes where necessary
1669 sp_node_adjust_handle(e,1);
1670 sp_node_adjust_handle(e->p.other,-1);
1672 sp_nodepath_update_handles(e->subpath->nodepath);
1674 update_object(e->subpath->nodepath);
1676 sp_nodepath_update_statusbar(e->subpath->nodepath);
1677 }
1680 /**
1681 * Call sp_nodepath_break() for all selected segments.
1682 */
1683 void sp_node_selected_break()
1684 {
1685 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1686 if (!nodepath) return;
1688 GList *temp = NULL;
1689 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1690 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1691 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
1692 if (nn == NULL) continue; // no break, no new node
1693 temp = g_list_prepend(temp, nn);
1694 }
1696 if (temp) {
1697 sp_nodepath_deselect(nodepath);
1698 }
1699 for (GList *l = temp; l != NULL; l = l->next) {
1700 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1701 }
1703 sp_nodepath_update_handles(nodepath);
1705 sp_nodepath_update_repr(nodepath, _("Break path"));
1706 }
1708 /**
1709 * Duplicate the selected node(s).
1710 */
1711 void sp_node_selected_duplicate()
1712 {
1713 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1714 if (!nodepath) {
1715 return;
1716 }
1718 GList *temp = NULL;
1719 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1720 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1721 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
1722 if (nn == NULL) continue; // could not duplicate
1723 temp = g_list_prepend(temp, nn);
1724 }
1726 if (temp) {
1727 sp_nodepath_deselect(nodepath);
1728 }
1729 for (GList *l = temp; l != NULL; l = l->next) {
1730 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
1731 }
1733 sp_nodepath_update_handles(nodepath);
1735 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
1736 }
1738 /**
1739 * Join two nodes by merging them into one.
1740 */
1741 void sp_node_selected_join()
1742 {
1743 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1744 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1746 if (g_list_length(nodepath->selected) != 2) {
1747 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1748 return;
1749 }
1751 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1752 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1754 g_assert(a != b);
1755 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1756 // someone tried to join an orphan node (i.e. a single-node subpath).
1757 // this is not worth an error message, just fail silently.
1758 return;
1759 }
1761 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1762 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1763 return;
1764 }
1766 /* a and b are endpoints */
1768 NR::Point c;
1769 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
1770 c = a->pos;
1771 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
1772 c = b->pos;
1773 } else {
1774 c = (a->pos + b->pos) / 2;
1775 }
1777 if (a->subpath == b->subpath) {
1778 Inkscape::NodePath::SubPath *sp = a->subpath;
1779 sp_nodepath_subpath_close(sp);
1780 sp_node_moveto (sp->first, c);
1782 sp_nodepath_update_handles(sp->nodepath);
1783 sp_nodepath_update_repr(nodepath, _("Close subpath"));
1784 return;
1785 }
1787 /* a and b are separate subpaths */
1788 Inkscape::NodePath::SubPath *sa = a->subpath;
1789 Inkscape::NodePath::SubPath *sb = b->subpath;
1790 NR::Point p;
1791 Inkscape::NodePath::Node *n;
1792 NRPathcode code;
1793 if (a == sa->first) {
1794 p = sa->first->n.pos;
1795 code = (NRPathcode)sa->first->n.other->code;
1796 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1797 n = sa->last;
1798 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1799 n = n->p.other;
1800 while (n) {
1801 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1802 n = n->p.other;
1803 if (n == sa->first) n = NULL;
1804 }
1805 sp_nodepath_subpath_destroy(sa);
1806 sa = t;
1807 } else if (a == sa->last) {
1808 p = sa->last->p.pos;
1809 code = (NRPathcode)sa->last->code;
1810 sp_nodepath_node_destroy(sa->last);
1811 } else {
1812 code = NR_END;
1813 g_assert_not_reached();
1814 }
1816 if (b == sb->first) {
1817 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
1818 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
1819 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1820 }
1821 } else if (b == sb->last) {
1822 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
1823 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
1824 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1825 }
1826 } else {
1827 g_assert_not_reached();
1828 }
1829 /* and now destroy sb */
1831 sp_nodepath_subpath_destroy(sb);
1833 sp_nodepath_update_handles(sa->nodepath);
1835 sp_nodepath_update_repr(nodepath, _("Join nodes"));
1837 sp_nodepath_update_statusbar(nodepath);
1838 }
1840 /**
1841 * Join two nodes by adding a segment between them.
1842 */
1843 void sp_node_selected_join_segment()
1844 {
1845 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
1846 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
1848 if (g_list_length(nodepath->selected) != 2) {
1849 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1850 return;
1851 }
1853 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
1854 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
1856 g_assert(a != b);
1857 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
1858 // someone tried to join an orphan node (i.e. a single-node subpath).
1859 // this is not worth an error message, just fail silently.
1860 return;
1861 }
1863 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
1864 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
1865 return;
1866 }
1868 if (a->subpath == b->subpath) {
1869 Inkscape::NodePath::SubPath *sp = a->subpath;
1871 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
1872 sp->closed = TRUE;
1874 sp->first->p.other = sp->last;
1875 sp->last->n.other = sp->first;
1877 sp_node_handle_mirror_p_to_n(sp->last);
1878 sp_node_handle_mirror_n_to_p(sp->first);
1880 sp->first->code = sp->last->code;
1881 sp->first = sp->last;
1883 sp_nodepath_update_handles(sp->nodepath);
1885 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
1887 return;
1888 }
1890 /* a and b are separate subpaths */
1891 Inkscape::NodePath::SubPath *sa = a->subpath;
1892 Inkscape::NodePath::SubPath *sb = b->subpath;
1894 Inkscape::NodePath::Node *n;
1895 NR::Point p;
1896 NRPathcode code;
1897 if (a == sa->first) {
1898 code = (NRPathcode) sa->first->n.other->code;
1899 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
1900 n = sa->last;
1901 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
1902 for (n = n->p.other; n != NULL; n = n->p.other) {
1903 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1904 }
1905 sp_nodepath_subpath_destroy(sa);
1906 sa = t;
1907 } else if (a == sa->last) {
1908 code = (NRPathcode)sa->last->code;
1909 } else {
1910 code = NR_END;
1911 g_assert_not_reached();
1912 }
1914 if (b == sb->first) {
1915 n = sb->first;
1916 sp_node_handle_mirror_p_to_n(sa->last);
1917 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
1918 sp_node_handle_mirror_n_to_p(sa->last);
1919 for (n = n->n.other; n != NULL; n = n->n.other) {
1920 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
1921 }
1922 } else if (b == sb->last) {
1923 n = sb->last;
1924 sp_node_handle_mirror_p_to_n(sa->last);
1925 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
1926 sp_node_handle_mirror_n_to_p(sa->last);
1927 for (n = n->p.other; n != NULL; n = n->p.other) {
1928 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
1929 }
1930 } else {
1931 g_assert_not_reached();
1932 }
1933 /* and now destroy sb */
1935 sp_nodepath_subpath_destroy(sb);
1937 sp_nodepath_update_handles(sa->nodepath);
1939 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
1940 }
1942 /**
1943 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
1944 */
1945 void sp_node_delete_preserve(GList *nodes_to_delete)
1946 {
1947 GSList *nodepaths = NULL;
1949 while (nodes_to_delete) {
1950 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
1951 Inkscape::NodePath::SubPath *sp = node->subpath;
1952 Inkscape::NodePath::Path *nodepath = sp->nodepath;
1953 Inkscape::NodePath::Node *sample_cursor = NULL;
1954 Inkscape::NodePath::Node *sample_end = NULL;
1955 Inkscape::NodePath::Node *delete_cursor = node;
1956 bool just_delete = false;
1958 //find the start of this contiguous selection
1959 //move left to the first node that is not selected
1960 //or the start of the non-closed path
1961 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
1962 delete_cursor = curr;
1963 }
1965 //just delete at the beginning of an open path
1966 if (!delete_cursor->p.other) {
1967 sample_cursor = delete_cursor;
1968 just_delete = true;
1969 } else {
1970 sample_cursor = delete_cursor->p.other;
1971 }
1973 //calculate points for each segment
1974 int rate = 5;
1975 float period = 1.0 / rate;
1976 std::vector<NR::Point> data;
1977 if (!just_delete) {
1978 data.push_back(sample_cursor->pos);
1979 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
1980 //just delete at the end of an open path
1981 if (!sp->closed && curr == sp->last) {
1982 just_delete = true;
1983 break;
1984 }
1986 //sample points on the contiguous selected segment
1987 NR::Point *bez;
1988 bez = new NR::Point [4];
1989 bez[0] = curr->pos;
1990 bez[1] = curr->n.pos;
1991 bez[2] = curr->n.other->p.pos;
1992 bez[3] = curr->n.other->pos;
1993 for (int i=1; i<rate; i++) {
1994 gdouble t = i * period;
1995 NR::Point p = bezier_pt(3, bez, t);
1996 data.push_back(p);
1997 }
1998 data.push_back(curr->n.other->pos);
2000 sample_end = curr->n.other;
2001 //break if we've come full circle or hit the end of the selection
2002 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2003 break;
2004 }
2005 }
2006 }
2008 if (!just_delete) {
2009 //calculate the best fitting single segment and adjust the endpoints
2010 NR::Point *adata;
2011 adata = new NR::Point [data.size()];
2012 copy(data.begin(), data.end(), adata);
2014 NR::Point *bez;
2015 bez = new NR::Point [4];
2016 //would decreasing error create a better fitting approximation?
2017 gdouble error = 1.0;
2018 gint ret;
2019 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2021 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2022 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2023 //the resulting nodes behave as expected.
2024 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2025 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2027 //adjust endpoints
2028 sample_cursor->n.pos = bez[1];
2029 sample_end->p.pos = bez[2];
2030 }
2032 //destroy this contiguous selection
2033 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2034 Inkscape::NodePath::Node *temp = delete_cursor;
2035 if (delete_cursor->n.other == delete_cursor) {
2036 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2037 delete_cursor = NULL;
2038 } else {
2039 delete_cursor = delete_cursor->n.other;
2040 }
2041 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2042 sp_nodepath_node_destroy(temp);
2043 }
2045 sp_nodepath_update_handles(nodepath);
2047 if (!g_slist_find(nodepaths, nodepath))
2048 nodepaths = g_slist_prepend (nodepaths, nodepath);
2049 }
2051 for (GSList *i = nodepaths; i; i = i->next) {
2052 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2053 // different nodepaths will give us one undo event per nodepath
2054 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2056 // if the entire nodepath is removed, delete the selected object.
2057 if (nodepath->subpaths == NULL ||
2058 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2059 //at least 2
2060 sp_nodepath_get_node_count(nodepath) < 2) {
2061 SPDocument *document = sp_desktop_document (nodepath->desktop);
2062 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2063 //delete this nodepath's object, not the entire selection! (though at this time, this
2064 //does not matter)
2065 sp_selection_delete();
2066 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2067 _("Delete nodes"));
2068 } else {
2069 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2070 sp_nodepath_update_statusbar(nodepath);
2071 }
2072 }
2074 g_slist_free (nodepaths);
2075 }
2077 /**
2078 * Delete one or more selected nodes.
2079 */
2080 void sp_node_selected_delete()
2081 {
2082 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2083 if (!nodepath) return;
2084 if (!nodepath->selected) return;
2086 /** \todo fixme: do it the right way */
2087 while (nodepath->selected) {
2088 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2089 sp_nodepath_node_destroy(node);
2090 }
2093 //clean up the nodepath (such as for trivial subpaths)
2094 sp_nodepath_cleanup(nodepath);
2096 sp_nodepath_update_handles(nodepath);
2098 // if the entire nodepath is removed, delete the selected object.
2099 if (nodepath->subpaths == NULL ||
2100 sp_nodepath_get_node_count(nodepath) < 2) {
2101 SPDocument *document = sp_desktop_document (nodepath->desktop);
2102 sp_selection_delete();
2103 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2104 _("Delete nodes"));
2105 return;
2106 }
2108 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2110 sp_nodepath_update_statusbar(nodepath);
2111 }
2113 /**
2114 * Delete one or more segments between two selected nodes.
2115 * This is the code for 'split'.
2116 */
2117 void
2118 sp_node_selected_delete_segment(void)
2119 {
2120 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2121 Inkscape::NodePath::Node *curr, *next; //Iterators
2123 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2124 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2126 if (g_list_length(nodepath->selected) != 2) {
2127 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2128 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2129 return;
2130 }
2132 //Selected nodes, not inclusive
2133 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2134 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2136 if ( ( a==b) || //same node
2137 (a->subpath != b->subpath ) || //not the same path
2138 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2139 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2140 {
2141 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2142 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2143 return;
2144 }
2146 //###########################################
2147 //# BEGIN EDITS
2148 //###########################################
2149 //##################################
2150 //# CLOSED PATH
2151 //##################################
2152 if (a->subpath->closed) {
2155 gboolean reversed = FALSE;
2157 //Since we can go in a circle, we need to find the shorter distance.
2158 // a->b or b->a
2159 start = end = NULL;
2160 int distance = 0;
2161 int minDistance = 0;
2162 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2163 if (curr==b) {
2164 //printf("a to b:%d\n", distance);
2165 start = a;//go from a to b
2166 end = b;
2167 minDistance = distance;
2168 //printf("A to B :\n");
2169 break;
2170 }
2171 distance++;
2172 }
2174 //try again, the other direction
2175 distance = 0;
2176 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2177 if (curr==a) {
2178 //printf("b to a:%d\n", distance);
2179 if (distance < minDistance) {
2180 start = b; //we go from b to a
2181 end = a;
2182 reversed = TRUE;
2183 //printf("B to A\n");
2184 }
2185 break;
2186 }
2187 distance++;
2188 }
2191 //Copy everything from 'end' to 'start' to a new subpath
2192 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2193 for (curr=end ; curr ; curr=curr->n.other) {
2194 NRPathcode code = (NRPathcode) curr->code;
2195 if (curr == end)
2196 code = NR_MOVETO;
2197 sp_nodepath_node_new(t, NULL,
2198 (Inkscape::NodePath::NodeType)curr->type, code,
2199 &curr->p.pos, &curr->pos, &curr->n.pos);
2200 if (curr == start)
2201 break;
2202 }
2203 sp_nodepath_subpath_destroy(a->subpath);
2206 }
2210 //##################################
2211 //# OPEN PATH
2212 //##################################
2213 else {
2215 //We need to get the direction of the list between A and B
2216 //Can we walk from a to b?
2217 start = end = NULL;
2218 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2219 if (curr==b) {
2220 start = a; //did it! we go from a to b
2221 end = b;
2222 //printf("A to B\n");
2223 break;
2224 }
2225 }
2226 if (!start) {//didn't work? let's try the other direction
2227 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2228 if (curr==a) {
2229 start = b; //did it! we go from b to a
2230 end = a;
2231 //printf("B to A\n");
2232 break;
2233 }
2234 }
2235 }
2236 if (!start) {
2237 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2238 _("Cannot find path between nodes."));
2239 return;
2240 }
2244 //Copy everything after 'end' to a new subpath
2245 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2246 for (curr=end ; curr ; curr=curr->n.other) {
2247 NRPathcode code = (NRPathcode) curr->code;
2248 if (curr == end)
2249 code = NR_MOVETO;
2250 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2251 &curr->p.pos, &curr->pos, &curr->n.pos);
2252 }
2254 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2255 for (curr = start->n.other ; curr ; curr=next) {
2256 next = curr->n.other;
2257 sp_nodepath_node_destroy(curr);
2258 }
2260 }
2261 //###########################################
2262 //# END EDITS
2263 //###########################################
2265 //clean up the nodepath (such as for trivial subpaths)
2266 sp_nodepath_cleanup(nodepath);
2268 sp_nodepath_update_handles(nodepath);
2270 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2272 sp_nodepath_update_statusbar(nodepath);
2273 }
2275 /**
2276 * Call sp_nodepath_set_line() for all selected segments.
2277 */
2278 void
2279 sp_node_selected_set_line_type(NRPathcode code)
2280 {
2281 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2282 if (nodepath == NULL) return;
2284 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2285 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2286 g_assert(n->selected);
2287 if (n->p.other && n->p.other->selected) {
2288 sp_nodepath_set_line_type(n, code);
2289 }
2290 }
2292 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2293 }
2295 /**
2296 * Call sp_nodepath_convert_node_type() for all selected nodes.
2297 */
2298 void
2299 sp_node_selected_set_type(Inkscape::NodePath::NodeType type)
2300 {
2301 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
2302 if (nodepath == NULL) return;
2304 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2305 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2306 }
2308 sp_nodepath_update_repr(nodepath, _("Change node type"));
2309 }
2311 /**
2312 * Change select status of node, update its own and neighbour handles.
2313 */
2314 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2315 {
2316 node->selected = selected;
2318 if (selected) {
2319 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2320 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2321 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2322 sp_knot_update_ctrl(node->knot);
2323 } else {
2324 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2325 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2326 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2327 sp_knot_update_ctrl(node->knot);
2328 }
2330 sp_node_update_handles(node);
2331 if (node->n.other) sp_node_update_handles(node->n.other);
2332 if (node->p.other) sp_node_update_handles(node->p.other);
2333 }
2335 /**
2336 \brief Select a node
2337 \param node The node to select
2338 \param incremental If true, add to selection, otherwise deselect others
2339 \param override If true, always select this node, otherwise toggle selected status
2340 */
2341 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2342 {
2343 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2345 if (incremental) {
2346 if (override) {
2347 if (!g_list_find(nodepath->selected, node)) {
2348 nodepath->selected = g_list_prepend(nodepath->selected, node);
2349 }
2350 sp_node_set_selected(node, TRUE);
2351 } else { // toggle
2352 if (node->selected) {
2353 g_assert(g_list_find(nodepath->selected, node));
2354 nodepath->selected = g_list_remove(nodepath->selected, node);
2355 } else {
2356 g_assert(!g_list_find(nodepath->selected, node));
2357 nodepath->selected = g_list_prepend(nodepath->selected, node);
2358 }
2359 sp_node_set_selected(node, !node->selected);
2360 }
2361 } else {
2362 sp_nodepath_deselect(nodepath);
2363 nodepath->selected = g_list_prepend(nodepath->selected, node);
2364 sp_node_set_selected(node, TRUE);
2365 }
2367 sp_nodepath_update_statusbar(nodepath);
2368 }
2371 /**
2372 \brief Deselect all nodes in the nodepath
2373 */
2374 void
2375 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2376 {
2377 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2379 while (nodepath->selected) {
2380 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2381 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2382 }
2383 sp_nodepath_update_statusbar(nodepath);
2384 }
2386 /**
2387 \brief Select or invert selection of all nodes in the nodepath
2388 */
2389 void
2390 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2391 {
2392 if (!nodepath) return;
2394 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2395 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2396 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2397 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2398 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2399 }
2400 }
2401 }
2403 /**
2404 * If nothing selected, does the same as sp_nodepath_select_all();
2405 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2406 * (i.e., similar to "select all in layer", with the "selected" subpaths
2407 * being treated as "layers" in the path).
2408 */
2409 void
2410 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2411 {
2412 if (!nodepath) return;
2414 if (g_list_length (nodepath->selected) == 0) {
2415 sp_nodepath_select_all (nodepath, invert);
2416 return;
2417 }
2419 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2420 GSList *subpaths = NULL;
2422 for (GList *l = copy; l != NULL; l = l->next) {
2423 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2424 Inkscape::NodePath::SubPath *subpath = n->subpath;
2425 if (!g_slist_find (subpaths, subpath))
2426 subpaths = g_slist_prepend (subpaths, subpath);
2427 }
2429 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2430 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2431 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2432 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2433 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2434 }
2435 }
2437 g_slist_free (subpaths);
2438 g_list_free (copy);
2439 }
2441 /**
2442 * \brief Select the node after the last selected; if none is selected,
2443 * select the first within path.
2444 */
2445 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2446 {
2447 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2449 Inkscape::NodePath::Node *last = NULL;
2450 if (nodepath->selected) {
2451 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2452 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2453 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2454 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2455 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2456 if (node->selected) {
2457 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2458 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2459 if (spl->next) { // there's a next subpath
2460 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2461 last = subpath_next->first;
2462 } else if (spl->prev) { // there's a previous subpath
2463 last = NULL; // to be set later to the first node of first subpath
2464 } else {
2465 last = node->n.other;
2466 }
2467 } else {
2468 last = node->n.other;
2469 }
2470 } else {
2471 if (node->n.other) {
2472 last = node->n.other;
2473 } else {
2474 if (spl->next) { // there's a next subpath
2475 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2476 last = subpath_next->first;
2477 } else if (spl->prev) { // there's a previous subpath
2478 last = NULL; // to be set later to the first node of first subpath
2479 } else {
2480 last = (Inkscape::NodePath::Node *) subpath->first;
2481 }
2482 }
2483 }
2484 }
2485 }
2486 }
2487 sp_nodepath_deselect(nodepath);
2488 }
2490 if (last) { // there's at least one more node after selected
2491 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2492 } else { // no more nodes, select the first one in first subpath
2493 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2494 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2495 }
2496 }
2498 /**
2499 * \brief Select the node before the first selected; if none is selected,
2500 * select the last within path
2501 */
2502 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2503 {
2504 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2506 Inkscape::NodePath::Node *last = NULL;
2507 if (nodepath->selected) {
2508 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2509 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2510 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2511 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2512 if (node->selected) {
2513 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2514 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2515 if (spl->prev) { // there's a prev subpath
2516 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2517 last = subpath_prev->last;
2518 } else if (spl->next) { // there's a next subpath
2519 last = NULL; // to be set later to the last node of last subpath
2520 } else {
2521 last = node->p.other;
2522 }
2523 } else {
2524 last = node->p.other;
2525 }
2526 } else {
2527 if (node->p.other) {
2528 last = node->p.other;
2529 } else {
2530 if (spl->prev) { // there's a prev subpath
2531 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2532 last = subpath_prev->last;
2533 } else if (spl->next) { // there's a next subpath
2534 last = NULL; // to be set later to the last node of last subpath
2535 } else {
2536 last = (Inkscape::NodePath::Node *) subpath->last;
2537 }
2538 }
2539 }
2540 }
2541 }
2542 }
2543 sp_nodepath_deselect(nodepath);
2544 }
2546 if (last) { // there's at least one more node before selected
2547 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2548 } else { // no more nodes, select the last one in last subpath
2549 GList *spl = g_list_last(nodepath->subpaths);
2550 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2551 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2552 }
2553 }
2555 /**
2556 * \brief Select all nodes that are within the rectangle.
2557 */
2558 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2559 {
2560 if (!incremental) {
2561 sp_nodepath_deselect(nodepath);
2562 }
2564 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2565 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2566 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2567 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2569 if (b.contains(node->pos)) {
2570 sp_nodepath_node_select(node, TRUE, TRUE);
2571 }
2572 }
2573 }
2574 }
2577 void
2578 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2579 {
2580 g_assert (n);
2581 g_assert (nodepath);
2582 g_assert (n->subpath->nodepath == nodepath);
2584 if (g_list_length (nodepath->selected) == 0) {
2585 if (grow > 0) {
2586 sp_nodepath_node_select(n, TRUE, TRUE);
2587 }
2588 return;
2589 }
2591 if (g_list_length (nodepath->selected) == 1) {
2592 if (grow < 0) {
2593 sp_nodepath_deselect (nodepath);
2594 return;
2595 }
2596 }
2598 double n_sel_range = 0, p_sel_range = 0;
2599 Inkscape::NodePath::Node *farthest_n_node = n;
2600 Inkscape::NodePath::Node *farthest_p_node = n;
2602 // Calculate ranges
2603 {
2604 double n_range = 0, p_range = 0;
2605 bool n_going = true, p_going = true;
2606 Inkscape::NodePath::Node *n_node = n;
2607 Inkscape::NodePath::Node *p_node = n;
2608 do {
2609 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
2610 if (n_node && n_going)
2611 n_node = n_node->n.other;
2612 if (n_node == NULL) {
2613 n_going = false;
2614 } else {
2615 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
2616 if (n_node->selected) {
2617 n_sel_range = n_range;
2618 farthest_n_node = n_node;
2619 }
2620 if (n_node == p_node) {
2621 n_going = false;
2622 p_going = false;
2623 }
2624 }
2625 if (p_node && p_going)
2626 p_node = p_node->p.other;
2627 if (p_node == NULL) {
2628 p_going = false;
2629 } else {
2630 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
2631 if (p_node->selected) {
2632 p_sel_range = p_range;
2633 farthest_p_node = p_node;
2634 }
2635 if (p_node == n_node) {
2636 n_going = false;
2637 p_going = false;
2638 }
2639 }
2640 } while (n_going || p_going);
2641 }
2643 if (grow > 0) {
2644 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
2645 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
2646 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
2647 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
2648 }
2649 } else {
2650 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
2651 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
2652 } else if (farthest_p_node && farthest_p_node->selected) {
2653 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
2654 }
2655 }
2656 }
2658 void
2659 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2660 {
2661 g_assert (n);
2662 g_assert (nodepath);
2663 g_assert (n->subpath->nodepath == nodepath);
2665 if (g_list_length (nodepath->selected) == 0) {
2666 if (grow > 0) {
2667 sp_nodepath_node_select(n, TRUE, TRUE);
2668 }
2669 return;
2670 }
2672 if (g_list_length (nodepath->selected) == 1) {
2673 if (grow < 0) {
2674 sp_nodepath_deselect (nodepath);
2675 return;
2676 }
2677 }
2679 Inkscape::NodePath::Node *farthest_selected = NULL;
2680 double farthest_dist = 0;
2682 Inkscape::NodePath::Node *closest_unselected = NULL;
2683 double closest_dist = NR_HUGE;
2685 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2686 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2687 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2688 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2689 if (node == n)
2690 continue;
2691 if (node->selected) {
2692 if (NR::L2(node->pos - n->pos) > farthest_dist) {
2693 farthest_dist = NR::L2(node->pos - n->pos);
2694 farthest_selected = node;
2695 }
2696 } else {
2697 if (NR::L2(node->pos - n->pos) < closest_dist) {
2698 closest_dist = NR::L2(node->pos - n->pos);
2699 closest_unselected = node;
2700 }
2701 }
2702 }
2703 }
2705 if (grow > 0) {
2706 if (closest_unselected) {
2707 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
2708 }
2709 } else {
2710 if (farthest_selected) {
2711 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
2712 }
2713 }
2714 }
2717 /**
2718 \brief Saves all nodes' and handles' current positions in their origin members
2719 */
2720 void
2721 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
2722 {
2723 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2724 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2725 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
2727 n->origin = n->pos;
2728 n->p.origin = n->p.pos;
2729 n->n.origin = n->n.pos;
2730 }
2731 }
2732 }
2734 /**
2735 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
2736 */
2737 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
2738 {
2739 if (!nodepath->selected) {
2740 return NULL;
2741 }
2743 GList *r = NULL;
2744 guint i = 0;
2745 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2746 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2747 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2748 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2749 i++;
2750 if (node->selected) {
2751 r = g_list_append(r, GINT_TO_POINTER(i));
2752 }
2753 }
2754 }
2755 return r;
2756 }
2758 /**
2759 \brief Restores selection by selecting nodes whose positions are in the list
2760 */
2761 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
2762 {
2763 sp_nodepath_deselect(nodepath);
2765 guint i = 0;
2766 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2767 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2768 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2769 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2770 i++;
2771 if (g_list_find(r, GINT_TO_POINTER(i))) {
2772 sp_nodepath_node_select(node, TRUE, TRUE);
2773 }
2774 }
2775 }
2777 }
2779 /**
2780 \brief Adjusts handle according to node type and line code.
2781 */
2782 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
2783 {
2784 double len, otherlen, linelen;
2786 g_assert(node);
2788 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
2789 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
2791 /** \todo fixme: */
2792 if (me->other == NULL) return;
2793 if (other->other == NULL) return;
2795 /* I have line */
2797 NRPathcode mecode, ocode;
2798 if (which_adjust == 1) {
2799 mecode = (NRPathcode)me->other->code;
2800 ocode = (NRPathcode)node->code;
2801 } else {
2802 mecode = (NRPathcode)node->code;
2803 ocode = (NRPathcode)other->other->code;
2804 }
2806 if (mecode == NR_LINETO) return;
2808 /* I am curve */
2810 if (other->other == NULL) return;
2812 /* Other has line */
2814 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2816 NR::Point delta;
2817 if (ocode == NR_LINETO) {
2818 /* other is lineto, we are either smooth or symm */
2819 Inkscape::NodePath::Node *othernode = other->other;
2820 len = NR::L2(me->pos - node->pos);
2821 delta = node->pos - othernode->pos;
2822 linelen = NR::L2(delta);
2823 if (linelen < 1e-18)
2824 return;
2825 me->pos = node->pos + (len / linelen)*delta;
2826 return;
2827 }
2829 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2831 me->pos = 2 * node->pos - other->pos;
2832 return;
2833 }
2835 /* We are smooth */
2837 len = NR::L2(me->pos - node->pos);
2838 delta = other->pos - node->pos;
2839 otherlen = NR::L2(delta);
2840 if (otherlen < 1e-18) return;
2842 me->pos = node->pos - (len / otherlen) * delta;
2843 }
2845 /**
2846 \brief Adjusts both handles according to node type and line code
2847 */
2848 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
2849 {
2850 g_assert(node);
2852 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
2854 /* we are either smooth or symm */
2856 if (node->p.other == NULL) return;
2858 if (node->n.other == NULL) return;
2860 if (node->code == NR_LINETO) {
2861 if (node->n.other->code == NR_LINETO) return;
2862 sp_node_adjust_handle(node, 1);
2863 return;
2864 }
2866 if (node->n.other->code == NR_LINETO) {
2867 if (node->code == NR_LINETO) return;
2868 sp_node_adjust_handle(node, -1);
2869 return;
2870 }
2872 /* both are curves */
2873 NR::Point const delta( node->n.pos - node->p.pos );
2875 if (node->type == Inkscape::NodePath::NODE_SYMM) {
2876 node->p.pos = node->pos - delta / 2;
2877 node->n.pos = node->pos + delta / 2;
2878 return;
2879 }
2881 /* We are smooth */
2882 double plen = NR::L2(node->p.pos - node->pos);
2883 if (plen < 1e-18) return;
2884 double nlen = NR::L2(node->n.pos - node->pos);
2885 if (nlen < 1e-18) return;
2886 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
2887 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
2888 }
2890 /**
2891 * Node event callback.
2892 */
2893 static gboolean node_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n)
2894 {
2895 gboolean ret = FALSE;
2896 switch (event->type) {
2897 case GDK_ENTER_NOTIFY:
2898 active_node = n;
2899 break;
2900 case GDK_LEAVE_NOTIFY:
2901 active_node = NULL;
2902 break;
2903 case GDK_SCROLL:
2904 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
2905 switch (event->scroll.direction) {
2906 case GDK_SCROLL_UP:
2907 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2908 break;
2909 case GDK_SCROLL_DOWN:
2910 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2911 break;
2912 default:
2913 break;
2914 }
2915 ret = TRUE;
2916 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
2917 switch (event->scroll.direction) {
2918 case GDK_SCROLL_UP:
2919 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2920 break;
2921 case GDK_SCROLL_DOWN:
2922 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2923 break;
2924 default:
2925 break;
2926 }
2927 ret = TRUE;
2928 }
2929 break;
2930 case GDK_KEY_PRESS:
2931 switch (get_group0_keyval (&event->key)) {
2932 case GDK_space:
2933 if (event->key.state & GDK_BUTTON1_MASK) {
2934 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
2935 stamp_repr(nodepath);
2936 ret = TRUE;
2937 }
2938 break;
2939 case GDK_Page_Up:
2940 if (event->key.state & GDK_CONTROL_MASK) {
2941 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
2942 } else {
2943 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
2944 }
2945 break;
2946 case GDK_Page_Down:
2947 if (event->key.state & GDK_CONTROL_MASK) {
2948 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
2949 } else {
2950 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
2951 }
2952 break;
2953 default:
2954 break;
2955 }
2956 break;
2957 default:
2958 break;
2959 }
2961 return ret;
2962 }
2964 /**
2965 * Handle keypress on node; directly called.
2966 */
2967 gboolean node_key(GdkEvent *event)
2968 {
2969 Inkscape::NodePath::Path *np;
2971 // there is no way to verify nodes so set active_node to nil when deleting!!
2972 if (active_node == NULL) return FALSE;
2974 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
2975 gint ret = FALSE;
2976 switch (get_group0_keyval (&event->key)) {
2977 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
2978 case GDK_BackSpace:
2979 np = active_node->subpath->nodepath;
2980 sp_nodepath_node_destroy(active_node);
2981 sp_nodepath_update_repr(np, _("Delete node"));
2982 active_node = NULL;
2983 ret = TRUE;
2984 break;
2985 case GDK_c:
2986 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_CUSP);
2987 ret = TRUE;
2988 break;
2989 case GDK_s:
2990 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SMOOTH);
2991 ret = TRUE;
2992 break;
2993 case GDK_y:
2994 sp_nodepath_set_node_type(active_node,Inkscape::NodePath::NODE_SYMM);
2995 ret = TRUE;
2996 break;
2997 case GDK_b:
2998 sp_nodepath_node_break(active_node);
2999 ret = TRUE;
3000 break;
3001 }
3002 return ret;
3003 }
3004 return FALSE;
3005 }
3007 /**
3008 * Mouseclick on node callback.
3009 */
3010 static void node_clicked(SPKnot *knot, guint state, gpointer data)
3011 {
3012 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3014 if (state & GDK_CONTROL_MASK) {
3015 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3017 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3018 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3019 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3020 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3021 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3022 } else {
3023 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3024 }
3025 sp_nodepath_update_repr(nodepath, _("Change node type"));
3026 sp_nodepath_update_statusbar(nodepath);
3028 } else { //ctrl+alt+click: delete node
3029 GList *node_to_delete = NULL;
3030 node_to_delete = g_list_append(node_to_delete, n);
3031 sp_node_delete_preserve(node_to_delete);
3032 }
3034 } else {
3035 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3036 }
3037 }
3039 /**
3040 * Mouse grabbed node callback.
3041 */
3042 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3043 {
3044 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3046 if (!n->selected) {
3047 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3048 }
3050 n->is_dragging = true;
3051 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3053 sp_nodepath_remember_origins (n->subpath->nodepath);
3054 }
3056 /**
3057 * Mouse ungrabbed node callback.
3058 */
3059 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data)
3060 {
3061 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3063 n->dragging_out = NULL;
3064 n->is_dragging = false;
3065 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3067 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3068 }
3070 /**
3071 * The point on a line, given by its angle, closest to the given point.
3072 * \param p A point.
3073 * \param a Angle of the line; it is assumed to go through coordinate origin.
3074 * \param closest Pointer to the point struct where the result is stored.
3075 * \todo FIXME: use dot product perhaps?
3076 */
3077 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3078 {
3079 if (a == HUGE_VAL) { // vertical
3080 *closest = NR::Point(0, (*p)[NR::Y]);
3081 } else {
3082 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3083 (*closest)[NR::Y] = a * (*closest)[NR::X];
3084 }
3085 }
3087 /**
3088 * Distance from the point to a line given by its angle.
3089 * \param p A point.
3090 * \param a Angle of the line; it is assumed to go through coordinate origin.
3091 */
3092 static double point_line_distance(NR::Point *p, double a)
3093 {
3094 NR::Point c;
3095 point_line_closest(p, a, &c);
3096 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]));
3097 }
3099 /**
3100 * Callback for node "request" signal.
3101 * \todo fixme: This goes to "moved" event? (lauris)
3102 */
3103 static gboolean
3104 node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3105 {
3106 double yn, xn, yp, xp;
3107 double an, ap, na, pa;
3108 double d_an, d_ap, d_na, d_pa;
3109 gboolean collinear = FALSE;
3110 NR::Point c;
3111 NR::Point pr;
3113 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3115 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3116 if (((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) || n->dragging_out) {
3118 NR::Point mouse = (*p);
3120 if (!n->dragging_out) {
3121 // This is the first drag-out event; find out which handle to drag out
3122 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3123 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3125 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3126 return FALSE;
3128 Inkscape::NodePath::NodeSide *opposite;
3129 if (appr_p > appr_n) { // closer to p
3130 n->dragging_out = &n->p;
3131 opposite = &n->n;
3132 n->code = NR_CURVETO;
3133 } else if (appr_p < appr_n) { // closer to n
3134 n->dragging_out = &n->n;
3135 opposite = &n->p;
3136 n->n.other->code = NR_CURVETO;
3137 } else { // p and n nodes are the same
3138 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3139 n->dragging_out = &n->p;
3140 opposite = &n->n;
3141 n->code = NR_CURVETO;
3142 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3143 n->dragging_out = &n->n;
3144 opposite = &n->p;
3145 n->n.other->code = NR_CURVETO;
3146 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3147 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);
3148 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);
3149 if (appr_other_p > appr_other_n) { // closer to other's p handle
3150 n->dragging_out = &n->n;
3151 opposite = &n->p;
3152 n->n.other->code = NR_CURVETO;
3153 } else { // closer to other's n handle
3154 n->dragging_out = &n->p;
3155 opposite = &n->n;
3156 n->code = NR_CURVETO;
3157 }
3158 }
3159 }
3161 // if there's another handle, make sure the one we drag out starts parallel to it
3162 if (opposite->pos != n->pos) {
3163 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3164 }
3166 // knots might not be created yet!
3167 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3168 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3169 }
3171 // pass this on to the handle-moved callback
3172 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3173 sp_node_update_handles(n);
3174 return TRUE;
3175 }
3177 if (state & GDK_CONTROL_MASK) { // constrained motion
3179 // calculate relative distances of handles
3180 // n handle:
3181 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3182 xn = n->n.pos[NR::X] - n->pos[NR::X];
3183 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3184 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3185 if (n->n.other) { // if there is the next point
3186 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3187 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3188 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3189 }
3190 }
3191 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3192 if (yn < 0) { xn = -xn; yn = -yn; }
3194 // p handle:
3195 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3196 xp = n->p.pos[NR::X] - n->pos[NR::X];
3197 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3198 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3199 if (n->p.other) {
3200 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3201 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3202 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3203 }
3204 }
3205 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3206 if (yp < 0) { xp = -xp; yp = -yp; }
3208 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3209 // sliding on handles, only if at least one of the handles is non-vertical
3210 // (otherwise it's the same as ctrl+drag anyway)
3212 // calculate angles of the handles
3213 if (xn == 0) {
3214 if (yn == 0) { // no handle, consider it the continuation of the other one
3215 an = 0;
3216 collinear = TRUE;
3217 }
3218 else an = 0; // vertical; set the angle to horizontal
3219 } else an = yn/xn;
3221 if (xp == 0) {
3222 if (yp == 0) { // no handle, consider it the continuation of the other one
3223 ap = an;
3224 }
3225 else ap = 0; // vertical; set the angle to horizontal
3226 } else ap = yp/xp;
3228 if (collinear) an = ap;
3230 // angles of the perpendiculars; HUGE_VAL means vertical
3231 if (an == 0) na = HUGE_VAL; else na = -1/an;
3232 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3234 // mouse point relative to the node's original pos
3235 pr = (*p) - n->origin;
3237 // distances to the four lines (two handles and two perpendiculars)
3238 d_an = point_line_distance(&pr, an);
3239 d_na = point_line_distance(&pr, na);
3240 d_ap = point_line_distance(&pr, ap);
3241 d_pa = point_line_distance(&pr, pa);
3243 // find out which line is the closest, save its closest point in c
3244 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3245 point_line_closest(&pr, an, &c);
3246 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3247 point_line_closest(&pr, ap, &c);
3248 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3249 point_line_closest(&pr, na, &c);
3250 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3251 point_line_closest(&pr, pa, &c);
3252 }
3254 // move the node to the closest point
3255 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3256 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3257 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y]);
3259 } else { // constraining to hor/vert
3261 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3262 sp_nodepath_selected_nodes_move(n->subpath->nodepath, (*p)[NR::X] - n->pos[NR::X], n->origin[NR::Y] - n->pos[NR::Y]);
3263 } else { // snap to vert
3264 sp_nodepath_selected_nodes_move(n->subpath->nodepath, n->origin[NR::X] - n->pos[NR::X], (*p)[NR::Y] - n->pos[NR::Y]);
3265 }
3266 }
3267 } else { // move freely
3268 if (n->is_dragging) {
3269 if (state & GDK_MOD1_MASK) { // sculpt
3270 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3271 } else {
3272 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3273 (*p)[NR::X] - n->pos[NR::X],
3274 (*p)[NR::Y] - n->pos[NR::Y],
3275 (state & GDK_SHIFT_MASK) == 0);
3276 }
3277 }
3278 }
3280 n->subpath->nodepath->desktop->scroll_to_point(p);
3282 return TRUE;
3283 }
3285 /**
3286 * Node handle clicked callback.
3287 */
3288 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3289 {
3290 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3292 if (state & GDK_CONTROL_MASK) { // "delete" handle
3293 if (n->p.knot == knot) {
3294 n->p.pos = n->pos;
3295 } else if (n->n.knot == knot) {
3296 n->n.pos = n->pos;
3297 }
3298 sp_node_update_handles(n);
3299 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3300 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3301 sp_nodepath_update_statusbar(nodepath);
3303 } else { // just select or add to selection, depending in Shift
3304 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3305 }
3306 }
3308 /**
3309 * Node handle grabbed callback.
3310 */
3311 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3312 {
3313 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3315 if (!n->selected) {
3316 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3317 }
3319 // remember the origin point of the handle
3320 if (n->p.knot == knot) {
3321 n->p.origin_radial = n->p.pos - n->pos;
3322 } else if (n->n.knot == knot) {
3323 n->n.origin_radial = n->n.pos - n->pos;
3324 } else {
3325 g_assert_not_reached();
3326 }
3328 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3329 }
3331 /**
3332 * Node handle ungrabbed callback.
3333 */
3334 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3335 {
3336 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3338 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3339 if (n->p.knot == knot) {
3340 n->p.origin_radial.a = 0;
3341 sp_knot_set_position(knot, &n->p.pos, state);
3342 } else if (n->n.knot == knot) {
3343 n->n.origin_radial.a = 0;
3344 sp_knot_set_position(knot, &n->n.pos, state);
3345 } else {
3346 g_assert_not_reached();
3347 }
3349 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3350 }
3352 /**
3353 * Node handle "request" signal callback.
3354 */
3355 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3356 {
3357 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3359 Inkscape::NodePath::NodeSide *me, *opposite;
3360 gint which;
3361 if (n->p.knot == knot) {
3362 me = &n->p;
3363 opposite = &n->n;
3364 which = -1;
3365 } else if (n->n.knot == knot) {
3366 me = &n->n;
3367 opposite = &n->p;
3368 which = 1;
3369 } else {
3370 me = opposite = NULL;
3371 which = 0;
3372 g_assert_not_reached();
3373 }
3375 NRPathcode const othercode = sp_node_path_code_from_side(n, opposite);
3377 SnapManager const &m = n->subpath->nodepath->desktop->namedview->snap_manager;
3379 if (opposite->other && (n->type != Inkscape::NodePath::NODE_CUSP) && (othercode == NR_LINETO)) {
3380 /* We are smooth node adjacent with line */
3381 NR::Point const delta = *p - n->pos;
3382 NR::Coord const len = NR::L2(delta);
3383 Inkscape::NodePath::Node *othernode = opposite->other;
3384 NR::Point const ndelta = n->pos - othernode->pos;
3385 NR::Coord const linelen = NR::L2(ndelta);
3386 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3387 NR::Coord const scal = dot(delta, ndelta) / linelen;
3388 (*p) = n->pos + (scal / linelen) * ndelta;
3389 }
3390 *p = m.constrainedSnap(Inkscape::Snapper::SNAP_POINT, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta), NULL).getPoint();
3391 } else {
3392 *p = m.freeSnap(Inkscape::Snapper::SNAP_POINT, *p, NULL).getPoint();
3393 }
3395 sp_node_adjust_handle(n, -which);
3397 return FALSE;
3398 }
3400 /**
3401 * Node handle moved callback.
3402 */
3403 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3404 {
3405 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3407 Inkscape::NodePath::NodeSide *me;
3408 Inkscape::NodePath::NodeSide *other;
3409 if (n->p.knot == knot) {
3410 me = &n->p;
3411 other = &n->n;
3412 } else if (n->n.knot == knot) {
3413 me = &n->n;
3414 other = &n->p;
3415 } else {
3416 me = NULL;
3417 other = NULL;
3418 g_assert_not_reached();
3419 }
3421 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3422 Radial rme(me->pos - n->pos);
3423 Radial rother(other->pos - n->pos);
3424 Radial rnew(*p - n->pos);
3426 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3427 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3428 /* 0 interpreted as "no snapping". */
3430 // The closest PI/snaps angle, starting from zero.
3431 double const a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3432 if (me->origin_radial.a == HUGE_VAL) {
3433 // ortho doesn't exist: original handle was zero length.
3434 rnew.a = a_snapped;
3435 } else {
3436 /* The closest PI/2 angle, starting from original angle (i.e. snapping to original,
3437 * its opposite and perpendiculars). */
3438 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3440 // Snap to the closest.
3441 rnew.a = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3442 ? a_snapped
3443 : a_ortho );
3444 }
3445 }
3447 if (state & GDK_MOD1_MASK) {
3448 // lock handle length
3449 rnew.r = me->origin_radial.r;
3450 }
3452 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3453 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && fabs(rme.a - rnew.a) > 0.001) {
3454 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3455 rother.a += rnew.a - rme.a;
3456 other->pos = NR::Point(rother) + n->pos;
3457 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3458 sp_knot_set_position(other->knot, &other->pos, 0);
3459 }
3461 me->pos = NR::Point(rnew) + n->pos;
3462 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3464 // move knot, but without emitting the signal:
3465 // we cannot emit a "moved" signal because we're now processing it
3466 sp_knot_moveto(me->knot, &(me->pos));
3468 update_object(n->subpath->nodepath);
3470 /* status text */
3471 SPDesktop *desktop = n->subpath->nodepath->desktop;
3472 if (!desktop) return;
3473 SPEventContext *ec = desktop->event_context;
3474 if (!ec) return;
3475 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3476 if (!mc) return;
3478 double degrees = 180 / M_PI * rnew.a;
3479 if (degrees > 180) degrees -= 360;
3480 if (degrees < -180) degrees += 360;
3481 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3482 degrees = angle_to_compass (degrees);
3484 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3486 mc->setF(Inkscape::NORMAL_MESSAGE,
3487 _("<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);
3489 g_string_free(length, TRUE);
3490 }
3492 /**
3493 * Node handle event callback.
3494 */
3495 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3496 {
3497 gboolean ret = FALSE;
3498 switch (event->type) {
3499 case GDK_KEY_PRESS:
3500 switch (get_group0_keyval (&event->key)) {
3501 case GDK_space:
3502 if (event->key.state & GDK_BUTTON1_MASK) {
3503 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3504 stamp_repr(nodepath);
3505 ret = TRUE;
3506 }
3507 break;
3508 default:
3509 break;
3510 }
3511 break;
3512 default:
3513 break;
3514 }
3516 return ret;
3517 }
3519 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3520 Radial &rme, Radial &rother, gboolean const both)
3521 {
3522 rme.a += angle;
3523 if ( both
3524 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3525 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3526 {
3527 rother.a += angle;
3528 }
3529 }
3531 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3532 Radial &rme, Radial &rother, gboolean const both)
3533 {
3534 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3536 gdouble r;
3537 if ( both
3538 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3539 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3540 {
3541 r = MAX(rme.r, rother.r);
3542 } else {
3543 r = rme.r;
3544 }
3546 gdouble const weird_angle = atan2(norm_angle, r);
3547 /* Bulia says norm_angle is just the visible distance that the
3548 * object's end must travel on the screen. Left as 'angle' for want of
3549 * a better name.*/
3551 rme.a += weird_angle;
3552 if ( both
3553 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3554 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3555 {
3556 rother.a += weird_angle;
3557 }
3558 }
3560 /**
3561 * Rotate one node.
3562 */
3563 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
3564 {
3565 Inkscape::NodePath::NodeSide *me, *other;
3566 bool both = false;
3568 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3569 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3571 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3572 me = &(n->p);
3573 other = &(n->n);
3574 } else if (!n->p.other) {
3575 me = &(n->n);
3576 other = &(n->p);
3577 } else {
3578 if (which > 0) { // right handle
3579 if (xn > xp) {
3580 me = &(n->n);
3581 other = &(n->p);
3582 } else {
3583 me = &(n->p);
3584 other = &(n->n);
3585 }
3586 } else if (which < 0){ // left handle
3587 if (xn <= xp) {
3588 me = &(n->n);
3589 other = &(n->p);
3590 } else {
3591 me = &(n->p);
3592 other = &(n->n);
3593 }
3594 } else { // both handles
3595 me = &(n->n);
3596 other = &(n->p);
3597 both = true;
3598 }
3599 }
3601 Radial rme(me->pos - n->pos);
3602 Radial rother(other->pos - n->pos);
3604 if (screen) {
3605 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
3606 } else {
3607 node_rotate_one_internal (*n, angle, rme, rother, both);
3608 }
3610 me->pos = n->pos + NR::Point(rme);
3612 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
3613 other->pos = n->pos + NR::Point(rother);
3614 }
3616 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
3617 // so here we just move all the knots without emitting move signals, for speed
3618 sp_node_update_handles(n, false);
3619 }
3621 /**
3622 * Rotate selected nodes.
3623 */
3624 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
3625 {
3626 if (!nodepath || !nodepath->selected) return;
3628 if (g_list_length(nodepath->selected) == 1) {
3629 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3630 node_rotate_one (n, angle, which, screen);
3631 } else {
3632 // rotate as an object:
3634 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3635 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3636 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3637 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3638 box.expandTo (n->pos); // contain all selected nodes
3639 }
3641 gdouble rot;
3642 if (screen) {
3643 gdouble const zoom = nodepath->desktop->current_zoom();
3644 gdouble const zmove = angle / zoom;
3645 gdouble const r = NR::L2(box.max() - box.midpoint());
3646 rot = atan2(zmove, r);
3647 } else {
3648 rot = angle;
3649 }
3651 NR::Matrix t =
3652 NR::Matrix (NR::translate(-box.midpoint())) *
3653 NR::Matrix (NR::rotate(rot)) *
3654 NR::Matrix (NR::translate(box.midpoint()));
3656 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3657 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3658 n->pos *= t;
3659 n->n.pos *= t;
3660 n->p.pos *= t;
3661 sp_node_update_handles(n, false);
3662 }
3663 }
3665 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
3666 }
3668 /**
3669 * Scale one node.
3670 */
3671 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
3672 {
3673 bool both = false;
3674 Inkscape::NodePath::NodeSide *me, *other;
3676 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
3677 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
3679 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
3680 me = &(n->p);
3681 other = &(n->n);
3682 n->code = NR_CURVETO;
3683 } else if (!n->p.other) {
3684 me = &(n->n);
3685 other = &(n->p);
3686 if (n->n.other)
3687 n->n.other->code = NR_CURVETO;
3688 } else {
3689 if (which > 0) { // right handle
3690 if (xn > xp) {
3691 me = &(n->n);
3692 other = &(n->p);
3693 if (n->n.other)
3694 n->n.other->code = NR_CURVETO;
3695 } else {
3696 me = &(n->p);
3697 other = &(n->n);
3698 n->code = NR_CURVETO;
3699 }
3700 } else if (which < 0){ // left handle
3701 if (xn <= xp) {
3702 me = &(n->n);
3703 other = &(n->p);
3704 if (n->n.other)
3705 n->n.other->code = NR_CURVETO;
3706 } else {
3707 me = &(n->p);
3708 other = &(n->n);
3709 n->code = NR_CURVETO;
3710 }
3711 } else { // both handles
3712 me = &(n->n);
3713 other = &(n->p);
3714 both = true;
3715 n->code = NR_CURVETO;
3716 if (n->n.other)
3717 n->n.other->code = NR_CURVETO;
3718 }
3719 }
3721 Radial rme(me->pos - n->pos);
3722 Radial rother(other->pos - n->pos);
3724 rme.r += grow;
3725 if (rme.r < 0) rme.r = 0;
3726 if (rme.a == HUGE_VAL) {
3727 if (me->other) { // if direction is unknown, initialize it towards the next node
3728 Radial rme_next(me->other->pos - n->pos);
3729 rme.a = rme_next.a;
3730 } else { // if there's no next, initialize to 0
3731 rme.a = 0;
3732 }
3733 }
3734 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3735 rother.r += grow;
3736 if (rother.r < 0) rother.r = 0;
3737 if (rother.a == HUGE_VAL) {
3738 rother.a = rme.a + M_PI;
3739 }
3740 }
3742 me->pos = n->pos + NR::Point(rme);
3744 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
3745 other->pos = n->pos + NR::Point(rother);
3746 }
3748 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
3749 // so here we just move all the knots without emitting move signals, for speed
3750 sp_node_update_handles(n, false);
3751 }
3753 /**
3754 * Scale selected nodes.
3755 */
3756 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3757 {
3758 if (!nodepath || !nodepath->selected) return;
3760 if (g_list_length(nodepath->selected) == 1) {
3761 // scale handles of the single selected node
3762 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3763 node_scale_one (n, grow, which);
3764 } else {
3765 // scale nodes as an "object":
3767 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3768 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3769 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3770 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3771 box.expandTo (n->pos); // contain all selected nodes
3772 }
3774 double scale = (box.maxExtent() + grow)/box.maxExtent();
3776 NR::Matrix t =
3777 NR::Matrix (NR::translate(-box.midpoint())) *
3778 NR::Matrix (NR::scale(scale, scale)) *
3779 NR::Matrix (NR::translate(box.midpoint()));
3781 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3782 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3783 n->pos *= t;
3784 n->n.pos *= t;
3785 n->p.pos *= t;
3786 sp_node_update_handles(n, false);
3787 }
3788 }
3790 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
3791 }
3793 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
3794 {
3795 if (!nodepath) return;
3796 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
3797 }
3799 /**
3800 * Flip selected nodes horizontally/vertically.
3801 */
3802 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
3803 {
3804 if (!nodepath || !nodepath->selected) return;
3806 if (g_list_length(nodepath->selected) == 1) {
3807 // flip handles of the single selected node
3808 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
3809 double temp = n->p.pos[axis];
3810 n->p.pos[axis] = n->n.pos[axis];
3811 n->n.pos[axis] = temp;
3812 sp_node_update_handles(n, false);
3813 } else {
3814 // scale nodes as an "object":
3816 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
3817 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
3818 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3819 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3820 box.expandTo (n->pos); // contain all selected nodes
3821 }
3823 NR::Matrix t =
3824 NR::Matrix (NR::translate(-box.midpoint())) *
3825 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
3826 NR::Matrix (NR::translate(box.midpoint()));
3828 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
3829 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
3830 n->pos *= t;
3831 n->n.pos *= t;
3832 n->p.pos *= t;
3833 sp_node_update_handles(n, false);
3834 }
3835 }
3837 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
3838 }
3840 //-----------------------------------------------
3841 /**
3842 * Return new subpath under given nodepath.
3843 */
3844 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
3845 {
3846 g_assert(nodepath);
3847 g_assert(nodepath->desktop);
3849 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
3851 s->nodepath = nodepath;
3852 s->closed = FALSE;
3853 s->nodes = NULL;
3854 s->first = NULL;
3855 s->last = NULL;
3857 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
3858 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
3859 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
3861 return s;
3862 }
3864 /**
3865 * Destroy nodes in subpath, then subpath itself.
3866 */
3867 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
3868 {
3869 g_assert(subpath);
3870 g_assert(subpath->nodepath);
3871 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
3873 while (subpath->nodes) {
3874 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
3875 }
3877 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
3879 g_free(subpath);
3880 }
3882 /**
3883 * Link head to tail in subpath.
3884 */
3885 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
3886 {
3887 g_assert(!sp->closed);
3888 g_assert(sp->last != sp->first);
3889 g_assert(sp->first->code == NR_MOVETO);
3891 sp->closed = TRUE;
3893 //Link the head to the tail
3894 sp->first->p.other = sp->last;
3895 sp->last->n.other = sp->first;
3896 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
3897 sp->first = sp->last;
3899 //Remove the extra end node
3900 sp_nodepath_node_destroy(sp->last->n.other);
3901 }
3903 /**
3904 * Open closed (loopy) subpath at node.
3905 */
3906 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
3907 {
3908 g_assert(sp->closed);
3909 g_assert(n->subpath == sp);
3910 g_assert(sp->first == sp->last);
3912 /* We create new startpoint, current node will become last one */
3914 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
3915 &n->pos, &n->pos, &n->n.pos);
3918 sp->closed = FALSE;
3920 //Unlink to make a head and tail
3921 sp->first = new_path;
3922 sp->last = n;
3923 n->n.other = NULL;
3924 new_path->p.other = NULL;
3925 }
3927 /**
3928 * Returns area in triangle given by points; may be negative.
3929 */
3930 inline double
3931 triangle_area (NR::Point p1, NR::Point p2, NR::Point p3)
3932 {
3933 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]);
3934 }
3936 /**
3937 * Return new node in subpath with given properties.
3938 * \param pos Position of node.
3939 * \param ppos Handle position in previous direction
3940 * \param npos Handle position in previous direction
3941 */
3942 Inkscape::NodePath::Node *
3943 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)
3944 {
3945 g_assert(sp);
3946 g_assert(sp->nodepath);
3947 g_assert(sp->nodepath->desktop);
3949 if (nodechunk == NULL)
3950 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
3952 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
3954 n->subpath = sp;
3956 if (type != Inkscape::NodePath::NODE_NONE) {
3957 // use the type from sodipodi:nodetypes
3958 n->type = type;
3959 } else {
3960 if (fabs (triangle_area (*pos, *ppos, *npos)) < 1e-2) {
3961 // points are (almost) collinear
3962 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
3963 // endnode, or a node with a retracted handle
3964 n->type = Inkscape::NodePath::NODE_CUSP;
3965 } else {
3966 n->type = Inkscape::NodePath::NODE_SMOOTH;
3967 }
3968 } else {
3969 n->type = Inkscape::NodePath::NODE_CUSP;
3970 }
3971 }
3973 n->code = code;
3974 n->selected = FALSE;
3975 n->pos = *pos;
3976 n->p.pos = *ppos;
3977 n->n.pos = *npos;
3979 n->dragging_out = NULL;
3981 Inkscape::NodePath::Node *prev;
3982 if (next) {
3983 //g_assert(g_list_find(sp->nodes, next));
3984 prev = next->p.other;
3985 } else {
3986 prev = sp->last;
3987 }
3989 if (prev)
3990 prev->n.other = n;
3991 else
3992 sp->first = n;
3994 if (next)
3995 next->p.other = n;
3996 else
3997 sp->last = n;
3999 n->p.other = prev;
4000 n->n.other = next;
4002 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"));
4003 sp_knot_set_position(n->knot, pos, 0);
4005 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4006 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4007 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4008 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4009 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4010 sp_knot_update_ctrl(n->knot);
4012 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4013 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4014 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4015 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4016 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4017 sp_knot_show(n->knot);
4019 // We only create handle knots and lines on demand
4020 n->p.knot = NULL;
4021 n->p.line = NULL;
4022 n->n.knot = NULL;
4023 n->n.line = NULL;
4025 sp->nodes = g_list_prepend(sp->nodes, n);
4027 return n;
4028 }
4030 /**
4031 * Destroy node and its knots, link neighbors in subpath.
4032 */
4033 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4034 {
4035 g_assert(node);
4036 g_assert(node->subpath);
4037 g_assert(SP_IS_KNOT(node->knot));
4039 Inkscape::NodePath::SubPath *sp = node->subpath;
4041 if (node->selected) { // first, deselect
4042 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4043 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4044 }
4046 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4048 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4049 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4050 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4051 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4052 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4053 g_object_unref(G_OBJECT(node->knot));
4055 if (node->p.knot) {
4056 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4057 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4058 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4059 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4060 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4061 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4062 g_object_unref(G_OBJECT(node->p.knot));
4063 node->p.knot = NULL;
4064 }
4066 if (node->n.knot) {
4067 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4068 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4069 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4070 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4071 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4072 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4073 g_object_unref(G_OBJECT(node->n.knot));
4074 node->n.knot = NULL;
4075 }
4077 if (node->p.line)
4078 gtk_object_destroy(GTK_OBJECT(node->p.line));
4079 if (node->n.line)
4080 gtk_object_destroy(GTK_OBJECT(node->n.line));
4082 if (sp->nodes) { // there are others nodes on the subpath
4083 if (sp->closed) {
4084 if (sp->first == node) {
4085 g_assert(sp->last == node);
4086 sp->first = node->n.other;
4087 sp->last = sp->first;
4088 }
4089 node->p.other->n.other = node->n.other;
4090 node->n.other->p.other = node->p.other;
4091 } else {
4092 if (sp->first == node) {
4093 sp->first = node->n.other;
4094 sp->first->code = NR_MOVETO;
4095 }
4096 if (sp->last == node) sp->last = node->p.other;
4097 if (node->p.other) node->p.other->n.other = node->n.other;
4098 if (node->n.other) node->n.other->p.other = node->p.other;
4099 }
4100 } else { // this was the last node on subpath
4101 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4102 }
4104 g_mem_chunk_free(nodechunk, node);
4105 }
4107 /**
4108 * Returns one of the node's two sides.
4109 * \param which Indicates which side.
4110 * \return Pointer to previous node side if which==-1, next if which==1.
4111 */
4112 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4113 {
4114 g_assert(node);
4116 switch (which) {
4117 case -1:
4118 return &node->p;
4119 case 1:
4120 return &node->n;
4121 default:
4122 break;
4123 }
4125 g_assert_not_reached();
4127 return NULL;
4128 }
4130 /**
4131 * Return the other side of the node, given one of its sides.
4132 */
4133 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4134 {
4135 g_assert(node);
4137 if (me == &node->p) return &node->n;
4138 if (me == &node->n) return &node->p;
4140 g_assert_not_reached();
4142 return NULL;
4143 }
4145 /**
4146 * Return NRPathcode on the given side of the node.
4147 */
4148 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4149 {
4150 g_assert(node);
4152 if (me == &node->p) {
4153 if (node->p.other) return (NRPathcode)node->code;
4154 return NR_MOVETO;
4155 }
4157 if (me == &node->n) {
4158 if (node->n.other) return (NRPathcode)node->n.other->code;
4159 return NR_MOVETO;
4160 }
4162 g_assert_not_reached();
4164 return NR_END;
4165 }
4167 /**
4168 * Return node with the given index
4169 */
4170 Inkscape::NodePath::Node *
4171 sp_nodepath_get_node_by_index(int index)
4172 {
4173 Inkscape::NodePath::Node *e = NULL;
4175 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4176 if (!nodepath) {
4177 return e;
4178 }
4180 //find segment
4181 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4183 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4184 int n = g_list_length(sp->nodes);
4185 if (sp->closed) {
4186 n++;
4187 }
4189 //if the piece belongs to this subpath grab it
4190 //otherwise move onto the next subpath
4191 if (index < n) {
4192 e = sp->first;
4193 for (int i = 0; i < index; ++i) {
4194 e = e->n.other;
4195 }
4196 break;
4197 } else {
4198 if (sp->closed) {
4199 index -= (n+1);
4200 } else {
4201 index -= n;
4202 }
4203 }
4204 }
4206 return e;
4207 }
4209 /**
4210 * Returns plain text meaning of node type.
4211 */
4212 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4213 {
4214 unsigned retracted = 0;
4215 bool endnode = false;
4217 for (int which = -1; which <= 1; which += 2) {
4218 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4219 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4220 retracted ++;
4221 if (!side->other)
4222 endnode = true;
4223 }
4225 if (retracted == 0) {
4226 if (endnode) {
4227 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4228 return _("end node");
4229 } else {
4230 switch (node->type) {
4231 case Inkscape::NodePath::NODE_CUSP:
4232 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4233 return _("cusp");
4234 case Inkscape::NodePath::NODE_SMOOTH:
4235 // TRANSLATORS: "smooth" is an adjective here
4236 return _("smooth");
4237 case Inkscape::NodePath::NODE_SYMM:
4238 return _("symmetric");
4239 }
4240 }
4241 } else if (retracted == 1) {
4242 if (endnode) {
4243 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4244 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4245 } else {
4246 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4247 }
4248 } else {
4249 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4250 }
4252 return NULL;
4253 }
4255 /**
4256 * Handles content of statusbar as long as node tool is active.
4257 */
4258 void
4259 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4260 {
4261 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");
4262 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4264 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4265 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4266 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4267 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4269 SPDesktop *desktop = NULL;
4270 if (nodepath) {
4271 desktop = nodepath->desktop;
4272 } else {
4273 desktop = SP_ACTIVE_DESKTOP;
4274 }
4276 SPEventContext *ec = desktop->event_context;
4277 if (!ec) return;
4278 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4279 if (!mc) return;
4281 if (selected_nodes == 0) {
4282 Inkscape::Selection *sel = desktop->selection;
4283 if (!sel || sel->isEmpty()) {
4284 mc->setF(Inkscape::NORMAL_MESSAGE,
4285 _("Select a single object to edit its nodes or handles."));
4286 } else {
4287 if (nodepath) {
4288 mc->setF(Inkscape::NORMAL_MESSAGE,
4289 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.",
4290 "<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.",
4291 total_nodes),
4292 total_nodes);
4293 } else {
4294 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4295 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4296 } else {
4297 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4298 }
4299 }
4300 }
4301 } else if (nodepath && selected_nodes == 1) {
4302 mc->setF(Inkscape::NORMAL_MESSAGE,
4303 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4304 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4305 total_nodes),
4306 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4307 } else {
4308 if (selected_subpaths > 1) {
4309 mc->setF(Inkscape::NORMAL_MESSAGE,
4310 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4311 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4312 total_nodes),
4313 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4314 } else {
4315 mc->setF(Inkscape::NORMAL_MESSAGE,
4316 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4317 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4318 total_nodes),
4319 selected_nodes, total_nodes, when_selected);
4320 }
4321 }
4322 }
4325 /*
4326 Local Variables:
4327 mode:c++
4328 c-file-style:"stroustrup"
4329 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4330 indent-tabs-mode:nil
4331 fill-column:99
4332 End:
4333 */
4334 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :